/* DevOps Build · Semantic Loop demonstrator
   Faithful port of the App.tsx flow:
     Step 1 Schema   — chat-driven prompt + file context → JSON schema
     Step 2 Refine   — view tables/columns/keys + chat refinements
     Step 3 Data     — sample data rows + a chart from the spec
     Step 4 Export   — SQL / User stories / API docs / Test cases
   Layouts: Tabs (default) · Wizard · Grid (all visible at once)
   LLM: window.claude.complete() (mirrors the Gemini service interface)
*/

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

/* ---------- Icons ---------- */
const ICONS = {
  zap: 'M13 2L3 14h9l-1 8 10-12h-9l1-8z',
  database: 'M3 5c0 1.66 4.03 3 9 3s9-1.34 9-3-4.03-3-9-3-9 1.34-9 3zM3 5v14c0 1.66 4.03 3 9 3s9-1.34 9-3V5M3 12c0 1.66 4.03 3 9 3s9-1.34 9-3',
  edit: 'M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.5L15.232 5.232z',
  chart: 'M3 3v18h18M7 14l3-3 4 4 5-6',
  upload: 'M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z',
  reset: 'M4 4v5h5M20 20v-5h-5M4 9a9 9 0 0114-3M20 15a9 9 0 01-14 3',
  check: 'M5 13l4 4L19 7',
  layoutTabs: 'M4 5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5z',
  layoutWizard: 'M17 8l4 4m0 0l-4 4m4-4H3',
  layoutGrid: 'M4 6a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6zm10 0a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2V6zM4 16a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-2zm10 0a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2v-2z',
  arrow: 'M5 12h14M13 6l6 6-6 6',
  sql: 'M16 18l6-6-6-6M8 6l-6 6 6 6',
  stories: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6zM14 2v6h6',
  api: 'M4 6h16M4 12h16M4 18h7',
  test: 'M9 11l3 3L22 4',
  copy: 'M9 9h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2V11a2 2 0 0 1 2-2zM5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1',
  download: 'M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2v-3M5 4h14',
};
const I = ({ name, size = 14, color = 'currentColor', style }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" style={style}>
    <path d={ICONS[name] || ICONS.zap} />
  </svg>
);

/* ---------- Constants ---------- */
const INITIAL_PROMPT = 'Create a schema for a grant application system with applicants, reviewers, grants, and scores.';

const EXAMPLE_PROMPTS = [
  'Schema for a grant application system with applicants, reviewers, grants, and scores.',
  'E-commerce: customers, orders, products, line items, payments.',
  'Multi-agent platform: agents, registrations, tasks, audit log, trust scores.',
  'Health records: patients, encounters, providers, diagnoses, prescriptions.',
];

// Fallback schema when LLM is unavailable so the prototype is fully demoable.
const FALLBACK_SCHEMA = {
  description: 'A grant application system with applicants, reviewers, grants, and review scores. Reviewers are matched to grants via assignments.',
  tables: [
    { name: 'Applicants', description: 'Organisations or individuals applying for funding.', columns: [
      { name: 'ApplicantID', type: 'INT', description: 'Primary key.', isPrimaryKey: true },
      { name: 'Name', type: 'VARCHAR(120)', description: 'Applicant name.' },
      { name: 'Email', type: 'VARCHAR(120)', description: 'Contact email.' },
      { name: 'Country', type: 'VARCHAR(60)', description: 'Country of operation.' },
      { name: 'CreatedAt', type: 'TIMESTAMP', description: 'Record created.' },
    ]},
    { name: 'Reviewers', description: 'Domain experts who score grant applications.', columns: [
      { name: 'ReviewerID', type: 'INT', description: 'Primary key.', isPrimaryKey: true },
      { name: 'Name', type: 'VARCHAR(120)', description: 'Reviewer name.' },
      { name: 'Expertise', type: 'VARCHAR(120)', description: 'Domain tag.' },
    ]},
    { name: 'Grants', description: 'Funding opportunities open for applications.', columns: [
      { name: 'GrantID', type: 'INT', description: 'Primary key.', isPrimaryKey: true },
      { name: 'Title', type: 'VARCHAR(200)', description: 'Public title.' },
      { name: 'Amount', type: 'DECIMAL(12,2)', description: 'Funding amount in USD.' },
      { name: 'Deadline', type: 'TIMESTAMP', description: 'Application close.' },
      { name: 'Status', type: 'VARCHAR(40)', description: 'open / closed / archived.' },
    ]},
    { name: 'Applications', description: 'A submitted application by an Applicant for a Grant.', columns: [
      { name: 'ApplicationID', type: 'INT', description: 'Primary key.', isPrimaryKey: true },
      { name: 'ApplicantID', type: 'INT', description: 'FK to Applicants.', isForeignKey: true, foreignKeyTo: { table: 'Applicants', column: 'ApplicantID' } },
      { name: 'GrantID', type: 'INT', description: 'FK to Grants.', isForeignKey: true, foreignKeyTo: { table: 'Grants', column: 'GrantID' } },
      { name: 'SubmittedAt', type: 'TIMESTAMP', description: 'Submission timestamp.' },
      { name: 'Summary', type: 'TEXT', description: 'Short proposal body.' },
    ]},
    { name: 'Scores', description: 'A reviewer\'s score for an application.', columns: [
      { name: 'ScoreID', type: 'INT', description: 'Primary key.', isPrimaryKey: true },
      { name: 'ApplicationID', type: 'INT', description: 'FK to Applications.', isForeignKey: true, foreignKeyTo: { table: 'Applications', column: 'ApplicationID' } },
      { name: 'ReviewerID', type: 'INT', description: 'FK to Reviewers.', isForeignKey: true, foreignKeyTo: { table: 'Reviewers', column: 'ReviewerID' } },
      { name: 'Score', type: 'INT', description: 'Score 1-10.' },
      { name: 'Comment', type: 'TEXT', description: 'Reviewer notes.' },
    ]},
  ],
};

