import { idle, timeout, Variable } from 'astal';
import { App, Astal, Gdk, Gtk } from 'astal/gtk4';
import { register } from 'astal/gobject';

import AstalAuth from 'gi://AstalAuth';
import Gtk4SessionLock from 'gi://Gtk4SessionLock';

import { Box, BoxClass, Entry, Label, Window } from '../subclasses';
import Separator from '../misc/separator';
import { get_hyprland_monitor_desc } from '../../lib';

// This file is generated by Nix
import Vars from './vars';

/* Types */
declare global {
    function authFinger(): void;
}
@register()
class BlurredBox extends BoxClass {
    geometry = {} as { w: number, h: number };
}


export default () => {
    const windows = new Map<Gdk.Monitor, Gtk.Window>();
    const blurBGs: BlurredBox[] = [];

    const transition_duration = 1000;
    const WINDOW_MARGINS = -2;
    const ENTRY_SPACING = 20;
    const CLOCK_SPACING = 60;

    const bgCSS = ({ w = 1, h = 1 } = {}) => `* {
        border: 2px solid rgba(189, 147, 249, 0.8);
        background: rgba(0, 0, 0, 0.2);
        min-height: ${h}px;
        min-width: ${w}px;
        transition: min-height ${transition_duration / 2}ms,
                    min-width ${transition_duration / 2}ms;
    }`;

    const lock = Gtk4SessionLock.Instance.new();

    const unlock = () => {
        blurBGs.forEach((b) => {
            b.css = bgCSS({
                w: b.geometry.w,
                h: 1,
            });

            timeout(transition_duration / 2, () => {
                b.css = bgCSS({
                    w: 1,
                    h: 1,
                });
            });
        });
        timeout(transition_duration, () => {
            lock.unlock();
            Gdk.Display.get_default()?.sync();
            App.quit();
        });
    };

    const Clock = () => {
        const time = Variable<string>('').poll(1000, () => {
            return (new Date().toLocaleString([], {
                hour: 'numeric',
                minute: 'numeric',
                hour12: true,
            }) ?? '')
                .replace('a.m.', 'AM')
                .replace('p.m.', 'PM');
        });

        return Label({
            cssClasses: ['lock-clock'],
            label: time(),
        });
    };

    const PasswordPrompt = (monitor: Gdk.Monitor, visible: boolean) => {
        const rev = new BlurredBox({ css: bgCSS() });

        idle(() => {
            rev.geometry = {
                w: monitor.get_geometry().width,
                h: monitor.get_geometry().height,
            };

            rev.css = bgCSS({
                w: rev.geometry.w,
                h: 1,
            });

            timeout(transition_duration / 2, () => {
                rev.css = bgCSS({
                    w: rev.geometry.w,
                    h: rev.geometry.h,
                });
            });
        });

        blurBGs.push(rev);

        Window({
            name: `blur-bg-${monitor.get_model()}`,
            namespace: `blur-bg-${monitor.get_model()}`,
            gdkmonitor: monitor,
            layer: Astal.Layer.OVERLAY,
            visible: true,

            anchor: Astal.WindowAnchor.TOP |
                Astal.WindowAnchor.LEFT |
                Astal.WindowAnchor.RIGHT |
                Astal.WindowAnchor.BOTTOM,

            margin: WINDOW_MARGINS,
            exclusivity: Astal.Exclusivity.IGNORE,

            child: Box({
                halign: Gtk.Align.CENTER,
                valign: Gtk.Align.CENTER,
                children: [rev],
            }),
        });

        const label = Label({ label: 'Enter password:' });

        return new Gtk.Window({
            child: visible ?
                Box({
                    vertical: true,
                    halign: Gtk.Align.CENTER,
                    valign: Gtk.Align.CENTER,
                    spacing: 16,

                    children: [
                        Clock(),

                        Separator({
                            size: CLOCK_SPACING,
                            vertical: true,
                        }),

                        Box({
                            halign: Gtk.Align.CENTER,
                            cssClasses: ['avatar'],
                        }),

                        Box({
                            cssClasses: ['entry-box'],
                            vertical: true,

                            children: [
                                label,

                                Separator({
                                    size: ENTRY_SPACING,
                                    vertical: true,
                                }),

                                Entry({
                                    halign: Gtk.Align.CENTER,
                                    xalign: 0.5,
                                    visibility: false,
                                    placeholder_text: 'password',

                                    onRealize: (self) => self.grab_focus(),

                                    onActivate: (self) => {
                                        self.set_sensitive(false);

                                        AstalAuth.Pam.authenticate(self.get_text() ?? '', (_, task) => {
                                            try {
                                                AstalAuth.Pam.authenticate_finish(task);
                                                unlock();
                                            }
                                            catch (e) {
                                                self.set_text('');
                                                label.set_label((e as Error).message);
                                                self.set_sensitive(true);
                                            }
                                        });
                                    },
                                }),
                            ],
                        }),
                    ],
                }) :
                Box(),
        });
    };

    const createWindow = (monitor: Gdk.Monitor) => {
        const hyprDesc = get_hyprland_monitor_desc(monitor);
        const entryVisible = Vars.mainMonitor === hyprDesc || Vars.dupeLockscreen;
        const win = PasswordPrompt(monitor, entryVisible);

        windows.set(monitor, win);
    };

    const lock_screen = () => {
        const display = Gdk.Display.get_default();

        for (let m = 0; m < (display?.get_monitors().get_n_items() ?? 0); m++) {
            const monitor = display?.get_monitors().get_item(m) as Gdk.Monitor;

            if (monitor) {
                createWindow(monitor);
            }
        }

        display?.get_monitors()?.connect('items-changed', () => {
            for (let m = 0; m < (display?.get_monitors().get_n_items() ?? 0); m++) {
                const monitor = display?.get_monitors().get_item(m) as Gdk.Monitor;

                if (monitor && !windows.has(monitor)) {
                    createWindow(monitor);
                }
            }
        });

        lock.lock();

        windows.forEach((win, monitor) => {
            lock.assign_window_to_monitor(win, monitor);
            win.show();
        });
    };

    if (Vars.hasFprintd) {
        globalThis.authFinger = () => AstalAuth.Pam.authenticate('', (_, task) => {
            try {
                AstalAuth.Pam.authenticate_finish(task);
                unlock();
            }
            catch (e) {
                console.error((e as Error).message);
            }
        });
        globalThis.authFinger();
    }
    lock_screen();
};