import { BrowserWindow, WebContentsView, View, Menu, clipboard, ipcMain, shell } from 'electron';
import { TAB_BAR_DATA_URL } from './tab-bar-html';
import { ALL_CLIENT_CSS } from './client-ui';
import { electronLog } from './logger';

const KRUNKER_SOCIAL = 'https://krunker.io/social.html';
const TAB_BAR_HEIGHT = 40;
const MAX_TABS = 20;

interface TabInfo {
    id: number;
    view: WebContentsView;
    title: string;
    url: string;
    loading: boolean;
}

interface TabWindowState {
    width: number;
    height: number;
    x: number | undefined;
    y: number | undefined;
    maximized: boolean;
}

type TabMode = 'same' | 'new';

export class TabManager {
    private tabs: TabInfo[] = [];
    private activeTabId: number | null = null;
    private tabBarView: WebContentsView;
    private containerView: View;
    private tabWindow: BrowserWindow | null = null;
    private visible = false;
    private nextId = 1;
    private mode: TabMode;
    private mainWin: BrowserWindow;
    private ses: Electron.Session;
    private preloadPath: string;
    private isGameURL: (url: string) => boolean;
    private titlePolls = new Map<number, ReturnType<typeof setInterval>>();
    private recentlyClosed: { url: string; title: string }[] = [];
    private getTabWindowState: () => TabWindowState;
    private saveTabWindowState: (state: TabWindowState) => void;
    private tabSaveTimer: ReturnType<typeof setTimeout> | null = null;

    constructor(
        win: BrowserWindow,
        ses: Electron.Session,
        preloadPath: string,
        mode: TabMode,
        isGameURL: (url: string) => boolean,
        getTabWindowState: () => TabWindowState,
        saveTabWindowState: (state: TabWindowState) => void,
    ) {
        this.mainWin = win;
        this.ses = ses;
        this.preloadPath = preloadPath;
        this.mode = mode;
        this.isGameURL = isGameURL;
        this.getTabWindowState = getTabWindowState;
        this.saveTabWindowState = saveTabWindowState;

        // ── Tab bar view (shared between both modes) ──
        this.tabBarView = new WebContentsView({
            webPreferences: {
                nodeIntegration: true,
                contextIsolation: false,
                sandbox: false,
            },
        });
        this.tabBarView.webContents.loadURL(TAB_BAR_DATA_URL);

        // ── Container view (holds tab bar + active tab content) ──
        this.containerView = new View();
        this.containerView.addChildView(this.tabBarView);

        // Tab bar keybinds (when tab bar itself is focused)
        this.tabBarView.webContents.on('before-input-event', (event, input) => {
            if (input.type !== 'keyDown') return;
            if (this.handleTabShortcut(event, input)) return;
        });

        if (mode === 'same') {
            this.initSameWindowMode();
        }
        // 'new' mode: tabWindow created lazily on first openTab()

        this.registerIPC();
    }

    // ── Same Window Mode Setup ──
    private initSameWindowMode(): void {
        this.mainWin.contentView.addChildView(this.containerView);
        this.containerView.setVisible(false);
        this.visible = false;
        this.mainWin.on('resize', () => this.updateLayout());
    }

    // ── New Window Mode: create/show the tab window ──
    private ensureTabWindow(): void {
        if (this.tabWindow && !this.tabWindow.isDestroyed()) return;

        const saved = this.getTabWindowState();

        this.tabWindow = new BrowserWindow({
            width: saved.width,
            height: saved.height,
            x: saved.x,
            y: saved.y,
            frame: true,
            backgroundColor: '#000000',
            autoHideMenuBar: true,
            title: 'KCC - Tabs',
            show: false,
            webPreferences: {
                nodeIntegration: false,
                contextIsolation: true,
                sandbox: true,
            },
        });
        this.tabWindow.removeMenu();

        if (saved.maximized) this.tabWindow.maximize();

        this.tabWindow.contentView.addChildView(this.containerView);
        this.containerView.setVisible(true);

        this.tabWindow.on('resize', () => {
            this.updateLayout();
            this.debounceSaveTabWindow();
        });
        this.tabWindow.on('move', () => this.debounceSaveTabWindow());
        this.tabWindow.on('close', () => {
            // Flush pending save before the window is destroyed
            if (this.tabSaveTimer) clearTimeout(this.tabSaveTimer);
            if (this.tabWindow && !this.tabWindow.isDestroyed()) {
                const bounds = this.tabWindow.getBounds();
                this.saveTabWindowState({
                    width: bounds.width,
                    height: bounds.height,
                    x: bounds.x,
                    y: bounds.y,
                    maximized: this.tabWindow.isMaximized(),
                });
            }
        });
        this.tabWindow.on('closed', () => {
            this.destroyAllTabs();
            this.tabWindow = null;
        });

        this.tabWindow.show();
    }