/* ---------- LLM helpers ---------- */
async function llmJSON(prompt, fallback) {
  try {
    const r = await window.claude.complete({ messages: [{ role: 'user', content: prompt }] });
    const m = r.match(/\{[\s\S]*\}/);
    if (m) {
      try { return JSON.parse(m[0]); } catch (e) { /* fall through */ }
    }
    return fallback;
  } catch (e) {
    return fallback;
  }
}
async function llmText(prompt, fallback) {
  try {
    return await window.claude.complete({ messages: [{ role: 'user', content: prompt }] });
  } catch (e) {
    return fallback;
  }
}

/* ---------- Sample data generator (client-side) ---------- */
function fakeRow(col, i) {
  const t = col.type.toUpperCase();
  if (col.isPrimaryKey) return i + 1;
  if (col.isForeignKey) return (i % 5) + 1;
  if (t.includes('INT')) return Math.floor(Math.random() * 100) + 1;
  if (t.includes('DECIMAL') || t.includes('FLOAT')) return +(Math.random() * 1000).toFixed(2);
  if (t.includes('TIMESTAMP') || t.includes('DATE')) return new Date(Date.now() - i * 86400000).toISOString().slice(0, 19);
  if (t.includes('BOOLEAN')) return Math.random() > 0.5;
  if (t.includes('TEXT')) return 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. (' + i + ')';
  const seeds = ['North Star Foundation', 'Atlas Labs', 'Beacon Health', 'Lyra Research', 'Orion Capital', 'Cygnus Med', 'Vega Studios', 'Hydra Open Source'];
  const names  = ['Aiko Tanaka', 'Marcus Bell', 'Priya Iyer', 'Lev Romanov', 'Sofia Marquez', 'Jordan Park', 'Noor Hassan', 'Ren Yamamoto'];
  if (col.name.toLowerCase().includes('email')) return (names[i % names.length].split(' ').join('.').toLowerCase()) + '@' + ['acme.io','beacon.org','quantumharmony.ai','artemis.city'][i % 4];
  if (col.name.toLowerCase().includes('name')) return names[i % names.length];
  if (col.name.toLowerCase().includes('country')) return ['USA', 'UK', 'JP', 'DE', 'IN'][i % 5];
  if (col.name.toLowerCase().includes('status')) return ['open', 'pending', 'closed', 'archived'][i % 4];
  return seeds[i % seeds.length];
}
function genSampleData(schema, rows) {
  const out = {};
  schema.tables.forEach((t) => {
    out[t.name] = [];
    for (let i = 0; i < rows; i++) {
      const r = {};
      t.columns.forEach((c) => { r[c.name] = fakeRow(c, i); });
      out[t.name].push(r);
    }
  });
  return out;
}

