/**
 * Zustand Store — 三国量化编排台状态管理
 * 基于 Edict 前端适配 moziplus 后端
 * HTTP 5s 轮询，无 WebSocket
 */

import { create } from 'zustand';
import {
  api,
  setApiProjectId,
  type Task,
  type LiveStatus,
  type AgentConfig,
  type OfficialsData,
  type AgentsStatusData,
  type MorningBrief,
  type SubConfig,
  type ChangeLogEntry,
  type TaskNode,
} from './api';

// ── SSE Notification 类型 ──
interface NotifItem {
  id: string;
  type: 'info' | 'warning' | 'success' | 'error';
  title: string;
  message: string;
  time: string;
  read: boolean;
  source: 'mail' | 'event';
  taskId?: string;
  projectId?: string;
}

// ── v2.6 V2Task 类型（对齐后端 tasks 表）──
export interface V2Task {
  id: string;
  title: string;
  description: string | null;
  status: 'pending' | 'claimed' | 'working' | 'review' | 'paused' | 'escalated' | 'waiting_human' | 'done' | 'failed' | 'blocked' | 'cancelled';
  assignee: string | null;
  assigned_by: string | null;
  depends_on: string | null;
  parent_task: string | null;
  priority: number;
  task_type: string | null;
  created_at: string;
  updated_at: string;
  claimed_at: string | null;
  completed_at: string | null;
  started_at: string | null;
  deadline: string | null;
  retry_count: number;
  max_retries: number;
  must_haves: string | null;
  risk_level: string | null;
  estimated_duration_minutes: number | null;
  escalated: number;
  archived: number;  // v2.8: 归档标记
  // API 聚合字段
  comments_count?: number;
  outputs_count?: number;
  review_status?: string | null;
  latest_event_detail?: string | null;
  // v2.7
  stage?: string | null;
  stages_json?: string;
  // expand=all 时附加
  comments?: any[];
  outputs?: any[];
  reviews?: any[];
  decisions?: any[];
  events?: any[];
  experiences?: any[];
}

// ── Pipeline removed (v3.0): old 7-step PIPE replaced by EdictBoard PIPELINE_STEPS + TaskModal VALID_TRANSITIONS ──

export function stateLabel(t: Task): string {
  const r = t.review_round || 0;
  const s = t.status || t.state; // moziplus status or legacy edict state
  // moziplus states
  if (s === 'challenging' && r > 1) return `审核中（第${r}轮）`;
  if (s === 'planning' && r > 0) return `修订方案（第${r}轮）`;
  // plan_status refinement
  const planStatus = t.plan_status;
  if (s === 'planning' && planStatus === 'approved') return '待派发';
  if (s === 'planning' && planStatus === 'rejected') return '方案驳回';
  if (s === 'planning' && planStatus === 'challenging') return '审核中';
  // BUG-6: 中间态标签
  if (s === 'cancelling') return '⏳ 取消中';
  if (s === 'pausing') return '⏳ 暂停中';
  return STATE_LABEL[s] || s;
}

export function isEdict(t: Task): boolean {
  // moziplus tasks: UUIDs or random short IDs (M2-08); Edict tasks: TSK-xxx
  const id = t.id || '';
  return /^TSK-/i.test(id) || /^[0-9a-f]{8}-/i.test(id) || /^[A-Za-z0-9]{10,16}$/.test(id);
}

// STATE_LABEL kept for backward compat (7 refs)
export const STATE_LABEL: Record<string, string> = {
  created: '新建', planning: '规划中', challenging: '审核中',
  assigned: '已派发', executing: '执行中', paused: '已暂停',
  cancelling: '取消中', pausing: '暂停中',
  reviewing: '验收中', completed: '已完成', failed: '已失败',
  cancelled: '已取消', escalated: '已升级',
};

export function isArchived(t: Task): boolean {
  return !!t.archived;
}

// getPipeStatus + PipeStatus removed (v3.0): dead code, zero references

// ── Tabs ──

export type TabKey =
  | 'tasks' | 'court' | 'monitor' | 'agents'
  | 'models' | 'skills' | 'sessions' | 'archives' | 'templates'
  | 'usage' | 'settings' | 'officials' | 'morning' | 'mail';

