// Main app — state machine + scenario routing + tweaks.
// Supports two modes:
//   - Mock mode (useMocks=true, default): fixture scenarios from mock-data.jsx.
//   - Live mode  (useMocks=false): real fetch to the backend via api-client.jsx.
const { useState: useStateApp, useEffect: useEffectApp, useMemo: useMemoApp } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "scenario": "ai",
  "errorState": "none",
  "apiKeyConfigured": true,
  "showLayersDialogOnLoad": false,
  "skipAnimation": false,
  "showConflictForMixed": true,
  "theme": "dark",
  "useMocks": false
}/*EDITMODE-END*/;

const SCEN = {
  ai:    { payload: () => window.SCEN_AI,    file: { name:'essay_draft.pdf',     sizeBytes:248192, type:'pdf', wordCount:1847 } },
  human: { payload: () => window.SCEN_HUMAN, file: { name:'thesis_chapter_03.docx', sizeBytes:612803, type:'docx', wordCount:4218 } },
  mixed: { payload: () => window.SCEN_MIXED, file: { name:'lit_review_v4.pdf',   sizeBytes:391022, type:'pdf', wordCount:2604 } },
};

function fileFromUpload(input){
  // Normalizes either a real File (live mode) or a mock metadata object
  // (preview / recent click) into a consistent shape for the analyzing screen.
  if (input instanceof File){
    const ext = (input.name.split('.').pop() || '').toLowerCase();
    return {
      name: input.name,
      sizeBytes: input.size,
      type: ext || 'pdf',
      wordCount: 0,
      _real: input,
    };
  }
  return input;
}

function Header({ apiOk, scenario, mode, onOpenSettings }){
  return (
    <header className="hdr">
      <div className="hdr-left">
        <div className="brand">
          <span>True&nbsp;Detect</span>
        </div>
        <div className="tagline">Not a score. A verdict with evidence.</div>
      </div>
      <div className="hdr-right">
        <div className="hdr-status">
          <span><b>engine</b> v0.4.2</span>
          <span><b>mode</b> {mode}</span>
          <span><b>scope</b> {scenario}</span>
        </div>
        <button className="api-pill" onClick={onOpenSettings}>
          <span className={`api-dot ${apiOk ? '' : 'warn'}`}></span>
          <span style={{color: apiOk ? 'var(--green)' : 'var(--amber)'}}>●</span>
          <span style={{color:'var(--text)'}}>API key {apiOk ? 'configured' : 'missing'}</span>
        </button>
      </div>
    </header>
  );
}