/* ---------- Step indicator (wizard) ---------- */
function Steps({ stepIdx }) {
  const steps = [
    { n: 1, label: 'Prompt AI',           extra: 'natural language' },
    { n: 2, label: 'Visualize Schema',    extra: 'review · refine' },
    { n: 3, label: 'Generate & Viz Data', extra: 'sample rows · chart' },
    { n: 4, label: 'Export Artifacts',    extra: 'sql · docs · tests' },
  ];
  const lastDone = stepIdx - 1;
  return (
    <div className="steps">
      <div className="row">
        <div className="track"></div>
        <div className="fill" style={{ width: `${(lastDone / (steps.length - 1)) * 100}%` }}></div>
        {steps.map((s) => {
          const isDone = stepIdx > s.n;
          const isCurr = stepIdx === s.n;
          return (
            <div key={s.n} className={'step' + (isCurr ? ' current' : '') + (isDone ? ' done' : '')}>
              <div className="circle">{isDone ? <I name="check" size={18} /> : s.n}</div>
              <div className="label">{s.label}</div>
              <div className="extra">{s.extra}</div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

/* ---------- Step 1: Schema prompt ---------- */
function SchemaStep({ chat, setChat, files, setFiles, onGenerate, loading }) {
  const [draft, setDraft] = useState('');
  const [over, setOver] = useState(false);
  const endRef = useRef(null);
  useEffect(() => { endRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [chat]);

  function send() {
    if (!draft.trim()) return;
    setChat([...chat, { role: 'user', text: draft.trim() }]);
    setDraft('');
  }

  function handleFiles(list) {
    if (!list) return;
    Array.from(list).forEach((f) => {
      if (!/\.(txt|md|json)$/i.test(f.name)) return;
      const reader = new FileReader();
      reader.onload = (e) => setFiles((arr) => [...arr, { name: f.name, content: e.target.result }]);
      reader.readAsText(f);
    });
  }

  return (
    <div>
      <span className="h-eyebrow">● Step 1 · Prompt AI</span>
      <h2 className="heading">Describe your schema</h2>
      <p className="lede">Describe the schema you need, or upload context files. The kernel routes this through the schema agent and returns a typed JSON spec.</p>

      <div className="schema-grid">
        <div className="chat-box">
          <header>
            <span style={{ fontWeight: 600, color: 'var(--txt)' }}>Conversation</span>
            <button className="btn" style={{ padding: '3px 8px' }} onClick={() => setChat([])}>Clear</button>
          </header>
          <div className="chat-stream">
            {chat.length === 0 && <div className="chat-msg system">no messages — pick a quick prompt →</div>}
            {chat.map((m, i) => (<div key={i} className={'chat-msg ' + m.role}>{m.text}</div>))}
            <div ref={endRef}></div>
          </div>
          <div className="chat-input">
            <input
              value={draft}
              onChange={(e) => setDraft(e.target.value)}
              onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); send(); } }}
              placeholder="Add to the conversation…"
              disabled={loading}
            />
            <button className="btn primary" disabled={!draft.trim() || loading} onClick={send}>Send</button>
          </div>
        </div>

        <div className="files-box">
          <header style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
            <span>Context files</span>
            <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--txt-2)', letterSpacing: '0.10em' }}>.txt · .md · .json</span>
          </header>
          <div
            className={'dropzone' + (over ? ' over' : '')}
            onDragEnter={(e) => { e.preventDefault(); setOver(true); }}
            onDragOver={(e) => { e.preventDefault(); }}
            onDragLeave={(e) => { e.preventDefault(); setOver(false); }}
            onDrop={(e) => { e.preventDefault(); setOver(false); handleFiles(e.dataTransfer.files); }}
          >
            <I name="upload" size={28} color="var(--txt-2)" />
            <div style={{ fontSize: 12 }}>
              Drag &amp; drop, or <label className="browse">browse
                <input type="file" multiple style={{ display: 'none' }} accept=".txt,.md,.json" onChange={(e) => handleFiles(e.target.files)} />
              </label>
            </div>
            <div style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--txt-2)', letterSpacing: '0.08em' }}>plain text · markdown · json</div>
          </div>
          {files.map((f, i) => (
            <div key={i} className="file-row">
              <span>📄 {f.name}</span>
              <span className="x" onClick={() => setFiles((arr) => arr.filter((_, j) => j !== i))}>✕</span>
            </div>
          ))}
          <div className="generate-btn-row">
            <div style={{ marginBottom: 8, fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--txt-2)', letterSpacing: '0.10em', textTransform: 'uppercase' }}>Quick prompts</div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 4, marginBottom: 12 }}>
              {EXAMPLE_PROMPTS.map((p, i) => (
                <button key={i} className="btn" style={{ textAlign: 'left', justifyContent: 'flex-start', padding: '7px 10px', fontSize: 12 }} onClick={() => setChat([{ role: 'user', text: p }])}>
                  <I name="zap" size={11} color="var(--acc)" /> <span style={{ flex: 1, whiteSpace: 'normal' }}>{p}</span>
                </button>
              ))}
            </div>
            <button className="btn primary" style={{ width: '100%', justifyContent: 'center', padding: '10px' }} disabled={loading || chat.length === 0} onClick={onGenerate}>
              {loading
                ? <><span className="loader"><span></span><span></span><span></span></span> Generating schema…</>
                : <><I name="zap" size={12} /> Generate schema</>}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ---------- Step 2: Refine ---------- */
function RefineStep({ schema, onRefine, loading }) {
  const [prompt, setPrompt] = useState('');
  function send() {
    if (!prompt.trim()) return;
    onRefine(prompt.trim());
    setPrompt('');
  }
  return (
    <div>
      <span className="h-eyebrow">● Step 2 · Visualize &amp; refine</span>
      <h2 className="heading">{schema?.tables?.length || 0} tables · ready for review</h2>
      <p className="lede">Inspect the schema. Type a change request to refine: rename, add/remove columns, change types, adjust relationships — the kernel mutates the JSON in place.</p>

      {schema?.description && (
        <div className="schema-desc"><strong>Overview · </strong>{schema.description}</div>
      )}

      <div className="refine-grid">
        <div>
          <div className="schema-canvas">
            {schema.tables.map((t) => (
              <div key={t.name} className="table-card">
                <div className="ttl">
                  <I name="database" />
                  <span>{t.name}</span>
                  <span style={{ marginLeft: 'auto', fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--txt-2)' }}>{t.columns.length}</span>
                </div>
                {t.columns.map((c) => (
                  <div key={c.name} className="col" title={c.description || ''}>
                    <span className="name">
                      {c.isPrimaryKey ? <span className="key-icon pk" title="primary key">P</span> :
                       c.isForeignKey ? <span className="key-icon fk" title={`FK -> ${c.foreignKeyTo?.table}.${c.foreignKeyTo?.column}`}>F</span> :
                       <span style={{ display: 'inline-block', width: 14 }}></span>}
                      {c.name}
                    </span>
                    <span className="type">{c.type}</span>
                  </div>
                ))}
              </div>
            ))}
          </div>
          {(() => {
            const rels = [];
            schema.tables.forEach((t) => t.columns.forEach((c) => {
              if (c.isForeignKey && c.foreignKeyTo) rels.push(`${t.name}.${c.name} → ${c.foreignKeyTo.table}.${c.foreignKeyTo.column}`);
            }));
            return rels.length > 0 ? (
              <div>
                <span className="h-eyebrow" style={{ display: 'block', marginBottom: 8 }}>● Relationships ({rels.length})</span>
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
                  {rels.map((r, i) => <span key={i} className="rel-hint"><span className="arrow">→</span> {r}</span>)}
                </div>
              </div>
            ) : null;
          })()}
        </div>

        <div className="chat-box" style={{ maxHeight: 'none', minHeight: 280 }}>
          <header><span style={{ fontWeight: 600, color: 'var(--txt)' }}>Refine via chat</span></header>
          <div className="chat-stream">
            <div className="chat-msg system">describe a change · the schema mutates in place</div>
            <div className="chat-msg model" style={{ fontSize: 12 }}>Try:<br/>"Add a Reviews table with reviewer and rating"<br/>"Make Email unique"<br/>"Rename Score to Rating"</div>
          </div>
          <div className="chat-input">
            <input
              value={prompt}
              onChange={(e) => setPrompt(e.target.value)}
              onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); send(); } }}
              placeholder="Refinement instruction…"
              disabled={loading}
            />
            <button className="btn primary" disabled={!prompt.trim() || loading} onClick={send}>
              {loading ? <span className="loader"><span></span><span></span><span></span></span> : 'Refine'}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ---------- Step 3: Data ---------- */
function DataStep({ schema, sampleData, setSampleData, chartSpec, setChartSpec, loading, onGenerate, onViz }) {
  const [prompt, setPrompt] = useState('Generate realistic entries for each table, ensuring foreign keys are consistent.');
  const [rows, setRows] = useState('10');
  const [vizPrompt, setVizPrompt] = useState('Bar chart of grants by status, colored by status.');

  if (!sampleData) {
    return (
      <div>
        <span className="h-eyebrow">● Step 3 · Generate &amp; visualize data</span>
        <h2 className="heading">Sample data &amp; chart</h2>
        <p className="lede">Tell the data agent what kind of rows to generate. Output respects foreign keys and your column types — then the chart agent draws something on top.</p>

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 200px', gap: 18, maxWidth: 720 }}>
          <div className="field">
            <label>Prompt</label>
            <textarea value={prompt} onChange={(e) => setPrompt(e.target.value)} placeholder="e.g. users with realistic names…" />
          </div>
          <div className="field">
            <label>Rows (approx)</label>
            <input type="number" value={rows} onChange={(e) => setRows(e.target.value)} min={1} max={500} />
            <div className="row-count">
              {['10', '50', '100'].map((v) => (
                <button key={v} className={rows === v ? 'on' : ''} onClick={() => setRows(v)}>{v}</button>
              ))}
            </div>
          </div>
        </div>
        <div style={{ marginTop: 22 }}>
          <button className="btn primary" disabled={loading || !prompt.trim()} onClick={() => onGenerate(prompt, +rows)}>
            {loading ? <><span className="loader"><span></span><span></span><span></span></span> Generating data…</> : <><I name="zap" size={12} /> Generate sample data</>}
          </button>
        </div>
      </div>
    );
  }

  return (
    <div>
      <span className="h-eyebrow">● Step 3 · Data + chart</span>
      <h2 className="heading">{Object.keys(sampleData).length} tables filled</h2>
      <p className="lede">Click a chart suggestion or describe one in plain language. The chart agent returns a Vega-Lite v5 spec; we render with deterministic SVG previews here.</p>

      <div className="data-grid">
        <div className="sample-tables">
          {Object.entries(sampleData).map(([name, rows]) => (
            <div key={name} className="sample-table">
              <h3>{name} <span className="row-tag">{rows.length} rows</span></h3>
              <div style={{ overflowX: 'auto' }}>
                <table>
                  <thead><tr>{Object.keys(rows[0] || {}).map((c) => <th key={c}>{c}</th>)}</tr></thead>
                  <tbody>
                    {rows.slice(0, 5).map((r, i) => (
                      <tr key={i}>{Object.values(r).map((v, j) => <td key={j}>{String(v).slice(0, 40)}</td>)}</tr>
                    ))}
                  </tbody>
                </table>
              </div>
            </div>
          ))}
        </div>

        <div className="chart-card">
          <div>
            <span className="h-eyebrow" style={{ display: 'block', marginBottom: 4 }}>● Chart suggestions</span>
            <div className="chart-suggestions">
              {[
                'Count of applications per grant',
                'Bar chart of grants by status',
                'Reviewer scores distribution',
                'Applicants by country',
              ].map((s) => (
                <span key={s} className="suggestion-pill" onClick={() => { setVizPrompt(s); onViz(s, sampleData); }}>{s}</span>
              ))}
            </div>
          </div>
          <div className="field" style={{ marginBottom: 4 }}>
            <label>Or describe a chart</label>
            <input value={vizPrompt} onChange={(e) => setVizPrompt(e.target.value)} placeholder="e.g. Bar chart of grants by status" />
          </div>
          <button className="btn primary" disabled={loading} onClick={() => onViz(vizPrompt, sampleData)} style={{ alignSelf: 'flex-start' }}>
            {loading ? <span className="loader"><span></span><span></span><span></span></span> : 'Render chart'}
          </button>
          <ChartViz spec={chartSpec} sampleData={sampleData} />
        </div>
      </div>
    </div>
  );
}

function ChartViz({ spec, sampleData }) {
  if (!spec) return (
    <div style={{ padding: 36, textAlign: 'center', color: 'var(--txt-2)', border: '1px dashed var(--bb)', borderRadius: 8 }}>
      <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, letterSpacing: '0.10em', textTransform: 'uppercase' }}>● chart preview</span>
      <div style={{ marginTop: 6, fontSize: 13 }}>Press render to generate a Vega-Lite-shaped chart.</div>
    </div>
  );
  const src = sampleData[spec.source] || sampleData[Object.keys(sampleData)[0]] || [];
  const counts = {};
  src.forEach((r) => { const v = String(r[spec.aggBy] ?? '—'); counts[v] = (counts[v] || 0) + 1; });
  const entries = Object.entries(counts).slice(0, 10);
  const max = Math.max(...entries.map((e) => e[1])) || 1;
  const w = 560, h = 240, pad = { l: 110, r: 16, t: 16, b: 24 };
  const bandH = (h - pad.t - pad.b) / Math.max(entries.length, 1);
  return (
    <div style={{ marginTop: 4 }}>
      <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--txt-2)', letterSpacing: '0.06em', marginBottom: 6 }}>{spec.title}</div>
      <svg viewBox={`0 0 ${w} ${h}`} style={{ width: '100%', height: 'auto', background: 'var(--bp)', border: '1px solid var(--bb)', borderRadius: 6 }}>
        {entries.map(([k, v], i) => {
          const bw = ((w - pad.l - pad.r) * v) / max;
          return (
            <g key={k} transform={`translate(${pad.l}, ${pad.t + i * bandH + 4})`}>
              <text x={-10} y={bandH / 2 + 1} fontSize="11" fontFamily="JetBrains Mono" fill="var(--txt-2)" textAnchor="end" dominantBaseline="middle">{String(k).slice(0, 14)}</text>
              <rect x={0} y={2} width={bw} height={bandH - 8} fill="#58a6ff" rx="2" />
              <text x={bw + 6} y={bandH / 2} fontSize="10" fontFamily="JetBrains Mono" fill="var(--txt)" dominantBaseline="middle">{v}</text>
            </g>
          );
        })}
      </svg>
      <details style={{ marginTop: 8 }}>
        <summary style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--txt-2)', letterSpacing: '0.10em', cursor: 'pointer' }}>vega-lite spec</summary>
        <pre style={{ marginTop: 6, padding: 10, background: '#050913', borderRadius: 6, fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--txt)', overflow: 'auto' }}>{JSON.stringify({
          $schema: 'https://vega.github.io/schema/vega-lite/v5.json',
          title: spec.title,
          data: { name: spec.source },
          mark: 'bar',
          encoding: {
            y: { field: spec.aggBy, type: 'nominal' },
            x: { aggregate: 'count', type: 'quantitative' },
            color: { field: spec.aggBy, type: 'nominal' },
          },
        }, null, 2)}</pre>
      </details>
    </div>
  );
}

