/* Execution & monitoring tab — run controls, progress, pre-flight checks, quarantine viewer */

const Execution = ({ stages, project, onTabChange, onSettingsSection, onSetFixTarget }) => {
  const [tick, setTick] = React.useState(0);
  const [running, setRunning] = React.useState(true);
  const [bump, setBump] = React.useState(0);

  React.useEffect(() => {
    if (!running) return;
    const id = setInterval(() => setTick(t => t + 1), 1200);
    return () => clearInterval(id);
  }, [running]);

  const handleStartRun = (mode, scopeOpts = {}) => {
    const { scope = 'all', tables, parentRunId, triggeredBy } = scopeOpts;
    if (typeof window.startPartialRun === 'function') {
      window.startPartialRun(project.id, { mode, scope, tables, parentRunId, triggeredBy });
    }
    setBump(b => b + 1);
    setRunning(true);
  };

  const s = stages.map(st => ({
    ...st,
    pct: st.tone === 'running' ? Math.min(st.pct + ((tick * 0.4) % 3.5), 99) : st.pct,
  }));
  const overall = s.reduce((a, x) => a + x.pct, 0) / s.length;

  /* Pre-flight checks — computed on every render from current project state. */
  const checks = React.useMemo(
    () => (window.getPreflightChecks ? window.getPreflightChecks(project) : []),
    [project, tick]
  );
  const counts = checks.reduce((a, c) => { a[c.status] = (a[c.status] || 0) + 1; return a; }, {});
  const hasBlocking = (counts.fail || 0) > 0;

  const navigateToFix = (fix) => {
    if (!fix || !onTabChange) return;
    if (fix.section && onSettingsSection) onSettingsSection(fix.section);
    if (fix.target && onSetFixTarget) onSetFixTarget(fix.target);
    onTabChange(fix.tab);
  };

  /* Quarantine reason 카드 → Mapping 탭 점프. sampleRows[0] 첫 키를
     컬럼 앵커 fallback 으로 사용. mapping.jsx 는 fixTarget.colName 이 있으면
     pulseColName 으로 해당 컬럼을 짧게 강조한다. */
  const handleJumpToMapping = (entry) => {
    if (!entry || !onTabChange) return;
    const colName = Object.keys(entry.sampleRows?.[0] || {})[0];
    if (onSetFixTarget) onSetFixTarget({
      side: 'tobe',
      tobeName: entry.table,
      internalName: entry.table,
      colName,
      highlight: 'quarantine',
    });
    onTabChange('mapping');
  };

  /* Quarantine reason 카드 → "이 테이블만 다시 이행". 활성 run 진행 중이면
     무시 (버튼은 비활성 상태). 항상 rehearsal mode 로 시범 이행. */
  const handleQuarantineRequeueTable = (entry) => {
    if (!entry?.table) return;
    if (window.getActiveRun?.(project.id)) return;
    handleStartRun('rehearsal', {
      scope: 'single',
      tables: [entry.table],
      triggeredBy: { actor: 'Admin', source: 'manual · quarantine · single' },
    });
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'auto' }}>
      {/* Run header */}
      <RunHeader project={project} running={running}
        onToggleRun={() => setRunning(r => !r)}
        onStartRun={handleStartRun}
        bump={bump}/>

      {/* Pre-flight checks */}
      <PreflightPanel checks={checks} counts={counts} hasBlocking={hasBlocking} onFix={navigateToFix}/>

      {/* Overall progress */}
      <div style={{ display: 'flex', borderBottom: '1px solid var(--border)', background: 'var(--panel)' }}>
        <div style={{ flex: 1, padding: '14px 18px' }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 8 }}>
            <div style={{ fontSize: 11, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 0.8 }}>Overall progress</div>
            <div style={{ fontFamily: 'var(--mono)', fontSize: 12, color: 'var(--text-2)' }}>{overall.toFixed(1)}% · 2 of 7 stages complete</div>
          </div>
          <div style={{ display: 'flex', gap: 2, height: 8, borderRadius: 2, overflow: 'hidden' }}>
            {s.map(st => (
              <div key={st.id} title={`${st.name} · ${st.pct.toFixed(0)}%`}
                style={{ flex: 1, background: 'var(--border)', position: 'relative', overflow: 'hidden' }}>
                <div style={{
                  width: `${st.pct}%`, height: '100%',
                  background: st.tone === 'ok' ? 'var(--green)' : st.tone === 'idle' ? 'var(--text-4)' : st.color,
                  transition: 'width .4s ease',
                }}/>
              </div>
            ))}
          </div>
          <div style={{ display: 'flex', gap: 2, marginTop: 4 }}>
            {s.map(st => (
              <div key={st.id} style={{ flex: 1, textAlign: 'center', fontSize: 10, color: 'var(--text-3)', fontFamily: 'var(--mono)' }}>
                {st.name.toLowerCase().split(' ')[0]}
              </div>
            ))}
          </div>
        </div>
      </div>

      {/* Stage list */}
      <div style={{ padding: '14px 18px', background: 'var(--panel)', borderBottom: '1px solid var(--border)' }}>
        <div style={{ fontSize: 11, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 0.8, marginBottom: 8 }}>Pipeline stages</div>
        <div style={{ border: '1px solid var(--border)', borderRadius: 4, overflow: 'hidden', background: 'var(--panel)' }}>
          {s.map((st, i) => (
            <div key={st.id} style={{
              display: 'grid',
              gridTemplateColumns: '24px 170px 1fr 80px 90px 80px 24px',
              gap: 14, alignItems: 'center',
              padding: '10px 14px',
              borderBottom: i < s.length - 1 ? '1px solid var(--border)' : 'none',
              background: st.tone === 'running' ? 'var(--amber-50)' : 'var(--panel)',
            }}>
              <div style={{ fontFamily: 'var(--mono)', fontSize: 11, color: 'var(--text-4)' }}>{String(i + 1).padStart(2, '0')}</div>
              <div>
                <div style={{ fontWeight: 500, fontSize: 13 }}>{st.name}</div>
                <div style={{ fontFamily: 'var(--mono)', fontSize: 11, color: 'var(--text-3)' }}>{st.sub}</div>
              </div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <div style={{ flex: 1 }}>
                  <ProgressBar pct={st.pct} tone={st.tone} color={st.tone !== 'idle' && st.tone !== 'ok' ? st.color : undefined}/>
                </div>
                <div style={{ width: 44, textAlign: 'right', fontFamily: 'var(--mono)', fontSize: 12, color: 'var(--text-2)' }}>{st.pct.toFixed(0)}%</div>
              </div>
              <div style={{ fontFamily: 'var(--mono)', fontSize: 11.5, color: 'var(--text-2)' }}>{st.rate}</div>
              <div style={{ fontFamily: 'var(--mono)', fontSize: 11.5, color: 'var(--text-3)' }}>eta {st.eta}</div>
              <div>
                {st.tone === 'ok' && <StatusBadge tone="ok">done</StatusBadge>}
                {st.tone === 'running' && <StatusBadge tone="running">live</StatusBadge>}
                {st.tone === 'idle' && <StatusBadge tone="queued">queued</StatusBadge>}
              </div>
              <div style={{ color: 'var(--text-4)', cursor: 'pointer' }}><Ic.chevR/></div>
            </div>
          ))}
        </div>
      </div>

      {/* Run history */}
      <RunHistory project={project}/>

      {/* Quarantine + workers */}
      <div style={{ display: 'flex', gap: 14, padding: 14, background: 'var(--bg)', flex: 1, minHeight: 260 }}>
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', minWidth: 0 }}>
          <QuarantineViewer
            entries={window.getQuarantine?.(project?.id) || []}
            projectId={project?.id}
            onTriggerPartialRun={handleQuarantineRequeueTable}
            onJumpToMapping={handleJumpToMapping}/>
        </div>

        {/* Workers */}
        <div style={{ width: 320, background: 'var(--panel)', border: '1px solid var(--border)', borderRadius: 4, padding: 12, alignSelf: 'flex-start' }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
            <div style={{ fontSize: 11, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 0.8 }}>Worker pool</div>
            <span style={{ fontFamily: 'var(--mono)', fontSize: 11.5, color: 'var(--text-2)' }}>12 / 16 active</span>
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(8, 1fr)', gap: 3, marginBottom: 12 }}>
            {Array.from({ length: 16 }).map((_, i) => {
              const state = i < 2 ? 'idle' : i < 12 ? 'running' : i < 14 ? 'ok' : 'idle';
              const c = state === 'running' ? 'var(--amber)' : state === 'ok' ? 'var(--green)' : 'var(--border-strong)';
              return (
                <div key={i} style={{
                  height: 22, borderRadius: 2, background: c,
                  display: 'grid', placeItems: 'center',
                  fontFamily: 'var(--mono)', fontSize: 9, color: state === 'idle' ? 'var(--text-3)' : '#fff',
                  fontWeight: 500,
                }}>{String(i + 1).padStart(2, '0')}</div>
              );
            })}
          </div>
          <div style={{ fontSize: 11.5, fontFamily: 'var(--mono)', color: 'var(--text-2)', lineHeight: 1.7 }}>
            <Kv k="queue depth" v="3"/>
            <Kv k="buffer" v="64 MB"/>
            <Kv k="lag" v="212 ms" tone="ok"/>
            <Kv k="retries" v="7"/>
            <Kv k="mem" v="4.2 / 8.0 GB"/>
            <Kv k="cpu" v="68%"/>
          </div>
        </div>
      </div>
    </div>
  );
};

