/* global React,
   DiagramMedicalSearch, DiagramDataWarehouse,
   DiagramFinancialDocs, DiagramLegalCase, DiagramClassicalML */
const { useState } = React;

const workStyles = {
  section: { background: 'var(--bg-1)' },
  intro: {
    fontSize: 16, fontWeight: 300, color: 'var(--fg-2)',
    margin: '0 0 var(--s-7) 0',
    maxWidth: 780, lineHeight: 1.5,
  },
  cards: { display: 'flex', flexDirection: 'column', gap: 'var(--s-7)' },
  card: {
    background: 'var(--bg-1)',
    border: 'var(--border-hair)',
    borderRadius: 'var(--r-3)',
    overflow: 'hidden',
  },
  cardHead: {
    padding: 'var(--s-6) var(--s-6) var(--s-5) var(--s-6)',
    display: 'grid', gridTemplateColumns: '60px 1fr', gap: 'var(--s-5)',
    alignItems: 'start',
    borderBottom: 'var(--border-hair)',
  },
  idx: { fontSize: 13, color: 'var(--fg-3)', letterSpacing: '0.08em', fontVariantNumeric: 'tabular-nums', paddingTop: 4 },
  headMid: { display: 'flex', flexDirection: 'column', gap: 8 },
  eyebrow: { fontSize: 11, fontWeight: 500, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'var(--fg-3)' },
  headline: { fontSize: 'clamp(20px, 2vw, 28px)', fontWeight: 500, letterSpacing: '-0.01em', lineHeight: 1.2, color: 'var(--fg-1)', margin: 0 },
  context: { fontSize: 14, color: 'var(--fg-2)', margin: 0 },
  chips: { display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 6 },
  stackChip: {
    fontSize: 12, padding: '4px 10px', border: '1px solid var(--line-1)',
    borderRadius: 'var(--r-pill)', color: 'var(--fg-2)', background: 'var(--bg-1)',
    whiteSpace: 'nowrap',
  },
  body: {
    padding: 'var(--s-6)',
    display: 'flex', flexDirection: 'column', gap: 'var(--s-7)',
  },
  diagramRow: {
    width: '100%', maxWidth: 760, margin: '0 auto',
    display: 'flex', flexDirection: 'column',
  },
  textGrid: {
    display: 'grid', gridTemplateColumns: '1fr 1fr',
    gap: 'var(--s-7) var(--s-8)',
    borderTop: 'var(--border-hair)',
    paddingTop: 'var(--s-6)',
  },
  textCol: { display: 'flex', flexDirection: 'column', gap: 'var(--s-5)' },
  block: { display: 'flex', flexDirection: 'column', gap: 8 },
  blockLabel: { fontSize: 11, fontWeight: 500, letterSpacing: '0.12em', textTransform: 'uppercase', color: 'var(--fg-3)' },
  blockBody: { fontSize: 14.5, lineHeight: 1.55, color: 'var(--fg-1)', margin: 0 },
  diagramSide: { display: 'flex', flexDirection: 'column' },
  legend: { marginTop: 'var(--s-3)', fontSize: 12, color: 'var(--fg-3)', lineHeight: 1.5, textAlign: 'center' },

  // ---- four-up grid ---------------------------------------------------
  fourUpGrid: {
    display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)',
    gap: 'var(--s-6) var(--s-7)',
    borderTop: 'var(--border-hair)',
    paddingTop: 'var(--s-6)',
  },
  // ---- side-by-side ---------------------------------------------------
  sideBody: {
    padding: 'var(--s-6)',
    display: 'grid', gridTemplateColumns: '6fr 5fr', gap: 'var(--s-7)',
  },
  sideText: { display: 'flex', flexDirection: 'column', gap: 'var(--s-5)' },
  sideDiagram: { position: 'relative' },

  // ---- collapsed expand button ---------------------------------------
  expandBtn: {
    display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 8,
    alignSelf: 'flex-start',
    padding: '10px 16px', borderRadius: 'var(--r-1)',
    background: 'var(--latus-dark-blue)', border: '1px solid var(--latus-dark-blue)',
    fontSize: 13, fontWeight: 500, letterSpacing: '0.02em',
    color: 'var(--latus-off-white)', cursor: 'pointer',
    transition: 'background var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out)',
  },
  statusRow: {
    display: 'flex', alignItems: 'center', gap: 8,
    fontSize: 11, fontWeight: 500, letterSpacing: '0.12em', textTransform: 'uppercase',
  },
  statusDot: { width: 8, height: 8, borderRadius: 999, background: 'var(--success)', display: 'inline-block' },
  statusDotWip: { background: 'var(--latus-indigo)' },
};