export const TAB_DEFS: { key: TabKey; label: string; icon: string }[] = [
  { key: 'tasks',     label: '任务看板', icon: '📜' },
  { key: 'mail',      label: '飞鸽传书', icon: '✉️' },
  { key: 'court',     label: '军议大厅', icon: '🏛️' },
  { key: 'monitor',   label: '编排调度', icon: '🔌' },
  { key: 'agents',    label: '将军总览', icon: '🤺' },
  { key: 'models',    label: '模型配置', icon: '🤖' },
  { key: 'skills',    label: '技能配置', icon: '🎯' },
  { key: 'sessions',  label: '传令巡哨', icon: '🔭' },
  { key: 'usage',     label: '花费总览', icon: '💰' },
  { key: 'archives',  label: '奏折阁',   icon: '📜' },
  { key: 'morning',   label: '早朝简报', icon: '🌅' },
  { key: 'templates', label: '任务模板', icon: '📋' },
  { key: 'settings',  label: '系统设置', icon: '⚙️' },
];

// ── DEPTS for monitor ──

export const DEPTS = [
  { id: 'pangtong',    label: '庞统',   emoji: '🐦', role: '副军师',   rank: '凤雏' },
  { id: 'simayi',      label: '司马懿', emoji: '🦅', role: '质量总监', rank: '仲达' },
  { id: 'zhugeliang',  label: '诸葛亮', emoji: '🪶', role: '总军师',   rank: '孔明' },
  { id: 'zhangfei',    label: '张飞',   emoji: '⚔️', role: '编码先锋', rank: '翼德' },
  { id: 'guanyu',      label: '关羽',   emoji: '🗡️', role: '风控守将', rank: '云长' },
  { id: 'zhaoyun',     label: '赵云',   emoji: '🏹', role: '数据总管', rank: '子龙' },
  { id: 'jiangwei',    label: '姜维',   emoji: '🛡️', role: '平台总督', rank: '伯约' },
];

// ── Templates ──

export interface TemplateParam {
  key: string;
  label: string;
  type: 'text' | 'textarea' | 'select';
  default?: string;
  required?: boolean;
  options?: string[];
}

export interface Template {
  id: string;
  cat: string;
  icon: string;
  name: string;
  desc: string;
  depts: string[];
  est: string;
  cost: string;
  params: TemplateParam[];
  command: string;
}