/* ─── Pre-flight panel ────────────────────────────────────────────
   Collapsible strip below the run header. Summarizes gate checks; expands
   to show each check with its status, detail and a Fix-link that deep-jumps
   to the relevant tab/section. */

const PreflightPanel = ({ checks, counts, hasBlocking, onFix }) => {
  /* Default open if any fail or >=2 warn; otherwise compact. */
  const shouldOpen = hasBlocking || (counts.warn || 0) >= 2;
  const [open, setOpen] = React.useState(shouldOpen);
  React.useEffect(() => { setOpen(shouldOpen); /* re-sync on data change */ }, [shouldOpen]);

  const toneFor = (s) => s === 'pass' ? 'ok' : s === 'warn' ? 'warn' : s === 'fail' ? 'err' : 'queued';

  return (
    <div style={{ borderBottom: '1px solid var(--border)', background: hasBlocking ? 'var(--red-50)' : (counts.warn ? 'var(--amber-50)' : 'var(--panel)') }}>
      <div onClick={() => setOpen(o => !o)} style={{
        padding: '9px 18px',
        display: 'flex', alignItems: 'center', gap: 10,
        cursor: 'pointer',
      }}>
        <span style={{ color: 'var(--text-4)', fontSize: 10, width: 10 }}>{open ? '▾' : '▸'}</span>
        <span style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.6, color: hasBlocking ? 'var(--red)' : (counts.warn ? 'var(--amber)' : 'var(--text-2)') }}>
          Pre-flight
        </span>
        <div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
          {counts.pass > 0 && <StatusBadge tone="ok">{counts.pass} pass</StatusBadge>}
          {counts.warn > 0 && <StatusBadge tone="warn">{counts.warn} warn</StatusBadge>}
          {counts.fail > 0 && <StatusBadge tone="err">{counts.fail} fail</StatusBadge>}
          {counts.skip > 0 && <StatusBadge tone="queued">{counts.skip} n/a</StatusBadge>}
        </div>
        <div style={{ flex: 1 }}/>
        <span style={{ fontSize: 11, color: 'var(--text-3)', fontFamily: 'var(--mono)' }}>
          {hasBlocking ? 'Run blocked — fix 위 항목을 해결하세요'
            : counts.warn ? 'Run 가능 · 주의 항목 확인 권장'
            : 'All checks pass — ready to run'}
        </span>
      </div>

      {open && (
        <div style={{ padding: '4px 18px 14px' }}>
          <div style={{ border: '1px solid var(--border)', borderRadius: 4, background: 'var(--panel)' }}>
            {checks.map((c, i) => (
              <div key={c.id} style={{
                display: 'grid', gridTemplateColumns: '24px 260px 1fr auto',
                alignItems: 'center', gap: 12,
                padding: '8px 14px',
                borderBottom: i < checks.length - 1 ? '1px solid var(--border)' : 'none',
                background: c.status === 'fail' ? 'var(--red-50)' : c.status === 'warn' ? 'var(--amber-50)' : 'var(--panel)',
              }}>
                <StatusDot tone={toneFor(c.status)} size={9}/>
                <span style={{ fontSize: 12, fontWeight: 500 }}>{c.title}</span>
                <span style={{ fontFamily: 'var(--mono)', fontSize: 11, color: c.status === 'fail' ? 'var(--red)' : c.status === 'warn' ? 'var(--amber)' : c.status === 'skip' ? 'var(--text-4)' : 'var(--text-2)' }}>
                  {c.detail}
                </span>
                {c.fix && c.status !== 'pass' && c.status !== 'skip' && (
                  <Btn kind="ghost" size="sm" onClick={() => onFix?.(c.fix)}>
                    Fix →
                  </Btn>
                )}
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
};

/* ─── Quarantine viewer ──────────────────────────────────────────
   Replaces the prior single-error / single-warning callouts. Groups
   quarantined rows by reason, lets user drill in to inspect sample rows
   and take action (requeue / discard / export — placeholder alerts for
   the prototype). */

const QuarantineViewer = ({ entries, projectId, onTriggerPartialRun, onJumpToMapping }) => {
  const [filter, setFilter] = React.useState('all'); // all | error | warning
  const [expanded, setExpanded] = React.useState(new Set([entries[0]?.id]));
  const blocked = !!(projectId && window.getActiveRun?.(projectId)); /* 활성 run 동시성 가드 */

  const filtered = entries.filter(e => filter === 'all' || e.severity === filter);
  const total = entries.reduce((a, e) => a + e.count, 0);
  const errs = entries.filter(e => e.severity === 'error');
  const warns = entries.filter(e => e.severity === 'warning');
  const errRows = errs.reduce((a, e) => a + e.count, 0);
  const warnRows = warns.reduce((a, e) => a + e.count, 0);

  const toggle = (id) => {
    const n = new Set(expanded);
    n.has(id) ? n.delete(id) : n.add(id);
    setExpanded(n);
  };

  return (
    <div style={{ border: '1px solid var(--border)', borderRadius: 4, background: 'var(--panel)', overflow: 'hidden', display: 'flex', flexDirection: 'column' }}>
      {/* Header */}
      <div style={{
        padding: '10px 14px', borderBottom: '1px solid var(--border)',
        display: 'flex', alignItems: 'center', gap: 12,
      }}>
        <div>
          <div style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.7 }}>Quarantine</div>
          <div style={{ fontSize: 10.5, color: 'var(--text-3)', fontFamily: 'var(--mono)', marginTop: 1 }}>
            {entries.length} groups · {total} rows ({errRows} error · {warnRows} warning)
          </div>
        </div>
        <div style={{ flex: 1 }}/>
        <div style={{ display: 'flex', height: 24, border: '1px solid var(--border)', borderRadius: 3, overflow: 'hidden', background: 'var(--panel)' }}>
          {[
            { k: 'all', l: 'All', n: entries.length },
            { k: 'error', l: 'Errors', n: errs.length },
            { k: 'warning', l: 'Warnings', n: warns.length },
          ].map((f, i) => (
            <button key={f.k} onClick={() => setFilter(f.k)} style={{
              padding: '0 10px', border: 'none',
              borderLeft: i ? '1px solid var(--border)' : 'none',
              background: filter === f.k ? 'var(--navy-50)' : 'transparent',
              color: filter === f.k ? 'var(--navy)' : 'var(--text-2)',
              fontWeight: filter === f.k ? 600 : 500, fontSize: 11.5,
              cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 5,
            }}>{f.l} <span style={{ fontSize: 10, color: 'var(--text-4)' }}>{f.n}</span></button>
          ))}
        </div>
        <Btn kind="ghost" size="sm" icon={<Ic.download/>}>Export all</Btn>
      </div>

      {/* List */}
      <div style={{ flex: 1, overflow: 'auto' }}>
        {filtered.length === 0 && (
          <div style={{ padding: 24, textAlign: 'center', color: 'var(--text-3)', fontSize: 12 }}>
            격리된 행이 없습니다.
          </div>
        )}
        {filtered.map((e, i) => {
          const isOpen = expanded.has(e.id);
          const tone = e.severity === 'error' ? 'err' : 'warn';
          const bdColor = e.severity === 'error' ? 'var(--red)' : 'var(--amber)';
          return (
            <div key={e.id} style={{
              borderBottom: i < filtered.length - 1 ? '1px solid var(--border)' : 'none',
              borderLeft: `3px solid ${bdColor}`,
            }}>
              <div onClick={() => toggle(e.id)} style={{
                display: 'grid', gridTemplateColumns: '16px 1fr auto auto auto',
                alignItems: 'center', gap: 10,
                padding: '8px 12px',
                cursor: 'pointer',
              }}
                onMouseEnter={ev => ev.currentTarget.style.background = 'var(--panel-2)'}
                onMouseLeave={ev => ev.currentTarget.style.background = 'transparent'}
              >
                <span style={{ color: 'var(--text-4)', fontSize: 10 }}>{isOpen ? '▾' : '▸'}</span>
                <div style={{ minWidth: 0 }}>
                  <div style={{ fontSize: 12.5, fontWeight: 500, color: e.severity === 'error' ? 'var(--red)' : 'var(--amber)' }}>
                    {e.reason}
                  </div>
                  <div style={{ fontSize: 10.5, color: 'var(--text-3)', fontFamily: 'var(--mono)', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                    {e.detail}
                  </div>
                </div>
                <StatusBadge tone={tone}>{e.count} {e.count === 1 ? 'row' : 'rows'}</StatusBadge>
                <span style={{ fontSize: 10.5, fontFamily: 'var(--mono)', color: 'var(--text-3)' }}>{e.stage}</span>
                <span style={{ fontSize: 10.5, fontFamily: 'var(--mono)', color: 'var(--text-4)' }}>{e.firstSeenAt}</span>
              </div>

              {isOpen && (
                <div style={{ padding: '4px 12px 12px 34px', background: 'var(--panel-2)' }}>
                  {/* Sample rows */}
                  <div style={{ fontSize: 10, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 0.6, marginBottom: 4 }}>
                    Sample rows ({Math.min(e.sampleRows.length, 4)} of {e.count})
                  </div>
                  <div style={{
                    border: '1px solid var(--border)', borderRadius: 3, background: 'var(--panel)',
                    fontFamily: 'var(--mono)', fontSize: 11,
                    overflow: 'auto',
                  }}>
                    <table style={{ width: '100%', borderCollapse: 'collapse' }}>
                      <thead>
                        <tr>
                          {Object.keys(e.sampleRows[0] || {}).map(k => (
                            <th key={k} style={{
                              padding: '3px 8px', textAlign: 'left',
                              fontWeight: 500, fontSize: 10, color: 'var(--text-3)',
                              textTransform: 'uppercase', letterSpacing: 0.5,
                              background: 'var(--panel-2)', borderBottom: '1px solid var(--border)',
                              whiteSpace: 'nowrap',
                            }}>{k}</th>
                          ))}
                        </tr>
                      </thead>
                      <tbody>
                        {e.sampleRows.slice(0, 4).map((row, ri) => (
                          <tr key={ri} style={{ borderBottom: ri < 3 ? '1px solid var(--border)' : 'none' }}>
                            {Object.entries(row).map(([k, v]) => (
                              <td key={k} style={{
                                padding: '2px 8px',
                                color: 'var(--text-2)',
                                whiteSpace: 'nowrap',
                              }}>{String(v)}</td>
                            ))}
                          </tr>
                        ))}
                      </tbody>
                    </table>
                  </div>
                  <div style={{ marginTop: 8, display: 'flex', gap: 6, flexWrap: 'wrap' }}>
                    <Btn kind="primary" size="sm"
                      onClick={() => onTriggerPartialRun?.(e)}
                      disabled={blocked || !e.table}
                      title={blocked ? '활성 run 진행 중 — 종료 후 재시도' : (e.table ? `${e.table} 테이블만 시범 이행` : '대상 테이블 미상')}>
                      이 테이블만 다시 이행
                    </Btn>
                    <Btn kind="secondary" size="sm"
                      onClick={() => onJumpToMapping?.(e)}
                      title="해당 TO-BE 테이블 매핑 화면으로 이동 (관련 컬럼 강조)">
                      매핑 열기
                    </Btn>
                    <Btn kind="ghost" size="sm" onClick={() => alert(`v1: ${e.count} rows을 파이프라인에 다시 넣습니다.`)}>Requeue {e.count} rows</Btn>
                    <Btn kind="ghost" size="sm" onClick={() => alert(`v1: ${e.count} rows을 영구 제거합니다. 감사 로그에 기록.`)}>Discard</Btn>
                    <Btn kind="ghost" size="sm" icon={<Ic.download/>} onClick={() => alert(`v1: ${e.table}_quarantine_${e.id}.csv 다운로드.`)}>Export CSV</Btn>
                    <div style={{ flex: 1 }}/>
                    <Btn kind="ghost" size="sm" onClick={() => alert('v1: 해당 테이블의 행 인스펙터를 엽니다 (full row data).')}>Open row inspector</Btn>
                  </div>
                </div>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
};

/* ─── Run header ─────────────────────────────────────────────────
   Shows the active run (if any) plus mode badge (rehearsal/cutover) and
   triggeredBy (scheduler / manual). The internal scheduler is the canonical
   trigger path — external CLI was removed. */

const RunHeader = ({ project, running, onToggleRun, onStartRun, bump }) => {
  const run = window.getActiveRun ? window.getActiveRun(project?.id) : null;
  const history = window.getRuns ? window.getRuns(project?.id) : [];
  const [dialogOpen, setDialogOpen] = React.useState(false);

  /* 매핑 완료 여부 — phase 가 rehearsal 이상이면 활성화 */
  const phase = project?.phase;
  const mappingReady = ['rehearsal', 'sign-off', 'cutover', 'hypercare'].includes(phase);
  const isDone = phase === 'done';

  if (!run) {
    const lastRun = history[0];
    return (
      <>
      <div style={{
        padding: '14px 18px',
        borderBottom: '1px solid var(--border)',
        background: 'var(--panel)',
        display: 'flex', alignItems: 'center', gap: 20,
      }}>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 10.5, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 0.8, marginBottom: 3 }}>No active run</div>
          <div style={{ fontSize: 12, color: 'var(--text-2)', fontFamily: 'var(--mono)' }}>
            {lastRun ? <>last run: <b>{lastRun.id}</b> · {lastRun.startedAt} · {lastRun.result}
              {(lastRun.scope && lastRun.scope !== 'all') && <span style={{ color: 'var(--text-3)' }}> · {lastRun.scopeLabel || lastRun.scope}</span>}
            </>
              : <>run 이력 없음 — 분석/설계 단계입니다</>}
          </div>
        </div>
        {!isDone && (
          <Btn
            kind={mappingReady ? 'primary' : 'secondary'}
            size="md"
            icon={<Ic.play/>}
            onClick={() => mappingReady && setDialogOpen(true)}
            title={mappingReady
              ? '매핑이 완료된 프로젝트 — 모드를 선택해 실행합니다'
              : '매핑 미완료 — Mapping 탭에서 모든 컬럼 매핑 후 실행 가능'}
            disabled={!mappingReady}
          >
            Start run
          </Btn>
        )}
      </div>
      {dialogOpen && (
        <StartRunDialog
          project={project}
          onClose={() => setDialogOpen(false)}
          onConfirm={(mode, scopeOpts) => { setDialogOpen(false); onStartRun?.(mode, scopeOpts); }}
        />
      )}
      </>
    );
  }

  return (
    <div style={{
      padding: '14px 18px',
      borderBottom: '1px solid var(--border)',
      background: 'var(--panel)',
      display: 'flex', alignItems: 'center', gap: 16,
      position: 'relative',
    }}>
      <div style={{ flex: 1, minWidth: 0 }}>
        <div style={{ fontSize: 10.5, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 0.8, marginBottom: 3 }}>
          Active run
        </div>
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, flexWrap: 'wrap' }}>
          <span style={{ fontFamily: 'var(--mono)', fontSize: 16, fontWeight: 600 }}>{run.id}</span>
          <StatusBadge tone={running ? 'running' : 'warn'}>{running ? 'running' : 'paused'}</StatusBadge>
          {(run.scope && run.scope !== 'all') && (
            <StatusBadge tone="info">{run.scopeLabel || run.scope}</StatusBadge>
          )}
          <span style={{ fontSize: 12, color: 'var(--text-3)', fontFamily: 'var(--mono)' }}>
            started {run.startedAt.split(' ')[1] || run.startedAt} · elapsed {run.elapsed}{run.eta ? ` · eta ${run.eta}` : ''}
          </span>
        </div>
        <div style={{ fontSize: 10.5, color: 'var(--text-3)', fontFamily: 'var(--mono)', marginTop: 3 }}>
          triggered by <b style={{ color: 'var(--text-2)' }}>{run.triggeredBy?.actor}</b>
          {run.triggeredBy?.source && <span> · {run.triggeredBy.source}</span>}
        </div>
      </div>
      <Btn kind="secondary" size="md" icon={running ? <Ic.pause/> : <Ic.play/>} onClick={onToggleRun}>{running ? 'Pause' : 'Resume'}</Btn>
      <Btn kind="danger" size="md" icon={<Ic.stop/>}>Abort</Btn>
    </div>
  );
};

/* ─── Run history section ────────────────────────────────────────
   Collapsible table of past runs — rehearsals + cutover(s). Shows mode,
   duration, result, quarantine trend, and who triggered. */

const RunHistory = ({ project }) => {
  const runs = window.getRuns ? window.getRuns(project?.id) : [];
  const [open, setOpen] = React.useState(false);
  if (runs.length <= 1) return null; // nothing interesting beyond the current one

  const rehearsals = runs.filter(r => r.mode === 'rehearsal');
  const cutovers   = runs.filter(r => r.mode === 'cutover');
  const converging = rehearsals.slice(0, 5).map(r => r.quarantineCount).filter(n => n != null);

  return (
    <div style={{ borderBottom: '1px solid var(--border)', background: 'var(--panel)' }}>
      <div onClick={() => setOpen(o => !o)} style={{
        padding: '9px 18px',
        display: 'flex', alignItems: 'center', gap: 10,
        cursor: 'pointer',
      }}>
        <span style={{ color: 'var(--text-4)', fontSize: 10, width: 10 }}>{open ? '▾' : '▸'}</span>
        <span style={{ fontSize: 11, fontWeight: 600, textTransform: 'uppercase', letterSpacing: 0.6, color: 'var(--text-2)' }}>Run history</span>
        <div style={{ display: 'flex', gap: 5 }}>
          <StatusBadge tone="info">{rehearsals.length} rehearsal{rehearsals.length === 1 ? '' : 's'}</StatusBadge>
          {cutovers.length > 0 && <StatusBadge tone="err">{cutovers.length} cutover{cutovers.length === 1 ? '' : 's'}</StatusBadge>}
        </div>
        <div style={{ flex: 1 }}/>
        {converging.length >= 2 && (
          <span style={{ fontSize: 10.5, color: 'var(--text-3)', fontFamily: 'var(--mono)' }}>
            quarantine trend: {converging.join(' → ')}
          </span>
        )}
      </div>

      {open && (
        <div style={{ padding: '4px 18px 12px' }}>
          <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 11.5 }}>
            <thead>
              <tr>
                {['Run ID', 'Mode', 'Started', 'Duration', 'Result', 'Quar.', 'Triggered by'].map(h => (
                  <th key={h} style={{
                    padding: '6px 10px', textAlign: 'left',
                    fontSize: 10.5, fontWeight: 500,
                    color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 0.6,
                    background: 'var(--panel-2)', borderBottom: '1px solid var(--border)',
                  }}>{h}</th>
                ))}
              </tr>
            </thead>
            <tbody>
              {runs.map((r, i) => {
                const isActive = r.result === 'running';
                const modeBd = r.mode === 'cutover' ? 'var(--red)' : 'var(--navy)';
                const resultTone = r.result === 'ok' ? 'ok' : r.result === 'warn' ? 'warn' : r.result === 'aborted' || r.result === 'failed' ? 'err' : 'running';
                return (
                  <tr key={r.id} style={{
                    background: isActive ? 'var(--navy-50)' : i % 2 ? 'var(--zebra)' : 'var(--panel)',
                    borderBottom: '1px solid var(--border)',
                  }}>
                    <td style={{ padding: '5px 10px', fontFamily: 'var(--mono)', fontWeight: 500 }}>
                      {r.id}
                      {isActive && <span style={{ marginLeft: 6, color: 'var(--navy)', fontSize: 10 }}>· current</span>}
                    </td>
                    <td style={{ padding: '5px 10px' }}>
                      <span style={{
                        fontSize: 9.5, fontWeight: 700, fontFamily: 'var(--mono)',
                        padding: '1px 6px', borderRadius: 2,
                        background: r.mode === 'cutover' ? 'var(--red-50)' : 'var(--navy-50)',
                        color: modeBd, border: `1px solid ${modeBd}`,
                        letterSpacing: 0.4, textTransform: 'uppercase',
                      }}>{r.mode}</span>
                      {(r.scope && r.scope !== 'all') && (
                        <span style={{
                          marginLeft: 4,
                          fontSize: 9.5, fontFamily: 'var(--mono)',
                          padding: '1px 5px', borderRadius: 2,
                          background: 'var(--panel-2)', color: 'var(--text-3)',
                          border: '1px solid var(--border)',
                        }}>{r.scopeLabel || r.scope}</span>
                      )}
                    </td>
                    <td style={{ padding: '5px 10px', fontFamily: 'var(--mono)', color: 'var(--text-2)' }}>{r.startedAt}</td>
                    <td style={{ padding: '5px 10px', fontFamily: 'var(--mono)', color: 'var(--text-3)' }}>{r.elapsed || '—'}</td>
                    <td style={{ padding: '5px 10px' }}>
                      <StatusBadge tone={resultTone}>{r.result}</StatusBadge>
                    </td>
                    <td style={{ padding: '5px 10px', fontFamily: 'var(--mono)', textAlign: 'right', color: r.quarantineCount > 0 ? 'var(--amber)' : 'var(--text-3)' }}>
                      {r.quarantineCount ?? '—'}
                    </td>
                    <td style={{ padding: '5px 10px', fontFamily: 'var(--mono)', fontSize: 11, color: 'var(--text-3)' }}>
                      {r.triggeredBy?.actor}
                      {r.triggeredBy?.source && <span style={{ color: 'var(--text-4)' }}> · {r.triggeredBy.source}</span>}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      )}
    </div>
  );
};

const Kv = ({ k, v, tone }) => (
  <div style={{ display: 'flex', justifyContent: 'space-between', padding: '2px 0', borderBottom: '1px dashed var(--border)' }}>
    <span style={{ color: 'var(--text-3)' }}>{k}</span>
    <span style={{ color: tone === 'ok' ? 'var(--green)' : 'var(--text)' }}>{v}</span>
  </div>
);

/* ─── Start run dialog ──────────────────────────────────────────
   사용자가 모드(REHEARSAL / CUTOVER) 를 명시적으로 선택해 새 run 을 시작.
   Cutover 는 추가 확인 체크박스 통과 시에만 활성. */

const StartRunDialog = ({ project, onClose, onConfirm }) => {
  const [mode, setMode] = React.useState('rehearsal');
  const [scope, setScope] = React.useState('all');
  const [confirmCutover, setConfirmCutover] = React.useState(false);

  const isCut = mode === 'cutover';

  /* 트리거 시점의 실패 테이블을 스냅샷 — run 시작 직후 status 가 running 으로
     변하기 전에 캡처. dialog 열려있는 동안에는 dialogue 가 한 번 계산한 값을 유지. */
  const failedSnapshot = React.useMemo(() => {
    if (isCut) {
      return window.getTablesFromLastFailedCutover?.(project?.id) || [];
    }
    return window.getFailedTables?.(project?.id) || [];
  }, [project?.id, isCut]);

  const failedCount = failedSnapshot.length;
  const failedDisabled = failedCount === 0;

  /* Cutover + failed-only 만 partial cutover 정책에서 허용. 그 외 mode/scope 조합
     은 canStartPartial 로 검증. 'all' 은 항상 허용 (legacy 경로). */
  const partialOk = scope === 'all' ||
    (window.canStartPartial ? window.canStartPartial(project, mode, scope) : true);

  /* failed-only 선택했는데 실패 카운트가 0 이면 자동으로 all 로 되돌림 */
  React.useEffect(() => {
    if (scope === 'failed-only' && failedDisabled) setScope('all');
  }, [scope, failedDisabled]);

  const canConfirm = partialOk && (!isCut || confirmCutover);

  const handleSubmit = () => {
    if (!canConfirm) return;
    const scopeOpts = scope === 'all'
      ? { scope: 'all' }
      : {
          scope,
          tables: failedSnapshot.slice(),
          triggeredBy: { actor: 'Admin', source: `manual · ${mode} · ${scope}` },
        };
    onConfirm(mode, scopeOpts);
  };

  return (
    <div onClick={onClose} style={{
      position: 'fixed', inset: 0, background: 'rgba(10,18,16,0.55)',
      display: 'grid', placeItems: 'center', zIndex: 2000,
    }}>
      <div onClick={e => e.stopPropagation()} style={{
        width: 460, background: 'var(--panel)',
        border: '1px solid var(--border-strong)', borderRadius: 5,
        boxShadow: '0 30px 80px rgba(10,20,18,0.35)',
      }}>
        {/* Header */}
        <div style={{
          padding: '12px 16px 10px',
          borderBottom: '1px solid var(--border)',
          display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12,
        }}>
          <div>
            <div style={{ fontSize: 13, fontWeight: 600 }}>Start run</div>
            <div style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 2, fontFamily: 'var(--mono)' }}>
              {project?.name} · {project?.phase}
            </div>
          </div>
          <button onClick={onClose} style={{
            width: 22, height: 22, border: '1px solid var(--border)',
            background: 'var(--panel)', borderRadius: 3, cursor: 'pointer',
            fontSize: 11, color: 'var(--text-3)',
          }}>✕</button>
        </div>

        {/* Body */}
        <div style={{ padding: '14px 16px 4px' }}>
          <div style={{ fontSize: 10.5, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 0.8, marginBottom: 8 }}>
            실행 모드
          </div>
          <ModeOption
            value="rehearsal" current={mode} onSelect={setMode}
            title="Rehearsal — 리허설"
            desc="테스트 타겟에 dry-run 으로 적용합니다. 운영 데이터에 영향 없음. 반복 실행 가능."
            tone="navy"
          />
          <ModeOption
            value="cutover" current={mode} onSelect={setMode}
            title="⚠ Cutover — 컷오버"
            desc="운영 타겟에 실제 데이터를 적용합니다. 단방향이며, 한 번 시작하면 완료까지 진행됩니다."
            tone="red"
          />

          {/* ─── 실행 범위 ─── */}
          <div style={{ marginTop: 14, fontSize: 10.5, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: 0.8, marginBottom: 8 }}>
            실행 범위
          </div>
          <ScopeOption
            value="all" current={scope} onSelect={setScope}
            title="전체 테이블"
            desc={isCut
              ? '운영 cutover 의 기본 옵션 — 모든 매핑 대상 테이블을 한 번에 적용합니다.'
              : '프로젝트의 모든 매핑 대상 테이블을 실행합니다.'}
            disabled={false}
          />
          <ScopeOption
            value="failed-only" current={scope} onSelect={setScope}
            title={`실패한 테이블만 (${failedCount})`}
            desc={isCut
              ? (failedDisabled
                  ? '직전 cutover 에 실패한 테이블이 없습니다 — 부분 cutover 재이행 대상 없음.'
                  : '직전 cutover 에서 실패한 테이블만 동일 운영 타겟에 재적용합니다.')
              : (failedDisabled
                  ? '현재 status 가 warn/blocked 인 테이블이 없습니다.'
                  : 'status 가 warn/blocked 인 테이블만 시범 이행합니다.')}
            disabled={failedDisabled}
          />

          {isCut && (
            <div style={{
              marginTop: 12, padding: 10,
              border: '1px solid var(--red)', background: 'var(--red-50)',
              borderRadius: 3, fontSize: 11, color: 'var(--red)', lineHeight: 1.55,
            }}>
              <div style={{ fontWeight: 600, marginBottom: 6 }}>운영 데이터 변경 확인</div>
              <label style={{ display: 'flex', gap: 8, alignItems: 'flex-start', cursor: 'pointer', color: 'var(--text-2)' }}>
                <input type="checkbox" checked={confirmCutover} onChange={e => setConfirmCutover(e.target.checked)}
                  style={{ marginTop: 2 }}/>
                <span>승인된 매핑 스냅샷·롤백 절차·D-day 일정을 확인했으며, 본 컷오버를 운영에 적용함을 동의합니다.</span>
              </label>
            </div>
          )}
        </div>

        {/* Actions */}
        <div style={{
          padding: '10px 16px 14px',
          display: 'flex', gap: 8, justifyContent: 'flex-end',
        }}>
          <Btn kind="secondary" size="sm" onClick={onClose}>Cancel</Btn>
          <Btn
            kind={isCut ? 'danger' : 'primary'} size="sm"
            icon={<Ic.play/>}
            onClick={handleSubmit}
            disabled={!canConfirm}
          >
            {isCut ? 'Start cutover' : 'Start rehearsal'}
            {scope !== 'all' && <span style={{ marginLeft: 4, fontSize: 10, opacity: 0.85 }}>· {scope === 'failed-only' ? `실패만 (${failedCount})` : scope}</span>}
          </Btn>
        </div>
      </div>
    </div>
  );
};

const ScopeOption = ({ value, current, onSelect, title, desc, disabled }) => {
  const isSelected = current === value;
  const accent = 'var(--navy)';
  return (
    <label onClick={() => !disabled && onSelect(value)}
      style={{
        display: 'flex', gap: 10, alignItems: 'flex-start',
        padding: '8px 12px',
        border: `1px solid ${isSelected ? accent : 'var(--border)'}`,
        background: isSelected ? 'var(--navy-50)' : 'var(--panel)',
        borderRadius: 4,
        marginBottom: 6,
        cursor: disabled ? 'not-allowed' : 'pointer',
        opacity: disabled ? 0.5 : 1,
      }}>
      <input type="radio" checked={isSelected} disabled={disabled} onChange={() => !disabled && onSelect(value)} style={{ marginTop: 3 }}/>
      <div style={{ flex: 1 }}>
        <div style={{ fontSize: 12, fontWeight: 600, color: isSelected ? accent : 'var(--text)' }}>{title}</div>
        <div style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 2, lineHeight: 1.5 }}>{desc}</div>
      </div>
    </label>
  );
};

const ModeOption = ({ value, current, onSelect, title, desc, tone }) => {
  const isSelected = current === value;
  const accent = tone === 'red' ? 'var(--red)' : 'var(--navy)';
  return (
    <label onClick={() => onSelect(value)}
      style={{
        display: 'flex', gap: 10, alignItems: 'flex-start',
        padding: '10px 12px',
        border: `1px solid ${isSelected ? accent : 'var(--border)'}`,
        background: isSelected ? (tone === 'red' ? 'var(--red-50)' : 'var(--navy-50)') : 'var(--panel)',
        borderRadius: 4,
        marginBottom: 6,
        cursor: 'pointer',
      }}>
      <input type="radio" checked={isSelected} onChange={() => onSelect(value)} style={{ marginTop: 3 }}/>
      <div style={{ flex: 1 }}>
        <div style={{ fontSize: 12, fontWeight: 600, color: isSelected ? accent : 'var(--text)' }}>{title}</div>
        <div style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 2, lineHeight: 1.5 }}>{desc}</div>
      </div>
    </label>
  );
};

window.Execution = Execution;
