import { createFileRoute, redirect, useNavigate } from "@tanstack/react-router";
import { AlertTriangle, ArrowLeft, Camera, CameraOff, ScanLine } from "lucide-react";
import jsQR from "jsqr";
import { useCallback, useEffect, useRef, useState } from "react";
import { AuthShell } from "../features/auth/AuthShell";
import { parseLinkApprovalUrl } from "../net/qr";
import { useAppStore } from "../store";
import { t } from "../i18n";
import { Button, Callout } from "../ui/primitives";
import s from "./link_.scan.module.css";

// 应用内扫码器：在已登录的 cdrop 里直接调相机扫码，解出 /link?r=&c= 后在同一已鉴权
// 会话内导航到批准页——绕开「系统相机打开链接落到未登录浏览器」的断流。批准走既有
// /link 页（step-up 开启时该页会自动要求再认证），本组件只负责「扫到合法码 → 导航」。
export const Route = createFileRoute("/link_/scan")({
    component: LinkScanPage,
    // 守卫：受限访客（scope=guest）不能授权新设备（后端 approve 亦 403），故连扫码
    // 授权页都不让进——直接弹回首页，避免误触。未登录交由批准页处理，这里只挡访客。
    beforeLoad: () =>
    {
        if (useAppStore.getState().sessionScope === "guest")
        {
            throw redirect({ to: "/" });
        }
    },
});

// 扫码器的几种态：请求相机中 / 正在扫 / 命中（淡出过渡）/ 无摄像头 / 权限被拒 / 其他错误。
type ScanState = "starting" | "scanning" | "hit" | "noCamera" | "denied" | "error";