export const TEMPLATES: Template[] = [
  {
    id: 'tpl-blank', cat: '快速开始', icon: '📜', name: '空白军令',
    desc: '自由输入需求描述，无预设参数',
    depts: ['庞统'], est: '自定义', cost: '自定义',
    params: [
      { key: 'requirement', label: '需求描述', type: 'textarea', required: true },
    ],
    command: '{requirement}',
  },
  {
    id: 'tpl-quant-strategy', cat: '量化策略', icon: '📈', name: '量化策略开发',
    desc: '从需求分析到回测验证的完整策略开发流程',
    depts: ['庞统', '张飞', '关羽'], est: '~60分钟', cost: '¥3',
    params: [
      { key: 'strategy_name', label: '策略名称', type: 'text', required: true },
      { key: 'universe', label: '股票池', type: 'select', options: ['沪深300', '中证500', '全A'], default: '沪深300' },
      { key: 'period', label: '回测区间', type: 'text', default: '2024-01-01 ~ 2025-12-31' },
    ],
    command: '开发量化策略「{strategy_name}」，股票池：{universe}，回测区间：{period}',
  },
  {
    id: 'tpl-data-download', cat: '数据采集', icon: '📊', name: '行情数据下载',
    desc: '批量下载A股行情数据（日线/分钟线）到NAS',
    depts: ['赵云'], est: '~30分钟', cost: '¥1',
    params: [
      { key: 'data_type', label: '数据类型', type: 'select', options: ['日线', '1分钟线', '5分钟线', 'Tick'], default: '日线' },
      { key: 'symbols', label: '股票代码', type: 'text', default: '沪深300成分股' },
      { key: 'date_range', label: '日期范围', type: 'text', required: true },
    ],
    command: '下载{data_type}数据，股票：{symbols}，日期：{date_range}',
  },
  {
    id: 'tpl-backtest', cat: '量化策略', icon: '🔬', name: '策略回测',
    desc: '对指定策略进行标准化回测，输出绩效报告',
    depts: ['姜维', '庞统'], est: '~45分钟', cost: '¥2',
    params: [
      { key: 'strategy', label: '策略文件路径', type: 'text', required: true },
      { key: 'benchmark', label: '基准', type: 'select', options: ['沪深300', '中证500', '上证50'], default: '沪深300' },
    ],
    command: '回测策略 {strategy}，基准：{benchmark}',
  },
  {
    id: 'tpl-risk-review', cat: '风控审查', icon: '🛡️', name: '风控审查',
    desc: '审查策略的风控逻辑，评估止损/仓位/滑点设置',
    depts: ['关羽', '司马懿'], est: '~20分钟', cost: '¥1.5',
    params: [
      { key: 'target', label: '审查目标', type: 'text', required: true },
      { key: 'scope', label: '审查范围', type: 'select', options: ['止损规则', '仓位管理', '滑点控制', '全部'], default: '全部' },
    ],
    command: '对 {target} 进行风控审查，范围：{scope}',
  },
  {
    id: 'tpl-code-impl', cat: '编码开发', icon: '💻', name: '编码实现',
    desc: '根据设计文档编码实现功能，强调最小实现、可运行优先',
    depts: ['张飞'], est: '~40分钟', cost: '¥2',
    params: [
      { key: 'design_doc', label: '设计文档路径', type: 'text', required: true },
      { key: 'module', label: '模块名称', type: 'text', required: true },
    ],
    command: '根据 {design_doc} 实现模块 {module}',
  },
  {
    id: 'tpl-deploy', cat: '编码开发', icon: '🚀', name: '环境部署',
    desc: '部署运行环境，安装依赖，配置服务',
    depts: ['姜维'], est: '~30分钟', cost: '¥1.5',
    params: [
      { key: 'project', label: '项目名称', type: 'text', required: true },
      { key: 'env', label: '部署环境', type: 'select', options: ['Docker', 'NAS', '本地开发'], default: 'Docker' },
    ],
    command: '部署 {project} 到 {env}',
  },
  {
    id: 'tpl-data-verify', cat: '数据采集', icon: '✅', name: '数据验证',
    desc: '验证数据完整性、正确性、质量检查',
    depts: ['赵云'], est: '~15分钟', cost: '¥0.5',
    params: [
      { key: 'data_path', label: '数据路径', type: 'text', required: true },
      { key: 'check', label: '检查项', type: 'select', options: ['完整性', '正确性', '异常值', '全部'], default: '全部' },
    ],
    command: '验证数据 {data_path}，检查：{check}',
  },
  {
    id: 'tpl-requirement', cat: '需求分析', icon: '📝', name: '需求分析',
    desc: '分析用户需求，撰写需求规格文档',
    depts: ['庞统'], est: '~20分钟', cost: '¥1',
    params: [
      { key: 'req', label: '需求描述', type: 'textarea', required: true },
      { key: 'domain', label: '领域', type: 'select', options: ['量化策略', '数据平台', '交易系统', '基础设施'], default: '量化策略' },
    ],
    command: '分析需求：{req}，领域：{domain}',
  },
  {
    id: 'tpl-code-review', cat: '风控审查', icon: '🔍', name: '代码审查',
    desc: '审查代码质量、逻辑正确性、异常处理',
    depts: ['司马懿', '关羽'], est: '~25分钟', cost: '¥1.5',
    params: [
      { key: 'repo', label: '仓库/文件路径', type: 'text', required: true },
      { key: 'scope', label: '审查范围', type: 'select', options: ['全量', '增量', '指定文件'], default: '增量' },
    ],
    command: '审查 {repo}，范围：{scope}',
  },
];

export const TPL_CATS = [
  { name: '全部', icon: '📋' },
  { name: '量化策略', icon: '📈' },
  { name: '数据采集', icon: '📊' },
  { name: '风控审查', icon: '🛡️' },
  { name: '编码开发', icon: '💻' },
  { name: '需求分析', icon: '📝' },
];

// ── Main Store ──

interface AppStore {
  // Data
  liveStatus: LiveStatus | null;
  agentConfig: AgentConfig | null;
  changeLog: ChangeLogEntry[];
  officialsData: OfficialsData | null;
  agentsStatusData: AgentsStatusData | null;
  morningBrief: MorningBrief | null;
  subConfig: SubConfig | null;

