import { App, Astal, Gdk, Gtk, Widget } from "astal/gtk3";
import { GLib, Variable, bind } from "astal";
import Tray from "gi://AstalTray";
import { execAsync } from "astal/process"
import Hyprland from "gi://AstalHyprland";
import { getIconName } from "./utils";
import Wp from "gi://AstalWp"
import Battery from "gi://AstalBattery"

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();
    return (
        <box>
            {bind(tray, "items").as((items) =>
                items.map((item) => {
                    if (item.iconThemePath) App.add_icons(item.iconThemePath);
                    const menu = item.create_menu();

                    return (
                        <button
                            className="systray"
                            tooltipMarkup={bind(item, "tooltipMarkup")}
                            onDestroy={() => menu?.destroy()}
                            onClickRelease={(self) => {
                                menu?.popup_at_widget(
                                    self,
                                    Gdk.Gravity.SOUTH,
                                    Gdk.Gravity.NORTH,
                                    null,
                                );
                            }}>
                            <icon gIcon={bind(item, "gicon")} className="systray-item" />
                        </button>
                    );
                }),
            )}
        </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 = Variable<string>("").poll(60000, () =>
        GLib.DateTime.new_now_local().format(format)!)
    return <button
        className="item"
        onDestroy={() => time.drop()}
        label={time()}
        onClicked={() => execAsync(['gnome-calendar'])}
    />
}

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

function Temp(): JSX.Element {
    let label = Variable<string>("N/A");
    if (sensorsAvailable) {
        label = Variable<string>("").poll(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
        className="item blue"
        onDestroy={() => label.drop()}
        label={label()}
    />
}

function Memory(): JSX.Element {
    const memory = Variable<string>("").poll(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
        className="item blue"
        onDestroy={() => memory.drop()}
        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 = Variable<string>("").poll(5000, command)
    return <label
        className="item"
        onDestroy={() => speed.drop()}
        label={speed()}
    />
}

function CPU(): JSX.Element {
    const usage = Variable<string>("").poll(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 className="item">
        <icon icon="speedometer" css="margin-right: 0.7em;" />
        <label
            onDestroy={() => usage.drop()}
            label={usage()}
        />
    </box>
}



function Right() : JSX.Element {
    return (
        <box className="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 />;
    return  <button className="battery-item" onClicked={() => execAsync(['gnome-power-statistics'])}>
        <box>
            {
                bind(battery, "percentage").as((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 <icon
                        icon={battery.charging? `battery-level-${icon}-${charging_name}-symbolic` : `battery-level-${icon}-symbolic`}
                    />
                })
            }
        </box>
    </button>
}

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

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

    const audio = wirePlumber.audio;
    const icon = bind(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 = bind(audio.default_speaker, "mute").as((mute) => {
        return mute ? "margin-left:0;": "margin-left: 0.7em;"
    });
    return (
        <button className="item blue" onClicked={() => audio.default_speaker.mute = !audio.default_speaker.mute}>
            <box>
                <icon icon={icon} />
                {
                    bind(audio.default_speaker, "volume").as((volume) => <box>
                        {
                            bind(audio.default_speaker, "mute").as((mute) => <box>
                                {
                                    <label label={mute? "": `${Math.floor(volume * 100)}%`} css={css} />
                                }
                            </box>)
                        }
                    </box>)
                }
            </box>
        </button>
    );
}


function Workspaces() : JSX.Element {
    const hyprland = Hyprland.get_default();
    return (
        <box className="workspaces">
            {bind(hyprland, "workspaces").as((wss) =>
                <box>
                    {bind(hyprland, "focusedMonitor").as((fm) =>
                        wss.sort((a, b) => a.id - b.id)
                        .filter(ws => ws && ws.get_monitor() && ws.get_monitor().get_id() === fm.get_id())
                        .map((ws) => (
                            <button
                                className={bind(hyprland, "focusedWorkspace").as((fw) => ws === fw ? "focused" : "",)}
                                onClicked={() => ws.focus()}
                            >
                                {`${ws.id}`.slice(-1)}
                            </button>
                        )))}
                </box>
            )}
        </box>
    );
}

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

function Clients() : JSX.Element {
    const hyprland = Hyprland.get_default();
    return (
        <box>
            {
                bind(hyprland, "focusedWorkspace").as(fw => (
                    <box className="clients">
                        {
                            bind(hyprland, "clients").as(cls =>
                                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())
                                .map(cl => (
                                    <box
                                        className={bind(hyprland, "focusedClient").as(a => a && a.address === cl.address ? "focused" : "unfocused")}
                                    >
                                        <icon
                                            icon={getIconName(cl)}
                                            className="app-icon"
                                        />
                                        <label label={bind(cl, 'title').as(title => shorten(title))} />
                                    </box>
                                )
                                )
                            )

                        }
                    </box>
                )
                )
            }
        </box>
    );
}

export default function Bar(gdkmonitor: Gdk.Monitor, scaleFactor: number = 1): Widget.Window {
    return new Widget.Window({
        gdkmonitor,
        css: "font-size: " + scaleFactor + "em;",
        exclusivity: Astal.Exclusivity.EXCLUSIVE,
        anchor: Astal.WindowAnchor.TOP | Astal.WindowAnchor.LEFT | Astal.WindowAnchor.RIGHT,
        application: App,
        className: "Bar",
        name: "top-bar",
        setup: self => self.connect("destroy", () => {
            print("Detroying bar");
            App.remove_window(self);
        }),
        child: <centerbox className="window-box">
                <Left />
                <Center />
                <Right />
            </centerbox>
    })
}