function CaseCard({ idx, eyebrow, headline, context, stack, status, problem, system, calls, shipped, Diagram, legend, layout = 'default' }) {
  const [replayKey, setReplayKey] = useState(0);
  const [expanded, setExpanded] = useState(false);
  const replay = () => setReplayKey(k => k + 1);

  // ---- Reusable block components --------------------------------------
  const Problem  = (
    <div style={workStyles.block}>
      <span style={workStyles.blockLabel}>The problem</span>
      <p style={workStyles.blockBody}>{problem}</p>
    </div>
  );
  const SystemBlock = (
    <div style={workStyles.block}>
      <span style={workStyles.blockLabel}>The system</span>
      <p style={workStyles.blockBody}>{system}</p>
    </div>
  );
  const CallsBlock = (
    <div style={workStyles.block}>
      <span style={workStyles.blockLabel}>Key calls I made</span>
      <ul style={{ margin: 0, padding: 0, listStyle: 'none', display: 'flex', flexDirection: 'column', gap: 8 }}>
        {calls.map((d, i) => (
          <li key={i} style={{ position: 'relative', paddingLeft: 18, fontSize: 14.5, lineHeight: 1.55, color: 'var(--fg-1)' }}>
            <span style={{ position: 'absolute', left: 0, top: '0.65em', width: 8, height: 1, background: 'var(--latus-dark-blue)' }}/>
            {d}
          </li>
        ))}
      </ul>
    </div>
  );
  const ShippedBlock = (
    <div style={workStyles.block}>
      <span style={workStyles.blockLabel}>{status === 'shipped' ? 'What shipped' : 'Status'}</span>
      <p style={workStyles.blockBody}>{shipped}</p>
    </div>
  );
  const DiagramRow = (
    <div style={workStyles.diagramRow}>
      <Diagram replayKey={replayKey} replay={replay}/>
      {legend && <div style={workStyles.legend}>{legend}</div>}
    </div>
  );

  return (
    <article style={workStyles.card} className="case-card reveal">
      <div style={workStyles.cardHead} className="case-head">
        <span style={workStyles.idx}>{idx}</span>
        <div style={workStyles.headMid}>
          <span style={workStyles.eyebrow}>{eyebrow}</span>
          <h3 style={workStyles.headline}>{headline}</h3>
          <p style={workStyles.context}>{context}</p>
          <div style={workStyles.chips}>
            {stack.map(s => <span key={s} style={workStyles.stackChip}>{s}</span>)}
          </div>
        </div>
      </div>

      {/* ---- LAYOUT: default · diagram top, 2-col text below -------------- */}
      {layout === 'default' && (
        <div style={workStyles.body} className="case-body">
          {DiagramRow}
          <div style={workStyles.textGrid} className="case-text-grid">
            <div style={workStyles.textCol}>{Problem}{SystemBlock}</div>
            <div style={workStyles.textCol}>{CallsBlock}{ShippedBlock}</div>
          </div>
        </div>
      )}

      {/* ---- LAYOUT: four-up · diagram top, 4-col text below ------------- */}
      {layout === 'four-up' && (
        <div style={workStyles.body} className="case-body">
          {DiagramRow}
          <div style={workStyles.fourUpGrid} className="case-fourup-grid">
            {Problem}{SystemBlock}{CallsBlock}{ShippedBlock}
          </div>
        </div>
      )}

      {/* ---- LAYOUT: side · text left, diagram right sticky -------------- */}
      {layout === 'side' && (
        <div style={workStyles.sideBody} className="case-body case-side-body">
          <div style={workStyles.sideText}>
            {Problem}{SystemBlock}{CallsBlock}{ShippedBlock}
          </div>
          <div style={workStyles.sideDiagram}>
            <div style={{ position: 'sticky', top: 80 }}>
              <Diagram replayKey={replayKey} replay={replay}/>
              {legend && <div style={{ ...workStyles.legend, textAlign: 'left' }}>{legend}</div>}
            </div>
          </div>
        </div>
      )}

      {/* ---- LAYOUT: collapsed · click to expand ------------------------- */}
      {layout === 'collapsed' && (
        <>
          <div style={workStyles.body} className="case-body">
            {DiagramRow}
            <button
              onClick={() => setExpanded(e => !e)}
              style={expanded
                ? { ...workStyles.expandBtn, background: 'transparent', color: 'var(--latus-dark-blue)', border: '1px solid var(--latus-dark-blue)' }
                : workStyles.expandBtn}
              className="no-print"
              onMouseEnter={(e) => {
                if (expanded) {
                  e.currentTarget.style.background = 'var(--bg-2)';
                } else {
                  e.currentTarget.style.background = '#01153D';
                }
              }}
              onMouseLeave={(e) => {
                if (expanded) {
                  e.currentTarget.style.background = 'transparent';
                } else {
                  e.currentTarget.style.background = 'var(--latus-dark-blue)';
                }
              }}
            >
              <span>{expanded ? 'Hide deep-dive' : 'Read the deep-dive'}</span>
              <span style={{
                display: 'inline-flex', alignItems: 'center',
                transform: expanded ? 'rotate(180deg)' : 'rotate(0deg)',
                transition: 'transform var(--dur-base) var(--ease-out)',
              }}>
                <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
                  <path d="M4 6l4 4 4-4"/>
                </svg>
              </span>
            </button>
          </div>
          {expanded && (
            <div style={{ ...workStyles.body, paddingTop: 0 }} className="case-body">
              <div style={workStyles.textGrid} className="case-text-grid">
                <div style={workStyles.textCol}>{Problem}{SystemBlock}</div>
                <div style={workStyles.textCol}>{CallsBlock}{ShippedBlock}</div>
              </div>
            </div>
          )}
        </>
      )}
    </article>
  );
}

