
// sulo-explorer.jsx — Interactive Postcard Explorer + AI Text-to-KG Demo

// Nodes positioned to match the official SULO postcard layout
const GRAPH_NODES = [
  { id: 'owl:Thing',         x:  70, y: 250, color: '#64748B', r: 30, def: 'The top-level OWL class. Everything in SULO is a subclass of owl:Thing.', examples: ['Any entity'] },
  { id: 'Process',           x: 220, y: 110, color: '#F59E0B', r: 32, def: 'Something that unfolds over time, with participants. Subclass of owl:Thing.', examples: ['Diagnosis', 'Drug administration', 'Baking', 'Surgery'] },
  { id: 'Object',            x: 220, y: 310, color: '#3B82F6', r: 32, def: 'Any physical or non-physical thing that exists independently. Subclass of owl:Thing.', examples: ['Patient', 'Drug', 'Pizza', 'Gene'] },
  { id: 'SpatialObject',     x: 355, y: 330, color: '#60A5FA', r: 22, def: 'An object that occupies space. Subclass of Object.', examples: ['Organ', 'Building', 'Region'] },
  { id: 'Feature',           x: 460, y: 200, color: '#10B981', r: 28, def: 'An intrinsic, contextual, or attributed characteristic. Subclass of owl:Thing.', examples: ['Severity', 'Color', 'Blood pressure'] },
  { id: 'Role',              x: 580, y: 100, color: '#34D399', r: 22, def: 'A contextual function played by an object in a setting. Subclass of Feature.', examples: ['Patient role', 'Prescriber role', 'Ingredient role'] },
  { id: 'Quality',           x: 595, y: 185, color: '#6EE7B7', r: 22, def: 'An intrinsic property of an object or process. Subclass of Feature.', examples: ['Color', 'Mass', 'Charge', 'Shape'] },
  { id: 'Capability',        x: 580, y: 270, color: '#A7F3D0', r: 22, def: 'A disposition or power of an object. Subclass of Feature.', examples: ['Solubility', 'Drug resistance', 'Toxicity'] },
  { id: 'InformationObject', x: 700, y: 185, color: '#06B6D4', r: 26, def: 'A representational entity — data, codes, documents, identifiers. Has refersTo relation to owl:Thing.', examples: ['ICD-10 code', 'FHIR record', 'Lab report', 'Identifier'] },
  { id: 'Collection',        x: 440, y: 380, color: '#8B5CF6', r: 24, def: 'A group of things. Uses hasItem relation. Subclass of owl:Thing.', examples: ['Patient cohort', 'Dataset', 'Ingredient list'] },
  { id: 'Quantity',          x: 610, y: 370, color: '#C4B5FD', r: 22, def: 'A measurable amount with value and unit. Subclass of owl:Thing.', examples: ['500 mg', '37 °C', '30 cm', 'pH 7.4'] },
  { id: 'Time',              x: 780, y: 260, color: '#94A3B8', r: 24, def: 'A temporal entity. Includes Duration, TimeInterval, TimeInstant (with StartTime, EndTime).', examples: ['Date of birth', 'Diagnosis date', 'Duration of treatment'] },
];

