import { Astal, Gdk } from "ags/gtk4";
import app from "ags/gtk4/app"
import Gtk from "gi://Gtk?version=4.0"
import { createBinding, createState, For, With, Accessor } from "ags"
import { createPoll } from "ags/time"
import Tray from "gi://AstalTray";
import { execAsync } from "ags/process"
import Hyprland from "gi://AstalHyprland";
import { getIconName } from "./utils";
import Wp from "gi://AstalWp"
import Battery from "gi://AstalBattery"
import GLib from "gi://GLib";

const battery = Battery.get_default()
const sensorsAvailable = await execAsync(['sensors']).then(() => true).catch(() => false);
const wirePlumber = Wp.get_default();

function SysTray(): JSX.Element {
    const tray = Tray.get_default();
    let items = createBinding(tray, "items");
    const init = (btn: Gtk.MenuButton, item: Tray.TrayItem) => {
        btn.menuModel = item.menuModel
        btn.insert_action_group("dbusmenu", item.actionGroup)
        item.connect("notify::action-group", () => {
            btn.insert_action_group("dbusmenu", item.actionGroup)
        })
    }
    return (
        <box>
            <For each={items}>
                {(item: Tray.TrayItem) => {
                    if (item.iconThemePath) app.add_icons(item.iconThemePath);
                    return (
                        <menubutton
                            $={(self) => init(self, item)}
                            class="systray"
                            tooltipMarkup={createBinding(item, "tooltipMarkup")}
                            menuModel={createBinding(item, "menuModel")}>
                            <image gicon={item.gicon} class="systray-item" />
                        </menubutton>
                    );
                }}
            </For>
        </box>
    );
}

function Left(): JSX.Element {
    return (
        <box hexpand halign={Gtk.Align.START}>
            <Clients />
        </box>
    );
}

function Center(): JSX.Element {
    return (
        <box>
            <Workspaces />
        </box>
    );
}

function Date({ format = "%Y-%m-%d" }): JSX.Element {
    const time = createPoll<string>("", 60000, () => GLib.DateTime.new_now_local().format(format)!)
    return <button
        class="item"
        label={time}
        onClicked={() => execAsync(['gnome-calendar'])}
    />
}

function Time({ format = "%H:%M:%S" }): JSX.Element {
    const time = createPoll<string>("", 1000, () => GLib.DateTime.new_now_local().format(format)!)
    return <label
        class="item blue"
        label={time}
    />
}

function Temp(): JSX.Element {
    let [label, _setlabel] = createState<string>("N/A");
    if (sensorsAvailable) {
        label = createPoll<string>("", 5000, 'sensors', out => {
            const match = out.split('\n').find(line => line.includes('Tctl') || line.includes('Package'))?.match(/[0-9.]*°C/);
            return match ? match[0] : "N/A";
        })
    }
    return <label
        class="item blue"
        label={label}
    />
}

function Memory(): JSX.Element {
    const memory = createPoll<string>("", 2000, "free", out => {
        const line = out.split('\n').find(line => line.includes('Mem:'));
        if (!line) return "N/A";
        const split = line.split(/\s+/).map(Number);
        return (split[2] / 1000000).toFixed(2) + "GB";
    });
    return <label
        class="item blue"
        label={memory}
    />
}

function ClockSpeed(): JSX.Element {
    const command = 'bash -c "cat /proc/cpuinfo | grep \\"MHz\\" | awk \'{print \\$4}\' | sort -n | tail -1 | awk \'{printf \\"%.2fGHz\\", \\$1/1000}\'"';
    const speed = createPoll<string>("", 5000, command, out => out)
    return <label
        class="item"
        label={speed}
    />
}

function CPU(): JSX.Element {
    const usage = createPoll<string>("", 2000, "top -b -n 1", out => {
        const line = out.split("\n").find(line => line.includes('Cpu(s)'));
        if (!line) return "N/A";
        return line.split(/\s+/)[1].replace(',', '.').toString() + "%";
    });
    return <box class="item">
        <image iconName="speedometer" css="margin-right: 0.7em;" />
        <label
            label={usage}
        />
    </box>
}



function Right() {
    return (
        <box class="right" hexpand halign={Gtk.Align.END} spacing={6}>
            <Icons />
            <Volume />
            <CPU />
            <Memory />
            <ClockSpeed />
            <Temp />
            <Date />
            <Time />
        </box>
    );
}

function BatteryIcon(): JSX.Element {
    if (battery.get_state() == 0) return <box />;
    let batteryPercentage = createBinding(battery, "percentage");
    return <button class="battery-item" onClicked={() => execAsync(['gnome-power-statistics'])}>
        <box>
            <With value={batteryPercentage}>
                {(percentage) => {
                    const thresholds = [...Array(11).keys()].map(i => i * 10);
                    const icon = thresholds.find(threshold => threshold >= percentage * 100)
                    const charging_name = battery.percentage >= 0.99 ? "charged" : "charging"
                    return <image
                        iconName={battery.charging ? `battery-level-${icon}-${charging_name}-symbolic` : `battery-level-${icon}-symbolic`}
                    />
                }}
            </With>
        </box>
    </button>
}