/* ---------- Step 4: Export ---------- */
function ExportStep({ schema, sqlDialect, setSqlDialect, artifacts, generate, loading }) {
  const [kind, setKind] = useState('sql');
  const tabs = [
    { id: 'sql',     label: 'SQL DDL',      icon: 'sql' },
    { id: 'stories', label: 'User stories', icon: 'stories' },
    { id: 'apis',    label: 'API docs',     icon: 'api' },
    { id: 'tests',   label: 'Test cases',   icon: 'test' },
  ];
  const content = artifacts[kind];
  return (
    <div>
      <span className="h-eyebrow">● Step 4 · Export</span>
      <h2 className="heading">Generate downstream artifacts</h2>
      <p className="lede">From a single schema, the kernel emits SQL DDL, user stories, REST docs, and Gherkin tests. Generate one or all — they stay in sync with the schema you refined.</p>

      <div className="export-grid">
        <div className="export-tabs">
          {tabs.map((t) => (
            <button key={t.id} className={'export-tab ' + (kind === t.id ? 'on' : '')} onClick={() => setKind(t.id)}>
              <I name={t.icon} /> {t.label}
              {artifacts[t.id] && <I name="check" size={12} color="var(--ok)" style={{ marginLeft: 'auto' }} />}
            </button>
          ))}
          {kind === 'sql' && (
            <div style={{ marginTop: 14 }}>
              <label className="field" style={{ display: 'block' }}>
                <span style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--txt-2)', letterSpacing: '0.10em', textTransform: 'uppercase', marginBottom: 4, display: 'block' }}>SQL dialect</span>
                <select value={sqlDialect} onChange={(e) => setSqlDialect(e.target.value)}>
                  <option>PostgreSQL</option>
                  <option>MySQL</option>
                </select>
              </label>
            </div>
          )}
        </div>

        <div style={{ display: 'flex', flexDirection: 'column' }}>
          <div className="export-toolbar">
            <span style={{ marginRight: 'auto', fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--txt-2)', letterSpacing: '0.08em', alignSelf: 'center' }}>
              {content ? `${content.split('\n').length} lines · ${(content.length / 1024).toFixed(1)} KB` : 'awaiting generation'}
            </span>
            {content && <button className="btn"><I name="copy" /> Copy</button>}
            {content && <button className="btn"><I name="download" /> Download</button>}
            <button className="btn primary" disabled={loading} onClick={() => generate(kind)}>
              {loading ? <span className="loader"><span></span><span></span><span></span></span> : (content ? 'Regenerate' : 'Generate')}
            </button>
          </div>
          {content ? (
            kind === 'sql'
              ? <pre className="export-output sql" dangerouslySetInnerHTML={{ __html: highlightSQL(content) }} />
              : <div className="export-output md" dangerouslySetInnerHTML={{ __html: renderMD(content) }} />
          ) : (
            <div className="export-output">
              <span style={{ color: 'var(--txt-2)' }}>Press <b style={{ color: 'var(--acc)' }}>Generate</b> to emit {tabs.find((t) => t.id === kind).label} from your schema.</span>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function highlightSQL(text) {
  const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;');
  return esc(text)
    .replace(/(--[^\n]*)/g, '<span class="c">$1</span>')
    .replace(/\b(CREATE|TABLE|PRIMARY|KEY|FOREIGN|REFERENCES|NOT|NULL|DEFAULT|UNIQUE|INDEX|CONSTRAINT|ON|DELETE|CASCADE|UPDATE|VARCHAR|INT|INTEGER|TIMESTAMP|TEXT|BOOLEAN|DECIMAL|FLOAT|DATE|SERIAL|BIGINT|AUTO_INCREMENT)\b/gi, '<span class="k">$&</span>')
    .replace(/'([^']*)'/g, '<span class="s">\'$1\'</span>')
    .replace(/\b(\d+)\b/g, '<span class="n">$&</span>');
}
function renderMD(text) {
  const lines = text.split('\n');
  const out = [];
  let codeBuf = null;
  let listBuf = []; let listType = null;
  function flushList() {
    if (!listBuf.length) return;
    const Tag = listType === 'ul' ? 'ul' : 'ol';
    out.push(`<${Tag}>` + listBuf.map((l) => `<li>${inline(l)}</li>`).join('') + `</${Tag}>`);
    listBuf = []; listType = null;
  }
  function inline(s) {
    return s
      .replace(/&/g, '&amp;').replace(/</g, '&lt;')
      .replace(/`([^`]+)`/g, '<code>$1</code>')
      .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
  }
  lines.forEach((line) => {
    if (line.startsWith('```')) {
      if (codeBuf === null) codeBuf = '';
      else { out.push(`<pre><code>${codeBuf}</code></pre>`); codeBuf = null; }
      return;
    }
    if (codeBuf !== null) { codeBuf += inline(line) + '\n'; return; }
    if (line.startsWith('### ')) { flushList(); out.push(`<h3>${inline(line.slice(4))}</h3>`); return; }
    if (line.startsWith('## '))  { flushList(); out.push(`<h2>${inline(line.slice(3))}</h2>`); return; }
    if (line.startsWith('# '))   { flushList(); out.push(`<h1>${inline(line.slice(2))}</h1>`); return; }
    if (/^\s*[-*]\s/.test(line)) { if (listType !== 'ul') flushList(); listType = 'ul'; listBuf.push(line.replace(/^\s*[-*]\s/, '')); return; }
    if (/^\s*\d+\.\s/.test(line)) { if (listType !== 'ol') flushList(); listType = 'ol'; listBuf.push(line.replace(/^\s*\d+\.\s/, '')); return; }
    if (line.trim() === '') { flushList(); return; }
    flushList();
    out.push(`<p>${inline(line)}</p>`);
  });
  flushList();
  return out.join('');
}

