/**
 * TreeNav — dropdown navigation for Targets and Sources tabs.
 * Shows a compact trigger bar with the active leaf; click to open a
 * grouped dropdown menu for switching between sections.
 *
 * Config format (supports arbitrary nesting):
 *   [
 *     { key, titleKey, icon?, children: [
 *         { key, titleKey, icon?, children: [...] },   // nested group
 *         { key, titleKey, icon?, count }               // leaf
 *     ] },
 *     { key, titleKey, icon?, count }   // standalone leaf (no children)
 *   ]
 */

import { t } from './i18n.ts';

/** Recursively sum leaf counts in a tree node. */
function _deepCount(node: any): number {
    if (!node.children) return node.count || 0;
    return node.children.reduce((sum: number, c: any) => sum + _deepCount(c), 0);
}

export class TreeNav {
    containerId: string;
    onSelect: any;
    _items: any[];
    _leafMap: Map<string, any>;
    _activeLeaf: string | null;
    _extraHtml: string;
    _observerSuppressed: boolean;
    _open: boolean;
    _outsideHandler: any;
    _escHandler: any;
    _suppressTimer: any;
    _observer: IntersectionObserver | null;

    /**
     * @param containerId - ID of the nav element to render into
     * @param opts
     * @param opts.onSelect - callback(leafKey, leafData) when a leaf is clicked
     */
    constructor(containerId: string, { onSelect }: { onSelect: any }) {
        this.containerId = containerId;
        this.onSelect = onSelect;
        this._items = [];
        this._leafMap = new Map();   // key → leaf config
        this._activeLeaf = null;
        this._extraHtml = '';
        this._observerSuppressed = false;
        this._open = false;
        this._outsideHandler = null;
        this._escHandler = null;
        this._suppressTimer = null;
        this._observer = null;
    }

    /** Temporarily suppress scroll-spy (e.g. during programmatic scroll). */
    suppressObserver(ms = 800) {
        this._observerSuppressed = true;
        clearTimeout(this._suppressTimer);
        this._suppressTimer = setTimeout(() => { this._observerSuppressed = false; }, ms);
    }

    /**
     * Full re-render of the nav.
     * @param items - tree structure (groups and/or standalone leaves)
     * @param activeLeafKey
     */
    update(items: any[], activeLeafKey: string) {
        this._items = items;
        this._activeLeaf = activeLeafKey;
        this._buildLeafMap();
        this._render();
    }

    /** Update only the counts without full re-render. */
    updateCounts(countMap: Record<string, any>) {
        const container = document.getElementById(this.containerId);
        if (!container) return;
        for (const [key, count] of Object.entries(countMap)) {
            // Update leaves in dropdown panel
            const els = container.querySelectorAll(`[data-tree-leaf="${key}"] .tree-count`);
            els.forEach(el => { el.textContent = String(count); });
            // Update in-memory
            const leaf = this._leafMap.get(key);
            if (leaf) leaf.count = count;
        }
        // Update group counts (bottom-up: deepest first)
        const groups = Array.from(container.querySelectorAll('[data-tree-group]')) as HTMLElement[];
        groups.reverse();
        for (const groupEl of groups) {
            let total = 0;
            for (const cnt of Array.from(groupEl.querySelectorAll(':scope > .tree-dd-children > [data-tree-leaf] .tree-count'))) {
                total += parseInt(cnt.textContent || '0', 10) || 0;
            }
            for (const cnt of Array.from(groupEl.querySelectorAll(':scope > .tree-dd-children > [data-tree-group] > .tree-dd-group-header .tree-dd-group-count'))) {
                total += parseInt(cnt.textContent || '0', 10) || 0;
            }
            const gc = groupEl.querySelector(':scope > .tree-dd-group-header .tree-dd-group-count');
            if (gc) gc.textContent = String(total);
        }
        // Update trigger display if active leaf count changed
        if (this._activeLeaf && countMap[this._activeLeaf] !== undefined) {
            this._updateTrigger();
        }
    }

    /** Set extra HTML appended in the trigger bar (expand/collapse buttons, etc.) */
    setExtraHtml(html: string) {
        this._extraHtml = html;
    }

    /** Highlight a specific leaf. */
    setActive(leafKey: string) {
        this._activeLeaf = leafKey;
        const container = document.getElementById(this.containerId);
        if (!container) return;
        container.querySelectorAll('[data-tree-leaf]').forEach(el =>
            (el as HTMLElement).classList.toggle('active', (el as HTMLElement).dataset.treeLeaf === leafKey)
        );
        this._updateTrigger();
    }

