// *****************************************************************************
// Copyright (C) 2025 EclipseSource and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { injectable, inject, named } from '@theia/core/shared/inversify';
import { CommandRegistry, PreferenceService, DisposableCollection } from '@theia/core/lib/common';
import { TerminalWidget } from '@theia/terminal/lib/browser/base/terminal-widget';
import { TerminalFrontendContribution, TerminalCommands } from '@theia/terminal/lib/browser/terminal-frontend-contribution';
import { ApplicationShell, WidgetManager, FrontendApplicationContribution, FrontendApplication } from '@theia/core/lib/browser';
import { TerminalManagerWidget } from './terminal-manager-widget';
import { TerminalManagerFrontendViewContribution } from './terminal-manager-frontend-view-contribution';
import { TerminalManagerPreferences } from './terminal-manager-preferences';
import { ILogger } from '@theia/core';
/**
 * Re-registers terminal commands (e.g. new terminal) to execute them via the terminal manager
 * instead of creating new, separate terminals.
 */
@injectable()
export class TerminalManagerFrontendContribution implements FrontendApplicationContribution {
    @inject(TerminalFrontendContribution)
    protected readonly terminalFrontendContribution: TerminalFrontendContribution;

    @inject(TerminalManagerFrontendViewContribution)
    protected readonly terminalManagerViewContribution: TerminalManagerFrontendViewContribution;

    @inject(WidgetManager)
    protected readonly widgetManager: WidgetManager;

    @inject(ApplicationShell)
    protected readonly shell: ApplicationShell;

    @inject(PreferenceService)
    protected readonly preferenceService: PreferenceService;

    @inject(TerminalManagerPreferences)
    protected readonly preferences: TerminalManagerPreferences;

    @inject(CommandRegistry)
    protected readonly commandRegistry: CommandRegistry;

    @inject(ILogger) @named('terminal-manager:TerminalManagerFrontendContribution')
    protected readonly logger: ILogger;

    protected commandHandlerDisposables = new DisposableCollection();

    onStart(app: FrontendApplication): void {
        this.preferenceService.ready.then(() => {
            this.preferenceService.onPreferenceChanged(change => {
                if (change.preferenceName === 'terminal.grouping.mode') {
                    this.handleTabsDisplayChange(this.preferences['terminal.grouping.mode']);
                }
            });
            if (this.preferences.get('terminal.grouping.mode') !== 'tree') {
                this.logger.debug('Terminal tab style is not tree. Use separate terminal views.');
                return;
            }
            this.logger.debug('Terminal tab style is tree. Override command handlers accordingly.');
            this.registerHandlers();
        });
    }

    async initializeLayout(): Promise<void> {
        await this.preferenceService.ready;
        if (this.preferences.get('terminal.grouping.mode') !== 'tree') {
            return;
        }
        await this.terminalManagerViewContribution.openView({ activate: false });
    }

    protected async handleTabsDisplayChange(newValue: string): Promise<void> {
        if (newValue === 'tree') {
            await this.migrateTerminalsToManager();
            this.registerHandlers();
        } else {
            this.unregisterHandlers();
            await this.migrateTerminalsToTabs();
        }
    }

    /**
     * Migrate terminals from tabs to terminal manager. Applies only to terminals currently in bottom panel.
     */
    protected async migrateTerminalsToManager(): Promise<void> {
        const bottomTerminals = this.shell.getWidgets('bottom')
            .filter((w): w is TerminalWidget => w instanceof TerminalWidget);

        if (bottomTerminals.length === 0) {
            return;
        }

        const managerWidget = await this.widgetManager.getOrCreateWidget<TerminalManagerWidget>(TerminalManagerWidget.ID);
        if (!(managerWidget instanceof TerminalManagerWidget)) {
            return;
        }

        // Before terminal manager attachment to precede creation of default widget.
        for (const terminal of bottomTerminals) {
            const specialConfig = managerWidget.treeWidget.model.getSpecialPageConfig(terminal.kind);
            managerWidget.addTerminalPage(terminal, specialConfig?.pageId);
            terminal.show(); // Clear hidden flag that may have been set by dock panel on removal.
        }

        await this.terminalManagerViewContribution.openView({ reveal: true });
    }