function App(){
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);

  // App state machine
  const [phase, setPhase] = useStateApp('idle');     // idle | analyzing | report
  const [activeScenario, setActiveScenario] = useStateApp(t.scenario);
  const [drawerLayer, setDrawerLayer] = useStateApp(null);
  const [settingsOpen, setSettingsOpen] = useStateApp(false);
  const [layersOpen, setLayersOpen] = useStateApp(t.showLayersDialogOnLoad);
  const [apiKey, setApiKey] = useStateApp(t.apiKeyConfigured ? 'sk-td-•••••••••stored' : '');
  const [toast, setToast] = useStateApp(null);

  // Live-mode state
  const [livePayload, setLivePayload] = useStateApp(null);
  const [liveFile, setLiveFile] = useStateApp(null);
  const [pendingResponse, setPendingResponse] = useStateApp(false);
  const [animationDone, setAnimationDone] = useStateApp(false);

  // Sync scenario tweak into active scenario when on idle
  useEffectApp(() => {
    if (phase === 'idle') setActiveScenario(t.scenario);
  }, [t.scenario, phase]);

  // Apply theme to body
  useEffectApp(() => {
    document.body.dataset.theme = t.theme || 'dark';
  }, [t.theme]);

  // Trigger error scenarios via tweak
  useEffectApp(() => {
    if (t.errorState === 'too_large'){
      setToast({ kind:'error', title:'413 · file too large', body:'Maximum upload size is 10 MB. Please compress your PDF or split the document.' });
      setPhase('idle');
    } else if (t.errorState === 'too_short'){
      setToast({ kind:'error', title:'422 · document too short', body:'47 words detected. Minimum 100 words for reliable forensic analysis.' });
      setPhase('idle');
    } else if (t.errorState === 'invalid_key'){
      setToast({ kind:'error', title:'401 · invalid API key',
                 body:'The API key was rejected. Re-enter your key in Settings.',
                 onAction: () => { setSettingsOpen(true); setToast(null); setTweak('errorState','none'); },
                 actionLabel: 'Open settings' });
      setPhase('idle');
    } else {
      setToast(null);
    }
  }, [t.errorState]);

  // Transition from analyzing → report when BOTH the local animation has
  // finished AND (mock mode OR live response arrived).
  useEffectApp(() => {
    if (phase !== 'analyzing') return;
    if (!animationDone) return;
    if (t.useMocks){
      setPhase('report');
      setAnimationDone(false);
      return;
    }
    if (livePayload && !pendingResponse){
      setPhase('report');
      setAnimationDone(false);
    }
  }, [phase, animationDone, livePayload, pendingResponse, t.useMocks]);

  const apiOk = t.apiKeyConfigured && t.errorState !== 'invalid_key';
  const mode = t.useMocks ? 'mock' : 'live';

  const startAnalysis = async (input, scenario) => {
    if (!apiOk){ setSettingsOpen(true); return; }

    // Mock-mode flow (or non-File input): use fixture scenarios.
    const isRealFile = input instanceof File;
    if (t.useMocks || !isRealFile){
      setActiveScenario(scenario);
      setLivePayload(null);
      setLiveFile(null);
      setPendingResponse(false);
      setAnimationDone(false);
      setPhase(t.skipAnimation ? 'report' : 'analyzing');
      return;
    }

    // Live-mode flow.
    setLivePayload(null);
    setLiveFile(fileFromUpload(input));
    setPendingResponse(true);
    setAnimationDone(false);
    setPhase('analyzing');

    try {
      const payload = await window.TrueDetectAPI.analyzeDocument(input);
      setLivePayload(payload);
      setPendingResponse(false);
    } catch(err){
      setPendingResponse(false);
      setPhase('idle');
      const titles = {
        invalid_key:  '401 · invalid API key',
        too_large:    '413 · file too large',
        too_short:    '422 · document too short',
        server_error: 'engine error',
      };
      const errKind = (err && err.kind) || 'server_error';
      const errBody = (err && err.detail) || 'Unknown error';
      const opts = { kind:'error', title: titles[errKind] || 'error', body: errBody };
      if (errKind === 'invalid_key'){
        opts.onAction = () => { setSettingsOpen(true); setToast(null); };
        opts.actionLabel = 'Open settings';
      }
      setToast(opts);
    }
  };

  const onUploadFromDrop = (info) => startAnalysis(info, t.scenario);
  const onPickRecent = (scenario) => startAnalysis(SCEN[scenario].file, scenario);

  const onAnalysisDone = () => setAnimationDone(true);
  const onCancel = () => {
    setPhase('idle');
    setLivePayload(null);
    setLiveFile(null);
    setPendingResponse(false);
    setAnimationDone(false);
  };
  const onReset = () => {
    setPhase('idle');
    setDrawerLayer(null);
    setLivePayload(null);
    setLiveFile(null);
  };

  // Resolve which payload + file to render with.
  const usingLive = !t.useMocks && livePayload;
  const payload = usingLive ? livePayload : SCEN[activeScenario].payload();
  const file = usingLive
    ? liveFile
    : (liveFile && !t.useMocks ? liveFile : SCEN[activeScenario].file);

  // For mixed scenario, allow toggling whether conflicts are visible
  const renderPayload = useMemoApp(() => {
    if (t.useMocks && activeScenario === 'mixed' && !t.showConflictForMixed){
      return { ...payload, verdict: { ...payload.verdict, conflicts_detected: false, conflicts: [] } };
    }
    return payload;
  }, [payload, activeScenario, t.showConflictForMixed, t.useMocks]);

  return (
    <div className="app">
      <Header apiOk={apiOk} scenario={activeScenario} mode={mode} onOpenSettings={() => setSettingsOpen(true)} />

      <div className="main">
        {phase === 'idle' && (
          <IdleScreen
            onUpload={onUploadFromDrop}
            onPickRecent={onPickRecent}
            onShowLayers={() => setLayersOpen(true)}
            recent={window.FAKE_RECENT}
          />
        )}
        {phase === 'analyzing' && (
          <AnalyzingScreen
            payload={payload}
            file={file}
            onDone={onAnalysisDone}
            onCancel={onCancel}
          />
        )}
        {phase === 'report' && (
          <ReportLayout
            payload={renderPayload}
            onLayer={setDrawerLayer}
            onReset={onReset}
          />
        )}
      </div>

      {drawerLayer && (
        <LayerDrawer
          layerKey={drawerLayer}
          payload={payload}
          onClose={() => setDrawerLayer(null)}
        />
      )}

      {settingsOpen && (
        <SettingsDialog
          apiKey={apiKey}
          onSave={(k) => {
            setApiKey(k);
            setTweak('apiKeyConfigured', !!k);
            if (window.TrueDetectAPI) window.TrueDetectAPI.setApiKey(k);
            if (t.errorState === 'invalid_key') setTweak('errorState','none');
          }}
          onClose={() => setSettingsOpen(false)}
        />
      )}
      {layersOpen && <WhyLayersDialog onClose={() => setLayersOpen(false)} />}

      {toast && (
        <Toast {...toast} onClose={() => { setToast(null); setTweak('errorState','none'); }} />
      )}

      <TweaksPanel>
        <TweakSection label="Mode" />
        <TweakRadio
          label="Data source"
          value={t.useMocks ? 'mock' : 'live'}
          options={[
            { value:'mock', label:'Mock (design preview)' },
            { value:'live', label:'Live (real backend)' },
          ]}
          onChange={(v) => setTweak('useMocks', v === 'mock')}
        />

        <TweakSection label="Theme" />
        <TweakRadio
          label="Theme"
          value={t.theme}
          options={[
            { value:'dark',  label:'Dark' },
            { value:'paper', label:'Paper' },
          ]}
          onChange={(v) => setTweak('theme', v)}
        />

        {t.useMocks && (
          <>
            <TweakSection label="Scenario" />
            <TweakSelect
              label="Verdict scenario"
              value={t.scenario}
              options={[
                { value:'ai',    label:'AI (essay_draft.pdf · 0.781)' },
                { value:'human', label:'Human (thesis_03.docx · 0.082)' },
                { value:'mixed', label:'Mixed (lit_review_v4.pdf · 0.518)' },
              ]}
              onChange={(v) => { setTweak('scenario', v); if (phase !== 'idle') setActiveScenario(v); }}
            />
            {t.scenario === 'mixed' && (
              <TweakToggle
                label="Show conflict banner"
                value={t.showConflictForMixed}
                onChange={(v) => setTweak('showConflictForMixed', v)}
              />
            )}
          </>
        )}

        <TweakSection label="State" />
        <TweakRadio
          label="Phase"
          value={phase}
          options={['idle','analyzing','report']}
          onChange={(v) => setPhase(v)}
        />
        <TweakToggle
          label="Skip analyzing animation"
          value={t.skipAnimation}
          onChange={(v) => setTweak('skipAnimation', v)}
        />

        <TweakSection label="Header / API" />
        <TweakToggle
          label="API key configured"
          value={t.apiKeyConfigured}
          onChange={(v) => setTweak('apiKeyConfigured', v)}
        />

        <TweakSection label="Errors & dialogs" />
        <TweakSelect
          label="Error state"
          value={t.errorState}
          options={[
            { value:'none',        label:'(none)' },
            { value:'too_large',   label:'413 · file too large' },
            { value:'too_short',   label:'422 · doc too short' },
            { value:'invalid_key', label:'401 · invalid API key' },
          ]}
          onChange={(v) => setTweak('errorState', v)}
        />
        <TweakButton label="Open Settings dialog" onClick={() => setSettingsOpen(true)} />
        <TweakButton label="Open “Why 10 layers”"  onClick={() => setLayersOpen(true)} />

        <TweakSection label="Drawer probe" />
        <TweakSelect
          label="Open layer drawer"
          value={drawerLayer || 'none'}
          options={[
            { value:'none', label:'(closed)' },
            ...window.LAYER_META.map(l => ({ value: l.key, label: `${l.short} · ${l.name}` })),
          ]}
          onChange={(v) => { if (phase !== 'report') setPhase('report'); setDrawerLayer(v === 'none' ? null : v); }}
        />
      </TweaksPanel>
    </div>
  );
}