const GRAPH_EDGES = [
  // Core semantic relations (solid)
  { from: 'Process',           to: 'Object',      label: 'hasParticipant', color: '#3B82F6', dashed: false, curve: -30 },
  { from: 'Object',            to: 'Feature',     label: 'hasFeature',     color: '#10B981', dashed: false, curve: -20 },
  { from: 'Process',           to: 'Feature',     label: 'hasFeature',     color: '#10B981', dashed: false, curve:  20 },
  { from: 'Process',           to: 'Time',        label: 'atTime',         color: '#F59E0B', dashed: false, curve: -20 },
  { from: 'Collection',        to: 'owl:Thing',   label: 'hasItem',        color: '#8B5CF6', dashed: false, curve:  40 },
  { from: 'InformationObject', to: 'owl:Thing',   label: 'refersTo',       color: '#06B6D4', dashed: false, curve: -70 },
  // hasPart self-loops
  { from: 'Process', to: 'Process', label: 'hasPart',   color: '#F59E0B', dashed: true,  self: true, selfOffset: 0 },
  { from: 'Object',  to: 'Object',  label: 'hasPart',   color: '#3B82F6', dashed: true,  self: true, selfOffset: 0 },
  // precedes self-loop (offset so it doesn't overlap hasPart)
  { from: 'Process', to: 'Process', label: 'precedes',  color: '#34D399', dashed: false, self: true, selfOffset: 1 },
  // subClassOf hierarchy (dotted gray)
  { from: 'Process',       to: 'owl:Thing', label: 'subClassOf', color: '#334155', dashed: true, curve:  30 },
  { from: 'Object',        to: 'owl:Thing', label: 'subClassOf', color: '#334155', dashed: true, curve: -30 },
  { from: 'Feature',       to: 'owl:Thing', label: 'subClassOf', color: '#334155', dashed: true, curve:   0 },
  { from: 'SpatialObject', to: 'Object',    label: 'subClassOf', color: '#334155', dashed: true, curve:   0 },
  { from: 'Role',          to: 'Feature',   label: 'subClassOf', color: '#334155', dashed: true, curve:   0 },
  { from: 'Quality',       to: 'Feature',   label: 'subClassOf', color: '#334155', dashed: true, curve:   0 },
  { from: 'Capability',    to: 'Feature',   label: 'subClassOf', color: '#334155', dashed: true, curve:   0 },
];

function getNode(id) { return GRAPH_NODES.find(n => n.id === id); }

function arrowPath(x1, y1, x2, y2, r2, curve) {
  const dx = x2 - x1, dy = y2 - y1;
  const dist = Math.sqrt(dx * dx + dy * dy);
  const ux = dx / dist, uy = dy / dist;
  const ex = x2 - ux * (r2 + 8), ey = y2 - uy * (r2 + 8);
  if (curve === 0) {
    return `M ${x1} ${y1} L ${ex} ${ey}`;
  }
  const mx = (x1 + x2) / 2 - uy * curve, my = (y1 + y2) / 2 + ux * curve;
  return `M ${x1} ${y1} Q ${mx} ${my} ${ex} ${ey}`;
}

function selfArrow(x, y, r, offset = 0) {
  // offset=0 → loops upper-right (hasPart); offset=1 → loops upper-left (precedes)
  if (offset === 0) {
    return `M ${x + r} ${y - 4} C ${x + r + 48} ${y - 48} ${x + 4} ${y - 58} ${x - 2} ${y - r - 5}`;
  }
  return `M ${x - r + 4} ${y - 8} C ${x - r - 44} ${y - 50} ${x - 8} ${y - 62} ${x + r - 4} ${y - 10}`;
}

function midpoint(x1, y1, x2, y2, curve) {
  if (curve === 0) return [(x1 + x2) / 2, (y1 + y2) / 2];
  const dx = x2 - x1, dy = y2 - y1;
  const dist = Math.sqrt(dx * dx + dy * dy);
  const ux = dx / dist, uy = dy / dist;
  const mx = (x1 + x2) / 2 - uy * curve, my = (y1 + y2) / 2 + ux * curve;
  return [mx * 0.5 + (x1 + x2) * 0.25, my * 0.5 + (y1 + y2) * 0.25];
}