    /**
     * Migrate terminals from the terminal manager to the bottom panel as separate tabs.
     */
    protected async migrateTerminalsToTabs(): Promise<void> {
        const managerWidget = this.terminalManagerViewContribution.tryGetWidget();
        if (!(managerWidget instanceof TerminalManagerWidget)) {
            return;
        }

        let toActivate: TerminalWidget | undefined = undefined;
        for (const terminal of managerWidget.drainWidgets()) {
            if (!terminal.isDisposed) {
                toActivate ??= terminal;
                this.shell.addWidget(terminal, { area: 'bottom' });
            }
        }

        if (toActivate) {
            this.shell.activateWidget(toActivate.id);
        }
    }

    protected unregisterHandlers(): void {
        this.commandHandlerDisposables.dispose();
        this.commandHandlerDisposables = new DisposableCollection();
    }

    protected registerHandlers(): void {
        this.unregisterHandlers();
        this.registerCommands();
    }

    protected registerCommands(): void {
        this.commandHandlerDisposables.push(this.commandRegistry.registerHandler(TerminalCommands.NEW.id, {
            execute: async () => {
                // Only create a new terminal if the view was existing as opening it automatically create a terminal
                const existing = this.terminalManagerViewContribution.tryGetWidget();
                const managerWidget = await this.terminalManagerViewContribution.openView({ reveal: true });
                if (managerWidget instanceof TerminalManagerWidget && existing) {
                    const terminalWidget = await managerWidget.createTerminalWidget();
                    managerWidget.addTerminalPage(terminalWidget);
                }
            }
        }));

        this.commandHandlerDisposables.push(this.commandRegistry.registerHandler(TerminalCommands.NEW_ACTIVE_WORKSPACE.id, {
            execute: async () => {
                // Only create a new terminal if the view was existing as opening it automatically create a terminal
                const existing = this.terminalManagerViewContribution.tryGetWidget();
                const managerWidget = await this.terminalManagerViewContribution.openView({ reveal: true });
                if (managerWidget instanceof TerminalManagerWidget && existing) {
                    const terminalWidget = await managerWidget.createTerminalWidget();
                    managerWidget.addTerminalPage(terminalWidget);
                }
            }
        }));

        this.commandHandlerDisposables.push(this.commandRegistry.registerHandler(TerminalCommands.SPLIT.id, {
            execute: async () => {
                const managerWidget = await this.terminalManagerViewContribution.openView({ reveal: true });
                if (managerWidget instanceof TerminalManagerWidget) {
                    const terminalWidget = await managerWidget.createTerminalWidget();
                    const { model } = managerWidget.treeWidget;
                    const activeGroupId = model.activeGroupNode?.id;
                    const activePageId = model.activePageNode?.id;

                    if (activeGroupId) {
                        managerWidget.addWidgetToTerminalGroup(terminalWidget, activeGroupId);
                    } else if (activePageId) {
                        managerWidget.addTerminalGroupToPage(terminalWidget, activePageId);
                    } else {
                        managerWidget.addTerminalPage(terminalWidget);
                    }
                }
            },
            isEnabled: w => w instanceof TerminalWidget || w instanceof TerminalManagerWidget,
            isVisible: w => w instanceof TerminalWidget || w instanceof TerminalManagerWidget,
        }));

        this.commandHandlerDisposables.push(this.commandRegistry.registerHandler(TerminalCommands.TOGGLE_TERMINAL.id, {
            execute: async () => {
                const existing = this.terminalManagerViewContribution.tryGetWidget();
                if (!existing || !(existing instanceof TerminalManagerWidget)) {
                    const managerWidget = await this.terminalManagerViewContribution.openView({ activate: true });
                    if (managerWidget instanceof TerminalManagerWidget && !this.shell.isExpanded('bottom')) {
                        this.shell.expandPanel('bottom');
                    }
                    return;
                }

                if (!existing.isAttached) {
                    await this.terminalManagerViewContribution.openView({ activate: true });
                    if (!this.shell.isExpanded('bottom')) {
                        this.shell.expandPanel('bottom');
                    }
                    return;
                }

                if (!this.shell.isExpanded('bottom')) {
                    this.shell.expandPanel('bottom');
                    this.shell.bottomPanel.activateWidget(existing);
                    return;
                }

                const active = this.shell.activeWidget;
                const isManagerOrChildActive = active === existing || Array.from(existing.terminalWidgets.values()).some(widget => widget === active);
                if (isManagerOrChildActive) {
                    this.shell.collapsePanel('bottom');
                } else {
                    this.shell.bottomPanel.activateWidget(existing);
                }
            }
        }));
    }
}
