'use client';

import { useCallback, useRef, useState } from 'react';
import { ApiCode } from '@pac/types';
import { env } from '@/lib/env';
import { tryRefresh } from '@/lib/api-client';
import { useAuthStore } from '@/stores/auth-store';
import type { ScriptSection } from './mock-data';

/**
 * 流式重新生成话术 hook。
 *
 * 后端 SSE 帧:
 *   event: start    \ndata: {invocationId, modelId, promptVersion}
 *   event: partial  \ndata: {structured, sections}            ← 多次
 *   event: done     \ndata: {planScriptId, source, sections, costYuan...}
 *   event: error    \ndata: {message}
 *
 * 用 fetch + ReadableStream 而非 EventSource(EventSource 不支持自定义 header,
 * 鉴权 Bearer token 必须走 Authorization header)。
 */

interface ServerSection {
  // 稳健/标准:固定 4 id(开场白/告知应治未治/复查建议/结束回访语);深度:段数不定,id = s0/s1/…(string)
  id: string;
  label: string;
  durationHint: string;
  markdown: string;
}

type FixedSectionId = 'opening' | 'informMissed' | 'reviewAdvice' | 'closing';

export type ScriptStreamState =
  | { status: 'idle' }
  | { status: 'streaming'; sections: ScriptSection[]; modelId?: string; invocationId?: string }
  | {
      status: 'done';
      sections: ScriptSection[];
      planScriptId: string | null;
      source: 'agent' | 'template_fallback';
      costYuan: number;
      promptTokens: number;
      completionTokens: number;
      fallbackReason?: string;
    }
  | { status: 'error'; message: string };

export interface UseScriptStream {
  state: ScriptStreamState;
  /** 触发流式重新生成 */
  regenerate: (planId: string, options?: { model?: string; tier?: string }) => Promise<void>;
  /** 中途停掉(用户点暂停 / 离开页面) */
  abort: () => void;
  reset: () => void;
}

// 2026-06 重构:4 模块 — opening / informMissed / reviewAdvice / closing
const LABEL_FALLBACK: Record<FixedSectionId, string> = {
  opening: '开场白',
  informMissed: '告知应治未治',
  reviewAdvice: '复查建议',
  closing: '结束回访语',
};
// 时长无意义,统一空串(UI 不再展示建议时长)
const DURATION_FALLBACK: Record<FixedSectionId, string> = {
  opening: '',
  informMissed: '',
  reviewAdvice: '',
  closing: '',
};