function ReportLayout({ payload, onLayer, onReset }){
  return (
    <div style={{flex:1, display:'flex', flexDirection:'column', overflow:'auto'}}>
      <ConflictBanner conflicts={payload.verdict.conflicts_detected ? payload.verdict.conflicts : null} />
      <HeroStrip verdict={payload.verdict} />

      {/* Executive summary */}
      <div style={{padding:'18px 32px', borderBottom:'1px solid var(--border)', background:'var(--surface)'}}>
        <div className="section-label" style={{marginBottom:8}}>▸ executive summary</div>
        <div className="sans" style={{fontSize:14, color:'var(--text-muted)', lineHeight:1.6, maxWidth:'90ch'}}>
          {payload.report.executive_summary}
        </div>
      </div>

      {/* Doc panel + layer table */}
      <div style={{display:'grid', gridTemplateColumns:'minmax(260px, 30%) 1fr', borderBottom:'1px solid var(--border)'}}>
        <DocumentPanel
          doc={payload.document}
          analysisTime={payload.analysis_time_seconds}
          onDownloadJson={() => downloadJson(payload)}
          onDownloadPdf={() => downloadPdf(payload)}
          onReset={onReset}
        />
        <LayerTable rows={payload.verdict.layer_breakdown} onRowClick={onLayer} />
      </div>

      <SentenceHeatmap heatmap={payload.report.sentence_heatmap} />
      <SuspiciousPassages passages={payload.report.suspicious_passages} />

      <footer style={{padding:'18px 32px', borderTop:'1px solid var(--border)', display:'flex', justifyContent:'space-between'}}>
        <div className="mono" style={{fontSize:10, color:'var(--text-dim)'}}>
          analysis id <span style={{color:'var(--text-muted)'}}>0xa3f1·b2e9·{Math.floor(Math.random()*9999).toString(16)}</span> · cached locally · expires in 24h
        </div>
        <div className="mono" style={{fontSize:10, color:'var(--text-dim)'}}>
          TrueDetect · v0.4.2 · this is forensic input — not a legal determination
        </div>
      </footer>
    </div>
  );
}

function downloadJson(payload){
  const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `truedetect-${(payload.document && payload.document.filename) || 'report'}.json`;
  a.click();
  URL.revokeObjectURL(url);
}

function downloadPdf(payload){
  if (payload.pdf_report_url && window.TrueDetectAPI){
    const base = window.TrueDetectAPI.getBaseUrl().replace(/\/$/, '');
    window.open(base + payload.pdf_report_url, '_blank');
  } else {
    alert('PDF report not available (mock mode or backend not configured).');
  }
}

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