  // v2.6: 多项目
  projects: Record<string, any>;
  selectedProjectId: string | null;

  // v2.6: 原生 V2Task
  v2tasks: V2Task[];
  v2taskDetail: V2Task | null;
  v2tasksLoading: boolean;

  // UI State
  activeTab: TabKey;
  edictFilter: 'active' | 'archived' | 'all';
  statusFilter: string;
  searchQuery: string;
  sessFilter: string;
  tplCatFilter: string;
  selectedOfficial: string | null;
  modalTaskId: string | null;
  countdown: number;

  // Toast
  toasts: { id: number; msg: string; type: 'ok' | 'err' }[];

  // Actions
  setActiveTab: (tab: TabKey) => void;
  setEdictFilter: (f: 'active' | 'archived' | 'all') => void;
  setStatusFilter: (f: string) => void;
  setSearchQuery: (q: string) => void;
  setSessFilter: (f: string) => void;
  setTplCatFilter: (f: string) => void;
  setSelectedOfficial: (id: string | null) => void;
  setModalTaskId: (id: string | null) => void;
  setCountdown: (n: number) => void;
  setSelectedProjectId: (pid: string | null) => void;
  toast: (msg: string, type?: 'ok' | 'err') => void;

  // v2.6 V2Task actions
  loadV2Tasks: () => Promise<void>;
  loadV2TaskDetail: (taskId: string) => Promise<void>;

  // Data fetching
  loadLive: () => Promise<void>;
  loadProjects: () => Promise<void>;
  loadAgentConfig: () => Promise<void>;
  loadOfficials: () => Promise<void>;
  loadAgentsStatus: () => Promise<void>;
  loadMorning: () => Promise<void>;
  loadSubConfig: () => Promise<void>;
  loadAll: () => Promise<void>;

  // Mail
  mails: any[];
  mailUnread: number;
  sseEvents: any[];
  loadMails: () => Promise<void>;
}

let _toastId = 0;

