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

import { App, Astal, Gdk } from "astal/gtk4"
import Notifd from "gi://AstalNotifd";
import Notification from "./notifications";
import { timeout, Variable } from "astal"

// ───────────────────────────────────────────────────────────────────
//                              Config
// ───────────────────────────────────────────────────────────────────
const TIMEOUT_DELAY = 5000;
let isRunning = false;
let notificationMenuOpen = false;

interface NotificationDetails {
    notification: Notifd.Notification;
    backendID: number;
    notifdID: number;
}


// ───────────────────────────────────────────────────────────────────
//          ╭───────────────────────────────────────────────╮
//          │                   Handler                     │
//          ╰───────────────────────────────────────────────╯
// ───────────────────────────────────────────────────────────────────
let ShownNotifications: Variable<number[]> = Variable( [] );
let Notifications: NotificationDetails[] = [];

const notifd = Notifd.get_default();
notifd.ignoreTimeout = true;


/**
 * Delete a notification by its internal ID
 * @param index The notifd ID of the notification
 */
const deleteNotification = ( index: number ): void => {
    hideNotification( index );
    Notifications.splice( index, 1 );
    if ( Notifications.length === 0 ) {
        notificationMenuOpen = false;
    }
}

// ───────────────────────────────────────────────────────────────────


/**
 * Delete a notification by notifd id
 * @param id The notifd ID of the notification
 */
const deleteNotificationByNotificationID = ( id: number ): void => {
    const index = findNotificationByNotificationID( id );
    if ( index > -1 ) {
        deleteNotification( index );
    }
}

// ───────────────────────────────────────────────────────────────────


/**
 * Find the internal ID from the notifd id for a notification (helper function)
 * @param id The notifd ID of the notification
 * @returns The internal ID or -1 if not found
 */
const findNotificationByNotificationID = ( id: number ): number => {
    // Find index in Notifications array
    for (let index = 0; index < Notifications.length; index++) {
        if ( Notifications[ index ].notifdID === id ) {
            return index;
        }
    }

    return -1;
}

// ───────────────────────────────────────────────────────────────────


/**
 * Add a notification to the notification handler
 * @param id The notifd ID of the notification
 */
const addNotification = ( id: number ): void => {
    print( '[ Notifications ] Notification with id ' + id + ' added.' );
    const currIndex = Notifications.length;
    Notifications.push( {
        notifdID: id,
        backendID: currIndex,
        notification: notifd.get_notification( id )
    } );
    
    showNotification( currIndex );
}

// ───────────────────────────────────────────────────────────────────


/**
 * Start the notifd runner and handle notifications.
 */
