import { App, Astal, Gtk, Gdk } from "astal/gtk4"
import { bind, exec, GLib, Variable } from "astal"
import AstalTray from "gi://AstalTray?version=0.1";
import AstalWp from "gi://AstalWp?version=0.1";

type NiriWorkspace = {
  id: number,
  idx: number,
  name: string | null,
  output: string,
  is_active: boolean,
  is_focused: boolean,
  active_window_id: number | null,
};

function getWorkspaces(): NiriWorkspace[] {
  // NOTE: this works only in non-systemd environment on NixOS
  // TODO: try to use Niri socket if it is documented
  return JSON.parse(exec("niri msg -j workspaces"));
}

function getWorkspacesByOutput(output: string): NiriWorkspace[] {
  return getWorkspaces().filter(workspace => workspace.output == output).sort((a, b) => a.idx - b.idx);
}

function focusWorkspace(idx: number) {
  // NOTE: this works only in non-systemd environment on NixOS
  // TODO: try to use Niri socket if it is documented
  exec(`niri msg action focus-workspace ${idx}`);
}

type WorkspaceButtonArguments = {
  idx: number,
  workspaces: Variable<NiriWorkspace[]>,
};

function WorkspaceButton(args: WorkspaceButtonArguments) {
  const classes: Variable<string[]> = Variable.derive([args.workspaces], workspaces => {
    const classes: string[] = [];
    const workspace = workspaces.find(v => v.idx == args.idx);

    workspace !== undefined && classes.push("used");
    workspace?.is_active && classes.push("active");
    workspace?.is_focused && classes.push("focused");

    return classes;
  });

  // TODO: this solution make me to have 9 workspace buttons no matter what
  // I'd like to dynamically hide/show some of them, but I guess this will cause rerenders
  // Maybe I just need to transform them in CSS
  return <button cssClasses={classes()} onClicked={() => focusWorkspace(args.idx)} />
}

type WorkspacesArguments = {
  connector: string,
};

function Workspaces(args: WorkspacesArguments) {
  // NOTE: it is pretty inefficient and not so much responsive
  // TODO: it would be better to use Niri socket in the future
  const workspaces: Variable<NiriWorkspace[]> = Variable(getWorkspacesByOutput(args.connector))
    .poll(1000, () => getWorkspacesByOutput(args.connector));

  const workspaceIndices = [...Array(10).keys()];
  return <box cssClasses={["Workspaces"]}>
    {workspaceIndices.map(i => <WorkspaceButton idx={i + 1} workspaces={workspaces} />)}
  </box>
}


function AudioVolume() {
  const wireplumber = AstalWp.get_default()!;
  const speaker = wireplumber.audio.get_default_speaker()!;

  return <box cssClasses={["AudioVolume"]}>
    <image iconName={bind(speaker, "volumeIcon")} />
    <slider
      hexpand
      onScroll={(_self, dx, dy) => speaker.volume = Math.min(1, Math.max(0, speaker.volume + (dx + dy) * -0.05))}
      onChangeValue={self => { speaker.volume = self.value; }}
      value={bind(speaker, "volume")}
    />
  </box>;
}


function Tray() {
  // TODO: rewrite this using more elements, as this is really restricted design
  const tray = AstalTray.get_default();

  return <box cssClasses={["Tray"]}>
    {bind(tray, "items").as(items =>
      // NOTE: this fixes bug with fantom icon, but I don't think that this is a good idea
      items.filter(item => item.gicon !== null).map(item =>
        <menubutton
          setup={self => self.insert_action_group("dbusmenu", item.actionGroup)}
          tooltipText={bind(item, "tooltipMarkup")}
        >
          <image gicon={bind(item, "gicon")} />
          {Gtk.PopoverMenu.new_from_model(item.menuModel)}
        </menubutton>
      )
    )}
  </box>
}


type TimeArguments = {
  format: string,
};

function Time(args: TimeArguments) {
  const time = Variable<GLib.DateTime>(GLib.DateTime.new_now_local()).poll(1000, () => GLib.DateTime.new_now_local())

  return <box>
    {time(v => v.format(args.format))}
  </box>
}


export default function Bar(gdkmonitor: Gdk.Monitor) {
  const { TOP, LEFT, RIGHT } = Astal.WindowAnchor

  return <window
    visible
    name={"Bar"}
    cssClasses={["Bar"]}
    gdkmonitor={gdkmonitor}
    exclusivity={Astal.Exclusivity.EXCLUSIVE}
    anchor={TOP | LEFT | RIGHT}
    application={App}>
    <centerbox cssClasses={["bar-container"]}>
      <box halign={Gtk.Align.START}>
        <Workspaces connector={gdkmonitor.get_connector()!} />
      </box>

      <box halign={Gtk.Align.CENTER}>
      </box>

      <box halign={Gtk.Align.END}>
        <AudioVolume />
        <Tray />
        <Time format="%I:%M:%S %p %Z" />
      </box>
    </centerbox>
  </window>
}