    /** Get leaf data for a key. */
    getLeaf(key: string) {
        return this._leafMap.get(key);
    }

    /** Find the first leaf key whose subTab matches. */
    getLeafForSubTab(subTab: string) {
        for (const [key, leaf] of Array.from(this._leafMap)) {
            if ((leaf.subTab || key) === subTab) return key;
        }
        return null;
    }

    // ── internal ──

    _buildLeafMap() {
        this._leafMap.clear();
        this._collectLeaves(this._items);
    }

    _collectLeaves(items: any[]) {
        for (const item of items) {
            if (item.children) {
                this._collectLeaves(item.children);
            } else {
                this._leafMap.set(item.key, item);
            }
        }
    }

    _render() {
        const container = document.getElementById(this.containerId);
        if (!container) return;

        const leaf = this._leafMap.get(this._activeLeaf!);
        const triggerIcon = leaf?.icon || '';
        const triggerTitle = leaf ? t(leaf.titleKey) : '';
        const triggerCount = leaf?.count ?? 0;

        const panelHtml = this._items.map(item =>
            item.children ? this._renderGroup(item, 0) : this._renderLeaf(item)
        ).join('');

        container.innerHTML = `
            <div class="tree-dd-trigger" data-tree-trigger>
                <span class="tree-dd-trigger-icon">${triggerIcon}</span>
                <span class="tree-dd-trigger-title">${triggerTitle}</span>
                <span class="tree-dd-trigger-count tree-count">${triggerCount}</span>
                <span class="tree-dd-chevron">&#9662;</span>
                ${this._extraHtml ? `<span class="tree-dd-extra">${this._extraHtml}</span>` : ''}
            </div>
            <div class="tree-dd-panel" data-tree-panel>
                ${panelHtml}
            </div>`;

        this._bindEvents(container);
    }

    _renderGroup(group: any, depth: number) {
        const groupCount = _deepCount(group);
        const childrenHtml = group.children.map((child: any) =>
            child.children ? this._renderGroup(child, depth + 1) : this._renderLeaf(child)
        ).join('');

        return `
        <div class="tree-dd-group" data-tree-group="${group.key}">
            <div class="tree-dd-group-header tree-dd-depth-${depth}">
                ${group.icon ? `<span class="tree-node-icon">${group.icon}</span>` : ''}
                <span class="tree-dd-group-title" data-i18n="${group.titleKey}">${t(group.titleKey)}</span>
                <span class="tree-dd-group-count">${groupCount}</span>
            </div>
            <div class="tree-dd-children">
                ${childrenHtml}
            </div>
        </div>`;
    }

    _renderLeaf(leaf: any) {
        const isActive = leaf.key === this._activeLeaf;
        return `
        <div class="tree-dd-leaf${isActive ? ' active' : ''}" data-tree-leaf="${leaf.key}">
            ${leaf.icon ? `<span class="tree-node-icon">${leaf.icon}</span>` : ''}
            <span class="tree-node-title" data-i18n="${leaf.titleKey}">${t(leaf.titleKey)}</span>
            <span class="tree-count">${leaf.count ?? 0}</span>
        </div>`;
    }

    _updateTrigger() {
        const container = document.getElementById(this.containerId);
        if (!container) return;
        const leaf = this._leafMap.get(this._activeLeaf!);
        if (!leaf) return;
        const icon = container.querySelector('.tree-dd-trigger-icon');
        const title = container.querySelector('.tree-dd-trigger-title');
        const count = container.querySelector('.tree-dd-trigger-count');
        if (icon) icon.innerHTML = leaf.icon || '';
        if (title) title.textContent = t(leaf.titleKey);
        if (count) count.textContent = String(leaf.count ?? 0);
    }

