// v2 App root — orchestrates Home + 5-stage shell, hydrates from /api/state.
//
// Renders only when the URL has ?v=2 (hash route opt-in). The legacy v1 app
// stays fully functional otherwise.

const V2App = () => {
  const [authChecked, setAuthChecked] = React.useState(false);
  const [user, setUser] = React.useState(null);
  const [session, setSession] = React.useState(null);
  const [state, setState] = React.useState({projects: []});
  const [loaded, setLoaded] = React.useState(false);
  const _readURL = () => {
    const sp = new URLSearchParams(location.search);
    return { pid: sp.get('p') || null, stage: sp.get('s') || 'data' };
  };
  const _initial = _readURL();
  const [activeProjectId, setActiveProjectId] = React.useState(_initial.pid);
  const [stage, _setStage] = React.useState(_initial.stage);
  const setStage = (s) => _setStage(s);
  const [toast, setToast] = React.useState(null);
  const [localPinned, setLocalPinned] = React.useState([]);
  const [runningJob, setRunningJob] = React.useState(null);
  const flash = (t) => { setToast(t); setTimeout(() => setToast(null), 2400); };

  // Sync URL ↔ activeProjectId / stage so each project has a unique deep-link.
  React.useEffect(() => {
    const onPop = () => { const u = _readURL(); setActiveProjectId(u.pid); _setStage(u.stage); };
    window.addEventListener('popstate', onPop);
    return () => window.removeEventListener('popstate', onPop);
  }, []);
  React.useEffect(() => {
    const sp = new URLSearchParams(location.search);
    const cur = { p: sp.get('p') || null, s: sp.get('s') || 'data' };
    const next = { p: activeProjectId || null, s: stage || 'data' };
    if (cur.p === next.p && cur.s === next.s) return;
    if (next.p) sp.set('p', next.p); else sp.delete('p');
    if (next.s && next.s !== 'data') sp.set('s', next.s); else sp.delete('s');
    const qs = sp.toString();
    history.pushState(null, '', qs ? '?' + qs : location.pathname);
  }, [activeProjectId, stage]);

  // Auth bootstrap (mirrors v1 flow)
  React.useEffect(() => {
    let unsub = null;
    (async () => {
      if (!window.palette_auth) { setAuthChecked(true); return; }
      const s = await window.palette_auth.waitForSession();
      setSession(s); setAuthChecked(true);
      unsub = window.palette_auth.onChange(next => { setSession(next); if (!next) { setUser(null); setLoaded(false); } });
    })();
    const onUnauth = () => { setSession(null); setUser(null); };
    window.addEventListener('palette:unauth', onUnauth);
    return () => { if (unsub) unsub(); window.removeEventListener('palette:unauth', onUnauth); };
  }, []);
  React.useEffect(() => {
    if (!session) return;
    (async () => {
      try { const me = await window.API.me(); setUser(me); await hydrate(); }
      catch(e) { console.error(e); flash('세션을 불러올 수 없어요'); }
    })();
  }, [session]);

  const hydrate = React.useCallback(async () => {
    const s = await window.API.state();
    setState(s); setLoaded(true);
    return s;
  }, []);

  // Auto-refresh state every 8s while on Data stage (catches analyze progress)
  React.useEffect(() => {
    if (!loaded || stage !== 'data') return;
    const t = setInterval(() => { window.API.state().then(setState).catch(()=>{}); }, 8000);
    return () => clearInterval(t);
  }, [loaded, stage]);

  // Pinned-board persistence callback. MUST be declared before any conditional
  // returns or React's hook order will diverge between renders.
  const persistPinned = React.useCallback(async (newSet, _palette, _pid, _board) => {
    if (!_pid) return;
    const map = {};
    (_palette?.blocks || []).forEach(b => (b.keywords || []).forEach(k => map[k.id] = k));
    const items = newSet.map(id => map[id]).filter(Boolean).map(k => ({
      w: k.en, ko: k.ko, why: k.gloss, tier: (k.type||'literal').toLowerCase(),
    }));
    try { await window.API.saveBoard(_pid, {pinned: items, watching: _board?.watching || []}); }
    catch(e) { console.error('saveBoard failed', e); }
  }, []);

  const _bootScreen = (msg) => (
    <div className="v2-app" style={{minHeight:'100vh',display:'flex',alignItems:'center',justifyContent:'center',
      flexDirection:'column',gap:16,background:'var(--bg)'}}>
      <span style={{fontFamily:'var(--font-mono)',fontSize:12,letterSpacing:'.18em',color:'var(--muted)'}}>PALETTE · MOSAIC</span>
      <V2Spinner size="lg" color="var(--accent)"/>
      <span style={{fontSize:12,color:'var(--sub)'}}>{msg}</span>
    </div>
  );
  if (!authChecked) return _bootScreen('인증 확인 중…');
  if (!session) {
    const path = window.location.pathname || '';
    const m = path.match(/\/invite\/([\w\-]+)/);
    const inviteToken = (m && m[1]) || new URLSearchParams(window.location.search).get('invite') || null;
    return <window.LoginScreen inviteToken={inviteToken} onReady={()=>{}}/>;
  }
  if (!loaded) return _bootScreen('워크스페이스 불러오는 중…');

  // Project list view (Home)
  const project = (state.projects || []).find(p => p.id === activeProjectId);
  if (!project) {
    return (
      <V2HomeStage
        projects={state.projects || []}
        user={user}
        onOpenProject={(p) => { setActiveProjectId(p.id); setStage('data'); }}
        onNewProject={async ({client, name, brief}) => {
          const created = await window.API.createProject({client, name, brief});
          await hydrate();
          setActiveProjectId(created.id); setStage('data');
        }}
      />
    );
  }

  // Build palette-data shape from live state
  const palette = window.v2BuildPaletteData(state, project.id) || {project, files: [], blocks: [], pinned: [], watching: []};

  // Pinned IDs are derived from the saved board — but board uses a different id
  // shape (we built v2 ids as `${block.id}_k${i}`). Use heat-based fallback so
  // the v2 UI never shows an empty board: pin the first 6 metaphor-tier kws if
  // the user hasn't explicitly pinned anything in v2 yet.
  const board = (state.boards || {})[project.id] || {pinned: [], watching: []};
  const v2PinnedFromBackend = (board.pinned || [])
    .map(p => `${p.w}|${p.ko}`)
    .reduce((acc, key) => { acc[key] = true; return acc; }, {});
  const pinnedIds = [];
  (palette.blocks || []).forEach(b => (b.keywords || []).forEach(k => {
    const key = `${k.en}|${k.ko}`;
    if (v2PinnedFromBackend[key]) pinnedIds.push(k.id);
  }));
  const allPinned = [...new Set([...pinnedIds, ...localPinned])];

  const onPin = async (kw) => {
    const isPinned = allPinned.includes(kw.id);
    const next = isPinned ? allPinned.filter(id => id !== kw.id) : [...allPinned, kw.id];
    setLocalPinned(next);
    await persistPinned(next, palette, project.id, board);
    await hydrate();
  };
  const onUnpin = async (id) => {
    const next = allPinned.filter(x => x !== id);
    setLocalPinned(next);
    await persistPinned(next, palette, project.id, board);
    await hydrate();
  };

  const onUpload = async (folderId, file, onProgress) => {
    await window.API.uploadFile(project.id, folderId, file, onProgress);
    await hydrate();
    flash(`업로드 완료 · ${file.name}`);
  };
  const onAnalyzeFile = async (fileId) => {
    flash('분석 시작…');
    try {
      const r = await window.API.analyzeFile(fileId);
      await hydrate();
      const a = r?.analysis || {};
      if (a.error) {
        flash('분석 실패: ' + String(a.error).slice(0, 140));
      } else if (a.low_signal) {
        flash('본문 신호가 약함 — 텍스트가 거의 없는 파일');
      } else {
        flash(`분석 완료 · ${a.block_count || 0} blocks · ${(a.keywords||[]).length} keywords`);
      }
    } catch(e) { flash('분석 실패: ' + e.message); }
  };
  const onAnalyzeAll = async () => {
    setRunningJob('대기 중…');
    flash('Batch analyze 시작…');
    try {
      const r = await window.API.analyzeProject(project.id);
      if (r.job_id) { pollJob(r.job_id); }
      else { setRunningJob(null); flash('할 일 없음'); }
    } catch(e) {
      setRunningJob(null);
      flash('Batch failed: ' + e.message);
    }
  };
  const pollJob = (jobId) => {
    const tick = async () => {
      try {
        const j = await window.API.getJob(jobId);
        const label = `분석 ${j.completed}/${j.total}`;
        setRunningJob(label);
        flash(label + '…');
        if (j.status === 'running') setTimeout(tick, 4000);
        else { setRunningJob(null); await hydrate(); flash(`완료 ${j.completed}/${j.total}`); }
      } catch(e) { console.error(e); setRunningJob(null); }
    };
    tick();
  };

  const onSaveSummary = async (block, summary) => {
    try {
      await window.API.patchBlock(block.file ? (block.file) : '', block.id, {summary});
      await hydrate(); flash('요약 저장됨');
    } catch(e) { flash('저장 실패: ' + e.message); }
  };

  const onDownloadRef = async ({candidate, folder_id}) => {
    flash('자료실로 다운로드 중…');
    try {
      const res = await window.API.downloadReference(project.id, {candidate, folder_id});
      await hydrate();
      flash(res.note ? `⚠ ${res.note}` : '저장 완료');
    } catch(e) { flash('다운로드 실패: ' + e.message); }
  };

  const onUpdateProject = async (patch) => {
    try {
      await window.API.patchProject(project.id, patch);
      await hydrate();
      flash('프로젝트 정보 저장됨');
    } catch(e) { flash('저장 실패: ' + e.message); }
  };

  return (
    <div className="v2-app" style={{display:'flex',flexDirection:'column',height:'100vh'}}>
      <V2TopNav stage={stage} setStage={setStage}
        project={{...project, code: project.client}} user={user}
        onHome={()=>setActiveProjectId(null)}
        onSignOut={async ()=>{ try { await window.palette_auth.signOut(); } catch(e){} setSession(null); setUser(null); }}
        onAnalyzeAll={onAnalyzeAll} runningJob={runningJob}/>

      <div style={{flex:1,minHeight:0}}>
        {stage === 'data' && (
          <V2DataStage palette={palette} project={project} user={user}
            onUpload={onUpload} onAnalyzeFile={onAnalyzeFile} onAnalyzeAll={onAnalyzeAll}
            onUpdateProject={onUpdateProject}/>
        )}
        {stage === 'analysis' && (
          <V2AnalysisStage palette={palette} project={project} pinnedIds={allPinned}
            onPin={onPin} onUnpin={onUnpin} onSaveSummary={onSaveSummary}
            onAnalyzeFile={onAnalyzeFile} onGoIdeate={()=>setStage('ideate')}/>
        )}
        {stage === 'ideate' && (
          <V2IdeateStage palette={palette} projectId={project.id} pinnedIds={allPinned}/>
        )}
        {stage === 'references' && (
          <V2RefStage palette={palette} projectId={project.id} pinnedIds={allPinned}
            onDownload={onDownloadRef}/>
        )}
        {stage === 'final' && (
          <V2FinalStage palette={palette} projectId={project.id} pinnedIds={allPinned}
            onExportDeck={()=>window.open(`/api/projects/${project.id}/deck.html`,'_blank')}
            onExportMarkdown={async () => {
              try {
                const r = await window.API.exportProject(project.id);
                const blob = new Blob([r.markdown], {type:'text/markdown'});
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url; a.download = (project.name||'project').replace(/[^\w]+/g,'_')+'.md';
                a.click(); URL.revokeObjectURL(url);
              } catch(e) { flash('Markdown 실패: '+e.message); }
            }}/>
        )}
      </div>

      {toast && (
        <div style={{position:'fixed',bottom:18,left:'50%',transform:'translateX(-50%)',
          padding:'10px 16px',background:'var(--ink)',color:'#F8F7F4',borderRadius:999,
          fontSize:12,fontFamily:'var(--font-mono)',zIndex:60,boxShadow:'0 10px 30px rgba(0,0,0,.12)'}}>
          {toast}
        </div>
      )}
    </div>
  );
};

window.V2App = V2App;