/* ---------- Fallback artifact generators ---------- */
function fallbackSQL(schema, dialect) {
  const idType = dialect === 'PostgreSQL' ? 'SERIAL PRIMARY KEY' : 'INT AUTO_INCREMENT PRIMARY KEY';
  return schema.tables.map((t) => {
    const cols = t.columns.map((c) => {
      if (c.isPrimaryKey) return `  ${c.name} ${idType}`;
      const fk = c.isForeignKey ? `,\n  FOREIGN KEY (${c.name}) REFERENCES ${c.foreignKeyTo.table}(${c.foreignKeyTo.column}) ON DELETE CASCADE` : '';
      return `  ${c.name} ${c.type}` + fk;
    }).join(',\n');
    return `-- ${t.description || ''}\nCREATE TABLE ${t.name} (\n${cols}\n);\n`;
  }).join('\n');
}
function fallbackStories(schema) {
  return schema.tables.map((t) => `## ${t.name}\n\n${(t.description || '')}\n\n### As a user, I want to create a new ${t.name.toLowerCase().replace(/s$/, '')} so that I can track ${(t.description || t.name).toLowerCase()}\n\n### As a user, I want to view a list of ${t.name.toLowerCase()} so that I can review records.\n\n### As a user, I want to update an existing ${t.name.toLowerCase().replace(/s$/, '')} so that data stays current.`).join('\n\n');
}
function fallbackAPI(schema) {
  return schema.tables.map((t) => {
    const pk = t.columns.find((c) => c.isPrimaryKey)?.name || 'id';
    return `## ${t.name}\n\n${t.description || ''}\n\n### GET /api/${t.name.toLowerCase()}\nList all ${t.name.toLowerCase()}.\n\n### GET /api/${t.name.toLowerCase()}/{${pk}}\nFetch one by id.\n\n### POST /api/${t.name.toLowerCase()}\nCreate a new ${t.name.toLowerCase().replace(/s$/, '')}.\n\n\`\`\`json\n${JSON.stringify(t.columns.reduce((a, c) => (a[c.name] = '<' + c.type + '>', a), {}), null, 2)}\n\`\`\`\n\n### PUT /api/${t.name.toLowerCase()}/{${pk}}\nUpdate. Body matches POST.\n\n### DELETE /api/${t.name.toLowerCase()}/{${pk}}\nRemove the record.`;
  }).join('\n\n');
}
function fallbackTests(schema) {
  return schema.tables.map((t) => `Feature: ${t.name} CRUD\n\n  Scenario: Create a new ${t.name.toLowerCase().replace(/s$/, '')}\n    Given the user is authenticated\n    When the user POSTs valid ${t.name} data\n    Then the response status is 201\n    And the response body contains the new id\n\n  Scenario: List ${t.name.toLowerCase()}\n    Given the user is authenticated\n    When the user GETs /api/${t.name.toLowerCase()}\n    Then the response status is 200\n    And the body is an array`).join('\n\n');
}