const CARDS = [
  {
    idx: '01',
    eyebrow: 'Het Kennisinstituut · AI search',
    headline: 'AI search across 11,000+ clinical guidelines, clinician-facing.',
    context: 'Dutch medical association · ~400K monthly website users · 70k searches/month atm',
    stack: ['Azure', 'Azure AI Search', 'RAG', 'Python', 'FastAPI', 'Langfuse'],
    status: 'in-build',
    problem: 'The website\'s existing keyword search couldn\'t reliably surface the right clinical guideline, even at ~70k searches a month. Wrong answers in this context are a safety issue, so retrieval has to be accurate, auditable, and safe with personal data.',
    system: 'Azure AI Search handles the vector index and retrieval. Documents are indexed with versioning. Clinician queries hit the search API, which strips personal data before the query reaches the index — any PII a clinician might inadvertently type never gets searched against. Built for the clinicians using the website across 11,000+ published documents.',
    calls: [
      'Query-side sanitisation in the API layer: PII a clinician might inadvertently type gets stripped before the query reaches the search index.',
      'Azure AI Search rather than a custom vector DB. Inherits the organisation\'s existing compliance posture and keeps operational overhead low.',
      'Clear ownership boundary: the platform-managed components stay platform-managed; everything else is observable and owned.',
    ],
    shipped: 'Architected and currently being built. Replacing the website\'s existing keyword search; volume expected to grow once the new system is live.',
    Diagram: DiagramMedicalSearch,
    legend: 'Clinician queries hit the API, which sanitises them before the query reaches the search index. Documents are indexed as-is.',
  },
  {
    idx: '02',
    eyebrow: 'Het Kennisinstituut · Data warehouse',
    headline: 'Answers in seconds instead of weeks, with new use cases shipping monthly.',
    context: 'Dutch medical association · administrative and project-management staff · ad-hoc inquiries and stakeholder reporting',
    stack: ['GCP', 'BigQuery', 'Cloud Functions', 'Cloud Scheduler', 'Python', 'ETL/ELT'],
    status: 'shipped',
    problem: 'Ad-hoc data inquiries used to queue behind an analyst. Turnaround was weeks, and only a handful of requests got handled at a time.',
    system: 'End-to-end ingestion pipeline pulling structured and unstructured data from APIs and scraped sources, combining them with quality monitoring at each stage. New use cases get scoped, codified into the pipeline, and deployed. Once a use case is live, an analyst triggers it on demand: the system runs the analysis and ships the report, no engineering work in the loop. New use cases ship monthly; existing ones can be triggered concurrently.',
    calls: [
      'Cloud Functions plus Cloud Scheduler rather than a full orchestration platform. Fewer moving parts, less operational overhead.',
      'Quality monitoring built into the ingestion pipeline, not bolted on later.',
      'Productized, deployable use cases that run on demand, rather than one-off custom dashboards, so the system keeps growing without me on every query.',
      'Combined structured and unstructured data into a single warehouse: three sources today, more being added. Eliminates the "which dataset do I trust?" question.',
    ],
    shipped: 'Production system in daily use by administrative and project-management staff. Powers internal and external stakeholder reporting. Where turnaround used to be weeks for a handful of queries, the system now serves many use cases concurrently and returns answers in seconds.',
    Diagram: DiagramDataWarehouse,
    legend: 'Two source types merge into one warehouse; analysts trigger pre-built use cases on demand.',
  },
  {
    idx: '03',
    eyebrow: 'Fintech · Mortgage reports',
    headline: 'Messy scans in, validated structured data out. Hands-off.',
    context: 'Production API for processing yearly financial reports on mortgage applications',
    stack: ['Azure OCR', 'Azure AI Document Intelligence', 'LLMs', 'PydanticAI', 'FastAPI', 'Python'],
    status: 'shipped',
    problem: 'OCR alone doesn\'t survive real-world financial documents: skew, noise, multi-column layouts. Naive LLM extraction silently hallucinates fields. The system has to know when it\'s wrong.',
    system: 'Documents are classified by report type first, then routed to type-specific extraction templates — different report types have different fields. The pipeline combines Azure Document Intelligence (OCR and field-level extraction), LLM classification, cross-field validation, and structured output. Edge cases are handled autonomously; low-confidence reads get flagged for review instead of guessed.',
    calls: [
      'Document type classified first; each report type routes to its own extraction template (different reports have different fields).',
      'Cross-field validation as a hard gate (line items must reconcile to the total).',
      'Structured output via PydanticAI rather than free-form JSON parsing.',
      'The system retries ambiguous cases automatically instead of failing the request.',
    ],
    shipped: 'Production API handling messy scans without human pre-processing.',
    Diagram: DiagramFinancialDocs,
    legend: 'Validation is the diamond hard-gate. Low-confidence routes to human review, not a guess.',
  },
  {
    idx: '04',
    eyebrow: 'Social services · Contract analysis',
    headline: 'Caseworker-facing tool that reads the contracts and tells you who\'s on the hook, with citations to back it up.',
    context: '100% accuracy (model + business rules)',
    stack: ['GCP', 'Google Document AI', 'Claude Agent SDK', 'AI agents', 'FastAPI', 'React', 'Vite'],
    status: 'shipped',
    problem: 'Caseworkers had to parse dense contracts to determine which provider was responsible for which services. Get it wrong, and the wrong provider gets called: services get delivered against the wrong contract, and the organisation pays for things it shouldn\'t have authorised. The system has to deliver a defensible answer, not just retrieved passages.',
    system: 'An OCR data pipeline on GCP turns contract documents into text, feeding a retrieval-then-reasoning model behind a caseworker-facing UI. Retrieval pulls relevant clauses; the reasoning step analyses terms and assigns responsibility, with citations chained back to source clauses on every claim. Business rules sit on top of the model output, catching the failure modes the model still gets wrong. A separate chat-to-docs tool sits alongside the AI assistant: caseworkers can query the source documents directly and see the underlying text returned, without any AI interpretation layered on top.',
    calls: [
      'Citations to source clauses on every assertion. No clause, no claim.',
      'Separation of retrieval and reasoning steps so each can be inspected independently.',
      'Business rules layered over the model output to catch known failure modes, taking accuracy from 96% (model alone) to 100% (model + rules).',
      'Chat-to-docs verification tool alongside the AI assistant: caseworkers can query the source documents directly and see raw retrieved text, no AI interpretation. For when they want to verify a claim against the contracts themselves.',
      'Caseworker-facing UI built with React and Vite. Standalone for now, shipped independently rather than blocked on integration into existing systems.',
    ],
    shipped: 'Production system in caseworker hands. The model alone hits 96% on responsibility allocation; with business rules layered on top, the system reaches 100% accuracy. Fewer wrong-provider calls means fewer mis-attributed services, and less paying for services that weren\'t the organisation\'s to pay for.',
    Diagram: DiagramLegalCase,
    legend: 'Retrieval and reasoning are deliberately separate; chat-to-docs is the side path with no AI on top.',
  },
  {
    idx: '05',
    eyebrow: 'ABN AMRO · Anomaly detection',
    headline: '8,000 anomaly-detection models running in parallel, watching for broken nodes in chatbot Anna.',
    context: 'Production at scale',
    stack: ['Databricks', 'PySpark', 'Prophet', 'MLOps', 'Python', 'Power BI'],
    status: 'shipped',
    problem: 'Chatbot Anna\'s routing tree had 8,000 nodes handling customer conversations. When a node started misbehaving (drift in routing logic, downstream service breakage), nobody noticed until users complained. The system needed automated detection that didn\'t depend on a human reading every node\'s traffic.',
    system: 'A model per node, 8,000 of them, each a time-series anomaly detector built on Facebook\'s Prophet. A PySpark pipeline trains, refreshes, and runs all of them in parallel on Databricks. Anomalies surface on a Power BI dashboard for the ops team to triage.',
    calls: [
      'One model per node rather than one global model. A one-size-fits-all detector would miss node-specific drift.',
      'Prophet (open-source, interpretable, battle-tested) rather than a custom anomaly-detection model.',
      'PySpark on Databricks for the parallelism; 8,000 model runs would never fit on a single machine.',
      'Power BI dashboard rather than a custom UI, because the ops team already lived in Power BI.',
    ],
    shipped: 'Production anomaly-detection running across all 8,000 nodes. Broken nodes get flagged automatically instead of via user complaints, with the ops team triaging from a Power BI dashboard they already used.',
    Diagram: DiagramClassicalML,
    legend: 'One model per node, PySpark fans the work out across the cluster, anomalies route to the existing dashboard.',
  },
];

function FeaturedWork({ layout = 'default' }) {
  return (
    <section id="work" className="section-pad" style={workStyles.section}>
      <div className="container">
        <SectionHead index="03 / Featured work" title="Five product builds, end-to-end."/>
        <p style={workStyles.intro} className="reveal">
          A handful of product builds I've led end-to-end. Each card opens into the architecture, the key calls I made, and where the work stands.
        </p>
        <div style={workStyles.cards}>
          {CARDS.map(c => <CaseCard key={c.idx} {...c} layout={layout}/>)}
        </div>
      </div>

      <style>{`
        @media (max-width: 1100px) {
          .case-head { grid-template-columns: 40px 1fr !important; gap: var(--s-4) !important; }
        }
        @media (max-width: 900px) {
          .case-side-body { grid-template-columns: 1fr !important; }
          .case-fourup-grid { grid-template-columns: repeat(2, 1fr) !important; }
        }
        @media (max-width: 760px) {
          .case-text-grid { grid-template-columns: 1fr !important; gap: var(--s-6) !important; }
          .case-fourup-grid { grid-template-columns: 1fr !important; }
        }
      `}</style>
    </section>
  );
}

window.FeaturedWork = FeaturedWork;
