/**
 * @jsxImportSource @wsxjs/wsx-core
 * DocSearch Component
 *
 * A search component that uses Fuse.js to search through documentation.
 * Supports real-time search, result highlighting, and keyboard navigation.
 */

import { LightComponent, autoRegister, state } from "@wsxjs/wsx-core";
import { createLogger } from "@wsxjs/wsx-logger";
import Fuse from "fuse.js";
import type { SearchIndex, SearchResult, SearchDocument } from "../../types";
import styles from "./DocSearch.css?inline";

const logger = createLogger("DocSearch");

/**
 * 搜索索引缓存（全局共享）
 * 导出以便测试时重置
 */
export const searchIndexCache: {
    data: SearchIndex | null;
    promise: Promise<SearchIndex> | null;
} = {
    data: null,
    promise: null,
};

/**
 * 加载搜索索引（带缓存）
 */
async function loadSearchIndex(): Promise<SearchIndex> {
    // 如果已有缓存，直接返回
    if (searchIndexCache.data) {
        return searchIndexCache.data;
    }

    // 如果正在加载，等待现有请求
    if (searchIndexCache.promise) {
        return searchIndexCache.promise;
    }

    // 创建新的加载请求
    searchIndexCache.promise = (async () => {
        try {
            const response = await fetch("/.wsx-press/search-index.json");
            if (!response.ok) {
                throw new Error(`Failed to load search index: ${response.statusText}`);
            }
            const data = (await response.json()) as SearchIndex;
            searchIndexCache.data = data;
            searchIndexCache.promise = null;
            return data;
        } catch (error) {
            searchIndexCache.promise = null;
            throw error;
        }
    })();

    return searchIndexCache.promise;
}

/**
 * DocSearch Component
 *
 * Provides real-time search functionality for documentation.
 */
@autoRegister({ tagName: "wsx-doc-search" })
export default class DocSearch extends LightComponent {
    @state private query: string = "";
    @state private results: SearchResult[] = [];
    @state private isOpen: boolean = false;
    @state private isLoading: boolean = false;
    @state private selectedIndex: number = -1;

    private fuse: Fuse<SearchDocument> | null = null;
    private searchTimeout: number | null = null;

    constructor() {
        super({
            styles,
            styleName: "wsx-doc-search",
        });
        logger.info("DocSearch initialized");
    }

    protected async onConnected() {
        super.onConnected?.();
        // 预加载搜索索引
        try {
            const index = await loadSearchIndex();
            this.fuse = new Fuse(index.documents, index.options);
        } catch (error) {
            logger.error("Failed to load search index", error);
        }
    }

    /**
     * 处理搜索输入
     */
    private handleInput = (e: Event) => {
        const target = e.target as HTMLInputElement;
        this.query = target.value;

        // 清除之前的定时器
        if (this.searchTimeout !== null) {
            clearTimeout(this.searchTimeout);
        }

        // 如果查询为空，清空结果
        if (!this.query.trim()) {
            this.results = [];
            this.selectedIndex = -1;
            this.isOpen = false;
            return;
        }

        // 延迟搜索（防抖）
        this.searchTimeout = window.setTimeout(() => {
            this.performSearch();
        }, 300);
    };

    /**
     * 执行搜索
     */
    private async performSearch(): Promise<void> {
        if (!this.fuse) {
            // 如果索引未加载，尝试加载
            try {
                this.isLoading = true;
                const index = await loadSearchIndex();
                this.fuse = new Fuse(index.documents, index.options);
            } catch (error) {
                logger.error("Failed to load search index", error);
                this.isLoading = false;
                return;
            }
        }

        this.isLoading = false;

        if (!this.query.trim()) {
            this.results = [];
            this.isOpen = false;
            return;
        }

        // 执行搜索
        const searchResults = this.fuse.search(this.query.trim());
        this.results = searchResults as SearchResult[];
        // 如果有查询但没有结果，也要显示"无结果"消息，所以 isOpen 应该为 true
        this.isOpen = this.query.trim().length > 0;
        this.selectedIndex = -1;
    }

