/* global React */
const { useState, useEffect, useMemo, useRef } = React;

/* =========================================================================
   NEW CONVERSATION — wider chat · reflowable · pinned architecture card
                  inline pipeline that collapses · suggest chips · tab badges
   ========================================================================= */
function shortId(id) {
  if (!id) return '';
  const s = String(id);
  return s.length <= 12 ? s : `${s.slice(0, 4)}…${s.slice(-4)}`;
}

function archSummary(architecture) {
  const nodes = architecture?.services?.length ?? 0;
  const edges = architecture?.connections?.length ?? 0;
  const region = architecture?.services?.find(s => s.region)?.region || 'eu-west-1';
  return `${nodes} node${nodes === 1 ? '' : 's'} · ${edges} edge${edges === 1 ? '' : 's'} · ${region}`;
}

// Backend's RateLimitError surfaces as a 429 with "Monthly limit ... exceeded".
// Catch both on shape and on message so we don't leak the raw error to chat.
function isQuotaError(err) {
  if (!err) return false;
  if (err.status === 429 || err.statusCode === 429) return true;
  return /monthly limit|rate limit|quota/i.test(err.message || String(err));
}

function NewAnalysis() {
  const { tier, pipelineState, setPipelineState, setRoute, refreshUsage } = useApp();
  const { PIPELINE_STEPS, SAMPLE_TF } = window.APP_DATA;

  const [inputMode, setInputMode] = useState('text');
  const [textVal, setTextVal] = useState('');
  const [iacVal, setIacVal] = useState(SAMPLE_TF);
  const [messages, setMessages] = useState([]);
  const [showQuotaBlock, setShowQuotaBlock] = useState(false);
  const [railOpen, setRailOpen] = useState(true);
  const [railTab, setRailTab] = useState('score');
  const [archVersion, setArchVersion] = useState('v1');
  const [versions, setVersions] = useState([
    { id: 'v1', label: 'Initial', at: 'just now', current: true },
  ]);
  // Live results from the most recent run.
  const [analysisId, setAnalysisId] = useState(null);
  const [architecture, setArchitecture] = useState(null);
  const [review, setReview] = useState(null);
  const [costEstimate, setCostEstimate] = useState(null);
  const [diagram, setDiagram] = useState(null);
  const [conversationId, setConversationId] = useState(null);
  const [chatBusy, setChatBusy] = useState(false);
  const [archTitle, setArchTitle] = useState(null);
  const chatRef = useRef(null);

  // Seed greeting
  useEffect(() => {
    if (messages.length === 0) {
      setMessages([
        { role: 'assistant', kind: 'greet', content: "Describe an AWS architecture, paste Terraform, or ask a question. I'll diagram it, review it against Well-Architected, and quote it — then we can iterate." },
      ]);
    }
  }, []);

  useEffect(() => { if (chatRef.current) chatRef.current.scrollTop = chatRef.current.scrollHeight; }, [messages]);

  // Mirror tweak state into latest pipeline turn (e.g. user clicks Retry).
  // Only the state field is mirrored — per-step state lives on the message.
  useEffect(() => {
    setMessages(ms => {
      let found = false;
      const next = [...ms].reverse().map(m => {
        if (!found && m.kind === 'pipeline') { found = true; return { ...m, state: pipelineState }; }
        return m;
      }).reverse();
      return next;
    });
  }, [pipelineState]);

  function archHasRun() { return messages.some(m => m.kind === 'pipeline'); }

  // Update the most-recent pipeline message immutably.
  function updateLatestPipeline(updater) {
    setMessages(ms => {
      const out = [...ms];
      for (let i = out.length - 1; i >= 0; i--) {
        if (out[i].kind === 'pipeline') { out[i] = updater(out[i]); break; }
      }
      return out;
    });
  }
  function patchStep(idx, patch) {
    updateLatestPipeline(msg => ({
      ...msg,
      stepStates: (msg.stepStates || PIPELINE_STEPS.map(() => ({ status: 'pending' })))
        .map((s, i) => i === idx ? { ...s, ...patch } : s),
    }));
  }

  async function followPipeline(runId, isRevision) {
    setPipelineState('running');
    setDiagram(null);
    setAnalysisId(runId);
    const pipelineStartedAt = Date.now();

    // Step 0: parse — sequential. Steps 1/2/3 (diagram, waf review, cost)
    // run in parallel after parse: diagram from waitForDiagram, waf+cost
    // both produced by getReview.
    const parseStart = Date.now();
    patchStep(0, { status: 'inprogress', startedAt: parseStart });

    let parsed;
    try {
      parsed = await window.API.waitForParse(runId);
    } catch (err) {
      const msg = err?.message || String(err);
      patchStep(0, { status: 'failed', durationMs: Date.now() - parseStart, error: msg });
      updateLatestPipeline(m => ({ ...m, totalMs: Date.now() - pipelineStartedAt }));
      setPipelineState('failed');
      if (isQuotaError(err)) {
        setShowQuotaBlock(true);
        setMessages(m => [...m, { role: 'assistant', kind: 'text', content: "You've hit the Free-tier limit for this month. The Pro plan is launching soon — leave your email to be notified the moment it opens up." }]);
      } else {
        setMessages(m => [...m, { role: 'assistant', kind: 'text', content: `Pipeline failed: ${msg}` }]);
      }
      return;
    }
    patchStep(0, { status: 'complete', durationMs: Date.now() - parseStart });
    setArchitecture(parsed.architecture || null);

    const diagStart = Date.now();
    const reviewStart = Date.now();
    patchStep(1, { status: 'inprogress', startedAt: diagStart });
    patchStep(2, { status: 'inprogress', startedAt: reviewStart });
    patchStep(3, { status: 'inprogress', startedAt: reviewStart });

    const diagPromise = window.API.waitForDiagram(runId).then(
      d => { patchStep(1, { status: 'complete', durationMs: Date.now() - diagStart }); setDiagram(d); return d; },
      err => { patchStep(1, { status: 'failed', durationMs: Date.now() - diagStart, error: err?.message || String(err) }); return null; },
    );
    const reviewPromise = window.API.getReview(runId).then(
      rev => {
        const ms = Date.now() - reviewStart;
        patchStep(2, { status: 'complete', durationMs: ms });
        patchStep(3, { status: 'complete', durationMs: ms });
        setReview(rev.scorecard ? rev : { scorecard: rev });
        setCostEstimate(rev.costEstimate || null);
        return rev;
      },
      err => {
        const ms = Date.now() - reviewStart;
        const msg = err?.message || String(err);
        patchStep(2, { status: 'failed', durationMs: ms, error: msg });
        patchStep(3, { status: 'failed', durationMs: ms, error: msg });
        return null;
      },
    );

    const [, rev] = await Promise.all([diagPromise, reviewPromise]);

    updateLatestPipeline(m => ({ ...m, totalMs: Date.now() - pipelineStartedAt }));
    setPipelineState('done');
    const newV = `v${versions.length + 1}`;
    setVersions(vs => vs.map(v => ({ ...v, current: false })).concat({ id: newV, label: isRevision ? 'Revision' : 'Initial', at: 'just now', current: true }));
    setArchVersion(newV);

    const findings = window.scorecardToFindings(rev?.scorecard);
    const high = findings.filter(f => f.sev === 'HIGH');
    setMessages(m => [...m, {
      role: 'assistant', kind: 'summary',
      content: high.length
        ? `Scorecard, cost and recommendations are ready in the right rail. ${high.length} HIGH finding${high.length === 1 ? '' : 's'} flagged.`
        : 'Scorecard, cost and recommendations are ready in the right rail.',
      suggests: ['Open the scorecard', 'Show me the cost breakdown', 'List the HIGH findings'],
    }]);
  }

  async function sendChat(raw) {
    if (!raw.trim() || chatBusy) return;
    setMessages(m => [...m, { role: 'user', kind: 'text', content: raw }]);
    setTextVal('');
    setChatBusy(true);

    try {
      let cid = conversationId;
      if (!cid) {
        const c = await window.API.createConversation();
        cid = c?.conversationId || c?.id || c?.convoId;
        if (!cid) throw new Error('Could not create conversation');
        setConversationId(cid);
      }
      const resp = await window.API.sendChatMessage(cid, raw);
      const reply = resp?.reply || resp?.assistantMessage?.content || '';
      const chips = resp?.assistantMessage?.suggestedChips;

      if (resp?.runId) {
        const isRevision = versions.length > 1 || archHasRun();
        if (resp.title) setArchTitle(resp.title);
        setMessages(m => [...m,
          { role: 'assistant', kind: 'text', content: reply || (isRevision ? 'Revising the architecture.' : 'Running the pipeline now.') },
          { role: 'assistant', kind: 'pipeline', state: 'running', startedAt: Date.now(),
            stepStates: window.APP_DATA.PIPELINE_STEPS.map(() => ({ status: 'pending' })) },
        ]);
        // The backend incremented the usage counter when this run was
        // accepted — re-pull /user so the topnav guard, dashboard count and
        // composer all see the new value as soon as the next send is tried.
        refreshUsage?.();
        await followPipeline(resp.runId, isRevision);
      } else {
        setMessages(m => [...m, {
          role: 'assistant', kind: 'text', content: reply,
          ...(chips && chips.length && { suggests: chips }),
        }]);
      }
    } catch (err) {
      if (isQuotaError(err)) {
        setShowQuotaBlock(true);
        setMessages(m => [...m, { role: 'assistant', kind: 'text', content: "You've hit the Free-tier limit for this month. The Pro plan is launching soon — leave your email to be notified the moment it opens up." }]);
      } else {
        setMessages(m => [...m, { role: 'assistant', kind: 'text', content: `Chat failed: ${err.message || err}` }]);
      }
    } finally {
      setChatBusy(false);
    }
  }

  function submitFromInput() {
    if (tier === 'free' && showQuotaBlock) return;
    sendChat(inputMode === 'text' ? textVal : iacVal);
  }

  const anyPipelineRun = messages.some(m => m.kind === 'pipeline');
  const diagramVisible = anyPipelineRun || pipelineState === 'done' || pipelineState === 'running' || pipelineState === 'failed';
  const showResults = pipelineState === 'done';

  // Reflowable widths: chat flexes between 360 and 520, rail is fixed.
  // Using minmax on the chat column keeps the center diagram from collapsing
  // on narrow viewports (~920px) while still letting chat breathe when wide.
  const chatWidth = 'minmax(360px, 520px)';
  const railWidth = showResults ? (railOpen ? 'minmax(300px, 380px)' : '44px') : '0px';

  return (
    <div className="page full" style={{display:'grid', gridTemplateColumns: `${chatWidth} minmax(0, 1fr) ${railWidth}`, height:'calc(100vh - 52px)', transition:'grid-template-columns .22s cubic-bezier(.4,0,.2,1)'}}>
      {/* LEFT — chat */}
      <aside style={{borderRight:'1px solid var(--line)', background:'var(--bg-1)', display:'flex', flexDirection:'column', minHeight:0}}>
        <div style={{padding:'10px 14px', borderBottom:'1px solid var(--line)', display:'flex', alignItems:'center', gap:10, background:'var(--bg-3)'}}>
          <div className="label">Conversation</div>
          {conversationId && <span className="pill ghost" title={conversationId}>{shortId(conversationId)}</span>}
          <div style={{flex:1}}/>
          <button className="btn sm ghost" title="New chat" onClick={() => { setMessages([]); setPipelineState('pending'); setVersions([{id:'v1', label:'Initial', at:'just now', current:true}]); setConversationId(null); setArchTitle(null); }}>
            <Icon name="plus" size={12}/>
          </button>
        </div>

        {/* Pinned architecture card (appears once a run has completed) */}
        {anyPipelineRun && (
          <div style={{padding:'10px 14px 0'}}>
            <div className="arch-card">
              <div style={{display:'flex', alignItems:'center', gap:8, marginBottom:6}}>
                <Icon name="grid" size={12} className="diag-edge"/>
                <div style={{fontSize:12.5, fontWeight:500, flex:1, minWidth:0, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap'}} title={archTitle || ''}>{archTitle || 'Architecture'}</div>
                <span className="pill ghost">{archVersion}</span>
              </div>
              <div style={{fontSize:11, color:'var(--ink-3)', fontFamily:'var(--font-mono)', marginBottom:8}}>{archSummary(architecture)}</div>
              <div style={{display:'flex', alignItems:'center', gap:6, flexWrap:'wrap'}}>
                {versions.map(v => (
                  <button key={v.id} onClick={() => setArchVersion(v.id)} title={`${v.label} · ${v.at}`}
                    style={{display:'inline-flex', alignItems:'center', gap:6, padding:'3px 8px', fontSize:11, fontFamily:'var(--font-mono)', border:'1px solid '+(archVersion===v.id?'var(--line-strong)':'transparent'), borderRadius:999, background: archVersion===v.id?'var(--bg-1)':'transparent', color: archVersion===v.id?'var(--ink-0)':'var(--ink-2)'}}>
                    <span className={`ver-dot ${archVersion===v.id?'current':''}`}/>{v.id}
                  </button>
                ))}
                <button className="btn sm ghost" style={{fontSize:11, color:'var(--ink-3)', marginLeft:'auto'}} title="Fork this version">
                  <Icon name="retry" size={10}/>fork
                </button>
              </div>
            </div>
          </div>
        )}

        <div ref={chatRef} style={{flex:1, overflow:'auto', padding:'8px 14px 14px'}}>
          {messages.map((m, i) => (
            <ChatTurn key={i} msg={m} steps={PIPELINE_STEPS}
                      isLatestPipeline={m.kind === 'pipeline' && i === messages.map(x=>x.kind).lastIndexOf('pipeline')}
                      runId={analysisId}
                      onRetry={() => setPipelineState('running')}
                      onSuggest={(q) => sendChat(q)}
                      onPickTab={(t) => { setRailOpen(true); setRailTab(t); }}/>
          ))}
          {messages.length <= 1 && (
            <div style={{padding:'12px 0 0'}}>
              <div className="label" style={{marginBottom:8}}>— try —</div>
              {[
                '3-tier web app on ECS Fargate with RDS Postgres and CloudFront',
                'Serverless REST API with API Gateway, Lambda, and DynamoDB',
                'Event-driven pipeline with SQS, SNS, and Lambda consumers',
              ].map((s, j) => (
                <button key={j} className="suggest-chip" onClick={() => sendChat(s)} style={{display:'flex', width:'100%', justifyContent:'flex-start', padding:'6px 8px', marginBottom:2, textAlign:'left', whiteSpace:'normal'}}>
                  <span className="arr">▸</span>{s}
                </button>
              ))}
            </div>
          )}
        </div>

        {/* Input dock */}
        <div style={{borderTop:'1px solid var(--line)', background:'var(--bg-3)', padding:10}}>
          <div style={{display:'flex', gap:4, marginBottom:8, alignItems:'center'}}>
            <button className={`btn sm ${inputMode==='text'?'primary':'ghost'}`} onClick={() => setInputMode('text')}><Icon name="sparkle" size={11}/>Chat</button>
            <button className={`btn sm ${inputMode==='iac'?'primary':'ghost'}`} onClick={() => setInputMode('iac')}><Icon name="code" size={11}/>Paste IaC</button>
            <div style={{flex:1}}/>
            {tier === 'free' && <button className="btn sm ghost" onClick={() => setShowQuotaBlock(s => !s)} style={{color:'var(--ink-3)', fontSize:11}}>sim quota</button>}
            <span className="pill ghost" style={{color:'var(--ink-3)'}}>{inputMode==='text' ? `${textVal.length} ch` : `${iacVal.split('\n').length} lines`}</span>
          </div>
          {inputMode === 'text' ? (
            <ChatComposer
              value={textVal}
              onChange={setTextVal}
              onSubmit={submitFromInput}
              disabled={chatBusy || pipelineState === 'running' || showQuotaBlock || !textVal.trim()}
              placeholder={messages.length > 1 ? 'Ask a follow-up, or describe a change…' : 'Describe your AWS architecture, or ask anything…'}
            />
          ) : (
            <>
              <textarea className="field mono" rows={6} style={{fontSize:11.5}} value={iacVal} onChange={e => setIacVal(e.target.value)}/>
              <div style={{display:'flex', marginTop:8, gap:8, alignItems:'center'}}>
                <span style={{fontSize:11.5, color:'var(--ink-3)', fontFamily:'var(--font-mono)'}}>region · eu-west-1</span>
                <div style={{flex:1}}/>
                <button className="btn primary sm" onClick={submitFromInput} disabled={chatBusy || pipelineState === 'running' || showQuotaBlock}><Icon name="send" size={11}/>Send</button>
              </div>
            </>
          )}
          {showQuotaBlock && (
            <div style={{marginTop:8, padding:'8px 10px', border:'1px solid color-mix(in srgb, var(--err) 30%, transparent)', background:'var(--err-soft)', borderRadius:4, fontSize:12}}>
              <div style={{color:'var(--err)', fontWeight:600, display:'flex', alignItems:'center', gap:6}}><Icon name="warn" size={11}/>Free-tier quota reached</div>
              <div style={{marginTop:6}}><UpgradeCta className="btn primary" size="sm"/></div>
            </div>
          )}
          <div style={{marginTop:6, fontSize:10.5, color:'var(--ink-3)', fontFamily:'var(--font-mono)', textAlign:'center'}}>enter ↵ · shift+enter newline</div>
        </div>
      </aside>

      {/* CENTER — diagram canvas */}
      <section style={{minWidth:0, minHeight:0, position:'relative', overflow:'hidden', display:'flex', flexDirection:'column'}}>
        {!diagramVisible
          ? <WaitingCanvas />
          : (
          <>
            <div style={{position:'absolute', top:10, left:12, zIndex:2, display:'flex', alignItems:'center', gap:8, fontFamily:'var(--font-mono)', fontSize:10.5, color:'var(--ink-3)', textTransform:'uppercase', letterSpacing:'0.08em', pointerEvents:'none'}}>
              <span>architecture</span>
              {analysisId && <><span>·</span><span>{analysisId}</span></>}
              {architecture?.services && <><span>·</span><span>{architecture.services.length} services · {architecture.connections?.length || 0} edges</span></>}
            </div>
            <div style={{flex:1, minHeight:0, position:'relative'}}>
              <DiagramImage bare url={diagram?.pngUrl} svgUrl={diagram?.svgUrl} drawioUrl={diagram?.drawioUrl} height="100%"/>
            </div>
          </>
        )}
      </section>

      {/* RIGHT — results rail (hidden until pipeline completes) */}
      {showResults && (
        <aside className={`rail ${railOpen ? '' : 'collapsed'}`}>
          {railOpen
            ? <ResultsRail tab={railTab} setTab={setRailTab} onCollapse={() => setRailOpen(false)} showResults={showResults} review={review} costEstimate={costEstimate}/>
            : <CollapsedResultsRail onExpand={(t) => { setRailOpen(true); if (t) setRailTab(t); }} active={railTab}/>
          }
        </aside>
      )}
    </div>
  );
}

/* =========================================================================
   Chat composer — single-line by default, grows with content up to 200px
   ========================================================================= */
function ChatComposer({ value, onChange, onSubmit, disabled, placeholder }) {
  return (
    <div className="composer">
      <div className="composer-input">
        <textarea
          rows={2}
          value={value}
          onChange={e => onChange(e.target.value)}
          onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); onSubmit(); } }}
          placeholder={placeholder}
        />
      </div>
      <button
        className="composer-send"
        onClick={onSubmit}
        disabled={disabled}
        aria-label="Send message">
        <Icon name="send" size={12}/>Send
      </button>
    </div>
  );
}

