/*
*               dotfiles - handler.ts
*
*   Created by Janis Hutz 03/21/2025, Licensed under the GPL V3 License
*           https://janishutz.com, development@janishutz.com
*
*
*/

import { Astal, Gtk, Gdk } from "astal/gtk3"
import Notifd from "gi://AstalNotifd";
import Notification from "./notifications/notifications";
import { type Subscribable } from "astal/binding";
import { Variable, bind, timeout } from "astal"

// Config
const TIMEOUT_DELAY = 5000;

class Notifier implements Subscribable {
    private display: Map<number, Gtk.Widget> = new Map();
    private notifications: Map<number, Notifd.Notification> = new Map();

    private notifd: Notifd.Notifd;
    private subscriberData: Variable<Gtk.Widget[]> = Variable( [] );
    private instanceID: number;
    private menuOpen: boolean;

    /**
     * Sets up the notifier
     */
    constructor( id: number ) {
        this.instanceID = id;
        this.menuOpen = false;
        this.notifd = Notifd.get_default();
        this.notifd.ignoreTimeout = true;

        this.notifd.connect( 'notified', ( _, id ) => {
            this.add( id );
        } );

        this.notifd.connect( 'resolved', ( _, id ) => {
            this.delete( id );
        } );
    }

    private notify () {
        this.subscriberData.set( [ ...this.display.values() ].reverse() );
    }

    private add ( id: number ) {
        this.notifications.set( id, this.notifd.get_notification( id )! );
        this.show( id );
    }

    /**
     * Show an element on screen
     * @param id - The id of the element to be shown
     */
    private show ( id: number ) {
        this.set( id, Notification( {
            notification: this.notifications.get( id )!,
            onHoverLost: () => { 
                if ( !this.menuOpen ) {
                    this.hide( id );
                }
            },
            setup: () => timeout( TIMEOUT_DELAY, () => {
                if ( !this.menuOpen ) {
                    this.hide( id );
                }
            } ),
            id: id,
            delete: deleteHelper,
            instanceID: this.instanceID
        } ) )
    }

    /**
     * Set a selected widget to be shown
     * @param id - The id of the element to be referenced for later
     * @param widget - A GTK widget instance
     */
    private set ( id: number, widget: Gtk.Widget ) {
        this.display.get( id )?.destroy();
        this.display.set( id, widget );
        this.notify();
    }

    /**
     * Hide, not delete notification (= send to notification centre)
     * @param id - The id of the notification to hide
     */
    private hide ( id: number ) {
        this.display.get( id )?.destroy();
        this.display.delete( id );
        this.notify();
    }

    /**
     * Delete a notification (from notification centre too)
     * @param id - The id of the notification to hide
     */
    delete ( id: number ) {
        this.hide( id );
        this.notifications.get( id )?.dismiss();
        this.notifications.delete( id );
        if ( this.notifications.size == 0 ) {
            this.menuOpen = false;
        }
    }

    openNotificationMenu () {
        // Show all notifications that have not been cleared
        if ( this.notifications.size > 0 ) {
            this.menuOpen = true;
            this.notifications.forEach( ( _, id ) => {
                this.show( id );
            } )
        }
    }

    hideNotifications () {
        this.menuOpen = false;
        this.notifications.forEach( ( _, id ) => {
            this.hide( id );
        } );
    }

    toggleNotificationMenu () {
        if ( this.menuOpen ) {
            this.hideNotifications();
        } else {
            this.openNotificationMenu();
        }
    }

    clearAllNotifications () {
        this.menuOpen = false;
        this.notifications.forEach( ( _, id ) => {
            this.delete( id );
        } )
    }

    clearNewestNotification () {
        this.delete( [ ...this.notifications.keys() ][0] );
    }

    subscribe(callback: (value: unknown) => void): () => void {
        return this.subscriberData.subscribe( callback );
    }

    get() {
        return this.subscriberData.get();
    }
}

const notifiers: Map<number, Notifier> = new Map();
const deleteHelper = ( id: number, instanceID: number ) => {
    notifiers.get( instanceID )?.delete( id );
}

const openNotificationMenu = ( id: number ) => {
    notifiers.get( id )?.openNotificationMenu();
}

const closeNotificationMenu = ( id: number ) => {
    notifiers.get( id )?.hideNotifications();
}

const toggleNotificationMenu = ( id: number ) => {
    notifiers.get( id )?.toggleNotificationMenu();
}

const clearAllNotifications = ( id: number ) => {
    notifiers.get( id )?.clearAllNotifications();
}

const clearNewestNotifications = ( id: number ) => {
    notifiers.get( id )?.clearNewestNotification();
}

const startNotificationHandler = (id: number, gdkmonitor: Gdk.Monitor) => {
    const { TOP, RIGHT } = Astal.WindowAnchor
    const notifier: Notifier = new Notifier( id );
    notifiers.set( id, notifier );

    return <window
        className="NotificationHandler"
        gdkmonitor={gdkmonitor}
        exclusivity={Astal.Exclusivity.EXCLUSIVE}
        anchor={TOP | RIGHT}>
        <box vertical noImplicitDestroy>
            {bind(notifier)}
        </box>
    </window>
}


export default {
    startNotificationHandler,
    openNotificationMenu,
    closeNotificationMenu,
    clearAllNotifications,
    clearNewestNotifications,
    toggleNotificationMenu
}