function LinkScanPage()
{
    const navigate = useNavigate();
    const videoRef = useRef<HTMLVideoElement | null>(null);
    // 解码用的离屏 canvas，逐帧把视频画上去再喂给 jsQR。
    const canvasRef = useRef<HTMLCanvasElement | null>(null);
    const streamRef = useRef<MediaStream | null>(null);
    const rafRef = useRef<number | null>(null);
    // 命中后置位：阻止 rAF 循环继续解码 / 重复导航。
    const doneRef = useRef(false);

    const [ state, setState ] = useState<ScanState>("starting");
    const [ errorMsg, setErrorMsg ] = useState<string | null>(null);
    // 扫到无关 / 非本站二维码：提示「继续对准」，但相机继续扫，不打断。
    const [ sawForeignCode, setSawForeignCode ] = useState(false);

    // stop 释放相机：停轨道 + 取消 rAF。卸载、命中、出错时都调用，确保相机指示灯熄灭。
    const stop = useCallback(() =>
    {
        if (rafRef.current !== null) { cancelAnimationFrame(rafRef.current); rafRef.current = null; }
        const stream = streamRef.current;
        if (stream) { stream.getTracks().forEach((tr) => tr.stop()); streamRef.current = null; }
    }, []);

    // tick：单帧解码循环。把当前视频帧画到离屏 canvas，jsQR 解一次；解出有效本站
    // /link 码即停机 + 应用内导航，否则下一帧继续。
    const tick = useCallback(() =>
    {
        if (doneRef.current) { return; }
        const video = videoRef.current;
        const canvas = canvasRef.current;
        if (!video || !canvas || video.readyState < video.HAVE_ENOUGH_DATA)
        {
            rafRef.current = requestAnimationFrame(tick);
            return;
        }

        const w = video.videoWidth;
        const h = video.videoHeight;
        if (!w || !h)
        {
            rafRef.current = requestAnimationFrame(tick);
            return;
        }

        canvas.width = w;
        canvas.height = h;
        const ctx = canvas.getContext("2d", { willReadFrequently: true });
        if (!ctx)
        {
            rafRef.current = requestAnimationFrame(tick);
            return;
        }

        ctx.drawImage(video, 0, 0, w, h);
        const image = ctx.getImageData(0, 0, w, h);
        const result = jsQR(image.data, w, h, { inversionAttempts: "dontInvert" });

        if (result && result.data)
        {
            const link = parseLinkApprovalUrl(result.data);
            if (link)
            {
                // 命中合法本站码：停机、淡出，应用内导航到批准页（留在已登录 SPA 会话）。
                doneRef.current = true;
                stop();
                setState("hit");
                // p 不带：批准页会回落到更安全的默认「仅此次」（受限访客）。
                navigate({ to: "/link", search: { r: link.requestId, c: link.code, p: undefined } });
                return;
            }
            // 扫到了码，但不是本站 /link 码（别的二维码 / 钓鱼链接）：提示继续对准。
            setSawForeignCode(true);
        }

        rafRef.current = requestAnimationFrame(tick);
    }, [ navigate, stop ]);

    // 进页面即请求相机并起解码循环。无摄像头 / 权限被拒分别落到清晰的引导态。
    useEffect(() =>
    {
        doneRef.current = false;
        let cancelled = false;

        const start = async () =>
        {
            if (!navigator.mediaDevices?.getUserMedia)
            {
                setState("noCamera");
                return;
            }
            try
            {
                const stream = await navigator.mediaDevices.getUserMedia({
                    // 优先后置相机（手机扫码自然朝外）；桌面无后置则退到任意可用相机。
                    video: { facingMode: { ideal: "environment" } },
                    audio: false,
                });
                if (cancelled) { stream.getTracks().forEach((tr) => tr.stop()); return; }
                streamRef.current = stream;
                const video = videoRef.current;
                if (video)
                {
                    video.srcObject = stream;
                    // iOS Safari 要求 playsInline + 显式 play()，否则不出画面。
                    await video.play().catch(() => { /* 自动播放被拦：画面仍随轨道更新 */ });
                }
                setState("scanning");
                rafRef.current = requestAnimationFrame(tick);
            }
            catch (e)
            {
                if (cancelled) { return; }
                const name = e instanceof DOMException ? e.name : "";
                if (name === "NotAllowedError" || name === "SecurityError")
                {
                    setState("denied");
                }
                else if (name === "NotFoundError" || name === "OverconstrainedError")
                {
                    setState("noCamera");
                }
                else
                {
                    setErrorMsg(e instanceof Error ? e.message : String(e));
                    setState("error");
                }
            }
        };

        void start();
        return () => { cancelled = true; stop(); };
    }, [ tick, stop ]);

    const goBack = () => navigate({ to: "/" });

    // ---- 渲染分支 --------------------------------------------------------

    // 无摄像头（桌面常见）：优雅降级，引导改用手机扫，不报错。
    if (state === "noCamera")
    {
        return (
            <AuthShell title={t("qr.scan.title")} hideBrand skipAutospace>
                <Callout tone="info" icon={<CameraOff size={16} />}>
                    {t("qr.scan.noCamera")}
                </Callout>
                <Button variant="secondary" size="lg" block leftIcon={<ArrowLeft size={16} />} onClick={goBack}>
                    {t("qr.scan.back")}
                </Button>
            </AuthShell>
        );
    }

    // 权限被拒：明确告诉用户去哪儿改回来。
    if (state === "denied")
    {
        return (
            <AuthShell title={t("qr.scan.title")} hideBrand skipAutospace>
                <Callout tone="warn" icon={<CameraOff size={16} />}>
                    {t("qr.scan.denied")}
                </Callout>
                <Button variant="secondary" size="lg" block leftIcon={<ArrowLeft size={16} />} onClick={goBack}>
                    {t("qr.scan.back")}
                </Button>
            </AuthShell>
        );
    }

    if (state === "error")
    {
        return (
            <AuthShell title={t("qr.scan.title")} hideBrand skipAutospace>
                <Callout tone="error" icon={<AlertTriangle size={16} />}>
                    {errorMsg ?? t("qr.scan.error")}
                </Callout>
                <Button variant="secondary" size="lg" block leftIcon={<ArrowLeft size={16} />} onClick={goBack}>
                    {t("qr.scan.back")}
                </Button>
            </AuthShell>
        );
    }

    return (
        <AuthShell title={t("qr.scan.title")} subtitle={t("qr.scan.subtitle")} hideBrand skipAutospace>
            <div className={s.stage}>
                <div className={`${s.viewport} ${state === "hit" ? s.viewportHit : ""}`}>
                    <video
                        ref={videoRef}
                        className={s.video}
                        muted
                        playsInline
                        // 仅装饰性预览，扫码内容不读屏；语义流程在批准页。
                        aria-hidden="true"
                    />
                    {state === "scanning" && <span className={s.beam} aria-hidden="true" />}
                    <span className={`${s.tick} ${s.tl}`} aria-hidden="true" />
                    <span className={`${s.tick} ${s.tr}`} aria-hidden="true" />
                    <span className={`${s.tick} ${s.bl}`} aria-hidden="true" />
                    <span className={`${s.tick} ${s.br}`} aria-hidden="true" />
                </div>
                <canvas ref={canvasRef} style={{ display: "none" }} />

                <div className={s.statusLine}>
                    {state === "starting"
                        ? <><Camera size={14} />{t("qr.scan.starting")}</>
                        : <><ScanLine size={14} />{t("qr.scan.aiming")}</>}
                </div>
            </div>

            {/* 扫到非本站码：安静提示继续对准，相机不中断。 */}
            {sawForeignCode && state === "scanning" && (
                <Callout tone="info" icon={<ScanLine size={16} />}>
                    {t("qr.scan.foreign")}
                </Callout>
            )}

            <ol className={s.steps} data-jz-level="paragraph">
                <li>{t("qr.scan.howto1")}</li>
                <li>{t("qr.scan.howto2")}</li>
            </ol>

            <div className={s.actions}>
                <Button variant="ghost" size="lg" block leftIcon={<ArrowLeft size={16} />} onClick={goBack}>
                    {t("qr.scan.back")}
                </Button>
            </div>
        </AuthShell>
    );
}