const PostcardExplorer = () => {
  const [selected, setSelected] = React.useState(null);
  const [hoverEdge, setHoverEdge] = React.useState(null);
  const [edgeDetail, setEdgeDetail] = React.useState(null);

  const selNode = GRAPH_NODES.find(n => n.id === selected);

  const handleNodeClick = (id) => {
    setEdgeDetail(null);
    setSelected(selected === id ? null : id);
  };
  const handleEdgeClick = (edge) => {
    setSelected(null);
    setEdgeDetail(edgeDetail === edge ? null : edge);
  };

  return (
    <section id="explore" style={{ padding: '120px 0', background: 'rgba(8,12,26,0.6)' }}>
      <div style={{ maxWidth: 1160, margin: '0 auto', padding: '0 clamp(1rem,5vw,3rem)' }}>
        <div style={{ marginBottom: 16 }}>
          <span style={{ fontFamily: 'Space Mono, monospace', fontSize: 11, color: '#00C9A7', letterSpacing: '0.1em', textTransform: 'uppercase' }}>Interactive Postcard</span>
        </div>
        <h2 style={{ fontFamily: 'Space Grotesk, sans-serif', fontWeight: 800, fontSize: 'clamp(2rem,4vw,3rem)', color: '#E4EAF5', letterSpacing: '-0.03em', margin: '0 0 12px' }}>Explore SULO visually.</h2>
        <p style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 16, color: '#6B7FA3', margin: '0 0 40px' }}>Click any class or relation to see its definition and usage.</p>

        <div style={{ display: 'grid', gridTemplateColumns: '1fr auto', gap: 24, alignItems: 'start' }}>
          {/* SVG Graph */}
          <div style={{ background: 'rgba(10,15,30,0.9)', border: '1px solid rgba(255,255,255,0.07)', borderRadius: 16, overflow: 'hidden', position: 'relative' }}>
            <svg viewBox="0 0 880 460" style={{ width: '100%', height: 'auto', display: 'block' }}>
              <defs>
                {['#3B82F6','#10B981','#F59E0B','#8B5CF6','#06B6D4','#34D399','#334155','#94A3B8','#C4B5FD','#64748B'].map(c => (
                  <marker key={c} id={`arrow-c-${c.replace('#','')}`} markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto">
                    <path d="M0,0 L0,6 L8,3 z" fill={c} />
                  </marker>
                ))}
              </defs>

              {/* Grid dots */}
              {Array.from({length: 20}, (_,i) => Array.from({length: 13}, (_,j) => (
                <circle key={`${i}-${j}`} cx={i * 46 + 10} cy={j * 42 + 10} r="1" fill="rgba(255,255,255,0.04)" />
              )))}

              {/* Edges */}
              {GRAPH_EDGES.map((edge, i) => {
                if (edge.self) {
                  const n = getNode(edge.from);
                  if (!n) return null;
                  const isHov = hoverEdge === i;
                  const d = selfArrow(n.x, n.y, n.r, edge.selfOffset || 0);
                  return (
                    <g key={i}>
                      <path d={d} fill="none" stroke="transparent" strokeWidth={12} style={{ cursor: 'pointer' }} onClick={() => handleEdgeClick(edge)} />
                      <path d={d} fill="none" stroke={isHov ? edge.color : edge.color + '60'} strokeWidth={isHov ? 2 : 1.5} strokeDasharray={edge.dashed ? '5,3' : 'none'}
                        markerEnd={`url(#arrow-c-${edge.color.replace('#','')})`} style={{ cursor: 'pointer', transition: 'stroke 0.2s' }}
                        onMouseEnter={() => setHoverEdge(i)} onMouseLeave={() => setHoverEdge(null)} onClick={() => handleEdgeClick(edge)} />
                    </g>
                  );
                }
                const a = getNode(edge.from), b = getNode(edge.to);
                if (!a || !b) return null;
                const isHov = hoverEdge === i;
                const path = arrowPath(a.x, a.y, b.x, b.y, b.r, edge.curve || 0);
                const [lx, ly] = midpoint(a.x, a.y, b.x, b.y, edge.curve || 0);
                return (
                  <g key={i}>
                    <path d={path} fill="none" stroke="transparent" strokeWidth={14} style={{ cursor: 'pointer' }}
                      onMouseEnter={() => setHoverEdge(i)} onMouseLeave={() => setHoverEdge(null)} onClick={() => handleEdgeClick(edge)} />
                    <path d={path} fill="none"
                      stroke={isHov ? edge.color : edge.color + '55'}
                      strokeWidth={isHov ? 2.5 : 1.5}
                      strokeDasharray={edge.dashed ? '5,4' : 'none'}
                      markerEnd={`url(#arrow-c-${edge.color.replace('#','')})`}
                      style={{ cursor: 'pointer', transition: 'all 0.2s', pointerEvents: 'none' }} />
                    {isHov && (
                      <text x={lx} y={ly - 6} textAnchor="middle" fontFamily="Space Mono, monospace" fontSize="10" fill={edge.color}>
                        {edge.label}
                      </text>
                    )}
                  </g>
                );
              })}

              {/* Nodes */}
              {GRAPH_NODES.map(n => {
                const isSel = selected === n.id;
                const isDim = selected && !isSel;
                return (
                  <g key={n.id} style={{ cursor: 'pointer' }} onClick={() => handleNodeClick(n.id)}>
                    {/* glow */}
                    <circle cx={n.x} cy={n.y} r={n.r + 16} fill={n.color + '12'} />
                    {isSel && <circle cx={n.x} cy={n.y} r={n.r + 6} fill="none" stroke={n.color} strokeWidth={1.5} strokeDasharray="4,3" />}
                    {/* main circle */}
                    <circle cx={n.x} cy={n.y} r={n.r}
                      fill={isSel ? n.color + '30' : isDim ? '#0A0F1E' : n.color + '20'}
                      stroke={isSel ? n.color : isDim ? n.color + '25' : n.color + '80'}
                      strokeWidth={isSel ? 2.5 : 1.5}
                      style={{ transition: 'all 0.2s' }} />
                    {/* label */}
                    {(() => {
                      const words = n.id === 'owl:Thing' ? ['owl:', 'Thing'] :
                                    n.id === 'SpatialObject' ? ['Spatial', 'Object'] :
                                    n.id === 'InformationObject' ? ['Information', 'Object'] :
                                    [n.id];
                      const fsize = n.r < 24 ? '9' : '11';
                      return words.length === 1
                        ? <text x={n.x} y={n.y + 4} textAnchor="middle" fontFamily="Space Grotesk, sans-serif" fontWeight="700" fontSize={fsize} fill={isSel ? n.color : isDim ? n.color + '35' : n.color} style={{ transition: 'fill 0.2s', userSelect: 'none', pointerEvents: 'none' }}>{words[0]}</text>
                        : <text x={n.x} y={n.y} textAnchor="middle" fontFamily="Space Grotesk, sans-serif" fontWeight="700" fontSize={fsize} fill={isSel ? n.color : isDim ? n.color + '35' : n.color} style={{ transition: 'fill 0.2s', userSelect: 'none', pointerEvents: 'none' }}>
                            <tspan x={n.x} dy="-5">{words[0]}</tspan>
                            <tspan x={n.x} dy="13">{words[1]}</tspan>
                          </text>;
                    })()}
                  </g>
                );
              })}

              {/* Legend */}
              <g>
                <text x={16} y={502} fontFamily="Space Mono, monospace" fontSize="9" fill="rgba(255,255,255,0.25)">— solid: domain/range connection</text>
                <text x={240} y={502} fontFamily="Space Mono, monospace" fontSize="9" fill="rgba(255,255,255,0.25)">- - - dotted: subClassOf</text>
              </g>
            </svg>
          </div>

          {/* Detail panel */}
          <div style={{ width: 240, minHeight: 200, background: 'rgba(10,15,30,0.9)', border: '1px solid rgba(255,255,255,0.08)', borderRadius: 14, padding: '24px 20px', transition: 'all 0.2s' }}>
            {selNode ? (
              <div>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 14 }}>
                  <div style={{ width: 10, height: 10, borderRadius: '50%', background: selNode.color, boxShadow: `0 0 10px ${selNode.color}` }} />
                  <span style={{ fontFamily: 'Space Mono, monospace', fontSize: 13, color: selNode.color, fontWeight: 700 }}>{selNode.id}</span>
                </div>
                <p style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 13, color: '#8A96B3', lineHeight: 1.65, margin: '0 0 16px' }}>{selNode.def}</p>
                <div style={{ fontFamily: 'Space Mono, monospace', fontSize: 10, color: selNode.color, marginBottom: 8 }}>EXAMPLES</div>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
                  {selNode.examples.map(ex => (
                    <span key={ex} style={{ fontFamily: 'Space Mono, monospace', fontSize: 11, color: '#6B7FA3', background: 'rgba(255,255,255,0.04)', padding: '4px 10px', borderRadius: 5, border: `1px solid ${selNode.color}20` }}>{ex}</span>
                  ))}
                </div>
              </div>
            ) : edgeDetail ? (
              <div>
                <div style={{ fontFamily: 'Space Mono, monospace', fontSize: 12, color: edgeDetail.color, marginBottom: 10, fontWeight: 700 }}>{edgeDetail.label}</div>
                <div style={{ fontFamily: 'Space Mono, monospace', fontSize: 10, color: '#3A4560', marginBottom: 6 }}>Domain</div>
                <div style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 13, color: '#8A96B3', marginBottom: 12 }}>{edgeDetail.from}</div>
                <div style={{ fontFamily: 'Space Mono, monospace', fontSize: 10, color: '#3A4560', marginBottom: 6 }}>Range</div>
                <div style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 13, color: '#8A96B3' }}>{edgeDetail.to}</div>
              </div>
            ) : (
              <div style={{ textAlign: 'center', paddingTop: 40 }}>
                <div style={{ fontSize: 32, marginBottom: 12, opacity: 0.3 }}>◉</div>
                <div style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 13, color: '#3A4560', lineHeight: 1.6 }}>Click a class or relation to explore its definition</div>
              </div>
            )}
          </div>
        </div>
      </div>
    </section>
  );
};

