const { useState, useEffect, useRef, useMemo } = React;

// Apply tweak CSS vars
const applyTweaks = (t) => {
  const r = document.documentElement;
  r.style.setProperty('--primary', t.primary);
  // Derive primary-600 (darker) and primary-light (tinted)
  r.style.setProperty('--primary-600', shade(t.primary, -0.12));
  r.style.setProperty('--primary-light', tint(t.primary, 0.85));
  r.style.setProperty('--accent', shade(t.primary, 0.15));
  r.style.setProperty('--quantity', t.skillTheme);
};

function hexToRgb(h) { const x = h.replace('#',''); return [0,2,4].map(i => parseInt(x.slice(i,i+2),16)); }
function rgbToHex(r,g,b) { return '#' + [r,g,b].map(v => Math.max(0,Math.min(255,Math.round(v))).toString(16).padStart(2,'0')).join(''); }
function shade(hex, p) { const [r,g,b] = hexToRgb(hex); const f = p < 0 ? 0 : 255; const t = Math.abs(p); return rgbToHex(r + (f - r) * t, g + (f - g) * t, b + (f - b) * t); }
function tint(hex, p) { const [r,g,b] = hexToRgb(hex); return rgbToHex(r + (255 - r) * p, g + (255 - g) * p, b + (255 - b) * p); }