    private debounceSaveTabWindow(): void {
        if (this.tabSaveTimer) clearTimeout(this.tabSaveTimer);
        this.tabSaveTimer = setTimeout(() => {
            if (!this.tabWindow || this.tabWindow.isDestroyed()) return;
            const bounds = this.tabWindow.getBounds();
            this.saveTabWindowState({
                width: bounds.width,
                height: bounds.height,
                x: bounds.x,
                y: bounds.y,
                maximized: this.tabWindow.isMaximized(),
            });
        }, 1000);
    }

    // ── IPC from tab bar ──
    private registerIPC(): void {
        ipcMain.on('tab-switch', (_e, id: number) => this.switchToTab(id));
        ipcMain.on('tab-close', (_e, id: number) => this.closeTab(id));
        ipcMain.on('tab-new', () => this.openTab(KRUNKER_SOCIAL));
        ipcMain.on('tab-reorder', (_e, fromId: number, toId: number, side: string) => {
            this.reorderTab(fromId, toId, side as 'before' | 'after');
        });
        ipcMain.on('tab-back-to-game', () => {
            if (this.mode === 'same') {
                this.hideTabs();
            } else {
                this.mainWin.focus();
            }
        });
    }

    // ── Open a new tab ──
    openTab(url: string): number {
        if (this.tabs.length >= MAX_TABS) {
            const existing = this.tabs.find(t => t.url === url);
            if (existing) {
                this.switchToTab(existing.id);
                return existing.id;
            }
            electronLog.warn('[KCC-Tabs] Tab limit reached, ignoring openTab');
            return -1;
        }

        const id = this.nextId++;
        const view = this.createTabView(id);
        const tab: TabInfo = { id, view, title: this.titleFromUrl(url), url, loading: true };
        this.tabs.push(tab);

        if (this.mode === 'new') {
            this.ensureTabWindow();
        }

        this.switchToTab(id);
        this.showTabs();
        view.webContents.loadURL(url);

        return id;
    }

    // ── Create a WebContentsView for a tab ──
    private createTabView(tabId: number): WebContentsView {
        const view = new WebContentsView({
            webPreferences: {
                preload: this.preloadPath,
                session: this.ses,
                contextIsolation: false,
                nodeIntegration: false,
                sandbox: false,
                spellcheck: false,
            },
        });

        const wc = view.webContents;

        wc.on('did-finish-load', () => {
            wc.insertCSS(ALL_CLIENT_CSS).catch(() => {});
            wc.send('main_did-finish-load-tab');
            ipcMain.emit('throttle-state', { sender: wc } as any, 'menu');
            this.updateTabInfo(tabId, { loading: false });
            this.startTitleWatcher(tabId, wc);
        });

        wc.on('did-start-loading', () => {
            this.updateTabInfo(tabId, { loading: true });
        });

        wc.on('did-stop-loading', () => {
            this.updateTabInfo(tabId, { loading: false });
        });

        wc.on('page-title-updated', (_e, title) => {
            if (this.isGenericTitle(title)) return;
            this.updateTabInfo(tabId, { title });
        });

        wc.on('did-navigate', (_e, url) => {
            this.updateTabInfo(tabId, { url, title: this.titleFromUrl(url) });
        });

        wc.setWindowOpenHandler(({ url: linkUrl }) => {
            if (linkUrl.includes('krunker.io')) {
                if (this.isGameURL(linkUrl)) {
                    this.mainWin.loadURL(linkUrl);
                    if (this.mode === 'same') this.hideTabs();
                    else this.mainWin.focus();
                } else {
                    setImmediate(() => this.openTab(linkUrl));
                }
            } else {
                setImmediate(() => shell.openExternal(linkUrl));
            }
            return { action: 'deny' as const };
        });

        wc.on('will-navigate', (event, navUrl) => {
            if (navUrl.includes('krunker.io') && this.isGameURL(navUrl)) {
                event.preventDefault();
                this.mainWin.loadURL(navUrl);
                if (this.mode === 'same') this.hideTabs();
                else this.mainWin.focus();
            }
        });

        wc.on('context-menu', (_e, params) => {
            if (!params.linkURL) return;
            const items: Electron.MenuItemConstructorOptions[] = [];
            if (params.linkURL.includes('krunker.io') && !this.isGameURL(params.linkURL)) {
                items.push({ label: 'Open in New Tab', click: () => this.openTab(params.linkURL) });
            }
            items.push({ label: 'Copy Link', click: () => clipboard.writeText(params.linkURL) });
            if (!params.linkURL.includes('krunker.io')) {
                items.push({ label: 'Open in Browser', click: () => shell.openExternal(params.linkURL) });
            }
            if (items.length) Menu.buildFromTemplate(items).popup();
        });

        wc.on('before-input-event', (event, input) => {
            if (input.type !== 'keyDown') return;
            if (this.handleTabShortcut(event, input)) return;
            if (input.key === 'F12' && !input.control && !input.shift && !input.alt) {
                wc.toggleDevTools();
                event.preventDefault();
            }
        });

        return view;
    }