    _openDropdown(container: HTMLElement) {
        if (this._open) return;
        this._open = true;
        const panel = container.querySelector('[data-tree-panel]') as HTMLElement | null;
        const trigger = container.querySelector('[data-tree-trigger]') as HTMLElement | null;
        if (panel) panel.classList.add('open');
        if (trigger) trigger.classList.add('open');

        this._outsideHandler = (e: Event) => {
            const inTrigger = trigger && trigger.contains(e.target as Node);
            const inPanel = panel && panel.contains(e.target as Node);
            if (!inTrigger && !inPanel) this._closeDropdown(container);
        };
        this._escHandler = (e: KeyboardEvent) => {
            if (e.key === 'Escape') this._closeDropdown(container);
        };
        // Use timeout so the current pointerdown doesn't immediately trigger close
        setTimeout(() => {
            window.addEventListener('mousedown', this._outsideHandler, true);
            window.addEventListener('pointerdown', this._outsideHandler, true);
            window.addEventListener('keydown', this._escHandler, true);
        }, 0);
    }

    _closeDropdown(container: HTMLElement) {
        if (!this._open) return;
        this._open = false;
        const panel = container.querySelector('[data-tree-panel]') as HTMLElement | null;
        const trigger = container.querySelector('[data-tree-trigger]') as HTMLElement | null;
        if (panel) panel.classList.remove('open');
        if (trigger) trigger.classList.remove('open');
        window.removeEventListener('mousedown', this._outsideHandler, true);
        window.removeEventListener('pointerdown', this._outsideHandler, true);
        window.removeEventListener('keydown', this._escHandler, true);
    }

    _bindEvents(container: HTMLElement) {
        // Trigger click — toggle dropdown
        const trigger = container.querySelector('[data-tree-trigger]');
        if (trigger) {
            trigger.addEventListener('pointerdown', (e) => {
                // Don't toggle when clicking extra buttons (expand/collapse/help)
                if ((e.target as HTMLElement).closest('.tree-dd-extra')) return;
                e.preventDefault();
                if (this._open) this._closeDropdown(container);
                else this._openDropdown(container);
            });
        }

        // Leaf click — select and close
        container.querySelectorAll('[data-tree-leaf]').forEach(el => {
            el.addEventListener('click', () => {
                const key = (el as HTMLElement).dataset.treeLeaf;
                this.setActive(key!);
                this._closeDropdown(container);
                this.suppressObserver();
                if (this.onSelect) this.onSelect(key, this._leafMap.get(key!));
            });
        });
    }

    /**
     * Start observing card-section elements so the active tree leaf
     * follows whichever section is currently visible on screen.
     * @param contentId - ID of the scrollable content container
     * @param sectionMap - optional { data-card-section → leafKey } override
     */
    observeSections(contentId: string, sectionMap?: Record<string, string>) {
        this.stopObserving();
        const content = document.getElementById(contentId);
        if (!content) return;

        // Build sectionKey → leafKey mapping
        const sectionToLeaf = new Map<string, string>();
        if (sectionMap) {
            for (const [sk, lk] of Object.entries(sectionMap)) sectionToLeaf.set(sk, lk);
        } else {
            for (const [key, leaf] of Array.from(this._leafMap)) {
                sectionToLeaf.set(leaf.sectionKey || key, key);
            }
        }

        const stickyTop = parseInt(getComputedStyle(document.documentElement)
            .getPropertyValue('--sticky-top')) || 90;

        // Track which sections are currently intersecting
        const visible = new Set<Element>();

        this._observer = new IntersectionObserver((entries) => {
            for (const entry of entries) {
                if (entry.isIntersecting) visible.add(entry.target);
                else visible.delete(entry.target);
            }
            if (this._observerSuppressed) return;
            // Read fresh rects to find the topmost visible section
            let bestEl: Element | null = null;
            let bestTop = Infinity;
            for (const el of Array.from(visible)) {
                const top = el.getBoundingClientRect().top;
                if (top < bestTop) { bestTop = top; bestEl = el; }
            }
            if (bestEl) {
                const sectionKey = (bestEl as HTMLElement).dataset.cardSection;
                const leafKey = sectionToLeaf.get(sectionKey!);
                if (leafKey && leafKey !== this._activeLeaf) {
                    this._activeLeaf = leafKey;
                    this.setActive(leafKey);
                }
            }
        }, {
            rootMargin: `-${stickyTop}px 0px -40% 0px`,
            threshold: 0
        });

        content.querySelectorAll('[data-card-section]').forEach(section => {
            if (sectionToLeaf.has((section as HTMLElement).dataset.cardSection!)) {
                this._observer!.observe(section);
            }
        });
    }

    /** Stop the scroll-spy observer. */
    stopObserving() {
        if (this._observer) {
            this._observer.disconnect();
            this._observer = null;
        }
    }
}
