/**
 * @jsxImportSource @wsxjs/wsx-core
 * DocPage Component
 *
 * A component that dynamically loads and displays documentation pages.
 * Supports loading states, error handling, race condition prevention, and metadata caching.
 */

import { LightComponent, autoRegister, state } from "@wsxjs/wsx-core";
import { createLogger } from "@wsxjs/wsx-logger";
// Import Markdown component to register it as a custom element
import "@wsxjs/wsx-marked-components";
import { RouterUtils } from "@wsxjs/wsx-router";
import type { DocMetadata, LoadingState } from "../../types";
import { DocumentLoadError } from "../../types";
import styles from "./DocPage.css?inline";

const logger = createLogger("DocPage");

/**
 * 元数据缓存（全局共享）
 * 导出以便测试时重置
 */
export const metadataCache: {
    data: Record<string, DocMetadata> | null;
    promise: Promise<Record<string, DocMetadata>> | null;
} = {
    data: null,
    promise: null,
};

/**
 * 创建带超时的 fetch 请求（工具函数）
 * 支持合并传入的 signal 和超时 signal，确保两者都能正确取消请求
 */
async function fetchWithTimeout(
    url: string,
    options: RequestInit = {},
    timeout: number = 10000
): Promise<Response> {
    // 创建超时 AbortController
    const timeoutController = new AbortController();
    const timeoutId = setTimeout(() => timeoutController.abort(), timeout);

    // 如果传入了 signal，需要合并两个 signal
    let finalSignal: AbortSignal;
    if (options.signal) {
        // 创建一个新的 AbortController 来合并两个 signal
        const mergedController = new AbortController();

        // 监听传入的 signal，如果被取消则取消合并的 controller
        if (options.signal.aborted) {
            mergedController.abort();
        } else {
            options.signal.addEventListener("abort", () => {
                mergedController.abort();
            });
        }

        // 监听超时 signal，如果被取消则取消合并的 controller
        timeoutController.signal.addEventListener("abort", () => {
            mergedController.abort();
        });

        finalSignal = mergedController.signal;
    } else {
        // 如果没有传入 signal，直接使用超时 signal
        finalSignal = timeoutController.signal;
    }

    try {
        const response = await fetch(url, {
            ...options,
            signal: finalSignal,
        });
        clearTimeout(timeoutId);
        return response;
    } catch (error) {
        clearTimeout(timeoutId);
        if (error instanceof Error && error.name === "AbortError") {
            // 检查是哪个 signal 触发的取消
            const isTimeout = timeoutController.signal.aborted;
            const isUserCancel = options.signal?.aborted;

            if (isUserCancel) {
                throw new Error("Request was cancelled");
            } else if (isTimeout) {
                throw new Error(`Request timeout after ${timeout}ms`);
            } else {
                throw new Error("Request was aborted");
            }
        }
        throw error;
    }
}

/**
 * 加载元数据（带缓存和超时）
 */
async function loadMetadata(): Promise<Record<string, DocMetadata>> {
    // 如果已有缓存，直接返回
    if (metadataCache.data) {
        return metadataCache.data;
    }

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

    // 创建新的加载请求（带超时）
    metadataCache.promise = (async () => {
        try {
            const response = await fetchWithTimeout("/.wsx-press/docs-meta.json", {}, 5000);
            if (!response.ok) {
                throw new Error(`Failed to load metadata: ${response.statusText}`);
            }
            const data = (await response.json()) as Record<string, DocMetadata>;
            metadataCache.data = data;
            metadataCache.promise = null;
            return data;
        } catch (error) {
            metadataCache.promise = null;
            throw error;
        }
    })();

    return metadataCache.promise;
}

/**
 * DocPage Component
 *
 * Displays a documentation page by dynamically loading markdown content.
 */
@autoRegister({ tagName: "wsx-doc-page" })
export default class DocPage extends LightComponent {
    @state private loadingState: LoadingState = "idle";
    @state private markdown: string = "";
    @state private metadata: DocMetadata | null = null;
    @state private error: DocumentLoadError | null = null;

    private currentLoadingPath: string | null = null;
    private routeChangeUnsubscribe: (() => void) | null = null;
    private loadingAbortController: AbortController | null = null;
    private isLoading: boolean = false;
    private visibilityChangeHandler: (() => void) | null = null;