    // ── Switch active tab ──
    switchToTab(id: number): void {
        const tab = this.tabs.find(t => t.id === id);
        if (!tab) return;

        if (this.activeTabId !== null) {
            const prev = this.tabs.find(t => t.id === this.activeTabId);
            if (prev) {
                this.containerView.removeChildView(prev.view);
            }
        }

        this.activeTabId = id;
        this.containerView.addChildView(tab.view);
        this.updateLayout();
        this.broadcastTabState();
    }

    // ── Close a tab ──
    closeTab(id: number): void {
        const idx = this.tabs.findIndex(t => t.id === id);
        if (idx === -1) return;

        const tab = this.tabs[idx];

        if (this.activeTabId === id) {
            this.containerView.removeChildView(tab.view);
            this.activeTabId = null;
        }

        this.recentlyClosed.push({ url: tab.url, title: tab.title });
        if (this.recentlyClosed.length > 10) this.recentlyClosed.shift();

        this.stopTitleWatcher(id);
        tab.view.webContents.close();
        this.tabs.splice(idx, 1);

        if (this.tabs.length > 0) {
            const nextIdx = Math.min(idx, this.tabs.length - 1);
            this.switchToTab(this.tabs[nextIdx].id);
        } else {
            if (this.mode === 'same') {
                this.hideTabs();
            } else {
                if (this.tabWindow && !this.tabWindow.isDestroyed()) {
                    this.tabWindow.contentView.removeChildView(this.containerView);
                    this.tabWindow.close();
                }
            }
        }

        this.broadcastTabState();
    }

    // ── Show / hide tabs ──
    showTabs(): void {
        if (this.mode === 'same') {
            this.containerView.setVisible(true);
            this.visible = true;
            this.updateLayout();
        } else {
            this.ensureTabWindow();
            if (this.tabWindow && !this.tabWindow.isDestroyed()) {
                this.tabWindow.show();
                this.tabWindow.focus();
            }
            this.visible = true;
        }
    }

    hideTabs(): void {
        if (this.mode === 'same') {
            this.containerView.setVisible(false);
            this.visible = false;
            this.mainWin.focus();
        } else {
            this.mainWin.focus();
            this.visible = false;
        }
    }

    // ── Tab navigation ──
    nextTab(): void {
        if (this.tabs.length < 2 || this.activeTabId === null) return;
        const idx = this.tabs.findIndex(t => t.id === this.activeTabId);
        const next = (idx + 1) % this.tabs.length;
        this.switchToTab(this.tabs[next].id);
    }

    prevTab(): void {
        if (this.tabs.length < 2 || this.activeTabId === null) return;
        const idx = this.tabs.findIndex(t => t.id === this.activeTabId);
        const prev = (idx - 1 + this.tabs.length) % this.tabs.length;
        this.switchToTab(this.tabs[prev].id);
    }

    closeCurrentTab(): void {
        if (this.activeTabId !== null) this.closeTab(this.activeTabId);
    }