const App = () => {
  // view: 'home' | 'chat' | 'detail'
  const [view, setView] = useState(() => localStorage.getItem('bb:view') || 'home');
  const [currentSession, setCurrentSession] = useState(() => localStorage.getItem('bb:session') || 'longhu');
  const [account, setAccount] = useState(() => window.BB_ACCOUNT.read());
  const [accountModal, setAccountModal] = useState(null);
  const [modalSkill, setModalSkill] = useState(null);
  const [phase, setPhase] = useState(0); // 0 idle, 1 scan, 2 measure, 3 materials, 4 done
  const [detailKind, setDetailKind] = useState(null);
  const [selectedTurnId, setSelectedTurnId] = useState(null);
  const [homeVariant, setHomeVariant] = useState('hero'); // hero | split
  const [resultVariant, setResultVariant] = useState('default'); // default | viz | minimal
  const [sidebarOpen, setSidebarOpen] = useState(true);
  const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
  const [tweaksMode, setTweaksMode] = useState(false);
  const [tweaks, setTweaksState] = useState(window.__TWEAKS);
  // Per-session turns: { [sessionId]: [{id, kind, source, status, phase, resultVariant, time}, ...] }
  const [turnsBySession, setTurnsBySession] = useState({});
  const turnIdRef = useRef(0);
  // User-created sessions (not in BB_DATA.SESSIONS)
  const [createdSessions, setCreatedSessions] = useState([]);
  const [sessionTitleOverrides, setSessionTitleOverrides] = useState({});
  const sessionIdRef = useRef(0);

  const setTweaks = (partial) => {
    const next = { ...tweaks, ...partial };
    setTweaksState(next);
    applyTweaks(next);
    if (window.parent !== window) {
      window.parent.postMessage({ type: '__edit_mode_set_keys', edits: partial }, '*');
    }
  };

  const updateAccount = (next) => {
    setAccount(prev => window.BB_ACCOUNT.normalize(typeof next === 'function' ? next(prev) : next));
  };

  const refreshAccount = async () => {
    try {
      const next = await window.BB_AUTH.refresh();
      setAccount(next);
      return next;
    } catch (err) {
      console.warn('刷新账号信息失败', err);
      return account;
    }
  };

  // Initial
  useEffect(() => { applyTweaks(tweaks); }, []);

  useEffect(() => {
    let alive = true;
    const initAccount = async () => {
      try {
        const next = await window.BB_AUTH.init((nextAccount) => {
          if (alive) setAccount(nextAccount);
        });
        if (!alive) return;
        setAccount(next);

        const params = new URLSearchParams(window.location.search || '');
        if (params.get('entry') === 'login' && !params.get('billing')) {
          setAccountModal(next.user ? 'overview' : 'login');
          params.delete('entry');
          const nextQuery = params.toString();
          window.history.replaceState(
            {},
            document.title,
            `${window.location.pathname}${nextQuery ? `?${nextQuery}` : ''}${window.location.hash || ''}`
          );
        }
      } catch (err) {
        console.warn('初始化账号失败', err);
      }
    };
    initAccount();
    return () => { alive = false; };
  }, []);

  useEffect(() => {
    const params = new URLSearchParams(window.location.search || '');
    if (!params.get('billing')) return;
    setAccountModal('subscription');
    refreshAccount();
    params.delete('billing');
    const nextQuery = params.toString();
    window.history.replaceState({}, document.title, `${window.location.pathname}${nextQuery ? `?${nextQuery}` : ''}${window.location.hash || ''}`);
  }, []);

  // Responsive
  useEffect(() => {
    const onResize = () => {
      const mob = window.innerWidth < 768;
      setIsMobile(mob);
      if (mob) setSidebarOpen(false);
      else setSidebarOpen(true);
    };
    onResize();
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);

  // Persist
  useEffect(() => { localStorage.setItem('bb:view', view); }, [view]);
  useEffect(() => { localStorage.setItem('bb:session', currentSession); }, [currentSession]);

  // Edit mode listener
  useEffect(() => {
    const handler = (e) => {
      const d = e.data;
      if (!d || typeof d !== 'object') return;
      if (d.type === '__activate_edit_mode') setTweaksMode(true);
      if (d.type === '__deactivate_edit_mode') setTweaksMode(false);
    };
    window.addEventListener('message', handler);
    window.parent.postMessage({ type: '__edit_mode_available' }, '*');
    return () => window.removeEventListener('message', handler);
  }, []);

  const nowLabel = () => {
    const now = new Date();
    return `${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}`;
  };

  const nextTurnId = () => {
    turnIdRef.current += 1;
    return `t${turnIdRef.current}`;
  };

  const updateTurn = (sessionId, turnId, patch) => {
    setTurnsBySession(prev => {
      const arr = prev[sessionId] || [];
      const idx = arr.findIndex(t => t.id === turnId);
      if (idx < 0) return prev;
      const next = [...arr];
      const current = next[idx];
      next[idx] = typeof patch === 'function' ? patch(current) : { ...current, ...patch };
      return { ...prev, [sessionId]: next };
    });
  };

  const runMockPhases = (sessionId, turnId) => {
    updateTurn(sessionId, turnId, { phase: 1, status: 'running' });
    setTimeout(() => updateTurn(sessionId, turnId, t => ({ ...t, phase: Math.max(t.phase || 0, 2) })), 1200);
    setTimeout(() => updateTurn(sessionId, turnId, t => ({ ...t, phase: Math.max(t.phase || 0, 3) })), 2600);
    setTimeout(() => updateTurn(sessionId, turnId, t => ({ ...t, phase: 4, status: 'done' })), 3800);
  };

  const runRealVisualPhases = (sessionId, turnId) => {
    const setRunningPhase = (p) => updateTurn(sessionId, turnId, t => (
      t.status === 'running' ? { ...t, phase: Math.max(t.phase || 0, p) } : t
    ));
    setRunningPhase(1);
    setTimeout(() => setRunningPhase(2), 1100);
    setTimeout(() => setRunningPhase(3), 2400);
  };

  const runPhasesForSession = (sessionId) => {
    const arr = turnsBySession[sessionId] || [];
    const last = arr[arr.length - 1];
    if (!last || last.source === 'real') return;
    runMockPhases(sessionId, last.id);
  };

  const canShowResult = (turn) => {
    if (!turn) return false;
    return turn.source === 'real' ? turn.status === 'done' && !!turn.viewModel : turn.phase >= 4;
  };

  // Append a new mock turn to a session and start running it
  const appendTurn = (sessionId, kind, opts = {}) => {
    const id = nextTurnId();
    const turn = {
      id,
      kind,
      source: 'mock',
      status: 'running',
      phase: 0,
      resultVariant: opts.resultVariant || 'default',
      time: nowLabel(),
    };
    setTurnsBySession(prev => ({
      ...prev,
      [sessionId]: [...(prev[sessionId] || []), turn],
    }));
    setTimeout(() => runMockPhases(sessionId, id), 200);
    return id;
  };

  // Ensure a session has at least one turn (its kind) on first visit
  const ensureInitialTurn = (sess) => {
    setTurnsBySession(prev => {
      if (prev[sess.id] && prev[sess.id].length) return prev;
      const turn = { id: nextTurnId(), kind: sess.kind, source: 'mock', status: 'done', phase: 4, resultVariant: 'default', time: sess.time };
      return { ...prev, [sess.id]: [turn] };
    });
  };

  const openSkill = (id) => {
    const skill = window.BB_DATA.SKILLS[id];
    setModalSkill(skill);
  };

  const sessionKindLabel = (kind) => (
    { quantity: '算量', quote: '报价审核', combined: '图纸+报价联审', compare: '多家报价对比' }[kind] || '会话'
  );

  const makeSessionTitle = (name, kind) => {
    const clean = String(name || '').trim();
    return `${clean || '新项目'} · ${sessionKindLabel(kind)}`;
  };

  const makeFileBasedSessionTitle = (files, kind = 'quantity') => {
    const arr = Array.from(files || []);
    const firstName = arr[0] && arr[0].name ? arr[0].name : '';
    const baseName = String(firstName)
      .split(/[\\/]/)
      .pop()
      .replace(/\.[^.]+$/, '')
      .trim();
    const defaultLabel = kind === 'quantity' ? '新图纸' : kind === 'combined' ? '新联审' : '新报价';
    const unitLabel = kind === 'quantity' ? '份图纸' : kind === 'combined' ? '份文件' : '份报价';
    const label = baseName || defaultLabel;
    const projectLabel = arr.length > 1 ? `${label} 等 ${arr.length} ${unitLabel}` : label;
    return makeSessionTitle(projectLabel, kind);
  };

  const reportProjectName = (apiResult) => {
    const name = apiResult && apiResult.report && apiResult.report.project && apiResult.report.project.name;
    return String(name || '').trim();
  };

  const updateSessionTitle = (sessionId, title, titleSource = 'report') => {
    const clean = String(title || '').trim();
    if (!clean) return;
    setCreatedSessions(prev => prev.map(s => (
      s.id === sessionId ? { ...s, title: clean, titleSource } : s
    )));
    setSessionTitleOverrides(prev => ({
      ...prev,
      [sessionId]: { title: clean, titleSource },
    }));
  };

  // Auto-generate a project name from the chosen mode (mocks "identified project name")
  const generateSessionTitle = (kind) => {
    // Pick a plausible mock project name based on mode
    const samples = {
      quantity: ['示例三室两厅', '李女士新房', '紫荆花园 #2-303', '滨江壹号 · 89㎡'],
      quote: ['和顺装饰报价', '业之峰报价 v2', '金螳螂主材清单', '百安居全包报价'],
      combined: ['示例 89㎡ 联审项目', '龙湖春江 · 二期', '万科大都会 #1808', '保利和光晨樾'],
      compare: ['三家报价横评', '4家装企对比 · 89㎡', '主材供应商比价'],
    };
    const pool = samples[kind] || ['新项目'];
    const proj = pool[Math.floor(Math.random() * pool.length)];
    return `演示数据 · ${makeSessionTitle(proj, kind)}`;
  };

  const createNewSession = (kind, opts = {}) => {
    sessionIdRef.current += 1;
    const id = `new-${Date.now()}-${sessionIdRef.current}`;
    const now = new Date();
    const time = `${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}`;
    const newSess = {
      id,
      group: opts.group || 'today',
      title: opts.title || generateSessionTitle(kind),
      titleSource: opts.titleSource || 'generated',
      kind,
      time,
      isNew: true,
      isDemo: !!opts.isDemo,
    };
    setCreatedSessions(prev => [newSess, ...prev]);
    return newSess;
  };

  const isPersistedSession = (sess) => !!(sess && sess.persisted);

  const promoteSessionId = (tempId, persistedId) => {
    if (!persistedId || persistedId === tempId) return;
    setCreatedSessions(prev => prev.map(s => (
      s.id === tempId ? { ...s, id: persistedId, persisted: true } : s
    )));
    setTurnsBySession(prev => {
      if (!prev[tempId]) return prev;
      const next = { ...prev, [persistedId]: prev[tempId] };
      delete next[tempId];
      return next;
    });
    setSessionTitleOverrides(prev => {
      if (!prev[tempId]) return prev;
      const next = { ...prev, [persistedId]: prev[tempId] };
      delete next[tempId];
      return next;
    });
    setCurrentSession(prev => (prev === tempId ? persistedId : prev));
  };

  const formatHistoryTime = (value) => {
    const d = value ? new Date(value) : new Date();
    if (Number.isNaN(d.getTime())) return nowLabel();
    return `${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`;
  };

  const historyGroup = (value) => {
    const d = value ? new Date(value) : new Date();
    const today = new Date();
    return d.toDateString() === today.toDateString() ? 'today' : 'earlier';
  };

  const fakeFilesFromRecords = (records) => (records || []).map(row => ({
    name: row.original_name || 'upload',
    size: Number(row.size_bytes || 0),
    fileId: row.id,
  }));

  const attachFileIds = (summaries, records) => (summaries || []).map((item, index) => ({
    ...item,
    fileId: records[index] && records[index].id,
  }));

  const fileSummariesFromHistory = (kind, records) => {
    const rows = records || [];
    if (kind === 'quantity') {
      return attachFileIds(window.BB_TAKEOFF.makeUploadFileSummaries(fakeFilesFromRecords(rows)), rows);
    }
    if (kind === 'compare') {
      return attachFileIds(window.BB_COMPARE.makeUploadFileSummaries(fakeFilesFromRecords(rows)), rows);
    }
    if (kind === 'combined') {
      const floorRows = rows.filter(row => row.purpose === 'floor_plan');
      const quoteRows = rows.filter(row => row.purpose === 'quote');
      return window.BB_COMBINED.makeUploadFileSummaries({
        floorPlans: fakeFilesFromRecords(floorRows),
        quoteFiles: fakeFilesFromRecords(quoteRows),
      });
    }
    return attachFileIds(window.BB_QUOTE.makeUploadFileSummaries(fakeFilesFromRecords(rows)), rows);
  };

  const buildHistoryViewModel = (message, records) => {
    const apiResult = { ...(message.result || {}), job_id: message.job_id };
    if (!message.result || message.status !== 'done') return null;
    if (message.kind === 'quantity') {
      return window.BB_TAKEOFF.adaptTakeoffResult(apiResult, fakeFilesFromRecords(records));
    }
    if (message.kind === 'compare') {
      return window.BB_COMPARE.adaptQuoteCompareResult(apiResult, fakeFilesFromRecords(records));
    }
    if (message.kind === 'combined') {
      const floorRows = records.filter(row => row.purpose === 'floor_plan');
      const quoteRows = records.filter(row => row.purpose === 'quote');
      return window.BB_COMBINED.adaptCombinedReviewResult(apiResult, {
        floorPlans: fakeFilesFromRecords(floorRows),
        quoteFiles: fakeFilesFromRecords(quoteRows),
      });
    }
    return window.BB_QUOTE.adaptQuoteReviewResult(apiResult, fakeFilesFromRecords(records));
  };

  const adaptHistoryTurn = (message, allFiles) => {
    const records = (allFiles || []).filter(row => row.message_id === message.id);
    const status = message.status === 'done' ? 'done' : (message.status === 'error' ? 'error' : 'running');
    return {
      id: message.id,
      kind: message.kind,
      source: 'real',
      persisted: true,
      conversationId: message.conversation_id,
      messageId: message.id,
      status,
      phase: status === 'done' ? 4 : (status === 'error' ? 3 : 1),
      resultVariant: 'default',
      time: formatHistoryTime(message.created_at),
      files: fileSummariesFromHistory(message.kind, records),
      rawFiles: [],
      context: message.context && message.context.user_note ? message.context.user_note : '',
      jobId: message.job_id,
      result: message.result ? { ...message.result, job_id: message.job_id } : null,
      viewModel: buildHistoryViewModel(message, records),
      error: message.error || null,
    };
  };

  const loadConversationDetail = async (conversationId) => {
    if (!account.user || !conversationId || conversationId.startsWith('new-')) return;
    try {
      const detail = await window.BB_API.getConversation(conversationId);
      const messages = detail.messages || [];
      const files = detail.files || [];
      setTurnsBySession(prev => ({
        ...prev,
        [conversationId]: messages.map(message => adaptHistoryTurn(message, files)),
      }));
    } catch (err) {
      console.warn('加载历史会话失败', err);
    }
  };

  useEffect(() => {
    let alive = true;
    const loadHistory = async () => {
      if (!account.user) {
        setCreatedSessions(prev => prev.filter(s => !s.persisted));
        setTurnsBySession(prev => {
          const next = {};
          Object.entries(prev).forEach(([key, value]) => {
            if (key.startsWith('new-')) next[key] = value;
          });
          return next;
        });
        return;
      }
      try {
        const data = await window.BB_API.listConversations();
        if (!alive) return;
        const sessions = (data.conversations || []).map(row => ({
          id: row.id,
          group: historyGroup(row.last_turn_at || row.created_at),
          title: row.title || makeSessionTitle('新项目', row.kind),
          titleSource: 'history',
          kind: row.kind || 'quantity',
          time: formatHistoryTime(row.last_turn_at || row.created_at),
          isNew: true,
          persisted: true,
        }));
        setCreatedSessions(prev => {
          const localRunning = prev.filter(s => !s.persisted);
          return [...sessions, ...localRunning];
        });
        const exists = sessions.some(s => s.id === currentSession);
        if (!exists && sessions.length && currentSession.startsWith('new-') === false && !window.BB_DATA.SESSIONS.some(s => s.id === currentSession)) {
          setCurrentSession(sessions[0].id);
        }
      } catch (err) {
        console.warn('加载会话列表失败', err);
      }
    };
    loadHistory();
    return () => { alive = false; };
  }, [account.user && account.user.id]);

  const appendRealTakeoffTurn = (sessionId, files, context, opts = {}) => {
    const id = nextTurnId();
    const fileSummaries = window.BB_TAKEOFF.makeUploadFileSummaries(files);
    const turn = {
      id,
      kind: 'quantity',
      source: 'real',
      status: 'running',
      phase: 1,
      resultVariant: 'default',
      time: nowLabel(),
      files: fileSummaries,
      rawFiles: Array.from(files || []),
      context: context || '',
      conversationId: opts.conversationId || null,
      renameSessionOnSuccess: !!opts.renameSessionOnSuccess,
    };
    setTurnsBySession(prev => ({
      ...prev,
      [sessionId]: [...(prev[sessionId] || []), turn],
    }));
    setTimeout(() => runRealVisualPhases(sessionId, id), 50);
    return id;
  };

  const runTakeoffForTurn = async (sessionId, turnId, files, context, opts = {}) => {
    try {
      const result = await window.BB_API.runTakeoff({
        files,
        context,
        provider: 'gemini',
        conversationId: opts.conversationId || '',
        conversationTitle: opts.conversationTitle || '',
      });
      const viewModel = window.BB_TAKEOFF.adaptTakeoffResult(result, files);
      updateTurn(sessionId, turnId, {
        status: 'done',
        phase: 4,
        conversationId: result.conversation_id || opts.conversationId || null,
        messageId: result.message_id || null,
        jobId: result.job_id,
        result,
        viewModel,
        error: null,
      });
      if (result.conversation_id) promoteSessionId(sessionId, result.conversation_id);
      refreshAccount();
      if (opts.renameSessionOnSuccess) {
        const projectName = reportProjectName(result);
        if (projectName) updateSessionTitle(result.conversation_id || sessionId, makeSessionTitle(projectName, 'quantity'));
      }
    } catch (err) {
      updateTurn(sessionId, turnId, t => ({
        ...t,
        status: 'error',
        phase: Math.max(t.phase || 1, 3),
        error: err && err.message ? err.message : '算量失败，请稍后重试。',
      }));
    }
  };

  const appendRealQuoteTurn = (sessionId, files, context, opts = {}) => {
    const id = nextTurnId();
    const fileSummaries = window.BB_QUOTE.makeUploadFileSummaries(files);
    const turn = {
      id,
      kind: 'quote',
      source: 'real',
      status: 'running',
      phase: 1,
      resultVariant: 'default',
      time: nowLabel(),
      files: fileSummaries,
      rawFiles: Array.from(files || []),
      context: context || '',
      conversationId: opts.conversationId || null,
      renameSessionOnSuccess: !!opts.renameSessionOnSuccess,
    };
    setTurnsBySession(prev => ({
      ...prev,
      [sessionId]: [...(prev[sessionId] || []), turn],
    }));
    setTimeout(() => runRealVisualPhases(sessionId, id), 50);
    return id;
  };

  const appendRealCompareTurn = (sessionId, files, context, opts = {}) => {
    const id = nextTurnId();
    const fileSummaries = window.BB_COMPARE.makeUploadFileSummaries(files);
    const turn = {
      id,
      kind: 'compare',
      source: 'real',
      status: 'running',
      phase: 1,
      resultVariant: 'default',
      time: nowLabel(),
      files: fileSummaries,
      rawFiles: Array.from(files || []),
      context: context || '',
      conversationId: opts.conversationId || null,
      renameSessionOnSuccess: !!opts.renameSessionOnSuccess,
    };
    setTurnsBySession(prev => ({
      ...prev,
      [sessionId]: [...(prev[sessionId] || []), turn],
    }));
    setTimeout(() => runRealVisualPhases(sessionId, id), 50);
    return id;
  };

  const appendRealCombinedTurn = (sessionId, quoteFiles, floorPlans, context, opts = {}) => {
    const id = nextTurnId();
    const fileSummaries = window.BB_COMBINED.makeUploadFileSummaries({ quoteFiles, floorPlans });
    const turn = {
      id,
      kind: 'combined',
      source: 'real',
      status: 'running',
      phase: 1,
      resultVariant: 'default',
      time: nowLabel(),
      files: fileSummaries,
      rawFiles: [...Array.from(floorPlans || []), ...Array.from(quoteFiles || [])],
      rawQuoteFiles: Array.from(quoteFiles || []),
      rawFloorPlans: Array.from(floorPlans || []),
      context: context || '',
      conversationId: opts.conversationId || null,
      renameSessionOnSuccess: !!opts.renameSessionOnSuccess,
    };
    setTurnsBySession(prev => ({
      ...prev,
      [sessionId]: [...(prev[sessionId] || []), turn],
    }));
    setTimeout(() => runRealVisualPhases(sessionId, id), 50);
    return id;
  };

  const runQuoteReviewForTurn = async (sessionId, turnId, files, context, opts = {}) => {
    try {
      const result = await window.BB_API.runQuoteReview({
        files,
        context,
        provider: 'gemini',
        conversationId: opts.conversationId || '',
        conversationTitle: opts.conversationTitle || '',
      });
      const viewModel = window.BB_QUOTE.adaptQuoteReviewResult(result, files);
      updateTurn(sessionId, turnId, {
        status: 'done',
        phase: 4,
        conversationId: result.conversation_id || opts.conversationId || null,
        messageId: result.message_id || null,
        jobId: result.job_id,
        result,
        viewModel,
        error: null,
      });
      if (result.conversation_id) promoteSessionId(sessionId, result.conversation_id);
      refreshAccount();
      if (opts.renameSessionOnSuccess) {
        const projectName = reportProjectName(result);
        if (projectName) updateSessionTitle(result.conversation_id || sessionId, makeSessionTitle(projectName, 'quote'));
      }
    } catch (err) {
      updateTurn(sessionId, turnId, t => ({
        ...t,
        status: 'error',
        phase: Math.max(t.phase || 1, 3),
        error: err && err.message ? err.message : '报价审核失败，请稍后重试。',
      }));
    }
  };

  const runQuoteCompareForTurn = async (sessionId, turnId, files, context, opts = {}) => {
    try {
      const result = await window.BB_API.runQuoteCompare({
        files,
        context,
        provider: 'gemini',
        conversationId: opts.conversationId || '',
        conversationTitle: opts.conversationTitle || '',
      });
      const viewModel = window.BB_COMPARE.adaptQuoteCompareResult(result, files);
      updateTurn(sessionId, turnId, {
        status: 'done',
        phase: 4,
        conversationId: result.conversation_id || opts.conversationId || null,
        messageId: result.message_id || null,
        jobId: result.job_id,
        result,
        viewModel,
        error: null,
      });
      if (result.conversation_id) promoteSessionId(sessionId, result.conversation_id);
      refreshAccount();
      if (opts.renameSessionOnSuccess && viewModel.projectName) {
        updateSessionTitle(result.conversation_id || sessionId, makeSessionTitle(viewModel.projectName, 'compare'));
      }
    } catch (err) {
      updateTurn(sessionId, turnId, t => ({
        ...t,
        status: 'error',
        phase: Math.max(t.phase || 1, 3),
        error: err && err.message ? err.message : '多家报价对比失败，请稍后重试。',
      }));
    }
  };

  const runCombinedReviewForTurn = async (sessionId, turnId, quoteFiles, floorPlans, context, opts = {}) => {
    try {
      const result = await window.BB_API.runCombinedReview({
        quoteFiles,
        floorPlans,
        context,
        provider: 'gemini',
        conversationId: opts.conversationId || '',
        conversationTitle: opts.conversationTitle || '',
      });
      const viewModel = window.BB_COMBINED.adaptCombinedReviewResult(result, { quoteFiles, floorPlans });
      updateTurn(sessionId, turnId, {
        status: 'done',
        phase: 4,
        conversationId: result.conversation_id || opts.conversationId || null,
        messageId: result.message_id || null,
        jobId: result.job_id,
        result,
        viewModel,
        error: null,
      });
      if (result.conversation_id) promoteSessionId(sessionId, result.conversation_id);
      refreshAccount();
      if (opts.renameSessionOnSuccess && viewModel.projectName) {
        updateSessionTitle(result.conversation_id || sessionId, makeSessionTitle(viewModel.projectName, 'combined'));
      }
    } catch (err) {
      updateTurn(sessionId, turnId, t => ({
        ...t,
        status: 'error',
        phase: Math.max(t.phase || 1, 3),
        error: err && err.message ? err.message : '联审失败，请稍后重试。',
      }));
    }
  };

  const startRealQuantity = (files, context) => {
    const createsNewSession = view !== 'chat';
    const canRenameCurrentSession = !createsNewSession && !!session;
    const shouldRenameSession = createsNewSession || canRenameCurrentSession;
    const fileTitle = makeFileBasedSessionTitle(files, 'quantity');
    const target = createsNewSession
      ? createNewSession('quantity', {
        title: fileTitle,
        titleSource: 'file',
      })
      : session;
    if (view !== 'chat') {
      setCurrentSession(target.id);
      setView('chat');
    }
    if (canRenameCurrentSession) {
      updateSessionTitle(target.id, fileTitle, 'file');
    }
    const conversationOpts = {
      renameSessionOnSuccess: shouldRenameSession,
      conversationId: isPersistedSession(target) ? target.id : '',
      conversationTitle: isPersistedSession(target) ? '' : fileTitle,
    };
    const turnId = appendRealTakeoffTurn(target.id, files, context, conversationOpts);
    setTimeout(() => runTakeoffForTurn(target.id, turnId, files, context, conversationOpts), 0);
  };

  const startRealQuote = (files, context) => {
    const createsNewSession = view !== 'chat';
    const canRenameCurrentSession = !createsNewSession && !!session;
    const shouldRenameSession = createsNewSession || canRenameCurrentSession;
    const fileTitle = makeFileBasedSessionTitle(files, 'quote');
    const target = createsNewSession
      ? createNewSession('quote', {
        title: fileTitle,
        titleSource: 'file',
      })
      : session;
    if (view !== 'chat') {
      setCurrentSession(target.id);
      setView('chat');
    }
    if (canRenameCurrentSession) {
      updateSessionTitle(target.id, fileTitle, 'file');
    }
    const conversationOpts = {
      renameSessionOnSuccess: shouldRenameSession,
      conversationId: isPersistedSession(target) ? target.id : '',
      conversationTitle: isPersistedSession(target) ? '' : fileTitle,
    };
    const turnId = appendRealQuoteTurn(target.id, files, context, conversationOpts);
    setTimeout(() => runQuoteReviewForTurn(target.id, turnId, files, context, conversationOpts), 0);
  };

  const startRealCompare = (files, context) => {
    const createsNewSession = view !== 'chat';
    const canRenameCurrentSession = !createsNewSession && !!session;
    const shouldRenameSession = createsNewSession || canRenameCurrentSession;
    const fileTitle = makeFileBasedSessionTitle(files, 'compare');
    const target = createsNewSession
      ? createNewSession('compare', {
        title: fileTitle,
        titleSource: 'file',
      })
      : session;
    if (view !== 'chat') {
      setCurrentSession(target.id);
      setView('chat');
    }
    if (canRenameCurrentSession) {
      updateSessionTitle(target.id, fileTitle, 'file');
    }
    const conversationOpts = {
      renameSessionOnSuccess: shouldRenameSession,
      conversationId: isPersistedSession(target) ? target.id : '',
      conversationTitle: isPersistedSession(target) ? '' : fileTitle,
    };
    const turnId = appendRealCompareTurn(target.id, files, context, conversationOpts);
    setTimeout(() => runQuoteCompareForTurn(target.id, turnId, files, context, conversationOpts), 0);
  };

  const startRealCombined = (quoteFiles, floorPlans, context) => {
    const createsNewSession = view !== 'chat';
    const canRenameCurrentSession = !createsNewSession && !!session;
    const shouldRenameSession = createsNewSession || canRenameCurrentSession;
    const titleFiles = [...Array.from(floorPlans || []), ...Array.from(quoteFiles || [])];
    const fileTitle = makeFileBasedSessionTitle(titleFiles, 'combined');
    const target = createsNewSession
      ? createNewSession('combined', {
        title: fileTitle,
        titleSource: 'file',
      })
      : session;
    if (view !== 'chat') {
      setCurrentSession(target.id);
      setView('chat');
    }
    if (canRenameCurrentSession) {
      updateSessionTitle(target.id, fileTitle, 'file');
    }
    const conversationOpts = {
      renameSessionOnSuccess: shouldRenameSession,
      conversationId: isPersistedSession(target) ? target.id : '',
      conversationTitle: isPersistedSession(target) ? '' : fileTitle,
    };
    const turnId = appendRealCombinedTurn(target.id, quoteFiles, floorPlans, context, conversationOpts);
    setTimeout(() => runCombinedReviewForTurn(target.id, turnId, quoteFiles, floorPlans, context, conversationOpts), 0);
  };

  const retryRealTask = (turn) => {
    if (!turn || !turn.rawFiles || !turn.rawFiles.length) {
      openSkill(turn && turn.kind ? turn.kind : 'quantity');
      return;
    }
    updateTurn(session.id, turn.id, { status: 'running', phase: 1, error: null });
    runRealVisualPhases(session.id, turn.id);
    const opts = {
      renameSessionOnSuccess: !!turn.renameSessionOnSuccess,
      conversationId: turn.conversationId || (session && session.persisted ? session.id : ''),
      conversationTitle: '',
    };
    if (turn.kind === 'compare') {
      runQuoteCompareForTurn(session.id, turn.id, turn.rawFiles, turn.context || '', opts);
    } else if (turn.kind === 'combined') {
      runCombinedReviewForTurn(session.id, turn.id, turn.rawQuoteFiles || [], turn.rawFloorPlans || [], turn.context || '', opts);
    } else if (turn.kind === 'quote') {
      runQuoteReviewForTurn(session.id, turn.id, turn.rawFiles, turn.context || '', opts);
    } else {
      runTakeoffForTurn(session.id, turn.id, turn.rawFiles, turn.context || '', opts);
    }
  };

  const handleExport = async (jobId, format) => {
    try {
      await window.BB_API.downloadExport(jobId, format);
    } catch (err) {
      alert(err && err.message ? err.message : '下载失败，请确认后端服务已启动。');
    }
  };

  const ensureRealAuth = async () => {
    try {
      const token = await window.BB_AUTH.getAccessToken();
      if (token) return true;
      const next = await window.BB_AUTH.refresh().catch(() => window.BB_ACCOUNT.normalize());
      setAccount(next);
    } catch (err) {
      console.warn('真实任务登录校验失败', err);
      const next = await window.BB_AUTH.refresh().catch(() => window.BB_ACCOUNT.normalize());
      setAccount(next);
    }
    setAccountModal('login');
    return false;
  };

  const startSkill = async (id, opts = {}) => {
    const isRealTask = ['quantity', 'quote', 'compare', 'combined'].includes(id) && opts.source === 'real';
    if (isRealTask && !(await ensureRealAuth())) {
      setAccountModal('login');
      return;
    }
    setModalSkill(null);
    if (id === 'quantity' && opts.source === 'real') {
      startRealQuantity(opts.files || [], opts.context || '');
      return;
    }
    if (id === 'quote' && opts.source === 'real') {
      startRealQuote(opts.files || [], opts.context || '');
      return;
    }
    if (id === 'compare' && opts.source === 'real') {
      startRealCompare(opts.files || [], opts.context || '');
      return;
    }
    if (id === 'combined' && opts.source === 'real') {
      startRealCombined(opts.quoteFiles || [], opts.floorPlans || [], opts.context || '');
      return;
    }
    const currentIsDemo = window.BB_DATA.SESSIONS.some(s => s.id === currentSession)
      || createdSessions.some(s => s.id === currentSession && s.isDemo);
    // 示例数据只能追加到演示会话，避免混入真实项目会话。
    if (view === 'chat' && currentIsDemo) {
      appendTurn(currentSession, id);
      return;
    }
    const sess = createNewSession(id, { group: 'demo', isDemo: true });
    setCurrentSession(sess.id);
    setView('chat');
    appendTurn(sess.id, id);
  };

  const openDetail = (kind, turnId) => {
    // If a kind is supplied, ensure session matches; otherwise use current session
    if (kind) {
      // find an existing session of that kind, OR keep current with overridden detail kind
      // For simplicity: just show the current session's detail at that kind
      setDetailKind(kind);
    }
    setSelectedTurnId(turnId || null);
    setView('detail');
  };

  const onNewSession = () => {
    setView('home');
    setPhase(0);
  };

  const onSelectSession = (id) => {
    setCurrentSession(id);
    setDetailKind(null);
    setSelectedTurnId(null);
    if (view !== 'detail') setView('chat');
    if (isMobile) setSidebarOpen(false);
  };

  const applySessionTitleOverride = (s) => (
    sessionTitleOverrides[s.id] ? { ...s, ...sessionTitleOverrides[s.id] } : s
  );
  const allSessions = [...createdSessions, ...window.BB_DATA.SESSIONS].map(applySessionTitleOverride);
  const session = allSessions.find(s => s.id === currentSession) || allSessions[0];
  const turns = turnsBySession[session.id] || [];
  const latestTurn = turns.length ? turns[turns.length - 1] : null;
  const selectedTurn = selectedTurnId
    ? turns.find(t => t.id === selectedTurnId)
    : (detailKind
      ? [...turns].reverse().find(t => t.kind === detailKind)
      : (view === 'detail' ? latestTurn : null));

  useEffect(() => {
    if (session && session.persisted && (!turnsBySession[session.id] || !turnsBySession[session.id].length)) {
      loadConversationDetail(session.id);
    }
  }, [session && session.id, account.user && account.user.id]);

  // On first visit to chat for a session, ensure there's an initial turn
  useEffect(() => {
    if (view === 'chat' && !session.isNew && !session.persisted) ensureInitialTurn(session);
  }, [view, session.id]);

  // Topbar title reflects latest turn's kind in chat view
  const latestTurnKind = latestTurn ? latestTurn.kind : session.kind;
  const topbarSkill = window.BB_DATA.SKILLS[latestTurnKind] || window.BB_DATA.SKILLS.quantity;
  const topbarKindLabel = ({ quantity: '算量', quote: '审核', combined: '联审', compare: '对比' })[latestTurnKind] || '算量';

  return (
    <div className="app">
      {sidebarOpen && (
        <Sidebar
          currentSessionId={currentSession}
          onSelectSession={onSelectSession}
          onNewSession={() => { onNewSession(); if (isMobile) setSidebarOpen(false); }}
          onClose={() => setSidebarOpen(false)}
          mobile={isMobile}
          createdSessions={createdSessions}
          sessionTitleOverrides={sessionTitleOverrides}
          account={account}
          onOpenLogin={() => setAccountModal('login')}
          onOpenProfile={() => setAccountModal(account.user ? 'overview' : 'login')}
          onOpenSubscribe={() => setAccountModal('subscription')}
        />
      )}

      <main className="main">
        {view !== 'detail' && (
          <div className="topbar">
            {!sidebarOpen && (
              <button className="sb-open-btn" onClick={() => setSidebarOpen(true)}>
                <Icon name="panel-left-open" size={16} />
              </button>
            )}
            <div className="topbar-title">
              {view === 'home' ? (
                <>
                  <Icon name="sparkles" size={14} style={{ color: 'var(--primary)' }} />
                  新建会话
                </>
              ) : (
                <>
                  <Icon name={topbarSkill.icon} size={14} style={{ color: topbarSkill.color }} />
                  {session.title}
                  <span className="topbar-chip" style={{ color: topbarSkill.color, background: topbarSkill.bg }}>{topbarKindLabel}</span>
                  {turns.length > 1 && <span className="topbar-chip" style={{ background: 'var(--bg-subtle)' }}>{turns.length} 轮</span>}
                </>
              )}
            </div>
            <div className="topbar-right">
              <div className="topbar-search">
                <Icon name="search" size={12} />
                搜索会话
                <kbd>⌘K</kbd>
              </div>
              <button className="topbar-btn"><Icon name="bell" size={16} /></button>
              {view === 'home' && (
                <div style={{ display: 'flex', gap: 2, marginLeft: 6, padding: 2, border: '1px solid var(--border)', borderRadius: 8 }}>
                  <button
                    className="topbar-btn"
                    title="居中排版"
                    style={{ width: 28, height: 26, background: homeVariant === 'hero' ? 'var(--bg-subtle)' : 'transparent' }}
                    onClick={() => setHomeVariant('hero')}
                  >
                    <Icon name="align-center" size={13} />
                  </button>
                  <button
                    className="topbar-btn"
                    title="分栏排版"
                    style={{ width: 28, height: 26, background: homeVariant === 'split' ? 'var(--bg-subtle)' : 'transparent' }}
                    onClick={() => setHomeVariant('split')}
                  >
                    <Icon name="columns-2" size={13} />
                  </button>
                </div>
              )}
              {view === 'chat' && canShowResult(latestTurn) && (
                <div style={{ display: 'flex', gap: 2, marginLeft: 6, padding: 2, border: '1px solid var(--border)', borderRadius: 8 }}>
                  {['default','viz','minimal'].map(v => (
                    <button
                      key={v}
                      className="topbar-btn"
                      title={`结果卡: ${v}`}
                      style={{ width: 28, height: 26, background: (latestTurn.resultVariant === v) ? 'var(--bg-subtle)' : 'transparent', fontSize: 10 }}
                      onClick={() => {
                        setTurnsBySession(prev => {
                          const arr = prev[session.id] || [];
                          if (!arr.length) return prev;
                          const next = [...arr];
                          next[next.length - 1] = { ...next[next.length - 1], resultVariant: v };
                          return { ...prev, [session.id]: next };
                        });
                      }}
                    >
                      {v === 'default' && <Icon name="rectangle-horizontal" size={13} />}
                      {v === 'viz' && <Icon name="bar-chart-3" size={13} />}
                      {v === 'minimal' && <Icon name="minus" size={13} />}
                    </button>
                  ))}
                </div>
              )}
            </div>
          </div>
        )}

        {view === 'home' && (
          <div className="content">
            <Home onOpenSkill={openSkill} variant={homeVariant} />
            <InputDock onOpenSkill={openSkill} isHome />
          </div>
        )}

        {view === 'chat' && (
          <div className="content" style={{ display: 'flex', flexDirection: 'column' }}>
            <div style={{ flex: 1, overflowY: 'auto' }}>
              <Chat
                session={session}
                turns={turns}
                onOpenDetail={openDetail}
                onExport={handleExport}
                onRetry={retryRealTask}
              />
            </div>
            <InputDock
              onOpenSkill={openSkill}
              hasResults={canShowResult(latestTurn)}
              onReplay={() => runPhasesForSession(session.id)}
              onPickKind={(id) => appendTurn(session.id, id)}
            />
          </div>
        )}

        {view === 'detail' && (
          <Detail
            key={session.id + (selectedTurn ? selectedTurn.id : (detailKind || ''))}
            kind={(selectedTurn && selectedTurn.kind) || detailKind || session.kind}
            turn={selectedTurn}
            onExport={handleExport}
            onBack={() => { setDetailKind(null); setSelectedTurnId(null); setView('chat'); }}
          />
        )}
      </main>

      {modalSkill && (
        <SkillModal
          skill={modalSkill}
          onClose={() => setModalSkill(null)}
          onStart={startSkill}
        />
      )}

      {accountModal && (
        <AccountModal
          initialView={accountModal}
          account={account}
          onChange={updateAccount}
          onClose={() => setAccountModal(null)}
        />
      )}

      <TweaksPanel
        visible={tweaksMode}
        onClose={() => setTweaksMode(false)}
        tweaks={tweaks}
        setTweaks={setTweaks}
      />

      {/* Fallback tweaks toggle if not using host */}
      {!tweaksMode && (
        <button
          style={{
            position: 'fixed', right: 16, bottom: 16, zIndex: 80,
            width: 40, height: 40, borderRadius: 20,
            background: 'white', border: '1px solid var(--border)',
            boxShadow: '0 4px 20px -6px rgba(0,0,0,0.15)',
            display: 'grid', placeItems: 'center',
            color: 'var(--text-secondary)',
          }}
          title="Tweaks"
          onClick={() => setTweaksMode(true)}
        >
          <Icon name="sliders-horizontal" size={16} />
        </button>
      )}
    </div>
  );
};

const InputDock = ({ onOpenSkill, onPickKind, isHome, hasResults, onReplay }) => {
  const { SKILLS } = window.BB_DATA;
  const [value, setValue] = useState('');
  const ta = useRef(null);

  useEffect(() => {
    if (ta.current) {
      ta.current.style.height = 'auto';
      ta.current.style.height = Math.min(ta.current.scrollHeight, 160) + 'px';
    }
  }, [value]);

  const quickBtns = [
    { id: 'quantity', label: '算量', icon: 'ruler' },
    { id: 'quote', label: '审核', icon: 'search-check' },
    { id: 'combined', label: '联审', icon: 'layers' },
    { id: 'compare', label: '对比', icon: 'scale' },
  ];

  return (
    <div className="input-dock">
      <div className="input-bar">
        {hasResults && (
          <div className="input-quick-row">
            {quickBtns.map(b => (
              <button key={b.id} className="input-quick-chip" onClick={() => onOpenSkill(b.id)}>
                <Icon name={b.icon} size={12} />
                {b.label}
              </button>
            ))}
            {onReplay && (
              <button className="input-quick-chip" onClick={onReplay} style={{ marginLeft: 'auto' }}>
                <Icon name="refresh-cw" size={12} />
                重放进度
              </button>
            )}
          </div>
        )}
        <div className="input-row">
          <div className="input-left">
            <button className="input-icon-btn" title="上传图纸" onClick={() => onOpenSkill('quantity')}><Icon name="paperclip" size={16} /></button>
            <button className="input-route" title="按前端选择的模式处理" onClick={() => onOpenSkill('quantity')}>
              <Icon name="ruler" size={14} />
              算量模式
              <Icon name="chevron-down" size={12} />
            </button>
          </div>
          <textarea
            ref={ta}
            className="input-textarea"
            rows={1}
            placeholder="请点击上方指定模式按钮开始"
            value={value}
            onChange={e => setValue(e.target.value)}
            onKeyDown={e => {
              if (e.key === 'Enter' && !e.shiftKey) {
                e.preventDefault();
                if (value.trim()) setValue('');
              }
            }}
          />
          <div className="input-right">
            <button className="input-icon-btn" title="语音"><Icon name="mic" size={16} /></button>
            <button className="input-send" disabled={!value.trim()} onClick={() => setValue('')}>
              <Icon name="arrow-up" size={16} strokeWidth={2.5} />
            </button>
          </div>
        </div>
      </div>
    </div>
  );
};

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