    constructor() {
        super({
            styles,
            styleName: "wsx-doc-page",
        });
        logger.debug("DocPage initialized");
    }

    protected onConnected() {
        super.onConnected?.();
        // 监听路由变化（当路由参数更新时重新加载文档）
        // 注意：使用 onRouteChange 确保在路由匹配完成后再加载文档
        this.routeChangeUnsubscribe = RouterUtils.onRouteChange(() => {
            // 路由切换时，重置加载状态并重新加载
            this.cancelLoading();
            this.loadDocument();
        });

        // 监听标签页/视图切换
        this.visibilityChangeHandler = () => {
            if (document.hidden) {
                // 标签页隐藏时，取消正在进行的加载
                this.cancelLoading();
            } else {
                // 标签页重新可见时，如果当前是 loading 状态，重新加载
                if (this.loadingState === "loading" && this.currentLoadingPath) {
                    logger.debug("Tab became visible, reloading document");
                    this.loadDocument();
                }
            }
        };
        document.addEventListener("visibilitychange", this.visibilityChangeHandler);

        // 延迟加载，确保路由已经初始化
        // 使用双重 requestAnimationFrame 确保路由匹配完成
        requestAnimationFrame(() => {
            requestAnimationFrame(() => {
                // 从 RouterUtils 获取路由参数（由 wsx-router 包维护）
                this.loadDocument();
            });
        });

        // Listen for hash changes
        window.addEventListener("hashchange", this.onHashChange);
    }

    protected onDisconnected() {
        super.onDisconnected?.();
        // 清理路由变化监听器
        if (this.routeChangeUnsubscribe) {
            this.routeChangeUnsubscribe();
            this.routeChangeUnsubscribe = null;
        }

        // 清理标签页切换监听器
        if (this.visibilityChangeHandler) {
            document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
            this.visibilityChangeHandler = null;
        }

        window.removeEventListener("hashchange", this.onHashChange);

        // 取消正在进行的加载请求
        this.cancelLoading();
    }

    /**
     * Handle hash change event
     */
    private onHashChange = () => {
        this.scrollToHash();
    };

    /**
     * 取消正在进行的加载请求
     */
    private cancelLoading(): void {
        if (this.loadingAbortController) {
            this.loadingAbortController.abort();
            this.loadingAbortController = null;
        }
        this.isLoading = false;
        // 如果当前是 loading 状态，重置为 idle
        if (this.loadingState === "loading") {
            this.loadingState = "idle";
        }
        this.currentLoadingPath = null;
    }

    protected onAttributeChanged(name: string, _oldValue: string, _newValue: string) {
        // params 属性变化时也重新加载（向后兼容，但主要依赖 RouterUtils）
        if (name === "params") {
            this.loadDocument();
        }
    }

    /**
     * 创建带超时的 fetch 请求（实例方法）
     */
    private async fetchWithTimeout(
        url: string,
        options: RequestInit = {},
        timeout: number = 10000
    ): Promise<Response> {
        return fetchWithTimeout(url, options, timeout);
    }

