/*
 * Deepkit Framework
 * Copyright (C) 2021 Deepkit UG, Marc J. Schmidt
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the MIT License.
 *
 * You should have received a copy of the MIT License along with this program.
 */

import { ChangeDetectorRef, Injectable, TemplateRef, ViewContainerRef } from '@angular/core';
import { arrayRemoveItem } from '@deepkit/core';
import { WindowMenuState } from './window-menu';
import { BehaviorSubject } from 'rxjs';
import { detectChangesNextFrame } from '../app/utils';

interface WinHeader {
    getBottomPosition(): number;
}

interface Win {
    id: number;
    electronWindow?: any;
    getClosestNonDialogWindow(): Win | undefined;
    header?: WinHeader;
    viewContainerRef: ViewContainerRef;
}

@Injectable()
export class WindowRegistry {
    id = 0;

    registry = new Map<Win, {
        state: WindowState,
        menu: WindowMenuState,
        cd: ChangeDetectorRef,
        viewContainerRef: ViewContainerRef
    }>();

    windowHistory: Win[] = [];
    activeWindow?: Win;

    /**
     * When BrowserWindow of electron is focused.
     */
    public focused = new BehaviorSubject(false);

    constructor() {
        this.focused.subscribe((v) => {
            for (const win of this.registry.values()) {
                win.state.focus.next(v);
            }
            detectChangesNextFrame();
        });
    }

    getAllElectronWindows(): any[] {
        return [...this.registry.keys()].filter(v => !!v.electronWindow).map(v => v.electronWindow);
    }

    register(win: Win, cd: ChangeDetectorRef, state: WindowState, menu: WindowMenuState, viewContainerRef: ViewContainerRef) {
        this.id++;
        win.id = this.id;

        this.registry.set(win, {
            state, menu, cd, viewContainerRef,
        });
    }

    /**
     * Finds the activeWindow and returns its most outer parent.
     */
    getOuterActiveWindow(): Win | undefined {
        if (this.activeWindow) return this.activeWindow.getClosestNonDialogWindow();
    }

    getCurrentViewContainerRef(): ViewContainerRef {
        if (this.activeWindow) {
            return this.activeWindow.viewContainerRef;
            // const reg = this.registry.get(this.activeWindow);
            // if (reg) {
            //     return reg.viewContainerRef;
            // }
        }

        throw new Error('No active window');
    }

    focus(win: Win) {
        if (this.activeWindow === win) return;

        const reg = this.registry.get(win);
        if (!reg) throw new Error('Window not registered');

        this.activeWindow = win;

        arrayRemoveItem(this.windowHistory, win);
        this.windowHistory.push(win);

        reg.state.focus.next(true);
        reg.menu.focus();
        detectChangesNextFrame();
    }

    blur(win: Win) {
        const reg = this.registry.get(win);
        if (reg) {
            reg.state.focus.next(false);
        }
        if (this.activeWindow === win) {
            delete this.activeWindow;
        }

        detectChangesNextFrame();
    }

    unregister(win: Win) {
        const reg = this.registry.get(win);
        if (reg) {
            reg.state.focus.next(false);
        }

        this.registry.delete(win);
        arrayRemoveItem(this.windowHistory, win);

        if (this.windowHistory.length) {
            this.focus(this.windowHistory[this.windowHistory.length - 1]);
        }
        detectChangesNextFrame();
    }
}

interface AlignedButtonGroup {
    sidebarMoved: () => void;
    activateOneTimeAnimation: () => void;
}

@Injectable()
export class WindowState {
    public buttonGroupAlignedToSidebar?: AlignedButtonGroup;
    public focus = new BehaviorSubject<boolean>(false);
    public disableInputs = new BehaviorSubject<boolean>(false);

    public toolbars: { [name: string]: TemplateRef<any>[] } = {};
    public toolbarContainers: { [name: string]: { toolbarsUpdated: () => void } } = {};

    closable = true;
    maximizable = true;
    minimizable = true;

    constructor() {
    }

    public addToolbarContainer(forName: string, template: TemplateRef<any>) {
        if (!this.toolbars[forName]) {
            this.toolbars[forName] = [];
        }

        this.toolbars[forName].push(template);

        if (this.toolbarContainers[forName]) {
            this.toolbarContainers[forName].toolbarsUpdated();
        }
    }

    public removeToolbarContainer(forName: string, template: TemplateRef<any>) {
        arrayRemoveItem(this.toolbars[forName], template);
        if (this.toolbarContainers[forName]) {
            this.toolbarContainers[forName].toolbarsUpdated();
        }
    }
}