    // ── Reorder tabs via drag ──
    reorderTab(fromId: number, toId: number, side: 'before' | 'after'): void {
        const fromIdx = this.tabs.findIndex(t => t.id === fromId);
        const toIdx = this.tabs.findIndex(t => t.id === toId);
        if (fromIdx === -1 || toIdx === -1 || fromIdx === toIdx) return;

        const [tab] = this.tabs.splice(fromIdx, 1);
        let insertIdx = this.tabs.findIndex(t => t.id === toId);
        if (side === 'after') insertIdx++;
        this.tabs.splice(insertIdx, 0, tab);
        this.broadcastTabState();
    }

    // ── Jump to tab by position (0-based, -1 = last) ──
    switchToTabByIndex(index: number): void {
        if (this.tabs.length === 0) return;
        if (index < 0 || index >= this.tabs.length) index = this.tabs.length - 1;
        this.switchToTab(this.tabs[index].id);
    }

    // ── Reopen last closed tab ──
    reopenTab(): void {
        const entry = this.recentlyClosed.pop();
        if (entry) this.openTab(entry.url);
    }

    // ── Shared shortcut handler (returns true if handled) ──
    private handleTabShortcut(event: Electron.Event, input: Electron.Input): boolean {
        if (input.key === 'Escape' && !input.control && !input.shift && !input.alt) {
            if (this.mode === 'same') this.hideTabs();
            else this.mainWin.focus();
            event.preventDefault();
            return true;
        } else if (input.key === 'w' && input.control && !input.shift && !input.alt) {
            this.closeCurrentTab();
            event.preventDefault();
            return true;
        } else if (input.key === 'Tab' && input.control && !input.shift && !input.alt) {
            this.nextTab();
            event.preventDefault();
            return true;
        } else if (input.key === 'Tab' && input.control && input.shift && !input.alt) {
            this.prevTab();
            event.preventDefault();
            return true;
        } else if (input.key === 't' && input.control && !input.shift && !input.alt) {
            this.openTab(KRUNKER_SOCIAL);
            event.preventDefault();
            return true;
        } else if (input.key === 'T' && input.control && input.shift && !input.alt) {
            this.reopenTab();
            event.preventDefault();
            return true;
        } else if (input.key >= '1' && input.key <= '8' && input.control && !input.shift && !input.alt) {
            this.switchToTabByIndex(parseInt(input.key) - 1);
            event.preventDefault();
            return true;
        } else if (input.key === '9' && input.control && !input.shift && !input.alt) {
            this.switchToTabByIndex(-1);
            event.preventDefault();
            return true;
        }
        return false;
    }

    // ── Cleanup ──
    destroyAll(): void {
        this.destroyAllTabs();

        ipcMain.removeAllListeners('tab-switch');
        ipcMain.removeAllListeners('tab-close');
        ipcMain.removeAllListeners('tab-new');
        ipcMain.removeAllListeners('tab-reorder');
        ipcMain.removeAllListeners('tab-back-to-game');

        if (this.tabWindow && !this.tabWindow.isDestroyed()) {
            this.tabWindow.contentView.removeChildView(this.containerView);
            this.tabWindow.close();
            this.tabWindow = null;
        }

        if (this.mode === 'same') {
            try { this.mainWin.contentView.removeChildView(this.containerView); } catch { /* may already be removed */ }
        }
    }

    private destroyAllTabs(): void {
        for (const tab of this.tabs) {
            this.stopTitleWatcher(tab.id);
            if (this.activeTabId === tab.id) {
                this.containerView.removeChildView(tab.view);
            }
            if (!tab.view.webContents.isDestroyed()) {
                tab.view.webContents.close();
            }
        }
        this.tabs = [];
        this.activeTabId = null;
        this.broadcastTabState();
    }

    // ── Layout ──
    private updateLayout(): void {
        let bounds: { width: number; height: number };

        if (this.mode === 'same') {
            const [w, h] = this.mainWin.getContentSize();
            bounds = { width: w, height: h };
            this.containerView.setBounds({ x: 0, y: 0, width: w, height: h });
        } else if (this.tabWindow && !this.tabWindow.isDestroyed()) {
            const [w, h] = this.tabWindow.getContentSize();
            bounds = { width: w, height: h };
            this.containerView.setBounds({ x: 0, y: 0, width: w, height: h });
        } else {
            return;
        }

        this.tabBarView.setBounds({
            x: 0, y: 0,
            width: bounds.width,
            height: TAB_BAR_HEIGHT,
        });

        if (this.activeTabId !== null) {
            const tab = this.tabs.find(t => t.id === this.activeTabId);
            if (tab) {
                tab.view.setBounds({
                    x: 0,
                    y: TAB_BAR_HEIGHT,
                    width: bounds.width,
                    height: bounds.height - TAB_BAR_HEIGHT,
                });
            }
        }
    }