export const useStore = create<AppStore>((set, get) => ({
  liveStatus: null,
  agentConfig: null,
  changeLog: [],
  // Mail
  mails: [],
  mailUnread: 0,
  sseEvents: [],
  officialsData: null,
  agentsStatusData: null,
  morningBrief: null,
  subConfig: null,

  // v2.6: 多项目
  projects: {},
  selectedProjectId: (() => { try { const v = localStorage.getItem('selectedProjectId'); return v !== null ? v : ''; } catch { return ''; } })(),

  // v2.6: 原生 V2Task
  v2tasks: [],
  v2taskDetail: null,
  v2tasksLoading: false,

  activeTab: 'tasks',
  edictFilter: 'active',
  statusFilter: 'all',
  searchQuery: '',
  sessFilter: 'all',
  tplCatFilter: '全部',
  selectedOfficial: null,
  modalTaskId: null,
  countdown: 5,

  toasts: [],

  setActiveTab: (tab) => {
    set({ activeTab: tab });
    const s = get();
    if (['models', 'skills', 'sessions'].includes(tab) && !s.agentConfig) s.loadAgentConfig();
    if (tab === 'officials' && !s.officialsData) s.loadOfficials();
    if (tab === 'monitor') s.loadAgentsStatus();
    if (tab === 'morning' && !s.morningBrief) s.loadMorning();
    if (tab === 'mail') s.loadMails();
  },
  setEdictFilter: (f) => set({ edictFilter: f }),
  setStatusFilter: (f) => set({ statusFilter: f }),
  setSearchQuery: (q) => set({ searchQuery: q }),
  setSessFilter: (f) => set({ sessFilter: f }),
  setTplCatFilter: (f) => set({ tplCatFilter: f }),
  setSelectedOfficial: (id) => set({ selectedOfficial: id }),
  setModalTaskId: (id) => set({ modalTaskId: id }),
  setCountdown: (n) => set({ countdown: n }),
  setSelectedProjectId: (pid) => {
    set({ selectedProjectId: pid });
    setApiProjectId(pid);
    try { localStorage.setItem('selectedProjectId', pid || ''); } catch {}
    // 切换项目后重新加载
    get().loadLive();
    get().loadV2Tasks();
  },

  toast: (msg, type = 'ok') => {
    const id = ++_toastId;
    set((s) => ({ toasts: [...s.toasts, { id, msg, type }] }));
    setTimeout(() => {
      set((s) => ({ toasts: s.toasts.filter((t) => t.id !== id) }));
    }, 3000);
  },

  // v2.6: 加载项目任务列表（含聚合统计）
  loadV2Tasks: async () => {
    const pid = get().selectedProjectId;
    set({ v2tasksLoading: true });
    try {
      if (!pid) {
        // 全部任务模式：聚合所有项目（排除 _mail），限制最多50个项目
        const projRes = await fetch('/api/projects');
        if (!projRes.ok) { set({ v2tasks: [], v2tasksLoading: false }); return; }
        const projData = await projRes.json();
        const allPids = Object.keys(projData.projects || {}).filter(p => !p.startsWith('_') && p !== '_mail').slice(0, 50);
        // 确保 _general 在列表中
        if (!allPids.includes('_general') && projData.projects?._general) allPids.unshift('_general');
        const allTasks: any[] = [];
        // 并行请求，每批10个
        for (let i = 0; i < allPids.length; i += 10) {
          const batch = allPids.slice(i, i + 10);
          const results = await Promise.allSettled(batch.map(async (p) => {
            const r = await fetch(`/api/projects/${p}/tasks`);
            if (r.ok) {
              const d = await r.json();
              return (d.tasks || []).map((t: any) => ({ ...t, _projectId: p, _projectName: (projData.projects[p] as any)?.name || p }));
            }
            return [];
          }));
          for (const r of results) {
            if (r.status === 'fulfilled') allTasks.push(...r.value);
          }
        }
        set({ v2tasks: allTasks });
      } else {
        const res = await fetch(`/api/projects/${pid}/tasks`);
        if (!res.ok) { set({ v2tasks: [], v2tasksLoading: false }); return; }
        const data = await res.json();
        const tasks = data.tasks || [];
        const enriched = await Promise.all(tasks.map(async (t: any) => {
          try {
            const r = await fetch(`/api/projects/${pid}/tasks/${t.id}`);
            if (r.ok) {
              const detail = await r.json();
              return { ...t, ...detail };
            }
          } catch { /* skip */ }
          return t;
        }));
        set({ v2tasks: enriched });
      }
    } catch { /* silently fail */ }
    finally { set({ v2tasksLoading: false }); }
  },

  // v2.6: 加载任务详情（expand=all）
  loadV2TaskDetail: async (taskId: string) => {
    const pid = get().selectedProjectId;
    if (!pid) {
      // 全部任务模式：从 v2tasks 中找任务的 _projectId
      const task = (get().v2tasks || []).find((t: any) => t.id === taskId);
      if (task && (task as any)._projectId) {
        try {
          const res = await fetch(`/api/projects/${(task as any)._projectId}/tasks/${taskId}?expand=all`);
          if (res.ok) {
            const detail = await res.json();
            set({ v2taskDetail: { ...detail, _projectId: (task as any)._projectId } });
          }
        } catch { /* silently fail */ }
      }
      return;
    }
    try {
      const res = await fetch(`/api/projects/${pid}/tasks/${taskId}?expand=all`);
      if (res.ok) {
        const task = await res.json();
        set({ v2taskDetail: task });
      }
    } catch { /* silently fail */ }
  },

  loadLive: async () => {
    try {
      const data = await api.liveStatus();
      set({ liveStatus: data });
      // 同步项目列表
      const projects = (data.syncStatus as any)?.projects?.projects || {};
      set({ projects });
      // 自动选择：仅在 localStorage 无记录时
      const s = get();
      const savedPid = localStorage.getItem('selectedProjectId');
      if (savedPid === null && Object.keys(projects).length > 0) {
        const realPids = Object.keys(projects).filter((p: string) => !p.startsWith('_'));
        const firstPid = realPids[0] || Object.keys(projects)[0] || '';
        set({ selectedProjectId: firstPid });
        setApiProjectId(firstPid);
        try { localStorage.setItem('selectedProjectId', firstPid); } catch {}
      } else if (savedPid !== null && s.selectedProjectId !== savedPid) {
        set({ selectedProjectId: savedPid });
        setApiProjectId(savedPid);
      }
      // Also preload officials for monitor tab
      if (!s.officialsData) {
        api.officialsStats().then((d) => set({ officialsData: d })).catch(() => {});
      }
    } catch {
      // silently fail
    }
  },

  loadProjects: async () => {
    try {
      const data = await api.projects();
      set({ projects: data.projects });
      const s = get();
      // 如果当前 selectedProjectId 不在项目列表里（且不是全部/一般），重新选择
      if (s.selectedProjectId && s.selectedProjectId !== '' && s.selectedProjectId !== '_general' && !(s.selectedProjectId in data.projects)) {
        if (Object.keys(data.projects).length > 0) {
          const firstPid = Object.keys(data.projects)[0];
          set({ selectedProjectId: firstPid });
          setApiProjectId(firstPid);
          try { localStorage.setItem('selectedProjectId', firstPid); } catch {}
        }
      }
    } catch {
      // silently fail
    }
  },

  loadAgentConfig: async () => {
    try {
      const cfg = await api.agentConfig();
      const log = await api.modelChangeLog();
      set({ agentConfig: cfg, changeLog: log });
    } catch {
      // silently fail
    }
  },

  loadMails: async () => {
    try {
      const res = await fetch('/api/mail');
      if (res.ok) {
        const data = await res.json();
        set({ mails: data.mails || [], mailUnread: (data.mails || []).filter((m: any) => !['done', 'failed', 'cancelled'].includes(m.status)).length });
      }
    } catch { /* silently fail */ }
  },

  loadOfficials: async () => {
    try {
      const data = await api.officialsStats();
      set({ officialsData: data });
    } catch {
      // silently fail
    }
  },

  loadAgentsStatus: async () => {
    try {
      const data = await api.agentsStatus();
      set({ agentsStatusData: data });
    } catch {
      set({ agentsStatusData: null });
    }
  },

  loadMorning: async () => {
    try {
      const [brief, config] = await Promise.all([api.morningBrief(), api.morningConfig()]);
      set({ morningBrief: brief, subConfig: config });
    } catch {
      // silently fail
    }
  },

  loadSubConfig: async () => {
    try {
      const config = await api.morningConfig();
      set({ subConfig: config });
    } catch {
      // silently fail
    }
  },

  loadAll: async () => {
    const s = get();
    await s.loadLive();
    const tab = s.activeTab;
    if (['models', 'skills'].includes(tab)) await s.loadAgentConfig();
  },
}));