// ---- AI TEXT-TO-KG DEMO ----
const EXAMPLE_PROMPTS = [
  "Maria, a 45-year-old patient, received 500mg amoxicillin to treat a bacterial infection diagnosed on March 15, 2024.",
  "A Margherita pizza has a 30cm diameter crust topped with tomato sauce and mozzarella. It was baked at 220°C.",
  "The clinical trial enrolled 120 patients with Type 2 diabetes. Each participant was assigned either metformin or a placebo.",
];

function buildKGPrompt(text) {
  return `You are a SULO ontology expert. Given this text, extract a knowledge graph using ONLY these SULO classes and properties.

SULO Classes: Object, Process, Feature, Role, Quantity, Collection, InformationObject, TimeInstant
SULO Properties: hasParticipant (Process→Object), hasFeature (Object|Process→Feature), hasPart (Object→Object), hasMember (Collection→Thing), atTime (Process→TimeInstant)

Text: "${text}"

Return ONLY valid JSON (no markdown, no explanation) in this exact format:
{
  "nodes": [{"id": "short_id", "label": "Human label", "type": "SULOClass"}],
  "edges": [{"from": "id1", "to": "id2", "label": "propertyName"}],
  "turtle": "# short turtle snippet showing 2-3 key triples"
}

Rules:
- Use 4-8 nodes maximum  
- id must be a short lowercase_underscore string
- type must be one of the 8 SULO classes exactly
- Keep labels short (2-4 words)
- Include only the most important triples`;
}