    // ── Update tab metadata and broadcast ──
    private updateTabInfo(id: number, updates: Partial<Pick<TabInfo, 'title' | 'url' | 'loading'>>): void {
        const tab = this.tabs.find(t => t.id === id);
        if (!tab) return;
        if (updates.title !== undefined) tab.title = updates.title;
        if (updates.url !== undefined) tab.url = updates.url;
        if (updates.loading !== undefined) tab.loading = updates.loading;
        this.broadcastTabState();
    }

    private broadcastTabState(): void {
        if (this.tabBarView.webContents.isDestroyed()) return;
        const data = this.tabs.map(t => ({
            id: t.id,
            title: t.title,
            active: t.id === this.activeTabId,
            loading: t.loading,
        }));
        this.tabBarView.webContents.send('tabs-update', data);
    }

    private static readonly GENERIC_TITLES = new Set([
        'krunker hub', 'krunker', 'krunker.io', '',
        'hub', 'social', 'profile', 'new tab', 'loading...',
    ]);

    private isGenericTitle(title: string): boolean {
        return TabManager.GENERIC_TITLES.has(title.toLowerCase().trim());
    }

    // ── Persistent URL watcher + DOM title extraction ──
    private startTitleWatcher(tabId: number, wc: Electron.WebContents): void {
        const existing = this.titlePolls.get(tabId);
        if (existing) clearInterval(existing);

        let lastUrl = '';
        let lastDom = '';
        const poll = setInterval(() => {
            if (wc.isDestroyed()) {
                clearInterval(poll);
                this.titlePolls.delete(tabId);
                return;
            }
            wc.executeJavaScript(
                `(function() {
                    var url = window.location.href;
                    var title = '';
                    var ph = document.getElementById('profileHolder');
                    if (ph && ph.style.display === 'block') {
                        var ns = document.getElementById('nameSwitch');
                        if (ns && ns.innerText) title = ns.innerText;
                    }
                    return JSON.stringify({ url: url, dom: title });
                })()`
            ).then((json: string) => {
                const { url, dom } = JSON.parse(json);
                if (url === lastUrl && dom === lastDom) return;
                lastUrl = url;
                lastDom = dom;

                const tab = this.tabs.find(t => t.id === tabId);
                if (!tab) return;

                if (dom) {
                    if (tab.title !== dom) {
                        this.updateTabInfo(tabId, { url, title: dom });
                    } else if (tab.url !== url) {
                        this.updateTabInfo(tabId, { url });
                    }
                    return;
                }

                if (tab.url !== url) {
                    this.updateTabInfo(tabId, { url, title: this.titleFromUrl(url) });
                }
            }).catch(() => {});
        }, 1000);
        this.titlePolls.set(tabId, poll);
    }

    private stopTitleWatcher(tabId: number): void {
        const poll = this.titlePolls.get(tabId);
        if (poll) {
            clearInterval(poll);
            this.titlePolls.delete(tabId);
        }
    }

    // ── Extract a display title from URL ──
    private titleFromUrl(url: string): string {
        try {
            const parsed = new URL(url);
            const p = parsed.searchParams.get('p');
            const q = parsed.searchParams.get('q');

            if (q) return q;

            if (p) {
                const pageMap: Record<string, string> = {
                    profile: 'Profile',
                    leaders: 'Leaderboard',
                    games: 'Games',
                    clans: 'Clans',
                    skins: 'Skins',
                    mods: 'Mods',
                    maps: 'Maps',
                    editor: 'Editor',
                    market: 'Market',
                    itemsales: 'Market Item',
                    inventory: 'Inventory',
                    settings: 'Settings',
                    feed: 'Hub',
                };
                return pageMap[p] || p.charAt(0).toUpperCase() + p.slice(1);
            }

            const path = parsed.pathname.replace(/\.html$/, '').replace(/^\//, '');
            if (path === 'social') return 'Hub';
            if (path) return path.charAt(0).toUpperCase() + path.slice(1);

            return 'New Tab';
        } catch {
            return 'New Tab';
        }
    }
}