// ── Countdown & Polling ──

let _cdTimer: ReturnType<typeof setInterval> | null = null;
let _blurFlushTimer: ReturnType<typeof setTimeout> | null = null;
let _lastActivity = Date.now();

// P0: 输入焦点保护
function hasActiveInput(): boolean {
  const el = document.activeElement;
  if (!el) return false;
  const tag = (el as HTMLElement).tagName;
  return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT'
    || (el as HTMLElement).isContentEditable;
}

// P0-2: blur 后补刷，执行前再次检查焦点
function scheduleBlurFlush() {
  if (_blurFlushTimer) clearTimeout(_blurFlushTimer);
  _blurFlushTimer = setTimeout(() => {
    _blurFlushTimer = null;
    if (!hasActiveInput()) {
      useStore.getState().loadAll();
    }
  }, 2000);
}

// P1: 活跃度追踪
export function markActivity() {
  _lastActivity = Date.now();
}

function getPollInterval(): number {
  const idle = Date.now() - _lastActivity;
  return idle > 60000 ? 30 : 10; // 空闲>60s → 30s，否则 10s
}

export function startPolling() {
  if (_cdTimer) return;
  (window as any).__markActivity = markActivity;
  useStore.getState().loadAll();
  _cdTimer = setInterval(() => {
    // P0-1: 有输入焦点时跳过整个 loadAll
    if (hasActiveInput()) return;
    const s = useStore.getState();
    const cd = s.countdown - 1;
    if (cd <= 0) {
      s.setCountdown(getPollInterval());
      s.loadAll();
    } else {
      s.setCountdown(cd);
    }
  }, 1000);

  // P0-2: blur 后补刷
  document.addEventListener('focusout', () => {
    scheduleBlurFlush();
  });
}

export function stopPolling() {
  if (_cdTimer) {
    clearInterval(_cdTimer);
    _cdTimer = null;
  }
  if (_blurFlushTimer) {
    clearTimeout(_blurFlushTimer);
    _blurFlushTimer = null;
  }
}

