/**
 * @jsxImportSource @wsxjs/wsx-core
 * DocTOC Component
 *
 * Table of Contents component that displays TOC data generated by wsx-press plugin.
 */

import { LightComponent, autoRegister, state } from "@wsxjs/wsx-core";
import { createLogger } from "@wsxjs/wsx-logger";
import { RouterUtils } from "@wsxjs/wsx-router";
import type { TOCItem } from "../../types";
import styles from "./DocTOC.css?inline";

const logger = createLogger("DocTOC");

/**
 * TOC 数据缓存（全局共享）
 */
const tocCache: {
    data: Record<string, TOCItem[]> | null;
    promise: Promise<Record<string, TOCItem[]>> | null;
} = {
    data: null,
    promise: null,
};

/**
 * 加载 TOC 数据（带缓存）
 */
async function loadTOCData(): Promise<Record<string, TOCItem[]>> {
    if (tocCache.data) {
        return tocCache.data;
    }

    if (tocCache.promise) {
        return tocCache.promise;
    }

    tocCache.promise = (async () => {
        try {
            const response = await fetch("/.wsx-press/docs-toc.json");
            if (!response.ok) {
                throw new Error(`Failed to load TOC data: ${response.statusText}`);
            }
            const data = (await response.json()) as Record<string, TOCItem[]>;
            tocCache.data = data;
            tocCache.promise = null;
            return data;
        } catch (error) {
            tocCache.promise = null;
            throw error;
        }
    })();

    return tocCache.promise;
}

/**
 * DocTOC Component
 *
 * Displays table of contents using TOC data generated by wsx-press plugin.
 */
@autoRegister({ tagName: "wsx-doc-toc" })
export default class DocTOC extends LightComponent {
    @state private items: TOCItem[] = [];
    @state private activeId: string = "";

    private observer: IntersectionObserver | null = null;
    private headingElements: Map<string, HTMLElement> = new Map();
    private routeChangeUnsubscribe: (() => void) | null = null;

    constructor() {
        super({
            styles,
            styleName: "wsx-doc-toc",
        });
    }

    protected async onConnected() {
        super.onConnected?.();
        // 加载 TOC 数据
        await this.loadTOC();

        // 监听路由变化
        this.routeChangeUnsubscribe = RouterUtils.onRouteChange(() => {
            this.loadTOC();
        });
    }

    protected onDisconnected() {
        super.onDisconnected?.();
        if (this.observer) {
            this.observer.disconnect();
            this.observer = null;
        }
        if (this.routeChangeUnsubscribe) {
            this.routeChangeUnsubscribe();
            this.routeChangeUnsubscribe = null;
        }
    }

    /**
     * 加载当前文档的 TOC 数据
     */
    private async loadTOC(): Promise<void> {
        try {
            const routeInfo = RouterUtils.getCurrentRoute();
            // 从路径中提取 docPath（移除 /docs/ 前缀）
            let docPath: string;
            if (routeInfo.path.startsWith("/docs/")) {
                docPath = routeInfo.path.slice(6); // 移除 "/docs/" 前缀
            } else {
                this.items = [];
                return;
            }

            if (!docPath || docPath.trim() === "") {
                this.items = [];
                return;
            }

            const tocData = await loadTOCData();
            this.items = tocData[docPath] || [];

            // 为标题元素设置 ID（如果还没有）
            requestAnimationFrame(() => {
                this.setupHeadingIds();
                // 在标题 ID 设置完成后，重新设置滚动观察器
                this.setupScrollObserver();
            });
        } catch (error) {
            logger.error("Failed to load TOC", error);
            this.items = [];
        }
    }