    /**
     * 从 RouterUtils 获取当前路由参数并加载文档
     */
    private async loadDocument(): Promise<void> {
        // 取消之前的加载请求（如果有）
        this.cancelLoading();

        try {
            // 创建新的 AbortController
            this.loadingAbortController = new AbortController();

            // 从 RouterUtils 获取当前路由信息
            const routeInfo = RouterUtils.getCurrentRoute();

            // 从路径中提取文档路径（支持多级路径）
            // 例如：/docs/guide/essentials/getting-started -> guide/essentials/getting-started
            let docPath: string;
            if (routeInfo.path.startsWith("/docs/")) {
                // 移除 /docs/ 前缀，得到文档路径
                docPath = routeInfo.path.slice(6); // 移除 "/docs/"
            } else {
                // 尝试从 params 获取（向后兼容）
                const params = routeInfo.params as { category?: string; page?: string };
                if (params.category && params.page) {
                    docPath = `${params.category}/${params.page}`;
                } else {
                    logger.warn("Invalid document path:", { path: routeInfo.path, params });
                    // 如果参数为空，可能是路由还未初始化，保持 idle 状态
                    if (Object.keys(params).length === 0) {
                        logger.debug("Route params not yet initialized, keeping idle state");
                        this.loadingState = "idle";
                        return;
                    }
                    this.loadingState = "error";
                    this.error = new DocumentLoadError(
                        "Invalid document path: path must start with /docs/",
                        "INVALID_PARAMS"
                    );
                    return;
                }
            }

            // 验证文档路径不为空
            if (!docPath || docPath.trim() === "") {
                logger.warn("Empty document path:", { path: routeInfo.path });
                this.loadingState = "error";
                this.error = new DocumentLoadError("Document path is empty", "INVALID_PARAMS");
                return;
            }

            // 防止竞态条件：记录当前加载路径
            this.currentLoadingPath = docPath;
            this.isLoading = true;

            // 重置状态
            this.loadingState = "loading";
            this.markdown = "";
            this.error = null;
            this.metadata = null;

            // 加载元数据（内部已有超时处理）
            const metadataMap = await loadMetadata();

            // 检查是否已切换文档（防止竞态条件）
            if (this.currentLoadingPath !== docPath || this.loadingAbortController.signal.aborted) {
                logger.debug(`Document switched after metadata, ignoring result for ${docPath}`);
                this.isLoading = false;
                this.loadingState = "idle";
                return;
            }

            // 查找文档元数据
            const meta = metadataMap[docPath];
            if (!meta) {
                // 检查是否已切换文档
                if (
                    this.currentLoadingPath !== docPath ||
                    this.loadingAbortController.signal.aborted
                ) {
                    this.isLoading = false;
                    this.loadingState = "idle";
                    return;
                }
                this.loadingState = "error";
                this.error = new DocumentLoadError(`Document not found: ${docPath}`, "NOT_FOUND");
                this.isLoading = false;
                return;
            }

            this.metadata = meta;

            // 加载 Markdown 内容（带超时和取消支持）
            const markdownPath = `/docs/${docPath}.md`;
            const response = await this.fetchWithTimeout(
                markdownPath,
                { signal: this.loadingAbortController.signal },
                10000
            );

            // 再次检查是否已切换文档
            if (this.currentLoadingPath !== docPath || this.loadingAbortController.signal.aborted) {
                logger.debug(`Document switched during fetch, ignoring result for ${docPath}`);
                this.isLoading = false;
                this.loadingState = "idle";
                return;
            }

            if (!response.ok) {
                if (response.status === 404) {
                    this.loadingState = "error";
                    this.error = new DocumentLoadError(
                        `Document file not found: ${markdownPath}`,
                        "NOT_FOUND"
                    );
                } else {
                    this.loadingState = "error";
                    this.error = new DocumentLoadError(
                        `Failed to load document: ${response.statusText}`,
                        "NETWORK_ERROR",
                        { status: response.status }
                    );
                }
                this.isLoading = false;
                return;
            }

            const markdownContent = await response.text();

            // 最后一次检查是否已切换文档
            if (this.currentLoadingPath !== docPath || this.loadingAbortController.signal.aborted) {
                logger.debug(`Document switched after fetch, ignoring result for ${docPath}`);
                this.isLoading = false;
                this.loadingState = "idle";
                return;
            }

            this.markdown = markdownContent;
            this.loadingState = "success";
            this.isLoading = false;

            // 关键修复：确保状态变化触发重新渲染
            // 问题：当 loadingState = "loading" 时，scheduleRerender() 设置 _isRendering = true
            // _rerender() 使用嵌套的 requestAnimationFrame，_isRendering 在第二个 RAF 后才清除
            // 如果 loadingState = "success" 在第一个渲染完成前设置，scheduleRerender() 会被跳过
            // 解决方案：使用双重 requestAnimationFrame 确保在 _isRendering 清除后调用
            requestAnimationFrame(() => {
                requestAnimationFrame(() => {
                    if (this.connected) {
                        this.scheduleRerender();
                    }
                });
            });
        } catch (error) {
            this.isLoading = false;

            // 如果是取消请求，重置为 idle 状态，不显示错误
            if (error instanceof Error && error.name === "AbortError") {
                logger.debug("Document loading was cancelled");
                // 如果当前路径还存在，说明是用户切换了路由，保持 idle 状态
                // 如果当前路径已被清除，说明组件已卸载，不需要更新状态
                if (this.currentLoadingPath) {
                    this.loadingState = "idle";
                    this.currentLoadingPath = null;
                }
                return;
            }

            // 检查是否已切换文档（使用当前加载路径）
            if (!this.currentLoadingPath) {
                // 如果没有当前加载路径，说明参数获取失败或已切换
                this.loadingState = "error";
                this.error = new DocumentLoadError(
                    `Failed to load document: ${error instanceof Error ? error.message : String(error)}`,
                    "NETWORK_ERROR",
                    error
                );
                logger.error("Failed to load document (no current path):", error);
                return;
            }

            const docPath = this.currentLoadingPath;
            // 如果路径已改变，说明用户切换了路由，不显示错误
            if (this.currentLoadingPath !== docPath) {
                this.loadingState = "idle";
                return;
            }

            // 显示错误信息
            this.loadingState = "error";
            this.error = new DocumentLoadError(
                `Failed to load document: ${error instanceof Error ? error.message : String(error)}`,
                "NETWORK_ERROR",
                error
            );
            logger.error("Failed to load document", error);

            // 确保错误状态触发重新渲染（使用双重 RAF 确保在 _isRendering 清除后调用）
            requestAnimationFrame(() => {
                requestAnimationFrame(() => {
                    if (this.connected) {
                        this.scheduleRerender();
                    }
                });
            });
        }
    }

