import {
    useEffect,
    useRef,
    useState,
    useSyncExternalStore,
    type ReactNode,
} from "react";
import { AlertTriangle, CheckCircle2, Info, X, XCircle } from "lucide-react";
import s from "./Toast.module.css";

export type ToastTone = "info" | "ok" | "warn" | "error";

export interface ToastInput
{
    title: ReactNode;
    /** 正文内容（可选,只有标题时省略）。 */
    content?: ReactNode;
    tone?: ToastTone;
    /** 持续时长（ms）。默认 5000;0 = 永不自动关闭。 */
    duration?: number;
}

interface ToastRecord extends Required<Omit<ToastInput, "content">>
{
    id: number;
    content?: ReactNode;
}

// ---- 模块内 manager:外部通过 toast.xxx() 调用即可,不需要 React context ----

type Listener = (list: ToastRecord[]) => void;
const listeners = new Set<Listener>();
let list: ToastRecord[] = [];
let nextId = 1;

function emit() { for (const l of listeners) { l(list); } }

function show(input: ToastInput): number
{
    const id = nextId++;
    const rec: ToastRecord = {
        id,
        title:    input.title,
        content:  input.content,
        tone:     input.tone ?? "info",
        duration: input.duration ?? 5000,
    };
    list = [ ...list, rec ];
    emit();
    return id;
}

function dismiss(id: number)
{
    list = list.filter((t) => t.id !== id);
    emit();
}

function subscribe(l: Listener): () => void
{
    listeners.add(l);
    return () => { listeners.delete(l); };
}

function snapshot() { return list; }

export const toast = {
    show,
    dismiss,
    info:  (title: ReactNode, content?: ReactNode, duration?: number) =>
        show({ title, content, tone: "info",  duration }),
    ok:    (title: ReactNode, content?: ReactNode, duration?: number) =>
        show({ title, content, tone: "ok",    duration }),
    warn:  (title: ReactNode, content?: ReactNode, duration?: number) =>
        show({ title, content, tone: "warn",  duration }),
    error: (title: ReactNode, content?: ReactNode, duration?: number) =>
        show({ title, content, tone: "error", duration: duration ?? 7000 }),
};

// ---- React 视图层 ----

const TONE_CLASS: Record<ToastTone, string> = {
    info:  s.toneInfo,
    ok:    s.toneOk,
    warn:  s.toneWarn,
    error: s.toneError,
};

const TONE_ICON: Record<ToastTone, ReactNode> = {
    info:  <Info size={16} />,
    ok:    <CheckCircle2 size={16} />,
    warn:  <AlertTriangle size={16} />,
    error: <XCircle size={16} />,
};

export function ToastViewport()
{
    const items = useSyncExternalStore(subscribe, snapshot, snapshot);
    return (
        <div className={s.viewport} aria-live="polite" aria-atomic="false">
            {items.map((it) => <ToastCard key={it.id} record={it} />)}
        </div>
    );
}

function ToastCard(props: { record: ToastRecord })
{
    const { record } = props;
    const [ paused, setPaused ] = useState(false);
    const [ leaving, setLeaving ] = useState(false);
    // 用 elapsed 实际逝去时间(ms),非匀速 tick — 暂停期间不累加。
    const [ elapsed, setElapsed ] = useState(0);
    const lastTickRef = useRef<number>(performance.now());

    const total = record.duration;
    const autoClose = total > 0;

    useEffect(() =>
    {
        if (!autoClose) { return; }
        let raf = 0;
        const tick = () =>
        {
            const now = performance.now();
            const dt = now - lastTickRef.current;
            lastTickRef.current = now;
            if (!paused)
            {
                setElapsed((e) =>
                {
                    const next = e + dt;
                    if (next >= total)
                    {
                        // 在下一帧触发退出动画 + 真正移除;避免在 setState 内部
                        // dispatch 引起其它 toast 重渲染问题。
                        setLeaving(true);
                        window.setTimeout(() => toast.dismiss(record.id), 220);
                        return total;
                    }
                    return next;
                });
            }
            raf = window.requestAnimationFrame(tick);
        };
        raf = window.requestAnimationFrame(tick);
        return () => window.cancelAnimationFrame(raf);
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [ paused, autoClose, total, record.id ]);

    const remaining = Math.max(0, total - elapsed);
    const widthPct = autoClose ? (remaining / total) * 100 : 100;

    return (
        <div
            className={[
                s.toast,
                TONE_CLASS[record.tone],
                leaving ? s.leaving : "",
            ].filter(Boolean).join(" ")}
            onMouseEnter={() => setPaused(true)}
            onMouseLeave={() => setPaused(false)}
            role={record.tone === "error" ? "alert" : "status"}
        >
            <span className={s.icon}>{TONE_ICON[record.tone]}</span>
            <div className={s.body}>
                <div className={s.title}>{record.title}</div>
                {record.content && <div className={`${s.content} justify`} data-jz-level="paragraph">{record.content}</div>}
            </div>
            <button
                type="button"
                className={s.close}
                aria-label="关闭"
                onClick={() =>
                {
                    setLeaving(true);
                    window.setTimeout(() => toast.dismiss(record.id), 220);
                }}
            >
                <X size={14} />
            </button>

            {autoClose && (
                <div className={s.progressTrack} aria-hidden="true">
                    <div className={s.progressFill} style={{ width: `${widthPct}%` }} />
                </div>
            )}
        </div>
    );
}
