"use client";

import { useCallback, useEffect, useRef, useState } from "react";

import { api } from "@/lib/api/client";
import { useToast } from "@/components/Toast";
import { useJobPoll } from "@/lib/jobs/useJobPoll";
import {
  authOpenUrl,
  connectPhase,
  toConnectionStatus,
  toDeviceDisplay,
  type ConnectPhase,
  type DeviceDisplay,
} from "./kimiOauth";

const START = "/settings/providers/kimi-code/oauth/start";
const DISCONNECT = "/settings/providers/kimi-code/oauth/disconnect";

export interface UseKimiOauth {
  // 派生连接阶段（idle/awaiting/connected/error），驱动 UI。
  phase: ConnectPhase;
  // device 启动后展示给用户的信息（user_code + 验证 URL）；未启动→null。
  device: DeviceDisplay | null;
  // 已连接时的 access token 过期时刻（ISO8601；可能为 null）。
  expiresAt: string | null;
  // 任一在途请求（start/disconnect）或正在轮询。
  busy: boolean;
  // 错误文案（device 启动失败 / 轮询失败 / 过期 / 拒绝）。
  error: string | null;
  // 发起连接：POST start → 拿 user_code + job_id → 开浏览器 + 轮询 job。
  connect: () => Promise<void>;
  // 断开：POST disconnect → 复位为未连接。
  disconnect: () => Promise<void>;
}

// 在浏览器打开授权页（device complete URL 优先）。SSR/无 window 时静默跳过。
function openVerification(device: DeviceDisplay): void {
  if (typeof window === "undefined") return;
  window.open(authOpenUrl(device), "_blank", "noopener,noreferrer");
}

// Kimi Code OAuth device-flow 连接编排（K1.4）。
// connect：POST .../oauth/start（202）→ 展示 user_code + 开浏览器 → 轮询 job 到 done/failed。
// status 进页传入 initialConnected/initialExpiresAt（Server Component 取）。
export function useKimiOauth(args: {
  initialConnected: boolean;
  initialExpiresAt: string | null;
}): UseKimiOauth {
  const [connected, setConnected] = useState(args.initialConnected);
  const [expiresAt, setExpiresAt] = useState<string | null>(
    args.initialExpiresAt,
  );
  const [device, setDevice] = useState<DeviceDisplay | null>(null);
  const [started, setStarted] = useState(false);
  const [inFlight, setInFlight] = useState(false);
  const poll = useJobPoll();
  const toast = useToast();
  const startedRef = useRef(false);

  const phase = connectPhase({ started, connected, poll });

  // refreshStatus 无依赖、稳定（useCallback []）；声明在 effect 之前以便纳入依赖。
  const refreshStatus = useCallback(async (): Promise<boolean> => {
    const { data, error } = await api.GET(
      "/settings/providers/kimi-code/oauth/status",
    );
    if (error || !data) return false;
    const status = toConnectionStatus(data);
    setExpiresAt(status.expiresAt);
    return status.connected;
  }, []);

  // 轮询终态：done 且 job.connected → 已连接（刷新状态）；error → 提示。
  // refreshStatus / toast 均稳定，poll.error 随状态变化——全部纳入依赖，无 stale-closure。
  useEffect(() => {
    if (!startedRef.current) return;
    if (poll.status === "done") {
      void refreshStatus().then((ok) => {
        if (ok) {
          setConnected(true);
          toast("已连接 Kimi Code。", "success");
        }
      });
    }
    if (poll.status === "error") {
      toast(`连接失败：${poll.error ?? "授权未完成或已过期"}`, "error");
    }
  }, [poll.status, poll.error, refreshStatus, toast]);

  const connect = useCallback<UseKimiOauth["connect"]>(async () => {
    setInFlight(true);
    try {
      const { data, error } = await api.POST(START, {});
      if (error || !data) {
        toast("发起 Kimi Code 连接失败，请稍后重试。", "error");
        return;
      }
      const dev = toDeviceDisplay(data);
      setDevice(dev);
      setStarted(true);
      startedRef.current = true;
      openVerification(dev);
      poll.poll(data.job_id);
    } finally {
      setInFlight(false);
    }
  }, [poll, toast]);

  const disconnect = useCallback<UseKimiOauth["disconnect"]>(async () => {
    setInFlight(true);
    try {
      const { data, error } = await api.POST(DISCONNECT, {});
      if (error || !data) {
        toast("断开 Kimi Code 失败，请稍后重试。", "error");
        return;
      }
      poll.reset();
      startedRef.current = false;
      setStarted(false);
      setDevice(null);
      setConnected(false);
      setExpiresAt(null);
      toast("已断开 Kimi Code。", "success");
    } finally {
      setInFlight(false);
    }
  }, [poll, toast]);

  return {
    phase,
    device,
    expiresAt,
    busy: inFlight || (startedRef.current && poll.status === "polling"),
    error: poll.status === "error" ? poll.error : null,
    connect,
    disconnect,
  };
}