    /**
     * 处理键盘导航
     */
    private handleKeyDown = (e: KeyboardEvent) => {
        if (!this.isOpen || this.results.length === 0) {
            return;
        }

        switch (e.key) {
            case "ArrowDown":
                e.preventDefault();
                this.selectedIndex = Math.min(this.selectedIndex + 1, this.results.length - 1);
                break;
            case "ArrowUp":
                e.preventDefault();
                this.selectedIndex = Math.max(this.selectedIndex - 1, -1);
                break;
            case "Enter":
                e.preventDefault();
                if (this.selectedIndex >= 0 && this.selectedIndex < this.results.length) {
                    this.selectResult(this.results[this.selectedIndex]);
                } else if (this.results.length > 0) {
                    this.selectResult(this.results[0]);
                }
                break;
            case "Escape":
                e.preventDefault();
                this.isOpen = false;
                this.query = "";
                this.results = [];
                this.selectedIndex = -1;
                break;
        }
    };

    /**
     * 选择搜索结果
     */
    private selectResult(result: SearchResult): void {
        // 触发自定义事件，让父组件处理导航
        const event = new CustomEvent("doc-search-select", {
            detail: { route: result.item.route },
            bubbles: true,
        });
        this.dispatchEvent(event);

        // 关闭搜索
        this.isOpen = false;
        this.query = "";
        this.results = [];
        this.selectedIndex = -1;
    }

    /**
     * 高亮匹配文本，返回 JSX 元素数组
     */
    private highlightTextNodes(
        text: string,
        matches: Array<{ indices: [number, number][] }> | undefined
    ): (HTMLElement | string)[] {
        if (!matches || matches.length === 0) {
            return [text];
        }

        // 合并所有匹配位置
        const allIndices: Array<[number, number]> = [];
        for (const match of matches) {
            if (match.indices) {
                allIndices.push(...match.indices);
            }
        }

        // 按开始位置排序
        allIndices.sort((a, b) => a[0] - b[0]);

        // 合并重叠的区间
        const merged: Array<[number, number]> = [];
        for (const [start, end] of allIndices) {
            if (merged.length === 0 || merged[merged.length - 1][1] < start) {
                merged.push([start, end + 1]);
            } else {
                merged[merged.length - 1][1] = Math.max(merged[merged.length - 1][1], end + 1);
            }
        }

        // 构建节点数组
        const nodes: (HTMLElement | string)[] = [];
        let lastIndex = 0;
        for (const [start, end] of merged) {
            // 添加匹配前的文本
            if (start > lastIndex) {
                nodes.push(text.substring(lastIndex, start));
            }
            // 添加高亮标记
            const mark = document.createElement("mark");
            mark.textContent = text.substring(start, end);
            nodes.push(mark);
            lastIndex = end;
        }
        // 添加剩余文本
        if (lastIndex < text.length) {
            nodes.push(text.substring(lastIndex));
        }

        return nodes;
    }

    render() {
        return (
            <div class="doc-search">
                <div class="search-input-wrapper">
                    <input
                        type="text"
                        class="search-input"
                        placeholder="搜索文档..."
                        value={this.query}
                        onInput={this.handleInput}
                        onKeyDown={this.handleKeyDown}
                        onFocus={() => {
                            if (this.results.length > 0) {
                                this.isOpen = true;
                            }
                        }}
                    />
                    {this.isLoading && <div class="search-loading">加载中...</div>}
                </div>

                {this.isOpen && this.results.length > 0 && (
                    <div class="search-results">
                        {this.results.map((result, index) => {
                            const isSelected = index === this.selectedIndex;
                            const titleMatches = result.matches?.find((m) => m.key === "title");
                            const contentMatches = result.matches?.find((m) => m.key === "content");

                            return (
                                <div
                                    key={result.item.id}
                                    class={`search-result ${isSelected ? "selected" : ""}`}
                                    onClick={() => this.selectResult(result)}
                                    onMouseEnter={() => {
                                        this.selectedIndex = index;
                                    }}
                                >
                                    <div class="result-title">
                                        {titleMatches
                                            ? this.highlightTextNodes(result.item.title, [
                                                  titleMatches,
                                              ])
                                            : result.item.title}
                                    </div>
                                    <div class="result-category">{result.item.category}</div>
                                    {contentMatches && (
                                        <div class="result-snippet">
                                            {this.highlightTextNodes(
                                                result.item.content.substring(0, 100),
                                                [contentMatches]
                                            )}
                                        </div>
                                    )}
                                </div>
                            );
                        })}
                    </div>
                )}

                {this.isOpen &&
                    this.query.trim() &&
                    this.results.length === 0 &&
                    !this.isLoading && <div class="search-no-results">未找到匹配的文档</div>}
            </div>
        );
    }
}