/* =========================================================================
   Chat turn renderer
   ========================================================================= */
function ChatTurn({ msg, steps, isLatestPipeline, runId, onRetry, onSuggest, onPickTab }) {
  const [pipeOpen, setPipeOpen] = useState(true);

  // Auto-collapse pipeline when it finishes — only for NOT-latest turns
  useEffect(() => {
    if (msg.kind === 'pipeline' && msg.state === 'done' && !isLatestPipeline) {
      setPipeOpen(false);
    }
  }, [msg.state, isLatestPipeline]);

  if (msg.kind === 'pipeline') {
    const isDone = msg.state === 'done';
    const isRunning = msg.state === 'running';
    const stepStates = msg.stepStates || steps.map(() => ({ status: 'pending' }));
    const fmt = (ms) => ms == null ? '—' : ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(2)}s`;
    const completeCount = stepStates.filter(s => s.status === 'complete').length;
    const totalLabel = msg.totalMs != null ? fmt(msg.totalMs) : '';

    if (!pipeOpen && isDone) {
      return (
        <div className="chat-msg assistant">
          <div className="who"><Icon name="cpu" size={13}/></div>
          <div className="content" style={{paddingTop:6}}>
            <button className="pipe-summary" onClick={() => setPipeOpen(true)}>
              <Icon name="check" size={11} className="diag-edge" style={{color:'var(--ok)'}}/>
              {completeCount} step{completeCount === 1 ? '' : 's'}{totalLabel && ` · ${totalLabel}`}
              <span style={{color:'var(--ink-3)'}}>· expand</span>
              <Icon name="chev" size={10} className="diag-edge"/>
            </button>
          </div>
        </div>
      );
    }

    return (
      <div className="chat-msg assistant">
        <div className="who"><Icon name="cpu" size={13}/></div>
        <div className="content" style={{paddingTop:0}}>
          <div className="meta" style={{display:'flex', alignItems:'center', gap:8}}>
            <span>PIPELINE</span>
            {isDone && <button onClick={() => setPipeOpen(false)} style={{marginLeft:'auto', fontSize:10, color:'var(--ink-3)', fontFamily:'var(--font-mono)', letterSpacing:'0.06em'}}>COLLAPSE ↑</button>}
          </div>
          <div className="ai-border" style={{border:'1px solid var(--line)', borderRadius:6, background:'var(--bg-2)', overflow:'hidden', marginTop:6}}>
            <div style={{display:'flex', alignItems:'center', gap:8, padding:'6px 12px', background:'var(--bg-1)', borderBottom:'1px solid var(--line)'}}>
              <span className="label" style={{margin:0}}>{runId ? `run · ${shortId(runId)}` : 'run'}</span>
              <div style={{flex:1}}/>
              {isRunning && <span className="pill accent"><span className="spinner" style={{width:8, height:8}}/>running</span>}
              {isDone    && <span className="pill ok"><Icon name="check" size={10}/>{totalLabel || 'done'}</span>}
              {msg.state === 'failed' && <span className="pill err"><Icon name="x" size={10}/>failed</span>}
            </div>
            <div style={{padding:'4px 12px 8px'}}>
              <div className="timeline">
                {steps.map((step, i) => {
                  const ss = stepStates[i] || { status: 'pending' };
                  const s = ss.status;
                  const dur = s === 'complete' || s === 'failed' ? fmt(ss.durationMs)
                            : s === 'inprogress' ? 'live' : '—';
                  return (
                    <div key={step.key} className="step-row" style={{padding:'6px 0'}}>
                      <div className={`step-icon ${s}`}>
                        {s === 'complete' && <Icon name="check" size={12}/>}
                        {s === 'failed'   && <Icon name="x" size={12}/>}
                        {s === 'inprogress' && <span className="spinner" />}
                        {s === 'pending'  && <span style={{fontFamily:'var(--font-mono)'}}>{i+1}</span>}
                      </div>
                      <div style={{minWidth:0}}>
                        <div className="step-head"><span className="title" style={{fontSize:12.5}}>{step.title}</span>
                          <span className="dur">{dur}</span>
                        </div>
                        {s === 'failed' && (
                          <>
                            <div className="step-logs" style={{maxHeight:52}}>
                              <div className="line err"><span className="t">{fmt(ss.durationMs)}</span>{ss.error || 'step failed'}</div>
                            </div>
                            <button className="btn sm" style={{marginTop:6}} onClick={onRetry}><Icon name="retry" size={10}/>Retry</button>
                          </>
                        )}
                      </div>
                    </div>
                  );
                })}
              </div>
            </div>
          </div>
        </div>
      </div>
    );
  }

  if (msg.kind === 'iac') {
    return (
      <div className="chat-msg user">
        <div className="who">You</div>
        <div className="content">
          <div className="meta">TERRAFORM INPUT</div>
          <pre style={{fontFamily:'var(--font-mono)', fontSize:11.5, background:'var(--bg-2)', border:'1px solid var(--line)', borderRadius:4, padding:10, maxHeight:240, overflow:'auto', whiteSpace:'pre', margin:'4px 0 0'}}>{msg.content}</pre>
        </div>
      </div>
    );
  }

  return (
    <div className={`chat-msg ${msg.role}`}>
      <div className="who">{msg.role === 'user' ? 'You' : <Icon name="sparkle" size={13}/>}</div>
      <div className="content">
        {msg.role === 'user' && <div className="meta">YOU</div>}
        <div style={{color:'var(--ink-1)'}}>{msg.content}</div>
        {msg.bullets && (
          <ul style={{margin:'8px 0 0', paddingLeft:18, color:'var(--ink-1)', fontSize:13, lineHeight:1.6}}>
            {msg.bullets.map((b, i) => <li key={i}>{b}</li>)}
          </ul>
        )}
        {msg.codeBlock && (
          <pre className="ai-border" style={{margin:'10px 0 0', padding:'10px 12px', fontFamily:'var(--font-mono)', fontSize:11.5, background:'var(--bg-sunken)', border:'1px solid var(--line)', borderRadius:4, whiteSpace:'pre', overflow:'auto'}}>
            {msg.codeBlock.split('\n').map((ln, j) => (
              <div key={j} style={{color: ln.startsWith('+') ? 'var(--ok)' : ln.startsWith('-') ? 'var(--err)' : 'var(--ink-1)'}}>{ln}</div>
            ))}
          </pre>
        )}
        {msg.kind === 'summary' && (
          <div style={{display:'flex', gap:6, marginTop:10, flexWrap:'wrap'}}>
            <button className="btn sm primary" onClick={() => onPickTab('score')}><Icon name="shield" size={11}/>Open scorecard</button>
            <button className="btn sm" onClick={() => onPickTab('cost')}><Icon name="dollar" size={11}/>Open cost</button>
            <button className="btn sm" onClick={() => onPickTab('recs')}><Icon name="sparkle" size={11}/>Recommendations</button>
          </div>
        )}
        {msg.suggests && msg.suggests.length > 0 && (
          <div style={{display:'flex', gap:2, marginTop:8, flexWrap:'wrap'}}>
            {msg.suggests.map((s, i) => (
              <button key={i} className="suggest-chip" onClick={() => onSuggest(s)}>
                <span className="arr">↳</span>{s}
              </button>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

/* =========================================================================
   Results rail
   ========================================================================= */
function CollapsedResultsRail({ onExpand, active }) {
  const items = [
    { id: 'score', icon: 'shield',  label: 'Scorecard' },
    { id: 'cost',  icon: 'dollar',  label: 'Cost' },
    { id: 'recs',  icon: 'sparkle', label: 'Recommendations' },
  ];
  return (
    <div className="rail-collapsed-strip">
      <button title="Expand" onClick={() => onExpand()} style={{color:'var(--ink-1)'}}>
        <Icon name="chev" size={14} style={{transform:'rotate(90deg)'}}/>
      </button>
      <div style={{height:1, width:20, background:'var(--line)'}}/>
      {items.map(it => (
        <button key={it.id} title={it.label} className={active === it.id ? 'active' : ''} onClick={() => onExpand(it.id)}>
          <Icon name={it.icon} size={14}/>
        </button>
      ))}
      <div className="rail-collapsed-label">results</div>
    </div>
  );
}

function ResultsRail({ tab, setTab, onCollapse, showResults, review, costEstimate }) {
  const { tier, setRoute } = useApp();
  const pillars = window.scorecardToPillars(review?.scorecard);
  const findings = window.scorecardToFindings(review?.scorecard);
  const lines = window.costToLines(costEstimate);
  const overall = review?.scorecard?.overallScore ?? (pillars.length ? Math.round(pillars.reduce((s, p) => s + p.score, 0) / pillars.length) : 0);
  const total = lines.reduce((s, l) => s + (l.mo || 0), 0);
  const hiddenTotal = lines.filter(l => l.hidden).reduce((s, l) => s + (l.mo || 0), 0);
  const highCount = findings.filter(f => f.sev === 'HIGH').length;
  const canCost = tier !== 'free';
  const canRecs = tier === 'pro' || tier === 'team' || tier === 'enterprise' || tier === 'admin';

  const tabs = [
    { id: 'score', icon: 'shield',  label: 'Scorecard' },
    { id: 'cost',  icon: 'dollar',  label: 'Cost',            locked: !canCost },
    { id: 'recs',  icon: 'sparkle', label: 'Recommendations', locked: !canRecs },
  ];

  return (
    <>
      <div style={{display:'flex', alignItems:'center', padding:'10px 10px 10px 14px', borderBottom:'1px solid var(--line)', gap:8, background:'var(--bg-3)'}}>
        <div className="label">Results</div>
        <div style={{flex:1}}/>
        <button className="btn sm ghost" onClick={onCollapse} title="Collapse">
          <Icon name="chev" size={13} style={{transform:'rotate(-90deg)'}}/>
        </button>
      </div>
      <div style={{display:'flex', borderBottom:'1px solid var(--line)', background:'var(--bg-1)'}}>
        {tabs.map(t => (
          <button key={t.id} onClick={() => setTab(t.id)} style={{
            flex:1, padding:'10px 6px', display:'flex', flexDirection:'column', alignItems:'center', gap:4,
            borderBottom: tab === t.id ? '2px solid var(--ink-0)' : '2px solid transparent',
            marginBottom:-1, color: tab === t.id ? 'var(--ink-0)' : 'var(--ink-2)', fontSize:11,
          }}>
            <div style={{display:'flex', alignItems:'center', gap:5}}>
              <Icon name={t.icon} size={12}/>
              {t.locked && <Icon name="lock" size={9} className="diag-edge"/>}
            </div>
            <div style={{display:'flex', alignItems:'center', gap:5}}>
              <span>{t.label}</span>
            </div>
          </button>
        ))}
      </div>
      <div style={{flex:1, overflow:'auto', padding:14}}>
        {!showResults && <RailPending/>}
        {showResults && tab === 'score' && <RailScore pillars={pillars} findings={findings} overall={overall}/>}
        {showResults && tab === 'cost' && (canCost ? <RailCost lines={lines} total={total} hiddenTotal={hiddenTotal} savings={costEstimate?.savingsSuggestions || []}/> : <RailGated title="Cost is a Pro feature" sub="Upgrade for line-item pricing and hidden cost detection."/>)}
        {showResults && tab === 'recs' && (canRecs ? <RailRecs recs={review?.recommendations || []}/> : <RailGated title="Recommendations are Pro" sub="Ranked fixes with IaC diffs."/>)}
      </div>
    </>
  );
}

function RailPending() {
  return (
    <div style={{padding:'24px 4px', color:'var(--ink-3)', fontSize:12.5}}>
      <div className="label">— nothing to show yet —</div>
    </div>
  );
}

function RailGated({ title, sub }) {
  return (
    <div style={{padding:'20px 12px', textAlign:'center'}}>
      <div style={{width:36, height:36, borderRadius:6, background:'var(--ink-0)', color:'var(--bg-2)', display:'grid', placeItems:'center', margin:'0 auto 12px'}}><Icon name="lock" size={15}/></div>
      <div style={{fontSize:13.5, fontWeight:600, marginBottom:4}}>{title}</div>
      <div style={{fontSize:12.5, color:'var(--ink-2)', marginBottom:14, lineHeight:1.5}}>{sub}</div>
      <UpgradeCta className="btn primary"/>
    </div>
  );
}

function RailScore({ pillars, findings, overall }) {
  const [expanded, setExpanded] = useState('rel');
  return (
    <div>
      <div style={{display:'flex', alignItems:'center', gap:14, padding:'4px 0 16px', borderBottom:'1px dashed var(--line)', marginBottom:10}}>
        <ScoreRing value={overall} size={76} stroke={7}/>
        <div>
          <div className="label">Overall WAF</div>
          <div style={{fontSize:22, fontFamily:'var(--font-display)', fontWeight:600, letterSpacing:'-0.02em', lineHeight:1}}>{overall}<span style={{fontSize:12, color:'var(--ink-3)', fontFamily:'var(--font-mono)', fontWeight:400, marginLeft:4}}>/ 100</span></div>
          <div style={{fontSize:11.5, color:'var(--ink-3)', marginTop:4, fontFamily:'var(--font-mono)'}}>142 · <span style={{color:'var(--err)'}}>5 fail</span> · <span style={{color:'var(--warn)'}}>14 warn</span></div>
        </div>
      </div>
      {pillars.map(p => (
        <div key={p.key}>
          <div className="pillar-row" onClick={() => setExpanded(e => e === p.key ? null : p.key)} style={{gridTemplateColumns:'1fr auto'}}>
            <div className="name" style={{fontSize:12.5}}>
              <Icon name="chev" size={10} style={{transform: expanded === p.key ? 'rotate(0deg)' : 'rotate(-90deg)', transition:'transform .12s'}} className="diag-edge"/>
              {p.name}
            </div>
            <div style={{display:'flex', alignItems:'center', gap:8}}>
              <div style={{width:72, height:3, background:'var(--bg-sunken)', borderRadius:2, overflow:'hidden'}}>
                <span style={{display:'block', height:'100%', width: `${p.score}%`, background: p.score >= 80 ? 'var(--ok)' : p.score >= 60 ? 'var(--warn)' : 'var(--err)'}}/>
              </div>
              <span className="mono" style={{fontSize:11.5, minWidth:30, textAlign:'right'}}>{p.score}</span>
            </div>
          </div>
          {expanded === p.key && (
            <div style={{padding:'4px 0 12px'}}>
              {findings.filter(f => f.pillar === p.key).length === 0 ? (
                <div style={{fontSize:11.5, color:'var(--ink-3)', padding:'6px 18px'}}>No findings.</div>
              ) : findings.filter(f => f.pillar === p.key).map((f, i) => (
                <div key={i} style={{padding:'8px 10px', border:'1px solid var(--line)', background:'var(--bg-2)', borderRadius:4, marginBottom:6, marginLeft:18}}>
                  <div style={{display:'flex', alignItems:'center', gap:6, marginBottom:4}}>
                    <span className={`pill ${f.sev==='HIGH'?'err':f.sev==='MED'?'warn':'ghost'}`}>{f.sev}</span>
                    <span style={{fontFamily:'var(--font-mono)', fontSize:11, color:'var(--ink-1)'}}>{f.rule}</span>
                    <div style={{flex:1}}/>
                    <span style={{fontFamily:'var(--font-mono)', fontSize:10, color:'var(--ink-3)'}}>{f.ref}</span>
                  </div>
                  <div style={{fontSize:12, color:'var(--ink-2)', lineHeight:1.5}}>{f.desc}</div>
                </div>
              ))}
            </div>
          )}
        </div>
      ))}
    </div>
  );
}

function RailCost({ lines, total, hiddenTotal, savings = [] }) {
  return (
    <div>
      <div style={{padding:'4px 0 14px', borderBottom:'1px dashed var(--line)', marginBottom:10}}>
        <div className="label">Estimated cost</div>
        <div style={{fontSize:26, fontFamily:'var(--font-display)', fontWeight:600, letterSpacing:'-0.02em', lineHeight:1.1}}>€{total.toFixed(2)}<span style={{fontSize:12, color:'var(--ink-3)', fontFamily:'var(--font-mono)', fontWeight:400, marginLeft:6}}>/ mo</span></div>
        <div style={{display:'flex', gap:14, marginTop:10, fontSize:11.5}}>
          <div><span style={{color:'var(--warn)'}}>€{hiddenTotal.toFixed(0)}</span> <span style={{color:'var(--ink-3)'}}>hidden</span></div>
          <div><span style={{color:'var(--ok)'}}>{savings.length}</span> <span style={{color:'var(--ink-3)'}}>savings</span></div>
          <div><span style={{color:'var(--ink-3)'}}>{lines.filter(l => l.unavailable).length} unavailable</span></div>
        </div>
      </div>
      {lines.map((l, i) => (
        <div key={i} style={{display:'grid', gridTemplateColumns:'1fr auto', padding:'8px 0', borderBottom:'1px dashed var(--line)', gap:8, alignItems:'center'}}>
          <div style={{minWidth:0}}>
            <div style={{fontSize:12.5, fontWeight:500, display:'flex', alignItems:'center', gap:6}}>
              {l.resource}
              {l.hidden && <span className="pill warn">hidden</span>}
              {l.unavailable && <span className="pill ghost">unavailable</span>}
            </div>
            <div style={{fontSize:11, color:'var(--ink-3)', fontFamily:'var(--font-mono)'}}>{l.detail}</div>
          </div>
          <div className="mono" style={{fontWeight: l.hidden ? 600 : 500, color: l.hidden ? 'var(--warn)' : l.unavailable ? 'var(--ink-3)' : undefined, fontSize:12, textAlign:'right'}}>{l.unavailable ? '—' : `€${l.mo.toFixed(2)}`}</div>
        </div>
      ))}
      <div style={{display:'flex', justifyContent:'space-between', padding:'10px 0 0', fontWeight:600, fontSize:13}}>
        <span>Total</span><span className="mono">€{total.toFixed(2)}</span>
      </div>
      {savings.length > 0 && (
        <div style={{marginTop:18, paddingTop:14, borderTop:'1px solid var(--line)'}}>
          <div className="label" style={{marginBottom:8, display:'flex', alignItems:'center', gap:8}}>
            <span>Savings opportunities</span>
            <span className="pill ok">{savings.length}</span>
          </div>
          <ul style={{margin:0, padding:'0 0 0 18px', display:'flex', flexDirection:'column', gap:6}}>
            {savings.map((s, i) => (
              <li key={i} style={{fontSize:11.5, lineHeight:1.5, color:'var(--ink-2)'}}>{s}</li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
}

function RailRecs({ recs }) {
  if (!recs || recs.length === 0) {
    return <div style={{padding:'24px 4px', color:'var(--ink-3)', fontSize:12.5}}>No recommendations yet — they appear after the review step finishes.</div>;
  }
  return (
    <div style={{display:'flex', flexDirection:'column', gap:10}}>
      {recs.map((r, i) => (
        <div key={i} className="ai-border" style={{border:'1px solid var(--line)', borderRadius:6, padding:'10px 12px', background:'var(--bg-2)', fontSize:12.5, lineHeight:1.5, color:'var(--ink-1)'}}>
          {r}
        </div>
      ))}
    </div>
  );
}

function WaitingCanvas() {
  return (
    <div style={{height:'100%', display:'grid', placeItems:'center', padding:40,
      background: `repeating-linear-gradient(0deg, transparent 0 39px, var(--grid) 39px 40px), repeating-linear-gradient(90deg, transparent 0 39px, var(--grid) 39px 40px), var(--bg-0)`}}>
    </div>
  );
}

window.NewAnalysis = NewAnalysis;