const hookToNotificationDaemon = (): void => {
    if ( isRunning ) {
        printerr( '[ Notifications ] Error: Already running' );
        return;
    }
    isRunning = true;

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

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

// ───────────────────────────────────────────────────────────────────


/**
 * Show a notification. It will stay on screen (regardless of removeAgain passed in), if 
 * critical urgency
 * @param id The internal id (index in Notifications array)
 * @param removeAgain = true If to remove the notification from the screen again automatically
 */
const showNotification = ( id: number, removeAgain: boolean = true ) => {
    // Add notification to UI for display
    const not = [...ShownNotifications.get()].reverse();
    not.push( id );
    ShownNotifications.set( not.reverse() );

    // Set delay to remove the notification again
    if ( removeAgain && Notifications[ id ].notification.get_urgency() !== Notifd.Urgency.CRITICAL ) {
        timeout( TIMEOUT_DELAY, () => {
            hideNotification( id );
        } );
    }
}

// ───────────────────────────────────────────────────────────────────


/**
 * Stop displaying notification 
 * @param id The internal id (index in the Notifications array)
 */
const hideNotification = ( id: number ) => {
    if ( !notificationMenuOpen ) {
        const not = [...ShownNotifications.get()];
        not.splice( not.indexOf( id ), 1 );
        ShownNotifications.set( not );
    }
}

// ───────────────────────────────────────────────────────────────────


/**
 * Open the notification menu. Called by toggleNotificationMenu too
 */
const openNotificationMenu = () => {
    // Simply show all notifications
    notificationMenuOpen = true;
    const not = [];
    for (let index = 0; index < Notifications.length; index++) {
        not.push( index );
    }
    ShownNotifications.set( not.reverse() );
}

// ───────────────────────────────────────────────────────────────────


/**
 * Close the notification menu. Called by toggleNotificationMenu too
 */
const closeNotificationMenu = () => {
    // Hide all notifications
    notificationMenuOpen = true;
    ShownNotifications.set( [] );
}

// ───────────────────────────────────────────────────────────────────


/**
 * Toggle the notification menu (i.e. show all notifications)
 */
const toggleNotificationMenu = (): string => {
    if ( notificationMenuOpen ) {
        closeNotificationMenu();
        return 'Toggle notification menu closed';
    } else {
        openNotificationMenu();
        return 'Toggled notification menu open';
    }
}

// ───────────────────────────────────────────────────────────────────


/**
 * Delete all notifications
 */
const clearAllNotifications = () => {
    Notifications = [];
    ShownNotifications.set( [] );
    // TODO: Hiding for each individual deleteNotification
    notificationMenuOpen = false;
}

// ───────────────────────────────────────────────────────────────────


/**
 * Delete the newest notifications
 */
const clearNewestNotifications = () => {
    const not = [...ShownNotifications.get()];
    not.splice( 0, 1 );
    ShownNotifications.set( not );

    Notifications.splice( Notifications.length - 1, 1 );
}



// ───────────────────────────────────────────────────────────────────
//          ╭───────────────────────────────────────────────╮
//          │                User Interface                 │
//          ╰───────────────────────────────────────────────╯
// ───────────────────────────────────────────────────────────────────
const startNotificationHandler = (gdkmonitor: Gdk.Monitor) => {
    const { TOP, RIGHT } = Astal.WindowAnchor

    hookToNotificationDaemon();

    return <window
        cssClasses={["NotificationHandler"]}
        gdkmonitor={gdkmonitor}
        exclusivity={Astal.Exclusivity.EXCLUSIVE}
        anchor={TOP | RIGHT}
        visible={ShownNotifications( list => list.length > 0 )}
        application={App}>
        <box vertical>
            {ShownNotifications( list => list.map( i => {
                // i is index in ShownNotifications array
                return <Notification id={i} delete={deleteNotification} notification={Notifications[ i ].notification}></Notification>
            } ) ) }
        </box>
    </window>
}

const cliHandler = ( args: string[] ): string => {
    if ( args[ 1 ] == 'show' ) {
        openNotificationMenu();
        return 'Showing all open notifications';
    } else if ( args[ 1 ] == 'hide' ) {
        closeNotificationMenu();
        return 'Hid all notifications';
    } else if ( args[ 1 ] == 'clear' ) {
        clearAllNotifications();
        return 'Cleared all notifications';
    } else if ( args[ 1 ] == 'clear-newest' ) {
        clearNewestNotifications();
        return 'Cleared newest notification';
    } else if ( args[ 1 ] == 'toggle' ) {
        return toggleNotificationMenu();
    } else if ( args[ 1 ] == 'list' ){
        if ( Notifications.length > 0 ) {
            let list = 'Currently unviewed notifications: ';
            for (let index = 0; index < Notifications.length; index++) {
                const element = Notifications[index];
                
                list += `\n - (${element.notifdID}) ${element.notification.get_app_name()}: ${element.notification.get_summary()}`;
            }
            return list;
        } else {
            return 'No currently unviewed notifications'
        }
    } else {
        return 'Unknown command!';
    }
}


export default {
    startNotificationHandler,
    cliHandler
}