    /**
     * 收集文档中的标题元素（使用已有的 ID）
     * 注意：不再重新设置 ID，而是使用 Heading 组件已经设置好的 ID
     */
    private setupHeadingIds(): void {
        const docContent = document.querySelector(".doc-content");
        if (!docContent) {
            return;
        }

        this.headingElements.clear();

        // 查找 wsx-marked-heading 组件内的标题元素（它们已经有正确的 ID）
        const markedHeadings = docContent.querySelectorAll("wsx-marked-heading");
        markedHeadings.forEach((wrapper) => {
            // 获取内部的 h1-h6 元素（Heading 组件渲染的带 ID 的元素）
            const heading = wrapper.querySelector("h1, h2, h3, h4, h5, h6");
            if (heading instanceof HTMLElement && heading.id) {
                this.headingElements.set(heading.id, heading);
            }
        });

        // 也支持直接的 h1-h6 元素（非 wsx-marked-heading 的情况）
        const directHeadings = docContent.querySelectorAll(
            ":scope > h1, :scope > h2, :scope > h3, :scope > h4, :scope > h5, :scope > h6"
        );
        directHeadings.forEach((heading) => {
            if (heading instanceof HTMLElement) {
                // 如果已经有 ID，直接使用
                if (heading.id) {
                    this.headingElements.set(heading.id, heading);
                } else {
                    // 否则生成 ID 并设置
                    const text = heading.textContent?.trim() || "";
                    if (text) {
                        const id = this.generateId(text);
                        heading.id = id;
                        this.headingElements.set(id, heading);
                    }
                }
            }
        });
    }

    /**
     * 生成锚点 ID
     * 必须与服务端 toc.ts 和 marked-utils.ts 中的 generateId 保持一致
     * 保留中文等 Unicode 字符，只移除特殊符号
     */
    private generateId(text: string): string {
        return text
            .toLowerCase()
            .replace(/\s+/g, "-") // 空格转连字符
            .replace(/[^\p{L}\p{N}-]/gu, "") // 保留字母、数字、连字符（Unicode-aware）
            .replace(/-+/g, "-") // 合并多个连字符
            .replace(/^-+|-+$/g, ""); // 移除首尾连字符
    }

    /**
     * 设置滚动观察器，高亮当前可见的标题
     */
    private setupScrollObserver(): void {
        if (!("IntersectionObserver" in window)) {
            return;
        }

        this.observer = new IntersectionObserver(
            (entries) => {
                // 找到最接近顶部的可见标题
                const visibleEntries = entries.filter((entry) => entry.isIntersecting);
                if (visibleEntries.length > 0) {
                    // 按位置排序，选择最接近顶部的
                    visibleEntries.sort((a, b) => {
                        const aTop = a.boundingClientRect.top;
                        const bTop = b.boundingClientRect.top;
                        return Math.abs(aTop) - Math.abs(bTop);
                    });
                    this.activeId = visibleEntries[0].target.id;
                }
            },
            {
                rootMargin: "-100px 0px -80% 0px",
                threshold: 0,
            }
        );

        // 观察所有标题元素
        this.headingElements.forEach((element) => {
            this.observer?.observe(element);
        });
    }

    /**
     * 处理 TOC 项点击
     */
    private handleTOCClick = (id: string, e: Event): void => {
        e.preventDefault();
        // 优先从缓存中获取，如果没有则通过 DOM 查找
        let element = this.headingElements.get(id);
        if (!element) {
            element = document.getElementById(id) as HTMLElement | null;
            if (element) {
                // 缓存找到的元素
                this.headingElements.set(id, element);
            }
        }
        if (element) {
            element.scrollIntoView({ behavior: "smooth", block: "start" });
            // 更新 URL hash（不触发滚动）
            window.history.replaceState(null, "", `#${id}`);
        } else {
            logger.warn(`Heading element not found for id: ${id}`);
        }
    };

    /**
     * 渲染 TOC 项
     */
    private renderTOCItem(item: TOCItem): HTMLElement {
        const isActive = this.activeId === item.id;
        const hasChildren = item.children.length > 0;

        return (
            <li class={`doc-toc-item doc-toc-item-level-${item.level}`}>
                <a
                    href={`#${item.id}`}
                    class={`doc-toc-link ${isActive ? "active" : ""}`}
                    onClick={(e) => this.handleTOCClick(item.id, e)}
                >
                    {item.text}
                </a>
                {hasChildren && (
                    <ul class="doc-toc-list doc-toc-sublist">
                        {item.children.map((child) => this.renderTOCItem(child))}
                    </ul>
                )}
            </li>
        ) as HTMLElement;
    }

    render() {
        if (this.items.length === 0) {
            return (
                <aside class="doc-toc">
                    <div class="doc-toc-empty">暂无目录</div>
                </aside>
            );
        }

        return (
            <aside class="doc-toc">
                <h3 class="doc-toc-title">目录</h3>
                <nav class="doc-toc-nav">
                    <ul class="doc-toc-list">
                        {this.items.map((item) => this.renderTOCItem(item))}
                    </ul>
                </nav>
            </aside>
        );
    }
}