export function useScriptStream(): UseScriptStream {
  const [state, setState] = useState<ScriptStreamState>({ status: 'idle' });
  const abortRef = useRef<AbortController | null>(null);

  const reset = useCallback(() => setState({ status: 'idle' }), []);

  const abort = useCallback(() => {
    abortRef.current?.abort();
    abortRef.current = null;
  }, []);

  const regenerate = useCallback(async (planId: string, options?: { model?: string; tier?: string }) => {
    abortRef.current?.abort();
    const controller = new AbortController();
    abortRef.current = controller;

    const url = new URL(
      `/pac/v1/plans/${encodeURIComponent(planId)}/script:stream`,
      env.apiBaseUrl,
    );
    if (options?.model) url.searchParams.set('model', options.model);
    if (options?.tier) url.searchParams.set('tier', options.tier);

    // 占位 sections(分档骨架),避免第一帧到达前 UI 闪
    setState({ status: 'streaming', sections: makeEmptySections(options?.tier) });

    try {
      // SSE 走原生 fetch(api-client 不处理 stream),自己实现 silent refresh:
      // 第一次拿当前 token → 401/auth-错误 → tryRefresh() → 重试一次
      const openStream = async (retried: boolean): Promise<Response> => {
        const token = useAuthStore.getState().accessToken;
        if (!token) throw new Error('未鉴权 — 请从宿主系统重新打开页面');

        const res = await fetch(url, {
          method: 'GET',
          headers: { Authorization: `Bearer ${token}`, Accept: 'text/event-stream' },
          signal: controller.signal,
        });

        // 后端 SSE 端点鉴权失败时会返回 wrap JSON(HTTP 200,code=10105),body 不是 event-stream
        // 简单检测:Content-Type 不是 text/event-stream → 当 auth/error 处理
        if (!res.ok || !res.headers.get('content-type')?.includes('text/event-stream')) {
          if (!retried && useAuthStore.getState().canRefresh()) {
            // 看是不是 auth 错误,尝试 refresh
            const cloned = res.clone();
            let isAuth = !res.ok && (res.status === 401 || res.status === 403);
            try {
              const body = await cloned.json();
              if (
                body?.code === ApiCode.AUTH_TOKEN_EXPIRED ||
                body?.code === ApiCode.AUTH_TOKEN_MISSING ||
                body?.code === ApiCode.AUTH_TOKEN_INVALID
              ) {
                isAuth = true;
              }
            } catch {
              /* 非 JSON,按 HTTP 状态判断 */
            }
            if (isAuth) {
              const refreshed = await tryRefresh();
              if (refreshed) return openStream(true);
            }
          }
          const text = await res.text().catch(() => '');
          throw new Error(`HTTP ${res.status} ${text.slice(0, 200) || res.statusText}`);
        }
        return res;
      };

      const res = await openStream(false);
      if (!res.body) throw new Error('SSE 响应无 body');

      const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
      let buffer = '';

      while (true) {
        const { value, done } = await reader.read();
        if (done) break;
        buffer += value;

        // SSE 帧以 \n\n 分隔
        let sepIdx: number;
        while ((sepIdx = buffer.indexOf('\n\n')) !== -1) {
          const rawFrame = buffer.slice(0, sepIdx);
          buffer = buffer.slice(sepIdx + 2);

          const evt = parseSseFrame(rawFrame);
          if (!evt) continue;

          if (evt.type === 'start') {
            setState({
              status: 'streaming',
              sections: makeEmptySections(options?.tier),
              modelId: evt.modelId,
              invocationId: evt.invocationId,
            });
          } else if (evt.type === 'partial') {
            setState((prev) =>
              prev.status === 'streaming'
                ? { ...prev, sections: serverToClientSections(evt.sections) }
                : prev,
            );
          } else if (evt.type === 'done') {
            setState({
              status: 'done',
              sections: serverToClientSections(evt.sections),
              planScriptId: evt.planScriptId,
              source: evt.source,
              costYuan: evt.costYuan,
              promptTokens: evt.promptTokens,
              completionTokens: evt.completionTokens,
              fallbackReason: evt.fallbackReason,
            });
          } else if (evt.type === 'error') {
            setState({ status: 'error', message: evt.message });
          }
        }
      }
    } catch (err) {
      if (controller.signal.aborted) {
        setState({ status: 'idle' });
        return;
      }
      setState({
        status: 'error',
        message: err instanceof Error ? err.message : String(err),
      });
    } finally {
      if (abortRef.current === controller) abortRef.current = null;
    }
  }, []);

  return { state, regenerate, abort, reset };
}

// ─────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────

interface SseStartEvent {
  type: 'start';
  invocationId: string;
  modelId: string;
  promptVersion: string;
}
interface SsePartialEvent {
  type: 'partial';
  structured: Record<string, unknown>;
  sections: ServerSection[];
}
interface SseDoneEvent {
  type: 'done';
  planScriptId: string | null;
  agentInvocationId: string;
  source: 'agent' | 'template_fallback';
  sections: ServerSection[];
  content: string;
  costYuan: number;
  promptTokens: number;
  completionTokens: number;
  fallbackReason?: string;
}
interface SseErrorEvent {
  type: 'error';
  message: string;
}
type SseEvent = SseStartEvent | SsePartialEvent | SseDoneEvent | SseErrorEvent;

function parseSseFrame(frame: string): SseEvent | null {
  // SSE 帧的每一行可能是 "event: <name>" / "data: <json>" / ": comment"
  const dataLines: string[] = [];
  for (const line of frame.split('\n')) {
    if (line.startsWith('data:')) dataLines.push(line.slice(5).trimStart());
    // event / id / retry / 注释行都忽略 — data 内本身已带 type
  }
  if (dataLines.length === 0) return null;
  try {
    return JSON.parse(dataLines.join('\n')) as SseEvent;
  } catch {
    return null;
  }
}

// loading 占位段(分档:骨架要贴各档的输出形态,别都套稳健的"4段固定标题")
//   稳健 = 4 段固定标题;标准 = 4 段(标题模型自起,占位不假装标题);深度 = 段数不定,只占一段。
function makeEmptySections(tier?: string): ScriptSection[] {
  if (tier === 'deep') {
    return [{ id: 's0', label: '生成中…', durationHint: '', markdown: '' }];
  }
  if (tier === 'standard') {
    return Array.from({ length: 4 }, (_, i) => ({
      id: `s${i}`,
      label: '生成中…',
      durationHint: '',
      markdown: '',
    }));
  }
  return (['opening', 'informMissed', 'reviewAdvice', 'closing'] as const).map((id) => ({
    id,
    label: LABEL_FALLBACK[id],
    durationHint: DURATION_FALLBACK[id],
    markdown: '',
  }));
}

function serverToClientSections(srv: ServerSection[]): ScriptSection[] {
  return srv.map((s) => ({
    id: s.id,
    label: s.label,
    durationHint: s.durationHint,
    markdown: s.markdown,
  }));
}