    /**
     * Scroll to the element matching the current hash
     * 使用重试机制处理异步渲染的内容
     */
    private scrollToHash(retryCount: number = 0) {
        const maxRetries = 5;
        const retryDelay = 100; // ms

        const hash = window.location.hash;
        if (!hash) return;

        // Remove the '#' character
        const id = decodeURIComponent(hash.slice(1));
        if (!id) return;

        // Try to find the element
        const element = document.getElementById(id);
        if (element) {
            // Scroll into view
            element.scrollIntoView({ behavior: "smooth", block: "start" });
            logger.debug(`Scrolled to anchor: ${id}`);
        } else if (retryCount < maxRetries) {
            // Element not found yet, retry after a short delay
            // This handles the case where markdown content is still being rendered
            setTimeout(() => {
                this.scrollToHash(retryCount + 1);
            }, retryDelay);
        } else {
            logger.warn(`Anchor element not found after ${maxRetries} retries: ${id}`);
        }
    }

    protected updated() {
        // Called after every render
        if (this.loadingState === "success") {
            // Attempt to scroll to hash if present
            // specific check to avoid scrolling on every update if not needed,
            // or we can rely on the fact that if user scrolled away, we shouldn't force it back?
            // Actually, typically we only do this on initial load or hash change.
            // But since content is loaded async, we need to do it once content is Ready.
            // Let's use a small delay to ensure DOM is ready.
            requestAnimationFrame(() => this.scrollToHash());
        }
    }

    render() {
        if (this.loadingState === "loading") {
            return (
                <div class="doc-page">
                    <div class="doc-loading">加载文档中...</div>
                </div>
            );
        }

        if (this.loadingState === "error") {
            return (
                <div class="doc-page">
                    <div class="doc-error">
                        <h2>加载失败</h2>
                        <p>{this.error?.message || "未知错误"}</p>
                        {this.error?.code === "NOT_FOUND" && (
                            <p>文档不存在，请检查路径是否正确。</p>
                        )}
                    </div>
                </div>
            );
        }

        if (this.loadingState === "success" && this.markdown) {
            return (
                <div class="doc-page">
                    {this.metadata && (
                        <div class="doc-header">
                            <h1 class="doc-title">{this.metadata.title}</h1>
                            {this.metadata.description && (
                                <p class="doc-description">{this.metadata.description}</p>
                            )}
                        </div>
                    )}
                    <div class="doc-content">
                        <wsx-markdown markdown={this.markdown} />
                    </div>
                </div>
            );
        }

        return (
            <div class="doc-page">
                <div class="doc-empty">请选择文档</div>
            </div>
        );
    }
}