// ── Utility ──

export function esc(s: string | undefined | null): string {
  if (!s) return '';
  return String(s)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}

export function timeAgo(iso: string | undefined): string {
  if (!iso) return '';
  try {
    const d = new Date(iso.includes('T') ? iso : iso.replace(' ', 'T') + 'Z');
    if (isNaN(d.getTime())) return '';
    const diff = Date.now() - d.getTime();
    const mins = Math.floor(diff / 60000);
    if (mins < 1) return '刚刚';
    if (mins < 60) return mins + '分钟前';
    const hrs = Math.floor(mins / 60);
    if (hrs < 24) return hrs + '小时前';
    return Math.floor(hrs / 24) + '天前';
  } catch {
    return '';
  }
}

// ── SSE 实时事件监听 ──

let _es: EventSource | null = null;
let _sseRetryCount = 0;
const SSE_MAX_RETRY = 5;


// 推送 SSE 事件到通知中心
function _pushSseEvent(item: NotifItem) {
  const sseEvents = (useStore.getState().sseEvents || []) as NotifItem[];
  useStore.setState({ sseEvents: [item, ...sseEvents].slice(0, 30) });
}

export function startSSE() {
  if (_es) return;
  try {
    _es = new EventSource('/api/events');
    _es.onopen = () => { _sseRetryCount = 0; };
    _es.addEventListener('task_updated', (e: MessageEvent) => {
      try {
        const data = JSON.parse(e.data);
        const s = useStore.getState();
        const tasks = [...s.v2tasks];
        const idx = tasks.findIndex((t: any) => t.id === data.task_id);
        if (idx >= 0) {
          tasks[idx] = { ...tasks[idx], status: data.new_status };
          useStore.setState({ v2tasks: tasks });
        } else {
          s.loadV2Tasks();
        }
        _pushSseEvent({ id: `sse-${Date.now()}`, type: 'info', title: `任务更新`, message: `${data.old_status || '?'} → ${data.new_status || '?'}`, time: new Date().toISOString(), read: false, source: 'event', taskId: data.task_id, projectId: data.project_id });
      } catch { /* ignore */ }
    });
    _es.addEventListener('task_created', () => {
      useStore.getState().loadV2Tasks();
    });
    _es.addEventListener('task_completed', (e: MessageEvent) => {
      try {
        const data = JSON.parse(e.data);
        const s = useStore.getState();
        const tasks = [...s.v2tasks];
        const idx = tasks.findIndex((t: any) => t.id === data.task_id);
        if (idx >= 0) {
          tasks[idx] = { ...tasks[idx], status: 'done' };
          useStore.setState({ v2tasks: tasks });
        }
        _pushSseEvent({ id: `sse-${Date.now()}`, type: 'success', title: `任务完成`, message: `${data.task_id || ''} 已完成`, time: new Date().toISOString(), read: false, source: 'event', taskId: data.task_id, projectId: data.project_id });
      } catch { /* ignore */ }
    });
    _es.addEventListener('task_failed', (e: MessageEvent) => {
      try {
        const data = JSON.parse(e.data);
        const s = useStore.getState();
        const tasks = [...s.v2tasks];
        const idx = tasks.findIndex((t: any) => t.id === data.task_id);
        if (idx >= 0) {
          tasks[idx] = { ...tasks[idx], status: 'failed' };
          useStore.setState({ v2tasks: tasks });
        }
        _pushSseEvent({ id: `sse-${Date.now()}`, type: 'error', title: `任务失败`, message: `${data.task_id || ''} 执行失败`, time: new Date().toISOString(), read: false, source: 'event', taskId: data.task_id, projectId: data.project_id });
      } catch { /* ignore */ }
    });
    _es.onerror = () => {
      _es?.close();
      _es = null;
      _sseRetryCount++;
      if (_sseRetryCount > SSE_MAX_RETRY) {
        console.warn(`SSE failed ${SSE_MAX_RETRY} times, stopping. Manual refresh required.`);
        return;
      }
      const delay = Math.min(3000 * Math.pow(2, _sseRetryCount - 1), 30000);
      setTimeout(startSSE, delay);
    };
  } catch { /* ignore */ }
}

export function stopSSE() {
  if (_es) {
    _es.close();
    _es = null;
  }
  _sseRetryCount = 0;
}