const TYPE_COLORS = {
  Object: '#3B82F6', Process: '#F59E0B', Feature: '#10B981',
  Role: '#34D399', Quantity: '#6EE7B7', Collection: '#8B5CF6',
  InformationObject: '#06B6D4', TimeInstant: '#94A3B8'
};

function layoutNodes(nodes) {
  const W = 560, H = 300;
  const cx = W / 2, cy = H / 2;
  const positions = {};
  const processes = nodes.filter(n => n.type === 'Process');
  const others = nodes.filter(n => n.type !== 'Process');

  processes.forEach((n, i) => {
    positions[n.id] = { x: cx - (processes.length - 1) * 70 + i * 140, y: cy - 40 };
  });
  others.forEach((n, i) => {
    const angle = (i / others.length) * 2 * Math.PI - Math.PI / 2;
    const rx = 200, ry = 100;
    positions[n.id] = { x: cx + rx * Math.cos(angle), y: cy + 60 + ry * Math.sin(angle) };
  });
  return positions;
}

const AIDemo = () => {
  const [input, setInput] = React.useState(EXAMPLE_PROMPTS[0]);
  const [loading, setLoading] = React.useState(false);
  const [result, setResult] = React.useState(null);
  const [error, setError] = React.useState(null);
  const [view, setView] = React.useState('graph');
  const [selNode, setSelNode] = React.useState(null);

  const run = async () => {
    setLoading(true); setError(null); setResult(null); setSelNode(null);
    try {
      const raw = await window.claude.complete(buildKGPrompt(input));
      const json = JSON.parse(raw.trim().replace(/^```json?\n?/, '').replace(/```$/, ''));
      setResult(json);
    } catch (e) {
      setError('Could not parse the response. Please try again.');
      console.error(e);
    }
    setLoading(false);
  };

  const positions = result ? layoutNodes(result.nodes) : {};

  return (
    <section id="demo" style={{ padding: '120px 0', background: 'rgba(8,12,26,0.8)' }}>
      <div style={{ maxWidth: 1160, margin: '0 auto', padding: '0 clamp(1rem,5vw,3rem)' }}>
        <div style={{ marginBottom: 16 }}>
          <span style={{ fontFamily: 'Space Mono, monospace', fontSize: 11, color: '#00C9A7', letterSpacing: '0.1em', textTransform: 'uppercase' }}>AI Demo</span>
        </div>
        <h2 style={{ fontFamily: 'Space Grotesk, sans-serif', fontWeight: 800, fontSize: 'clamp(2rem,4vw,3rem)', color: '#E4EAF5', letterSpacing: '-0.03em', margin: '0 0 12px' }}>Text → SULO Knowledge Graph.</h2>
        <p style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 16, color: '#6B7FA3', margin: '0 0 40px', maxWidth: 560 }}>
          Describe anything in plain language. Watch it get converted into a SULO-compliant knowledge graph with classes, relations, and RDF triples.
        </p>

        {/* Example pills */}
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginBottom: 20 }}>
          {EXAMPLE_PROMPTS.map((p, i) => (
            <button key={i} onClick={() => setInput(p)}
              style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 12, color: input === p ? '#00C9A7' : '#6B7FA3', background: input === p ? 'rgba(0,201,167,0.1)' : 'rgba(255,255,255,0.04)', border: `1px solid ${input === p ? 'rgba(0,201,167,0.3)' : 'rgba(255,255,255,0.08)'}`, padding: '5px 12px', borderRadius: 20, cursor: 'pointer', maxWidth: 220, textOverflow: 'ellipsis', overflow: 'hidden', whiteSpace: 'nowrap' }}>
              {['Clinical', 'Pizza', 'Trial'][i]} example
            </button>
          ))}
        </div>

        {/* Input */}
        <div style={{ background: 'rgba(10,15,30,0.9)', border: '1px solid rgba(255,255,255,0.09)', borderRadius: 14, padding: 4, marginBottom: 16 }}>
          <textarea value={input} onChange={e => setInput(e.target.value)} rows={3}
            style={{ width: '100%', background: 'transparent', border: 'none', outline: 'none', fontFamily: 'Space Grotesk, sans-serif', fontSize: 15, color: '#E4EAF5', lineHeight: 1.65, padding: '16px 20px', resize: 'vertical', boxSizing: 'border-box' }}
            placeholder="Describe anything in plain text…" />
          <div style={{ display: 'flex', justifyContent: 'flex-end', padding: '8px 12px' }}>
            <button onClick={run} disabled={loading || !input.trim()}
              style={{ fontFamily: 'Space Grotesk, sans-serif', fontWeight: 700, fontSize: 14, color: '#080C1A', background: loading ? '#3A4560' : 'linear-gradient(135deg,#00C9A7,#00A0FF)', border: 'none', padding: '10px 24px', borderRadius: 9, cursor: loading ? 'not-allowed' : 'pointer', transition: 'opacity 0.15s', display: 'inline-flex', alignItems: 'center', gap: 8 }}>
              {loading ? (
                <><span style={{ display: 'inline-block', width: 14, height: 14, border: '2px solid #8A96B3', borderTopColor: '#E4EAF5', borderRadius: '50%', animation: 'spin 0.7s linear infinite' }} /> Analyzing…</>
              ) : '→ Build Knowledge Graph'}
            </button>
          </div>
        </div>

        {error && <div style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 14, color: '#F87171', background: 'rgba(248,113,113,0.1)', border: '1px solid rgba(248,113,113,0.2)', borderRadius: 8, padding: '12px 16px', marginBottom: 20 }}>{error}</div>}

        {result && (
          <div style={{ background: 'rgba(10,15,30,0.9)', border: '1px solid rgba(0,201,167,0.2)', borderRadius: 16, overflow: 'hidden' }}>
            {/* Tabs */}
            <div style={{ display: 'flex', borderBottom: '1px solid rgba(255,255,255,0.06)', padding: '0 24px' }}>
              {['graph', 'turtle'].map(v => (
                <button key={v} onClick={() => setView(v)}
                  style={{ fontFamily: 'Space Mono, monospace', fontSize: 12, color: view === v ? '#00C9A7' : '#3A4560', background: 'transparent', border: 'none', borderBottom: `2px solid ${view === v ? '#00C9A7' : 'transparent'}`, padding: '14px 16px', cursor: 'pointer', transition: 'all 0.15s', marginBottom: -1 }}>
                  {v === 'graph' ? 'Graph View' : 'Turtle / RDF'}
                </button>
              ))}
            </div>

            {view === 'graph' && (
              <div style={{ display: 'grid', gridTemplateColumns: '1fr auto', gap: 0 }}>
                <svg viewBox="0 0 560 320" style={{ width: '100%', height: 'auto', padding: '16px 0' }}>
                  <defs>
                    {Object.entries(TYPE_COLORS).map(([t, c]) => (
                      <marker key={t} id={`kg-arrow-${t}`} markerWidth="7" markerHeight="7" refX="5" refY="3" orient="auto">
                        <path d="M0,0 L0,6 L7,3 z" fill={c} />
                      </marker>
                    ))}
                  </defs>
                  {/* Edges */}
                  {result.edges.map((edge, i) => {
                    const a = positions[edge.from], b = positions[edge.to];
                    if (!a || !b) return null;
                    const dx = b.x - a.x, dy = b.y - a.y;
                    const dist = Math.sqrt(dx*dx + dy*dy);
                    if (dist < 1) return null;
                    const ux = dx/dist, uy = dy/dist;
                    const srcNode = result.nodes.find(n => n.id === edge.from);
                    const tgtNode = result.nodes.find(n => n.id === edge.to);
                    const sr = srcNode ? 28 : 22, tr = tgtNode ? 28 : 22;
                    const x1 = a.x + ux*sr, y1 = a.y + uy*sr;
                    const x2 = b.x - ux*(tr+6), y2 = b.y - uy*(tr+6);
                    const edgeColor = srcNode ? (TYPE_COLORS[srcNode.type] || '#6B7FA3') : '#6B7FA3';
                    const mx = (x1+x2)/2, my = (y1+y2)/2;
                    return (
                      <g key={i}>
                        <line x1={x1} y1={y1} x2={x2} y2={y2} stroke={edgeColor + '60'} strokeWidth={1.5} markerEnd={`url(#kg-arrow-${srcNode?.type || 'Object'})`} />
                        <text x={mx} y={my - 5} textAnchor="middle" fontFamily="Space Mono, monospace" fontSize="8" fill={edgeColor + 'CC'}>{edge.label}</text>
                      </g>
                    );
                  })}
                  {/* Nodes */}
                  {result.nodes.map(node => {
                    const pos = positions[node.id];
                    if (!pos) return null;
                    const color = TYPE_COLORS[node.type] || '#6B7FA3';
                    const isSel = selNode === node.id;
                    return (
                      <g key={node.id} style={{ cursor: 'pointer' }} onClick={() => setSelNode(isSel ? null : node.id)}>
                        <circle cx={pos.x} cy={pos.y} r={28} fill={color + '20'} stroke={isSel ? color : color + '70'} strokeWidth={isSel ? 2.5 : 1.5} />
                        {isSel && <circle cx={pos.x} cy={pos.y} r={34} fill="none" stroke={color + '40'} strokeWidth={1} />}
                        <text x={pos.x} y={pos.y - 3} textAnchor="middle" fontFamily="Space Grotesk, sans-serif" fontWeight="700" fontSize="9" fill={color}>{node.type}</text>
                        <text x={pos.x} y={pos.y + 10} textAnchor="middle" fontFamily="Space Grotesk, sans-serif" fontSize="9" fill="#8A96B3">{node.label}</text>
                      </g>
                    );
                  })}
                </svg>
                {/* Node legend */}
                <div style={{ width: 180, padding: '20px 16px', borderLeft: '1px solid rgba(255,255,255,0.06)' }}>
                  <div style={{ fontFamily: 'Space Mono, monospace', fontSize: 10, color: '#3A4560', marginBottom: 12 }}>ENTITIES</div>
                  {result.nodes.map(n => (
                    <div key={n.id} onClick={() => setSelNode(selNode === n.id ? null : n.id)}
                      style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '5px 8px', borderRadius: 6, marginBottom: 4, cursor: 'pointer', background: selNode === n.id ? 'rgba(255,255,255,0.06)' : 'transparent' }}>
                      <div style={{ width: 8, height: 8, borderRadius: '50%', background: TYPE_COLORS[n.type] || '#6B7FA3', flexShrink: 0 }} />
                      <div>
                        <div style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 11, color: '#E4EAF5' }}>{n.label}</div>
                        <div style={{ fontFamily: 'Space Mono, monospace', fontSize: 9, color: TYPE_COLORS[n.type] + 'AA' || '#6B7FA3' }}>{n.type}</div>
                      </div>
                    </div>
                  ))}
                </div>
              </div>
            )}

            {view === 'turtle' && (
              <div style={{ padding: '24px 28px' }}>
                <div style={{ fontFamily: 'Space Mono, monospace', fontSize: 11, color: '#3A4560', marginBottom: 16 }}>GENERATED SULO TRIPLES</div>
                <pre style={{ fontFamily: 'Space Mono, monospace', fontSize: 12, color: '#8A96B3', margin: 0, lineHeight: 2, background: 'rgba(0,0,0,0.3)', padding: '20px 24px', borderRadius: 10, overflowX: 'auto' }}>
                  {`@prefix sulo: <https://w3id.org/sulo/> .\n@prefix ex:   <https://example.org/> .\n\n`}
                  {result.nodes.map(n => `ex:${n.id} a sulo:${n.type} ;\n    rdfs:label "${n.label}" .\n`).join('\n')}
                  {`\n`}
                  {result.edges.map(e => `ex:${e.from} sulo:${e.label} ex:${e.to} .`).join('\n')}
                </pre>
                {result.turtle && (
                  <div style={{ marginTop: 20 }}>
                    <div style={{ fontFamily: 'Space Mono, monospace', fontSize: 11, color: '#3A4560', marginBottom: 10 }}>AI-SUGGESTED PATTERN</div>
                    <pre style={{ fontFamily: 'Space Mono, monospace', fontSize: 12, color: '#6B7FA3', margin: 0, lineHeight: 1.9, padding: '16px 20px', background: 'rgba(0,201,167,0.04)', border: '1px solid rgba(0,201,167,0.1)', borderRadius: 8 }}>{result.turtle}</pre>
                  </div>
                )}
              </div>
            )}
          </div>
        )}

        {!result && !loading && (
          <div style={{ textAlign: 'center', padding: '60px 20px', background: 'rgba(10,15,30,0.5)', border: '1px dashed rgba(255,255,255,0.08)', borderRadius: 16 }}>
            <div style={{ fontSize: 40, marginBottom: 12, opacity: 0.3 }}>◈</div>
            <div style={{ fontFamily: 'Space Grotesk, sans-serif', fontSize: 15, color: '#3A4560' }}>Your SULO knowledge graph will appear here</div>
          </div>
        )}
      </div>
      <style>{`@keyframes spin { to { transform: rotate(360deg); } }`}</style>
    </section>
  );
};

Object.assign(window, { PostcardExplorer, AIDemo });