/* ---------- ROOT ---------- */
function App() {
  const [chat, setChat] = useState([{ role: 'user', text: INITIAL_PROMPT }]);
  const [files, setFiles] = useState([]);
  const [schema, setSchema] = useState(null);
  const [sampleData, setSampleData] = useState(null);
  const [chartSpec, setChartSpec] = useState(null);
  const [artifacts, setArtifacts] = useState({});
  const [sqlDialect, setSqlDialect] = useState('PostgreSQL');
  const [loading, setLoading] = useState(false);
  const [layout, setLayout] = useState('tabs');
  const [active, setActive] = useState('schema');

  async function generateSchema() {
    if (chat.length === 0) return;
    setLoading(true);
    const userMsgs = chat.filter((m) => m.role === 'user').map((m) => m.text).join('\n');
    const prompt = `You are a senior database architect. From this request, output ONLY a valid JSON object describing the schema (no markdown fences, no commentary). Shape: { description: string, tables: Array<{ name, description, columns: Array<{ name, type, description?, isPrimaryKey?, isForeignKey?, foreignKeyTo?: {table,column} }> }> }. Use standard SQL types. Every table must have a primary key. Use 4-6 tables.\n\nRequest:\n${userMsgs}\n\nContext files (if any):\n${files.map((f) => `--- ${f.name} ---\n${f.content.slice(0, 1200)}`).join('\n\n')}`;
    const s = await llmJSON(prompt, FALLBACK_SCHEMA);
    setSchema(s);
    setLoading(false);
    setActive('refine');
  }

  async function refineSchema(instruction) {
    if (!schema) return;
    setLoading(true);
    const prompt = `Refine this schema per the instruction. Output ONLY valid JSON in the same shape (no markdown). Preserve unchanged tables/columns. Keep primary keys; keep/update foreign keys consistently.\n\nInstruction: ${instruction}\n\nSchema:\n${JSON.stringify(schema, null, 2)}`;
    const next = await llmJSON(prompt, schema);
    setSchema(next);
    setSampleData(null); setChartSpec(null); setArtifacts({});
    setLoading(false);
  }

  async function generateSample(prompt, rows) {
    if (!schema) return;
    setLoading(true);
    const fake = genSampleData(schema, rows);
    setSampleData(fake);
    setChartSpec(null);
    setLoading(false);
    setActive('data');
  }

  async function generateChart(vizPrompt) {
    if (!schema || !sampleData) return;
    setLoading(true);
    const prompt = `Given the schema and a viz request, decide which table and which column to aggregate by. Output ONLY JSON: { "source": "<TableName>", "aggBy": "<ColumnName>", "title": "<short title>" }.\n\nViz request: ${vizPrompt}\n\nTables: ${schema.tables.map((t) => t.name + ' (' + t.columns.map((c) => c.name).join(', ') + ')').join('; ')}`;
    const fallback = {
      source: schema.tables[0]?.name,
      aggBy: schema.tables[0]?.columns.find((c) => !c.isPrimaryKey && !c.isForeignKey)?.name || schema.tables[0]?.columns[0]?.name,
      title: vizPrompt,
    };
    const spec = await llmJSON(prompt, fallback);
    setChartSpec(spec);
    setLoading(false);
  }

  async function generateArtifact(kind) {
    if (!schema) return;
    setLoading(true);
    const schemaJSON = JSON.stringify(schema, null, 2);
    let prompt = ''; let fb = '';
    if (kind === 'sql') {
      prompt = `Generate ${sqlDialect}-compatible CREATE TABLE DDL for this schema. Output only the SQL (no markdown fences, no commentary). Include primary keys and foreign keys with ON DELETE CASCADE. Add comments where helpful.\n\nSchema:\n${schemaJSON}`;
      fb = fallbackSQL(schema, sqlDialect);
    } else if (kind === 'stories') {
      prompt = `Generate user stories in markdown for this schema. Group by feature. Use format "As a [role], I want to [action] so that [benefit]." Each story is a level-3 heading (###).\n\nSchema:\n${schemaJSON}`;
      fb = fallbackStories(schema);
    } else if (kind === 'apis') {
      prompt = `Generate API docs in markdown for this schema. For each table, define CRUD endpoints (GET list, GET one, POST, PUT, DELETE). Include example request/response JSON. Use level-2 headings per table.\n\nSchema:\n${schemaJSON}`;
      fb = fallbackAPI(schema);
    } else if (kind === 'tests') {
      prompt = `Generate Gherkin test cases for CRUD on this schema. One Feature per table. Output plain text, no markdown fences.\n\nSchema:\n${schemaJSON}`;
      fb = fallbackTests(schema);
    }
    const content = await llmText(prompt, fb);
    setArtifacts((a) => ({ ...a, [kind]: content }));
    setLoading(false);
  }

  function reset() {
    setChat([{ role: 'user', text: INITIAL_PROMPT }]);
    setFiles([]); setSchema(null); setSampleData(null); setChartSpec(null); setArtifacts({});
    setActive('schema');
  }

  const stepIdx = active === 'schema' ? 1 : active === 'refine' ? 2 : active === 'data' ? 3 : 4;

  function renderContent(tab) {
    if (tab === 'schema') return <SchemaStep chat={chat} setChat={setChat} files={files} setFiles={setFiles} onGenerate={generateSchema} loading={loading && !schema} />;
    if (tab === 'refine') {
      if (!schema) return <Gate label="No schema yet" body="Generate a schema in step 1 to start refining." />;
      return <RefineStep schema={schema} onRefine={refineSchema} loading={loading} />;
    }
    if (tab === 'data') {
      if (!schema) return <Gate label="No schema yet" body="Generate a schema first — sample data needs a target." />;
      return <DataStep schema={schema} sampleData={sampleData} setSampleData={setSampleData} chartSpec={chartSpec} setChartSpec={setChartSpec} loading={loading} onGenerate={generateSample} onViz={generateChart} />;
    }
    if (tab === 'export') {
      if (!schema) return <Gate label="No schema yet" body="Generate a schema first — artifacts are derived from it." />;
      return <ExportStep schema={schema} sqlDialect={sqlDialect} setSqlDialect={setSqlDialect} artifacts={artifacts} generate={generateArtifact} loading={loading} />;
    }
  }

  return (
    <div className="app">
      <header className="header">
        <div className="brand">
          <span className="mark"><I name="zap" size={18} /></span>
          <div>
            <h1>Semantic Loop · DevOps Demonstrator</h1>
            <span className="sub">prompt → schema → refine → data → export</span>
          </div>
        </div>
        <div className="head-actions">
          <span style={{ display: 'inline-flex', gap: 6, alignItems: 'center', fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--txt-2)', letterSpacing: '0.08em', textTransform: 'uppercase' }}>
            <span className="pulse-dot"></span> wired · window.claude.complete
          </span>
          <div className="layout-pick">
            {[
              { id: 'tabs',   icon: 'layoutTabs',   label: 'Tabs' },
              { id: 'wizard', icon: 'layoutWizard', label: 'Wizard' },
              { id: 'grid',   icon: 'layoutGrid',   label: 'Grid' },
            ].map((o) => (
              <button key={o.id} className={layout === o.id ? 'on' : ''} onClick={() => setLayout(o.id)}>
                <I name={o.icon} size={12} /> {o.label}
              </button>
            ))}
          </div>
          <button className="btn" onClick={reset}><I name="reset" /> Start over</button>
        </div>
      </header>

      {layout === 'tabs' && (
        <>
          <nav className="tabs">
            {[
              { id: 'schema', label: 'Schema', icon: 'database' },
              { id: 'refine', label: 'Refine', icon: 'edit' },
              { id: 'data',   label: 'Data',   icon: 'chart' },
              { id: 'export', label: 'Export', icon: 'upload' },
            ].map((t) => (
              <button key={t.id}
                className={'tab' + (active === t.id ? ' on' : '')}
                disabled={!schema && t.id !== 'schema'}
                onClick={() => setActive(t.id)}>
                {((t.id === 'schema' && schema) ||
                  (t.id === 'refine' && schema) ||
                  (t.id === 'data' && sampleData) ||
                  (t.id === 'export' && Object.keys(artifacts).length > 0))
                  ? <I name="check" size={14} color="var(--ok)" />
                  : <I name={t.icon} />}
                {t.label}
              </button>
            ))}
          </nav>
          <main className="stage">
            <div className="stage-inner">{renderContent(active)}</div>
          </main>
        </>
      )}

      {layout === 'wizard' && (
        <>
          <Steps stepIdx={stepIdx} />
          <main className="stage">
            <div className="stage-inner">{renderContent(active)}</div>
          </main>
          <div className="wizard-nav">
            <button className="btn" disabled={active === 'schema'} onClick={() => setActive(active === 'export' ? 'data' : active === 'data' ? 'refine' : 'schema')}>← Back</button>
            <button className="btn primary"
              disabled={!schema || (active === 'data' && !sampleData) || active === 'export'}
              onClick={() => setActive(active === 'schema' ? 'refine' : active === 'refine' ? 'data' : 'export')}>
              Next →
            </button>
          </div>
        </>
      )}

      {layout === 'grid' && (
        <main className="grid-layout">
          <div className="panel">
            <div className="panel-head"><h2>1 · Describe schema</h2><span className="meta">{schema ? '✓ generated' : 'pending'}</span></div>
            <div className="panel-body">{renderContent('schema')}</div>
          </div>
          <div className="panel">
            <div className="panel-head"><h2>2 · Visualize &amp; refine</h2><span className="meta">{schema ? `${schema.tables.length} tables` : 'pending'}</span></div>
            <div className="panel-body">{renderContent('refine')}</div>
          </div>
          <div className="panel">
            <div className="panel-head"><h2>3 · Generate data</h2><span className="meta">{sampleData ? `${Object.values(sampleData)[0]?.length || 0} rows/table` : 'pending'}</span></div>
            <div className="panel-body">{renderContent('data')}</div>
          </div>
          <div className="panel">
            <div className="panel-head"><h2>4 · Export artifacts</h2><span className="meta">{Object.keys(artifacts).length} of 4</span></div>
            <div className="panel-body">{renderContent('export')}</div>
          </div>
        </main>
      )}
    </div>
  );
}

function Gate({ label, body }) {
  return (
    <div className="gate">
      <span className="badge">● {label}</span>
      <h3>Previous step required</h3>
      <p>{body}</p>
    </div>
  );
}

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