function Icons() {
    return (
        <box class="item icon-group">
            <SysTray />
            <BatteryIcon />
        </box>
    )
}

function Volume(): JSX.Element {
    if (!wirePlumber) return <box />;

    const audio = wirePlumber.audio;
    const icon = createBinding(audio.default_speaker, "volume").as((volume) => {
        const vol = volume * 100
        const icon = [
            [101, 'overamplified'],
            [67, 'high'],
            [34, 'medium'],
            [1, 'low'],
            [0, 'muted'],
        ].find(([threshold]) => Number(threshold) <= vol)?.[1]
        return `audio-volume-${icon}-symbolic`
    });
    const css = createBinding(audio.default_speaker, "mute").as((mute) => {
        return mute ? "margin-left:0;" : "margin-left: 0.7em;"
    });
    let volume = createBinding(audio.default_speaker, "volume");
    let mute = createBinding(audio.default_speaker, "mute");
    return (
        <button class="item blue" onClicked={() => audio.default_speaker.mute = !audio.default_speaker.mute}>
            <box>
                <image iconName={icon} />
                <With value={volume}>
                    {(vol) => <box>
                        <With value={mute}>
                            {(muted) => {
                                return (
                                    <label label={muted ? "" : `${Math.floor(vol * 100)}%`} css={css} />
                                )
                            }}
                        </With>
                    </box>}
                </With>
            </box>
        </button>
    );
}


function Workspaces(): JSX.Element {
    const hyprland = Hyprland.get_default();
    let workspaces = createBinding(hyprland, "workspaces");
    return (
        <box class="workspaces">
            <With value={workspaces}>
                {(wss: Array<Hyprland.Workspace>) => (
                    <box>
                        <With value={createBinding(hyprland, "focusedMonitor")}>
                            {(fm: Hyprland.Monitor) => {
                                let filtered_wss = new Accessor(() => wss.sort((a, b) => a.id - b.id)
                                    .filter(ws => ws && ws.get_monitor() && ws.get_monitor().get_id() === fm.get_id()))
                                return (
                                    <box>
                                        <For each={filtered_wss}>
                                            {(ws: Hyprland.Workspace, _index) => (
                                                <button
                                                    class={createBinding(hyprland, "focusedWorkspace").as((fw) => ws === fw ? "focused" : "",)}
                                                    onClicked={() => ws.focus()}
                                                >
                                                    {`${ws.id}`.slice(-1)}
                                                </button>
                                            )}
                                        </For>
                                    </box>
                                )
                            }}
                        </With>
                    </box>
                )}
            </With>
        </box>
    );
}

function shorten(title: string) {
    return title.length > 40 ? title.slice(0, 20) + "..." : title
}

function Clients(): JSX.Element {
    const hyprland = Hyprland.get_default();
    let clients = createBinding(hyprland, "clients")
    return (
        <box>
            <With value={createBinding(hyprland, "focusedWorkspace")}>
                {(fw: Hyprland.Workspace) => (
                    <box class="clients">
                        <With value={clients}>
                            {(cls: Array<Hyprland.Client>) => {
                                let filtered_clients = new Accessor(() => cls
                                    .sort((a, b) => a.pid - b.pid)
                                    .filter(cl => !cl.title.includes("rofi"))
                                    .filter(cl => fw && cl.get_workspace().get_id() === fw.get_id()))

                                return (
                                    <box>
                                        <For each={filtered_clients}>
                                            {(cl: Hyprland.Client, _index) => (
                                                <box
                                                    class={createBinding(hyprland, "focusedClient").as(a => a && a.address === cl.address ? "focused" : "unfocused")}
                                                >
                                                    <image
                                                        iconName={getIconName(cl)}
                                                        class="app-icon"
                                                    />
                                                    <label label={createBinding(cl, 'title').as(title => shorten(title))} />
                                                </box>
                                            )}
                                        </For>
                                    </box>
                                )
                            }
                            }
                        </With>
                    </box>
                )
                }
            </With >
        </box >
    );
}

export default function Bar(gdkmonitor: Gdk.Monitor, scaleFactor: number = 1) {
    console.log("Creating Bar on monitor:", gdkmonitor);
    return (
        <window
            visible
            gdkmonitor={gdkmonitor}
            css={"font-size: " + scaleFactor + "em;"}
            exclusivity={Astal.Exclusivity.EXCLUSIVE}
            anchor={Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT | Astal.WindowAnchor.RIGHT}
            application={app}
            class="Bar"
            name="top-bar" >
            <centerbox class="window-box">
                <Left $type="start" />
                <Center $type="center" />
                <Right $type="end" />
            </centerbox>
        </window >
    )
}
