// Workspace — Vault Editor (split view) + Conflict banner + History drawer

const SAMPLE_MD = `# PML — Parte 1: Foundation

## 1. Objetivos del lenguaje

PML (Procedural Modeling Language) es un lenguaje de propósito específico
para describir transformaciones de datos estructurados con garantías
estáticas de tipo y trazabilidad de origen.

**Principios rectores:**

- Sintaxis explícita, sin sobrecarga implícita
- Trazabilidad de spans en todo el pipeline
- Type system estructural con inferencia local
- Tooling first-class: LSP, formatter, debugger

## 2. Modelo de tipos base

Los tipos primitivos del lenguaje son:

| Tipo     | Descripción                          |
|----------|--------------------------------------|
| \`Int\`    | Entero 64-bit con signo              |
| \`Float\`  | IEEE 754 double                      |
| \`Str\`    | Secuencia UTF-8 inmutable            |
| \`Bool\`   | Booleano                             |
| \`Bytes\`  | Buffer binario                       |

### 2.1 Tipos compuestos

\`\`\`pml
type Point = { x: Float, y: Float }
type Maybe[T] = Some(T) | None

fn distance(a: Point, b: Point) -> Float {
  let dx = a.x - b.x
  let dy = a.y - b.y
  sqrt(dx * dx + dy * dy)
}
\`\`\`

## 3. Hitos de la fase

- [x] Documento de tipografía aprobado
- [x] Bootstrap del repositorio + CI mínima
- [x] Convenciones de naming en AST
- [ ] Spec de operadores y precedencia
- [ ] Lexer base con tests
- [ ] Diagnostics con source spans

## 4. Ejemplo TypeScript de equivalencia

\`\`\`typescript
interface Point { x: number; y: number; }
type Maybe<T> = { kind: 'some', value: T } | { kind: 'none' };

function distance(a: Point, b: Point): number {
  const dx = a.x - b.x;
  const dy = a.y - b.y;
  return Math.sqrt(dx * dx + dy * dy);
}
\`\`\`

> **Nota:** este documento es la fuente de verdad de la fase II-6 y
> precede a las decisiones técnicas registradas en \`/decisiones\`.
`;

function VaultEditor({ docPath, identity, showConflict: showConflictProp, onShowHistory, onDeleted }) {
  const [view, setView] = React.useState('split');
  const [doc, setDoc] = React.useState(null);
  const [body, setBody] = React.useState('');
  const [error, setError] = React.useState(null);
  const [conflict, setConflict] = React.useState(null);
  const [saveStatus, setSaveStatus] = React.useState('saved');
  const [deleting, setDeleting] = React.useState(false);

  const handleDelete = async () => {
    if (!doc) return;
    if (deleting) return;
    if (!confirm(`¿Borrar el documento "${doc.title || docPath}"?\n\nEsto lo manda a la papelera (soft-delete).`)) return;
    setDeleting(true);
    try {
      await window.deleteDoc(doc.id);
      onDeleted && onDeleted();
    } catch (e) {
      alert(e?.message || 'No se pudo borrar el documento.');
      setDeleting(false);
    }
  };

  React.useEffect(() => {
    let cancelled = false;
    setError(null); setConflict(null);
    window.fetchDocByPath(docPath)
      .then((d) => { if (!cancelled) { setDoc(d); setBody(d.bodyMd || ''); setSaveStatus('saved'); } })
      .catch((e) => { if (!cancelled) setError(e.message || String(e)); });
    return () => { cancelled = true; };
  }, [docPath]);

  React.useEffect(() => {
    if (!doc) return;
    if (body === doc.bodyMd) { setSaveStatus('saved'); return; }
    setSaveStatus('dirty');
    const t = setTimeout(async () => {
      setSaveStatus('saving');
      try {
        const updated = await window.patchDoc(doc.id, body, doc.updatedAt);
        setDoc(updated);
        setSaveStatus('saved');
        setConflict(null);
      } catch (e) {
        if (e.status === 409) {
          setConflict(e.body && e.body.details);
        } else {
          setError(e.message || String(e));
        }
        setSaveStatus('dirty');
      }
    }, 1500);
    return () => clearTimeout(t);
  }, [body, doc]);

  React.useEffect(() => {
    if (!doc) return;
    return window.onRealtime((msg) => {
      if (msg.type === 'doc.updated' && msg.docId === doc.id && msg.by !== (window.ME && window.ME.id)) {
        if (saveStatus === 'saved') {
          window.fetchDocByPath(docPath).then((d) => { setDoc(d); setBody(d.bodyMd || ''); }).catch(() => {});
        } else {
          setConflict({ realtimeBy: msg.by, currentUpdatedAt: msg.updatedAt });
        }
      }
    });
  }, [doc, saveStatus, docPath]);

  const md = body;
  const lines = md.split('\n');
  const wordCount = md.split(/\s+/).filter(Boolean).length;
  const breadcrumb = docPath.split('/');
  const showConflict = showConflictProp || !!conflict;

  return (
    <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
      {/* doc top bar */}
      <div style={{
        height: 44, flexShrink: 0,
        display: 'flex', alignItems: 'center',
        padding: '0 16px',
        background: 'var(--bg-1)',
        borderBottom: '1px solid var(--border)',
        gap: 12,
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 12.5, color: 'var(--text-2)' }} className="mono">
          {breadcrumb.map((seg, i) => (
            <React.Fragment key={i}>
              {i > 0 && <span style={{ color: 'var(--text-3)' }}>/</span>}
              <span style={{
                color: i === breadcrumb.length - 1 ? 'var(--text-0)' : 'var(--text-2)',
                cursor: 'pointer',
              }}>{seg}</span>
            </React.Fragment>
          ))}
          <span style={{ marginLeft: 8 }}>
            <Tag color="var(--success)" mono>guardado</Tag>
          </span>
        </div>

        <div style={{ flex: 1 }} />

        {/* view toggle */}
        <div style={{ display: 'flex', background: 'var(--bg-2)', border: '1px solid var(--border)', borderRadius: 'var(--r-md)', padding: 2 }}>
          {[
            { v: 'editor', icon: 'pencil-line', label: 'Editor' },
            { v: 'split', icon: 'columns-2', label: 'Split' },
            { v: 'preview', icon: 'eye', label: 'Preview' },
          ].map(t => (
            <button key={t.v} className="btn-reset" onClick={() => setView(t.v)}
              title={t.label}
              style={{
                display: 'flex', alignItems: 'center', gap: 5,
                padding: '4px 9px', borderRadius: 5,
                background: view === t.v ? 'var(--bg-3)' : 'transparent',
                color: view === t.v ? 'var(--text-0)' : 'var(--text-2)',
                fontSize: 11.5, fontWeight: 500,
              }}>
              <Icon name={t.icon} size={12} /> {t.label}
            </button>
          ))}
        </div>

        <Button variant="ghost" size="sm" icon="history" onClick={onShowHistory}>Historial</Button>
        <Button variant="danger" size="sm" icon="trash-2" onClick={handleDelete} disabled={deleting || !doc}>
          {deleting ? 'Borrando…' : 'Borrar'}
        </Button>
      </div>

      {/* Conflict banner */}
      {showConflict && (
        <div style={{
          flexShrink: 0,
          background: 'rgba(245,158,11,.08)',
          borderBottom: '1px solid rgba(245,158,11,.30)',
          padding: '10px 16px',
          display: 'flex', alignItems: 'center', gap: 12,
          fontSize: 13,
        }}>
          <Icon name="alert-triangle" size={15} color="var(--warn)" />
          <span style={{ color: 'var(--text-0)' }}>
            Este documento fue modificado por <span style={{ color: 'var(--code)', fontWeight: 500 }}>Claude Code</span> hace 2 minutos.
          </span>
          <div style={{ flex: 1 }} />
          <Button variant="ghost" size="sm">Ver cambios</Button>
          <Button variant="secondary" size="sm">Recargar</Button>
          <Button variant="primary" size="sm">Sobreescribir mis cambios</Button>
        </div>
      )}

      {/* body */}
      <div style={{ flex: 1, display: 'flex', minHeight: 0 }}>
        {(view === 'editor' || view === 'split') && (
          <EditorPane body={body} onChange={setBody} flex={view === 'split' ? 1 : 1} />
        )}
        {view === 'split' && <div style={{ width: 1, background: 'var(--border)' }} />}
        {(view === 'preview' || view === 'split') && (
          <PreviewPane md={md} flex={view === 'split' ? 1 : 1} />
        )}
      </div>

      {/* footer */}
      <div style={{
        height: 28, flexShrink: 0,
        background: 'var(--bg-1)', borderTop: '1px solid var(--border)',
        display: 'flex', alignItems: 'center', padding: '0 16px',
        fontSize: 11.5, color: 'var(--text-2)', gap: 14,
      }}>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
          <span style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--success)' }} />
          {(() => {
            // Source of truth: the doc's persisted `updatedBy` / `updatedAt`,
            // not the logged-in actor. The current viewer can be anyone; the
            // last save may have been by anyone (Genaro, Claude Code, …).
            const by = (doc && doc.updatedBy) || identity;
            const when = doc && doc.updatedAt ? doc.updatedAt : null;
            const ago = (() => {
              if (!when) return '';
              const ms = Date.now() - new Date(when).getTime();
              if (ms < 30_000) return 'hace segundos';
              const m = Math.floor(ms / 60_000);
              if (m < 1) return 'hace segundos';
              if (m < 60) return `hace ${m}m`;
              const h = Math.floor(m / 60);
              if (h < 24) return `hace ${h}h`;
              return `hace ${Math.floor(h / 24)}d`;
            })();
            return <>Guardado {ago} por <Avatar actor={by} size={14} /> {ACTORS[by]?.name || by}</>;
          })()}
        </span>
        <div style={{ flex: 1 }} />
        <span><span className="mono">{wordCount}</span> palabras</span>
        <span><span className="mono">{md.length}</span> caracteres</span>
        <span><span className="mono">Ln 1, Col 1</span></span>
        <span>UTF-8</span>
        <span>Markdown</span>
      </div>
    </div>
  );
}

function EditorPane({ body, onChange, flex }) {
  const lineCount = (body || '').split('\n').length;
  return (
    <div style={{ flex, display: 'flex', flexDirection: 'column', minWidth: 0, background: 'var(--bg-0)' }}>
      <EditorToolbar />
      <div style={{ flex: 1, overflow: 'hidden', display: 'flex', minHeight: 0 }}>
        <div style={{
          padding: '14px 0', textAlign: 'right',
          color: 'var(--text-3)', fontSize: 12, lineHeight: 1.7,
          fontFamily: 'var(--font-mono)',
          width: 44, flexShrink: 0,
          userSelect: 'none',
          background: 'var(--bg-0)',
          borderRight: '1px solid var(--border)',
          overflow: 'hidden',
        }}>
          {Array.from({ length: lineCount }, (_, i) => (
            <div key={i} style={{ padding: '0 10px 0 0' }}>{i + 1}</div>
          ))}
        </div>
        <textarea
          value={body || ''}
          onChange={(e) => onChange(e.target.value)}
          spellCheck={false}
          style={{
            flex: 1, minWidth: 0, padding: '14px 18px',
            color: 'var(--text-0)',
            background: 'var(--bg-0)',
            border: 'none', outline: 'none', resize: 'none',
            fontFamily: 'var(--font-mono)',
            fontSize: 12.5, lineHeight: 1.7,
          }}
        />
      </div>
    </div>
  );
}

function highlightMd(line) {
  if (line.startsWith('# '))   return <span><span className="tok-key"># </span><span style={{color:'var(--text-0)'}}>{line.slice(2)}</span></span>;
  if (line.startsWith('## '))  return <span><span className="tok-key">## </span><span style={{color:'var(--text-0)'}}>{line.slice(3)}</span></span>;
  if (line.startsWith('### ')) return <span><span className="tok-key">### </span><span style={{color:'var(--text-0)'}}>{line.slice(4)}</span></span>;
  if (line.startsWith('```'))  return <span className="tok-com">{line}</span>;
  if (line.startsWith('> '))   return <span style={{color:'var(--text-2)'}}>{line}</span>;
  if (/^- \[[x ]\]/.test(line)) return <span><span className="tok-num">{line.slice(0, 6)}</span><span>{line.slice(6)}</span></span>;
  if (line.startsWith('- '))   return <span><span className="tok-fn">- </span><span>{line.slice(2)}</span></span>;
  if (/^\|/.test(line))        return <span className="tok-pun">{line}</span>;
  // inline code & bold
  const parts = [];
  let i = 0; let buf = ''; let key = 0;
  while (i < line.length) {
    if (line[i] === '`') {
      const end = line.indexOf('`', i + 1);
      if (end > i) { if (buf) parts.push(<span key={key++}>{buf}</span>); buf = '';
        parts.push(<span key={key++} className="tok-str">{line.slice(i, end + 1)}</span>); i = end + 1; continue; }
    }
    if (line[i] === '*' && line[i+1] === '*') {
      const end = line.indexOf('**', i + 2);
      if (end > i) { if (buf) parts.push(<span key={key++}>{buf}</span>); buf = '';
        parts.push(<span key={key++} style={{ color: 'var(--text-0)', fontWeight: 600 }}>{line.slice(i, end + 2)}</span>); i = end + 2; continue; }
    }
    buf += line[i]; i++;
  }
  if (buf) parts.push(<span key={key++}>{buf}</span>);
  return parts.length ? parts : (line || '\u00A0');
}

function EditorToolbar() {
  const tools = [
    { i: 'bold', t: 'Bold' },
    { i: 'italic', t: 'Italic' },
    { i: 'link', t: 'Link' },
    { i: 'code', t: 'Code inline' },
    null,
    { i: 'heading-1', t: 'H1' },
    { i: 'heading-2', t: 'H2' },
    { i: 'list', t: 'Lista' },
    { i: 'list-ordered', t: 'Lista numerada' },
    { i: 'check-square', t: 'Checkbox' },
    null,
    { i: 'square-code', t: 'Code block' },
    { i: 'image', t: 'Imagen' },
    { i: 'table', t: 'Tabla' },
  ];
  return (
    <div style={{
      height: 36, flexShrink: 0,
      borderBottom: '1px solid var(--border)',
      background: 'var(--bg-1)',
      display: 'flex', alignItems: 'center', padding: '0 8px', gap: 1,
    }}>
      {tools.map((tt, i) => tt === null ? (
        <div key={i} style={{ width: 1, height: 16, background: 'var(--border)', margin: '0 4px' }} />
      ) : (
        <button key={i} className="btn-reset" title={tt.t}
          style={{ width: 26, height: 26, display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: 5, color: 'var(--text-1)' }}
          onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-2)'}
          onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
          <Icon name={tt.i} size={13} />
        </button>
      ))}
    </div>
  );
}

function PreviewPane({ md, flex }) {
  return (
    <div style={{ flex, overflow: 'auto', background: 'var(--bg-0)', minWidth: 0 }}>
      <div style={{ maxWidth: 720, margin: '0 auto', padding: '32px 36px 80px' }}>
        <RenderedMd md={md} />
      </div>
    </div>
  );
}

function RenderedMd({ md }) {
  // Simple block renderer: paragraphs, headings, lists, code, table, blockquote
  const lines = md.split('\n');
  const out = []; let i = 0; let key = 0;

  while (i < lines.length) {
    const line = lines[i];
    if (line.startsWith('# ')) { out.push(<h1 key={key++} style={{ fontSize: 28, fontWeight: 700, letterSpacing: -0.5, margin: '8px 0 18px', color: 'var(--text-0)' }}>{inline(line.slice(2))}</h1>); i++; continue; }
    if (line.startsWith('## ')) { out.push(<h2 key={key++} style={{ fontSize: 19, fontWeight: 600, margin: '28px 0 12px', color: 'var(--text-0)', letterSpacing: -0.3 }}>{inline(line.slice(3))}</h2>); i++; continue; }
    if (line.startsWith('### ')) { out.push(<h3 key={key++} style={{ fontSize: 15, fontWeight: 600, margin: '22px 0 10px', color: 'var(--text-0)' }}>{inline(line.slice(4))}</h3>); i++; continue; }
    if (line.startsWith('```')) {
      const lang = line.slice(3);
      const buf = [];
      i++;
      while (i < lines.length && !lines[i].startsWith('```')) { buf.push(lines[i]); i++; }
      i++;
      out.push(<CodeBlock key={key++} lang={lang} src={buf.join('\n')} />);
      continue;
    }
    if (line.startsWith('> ')) {
      out.push(<blockquote key={key++} style={{
        margin: '14px 0', padding: '10px 14px',
        borderLeft: '2px solid var(--accent)',
        background: 'rgba(79,142,255,.06)',
        color: 'var(--text-1)', fontSize: 13.5, borderRadius: '0 6px 6px 0',
      }}>{inline(line.slice(2))}</blockquote>);
      i++; continue;
    }
    if (/^- \[[x ]\]/.test(line)) {
      const items = [];
      while (i < lines.length && /^- \[[x ]\]/.test(lines[i])) {
        const checked = lines[i][3] === 'x';
        items.push({ checked, text: lines[i].slice(6) });
        i++;
      }
      out.push(<ul key={key++} style={{ margin: '10px 0', padding: 0, listStyle: 'none' }}>
        {items.map((it, j) => (
          <li key={j} style={{ display: 'flex', alignItems: 'center', gap: 9, padding: '4px 0', fontSize: 13.5, color: it.checked ? 'var(--text-2)' : 'var(--text-0)' }}>
            <Checkbox checked={it.checked} onChange={() => {}} />
            <span style={{ textDecoration: it.checked ? 'line-through' : 'none' }}>{inline(it.text)}</span>
          </li>
        ))}
      </ul>);
      continue;
    }
    if (line.startsWith('- ')) {
      const items = [];
      while (i < lines.length && lines[i].startsWith('- ')) {
        items.push(lines[i].slice(2)); i++;
      }
      out.push(<ul key={key++} style={{ margin: '8px 0 8px 20px', padding: 0, color: 'var(--text-0)', fontSize: 13.5, lineHeight: 1.7 }}>
        {items.map((t, j) => <li key={j}>{inline(t)}</li>)}
      </ul>);
      continue;
    }
    if (/^\|/.test(line) && /^\|/.test(lines[i+1] || '')) {
      const rows = [];
      while (i < lines.length && /^\|/.test(lines[i])) {
        rows.push(lines[i].split('|').slice(1, -1).map(s => s.trim()));
        i++;
      }
      const header = rows[0]; const body = rows.slice(2);
      out.push(<div key={key++} style={{ margin: '14px 0', overflow: 'hidden', border: '1px solid var(--border)', borderRadius: 6 }}>
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
          <thead><tr>{header.map((h, j) => <th key={j} style={{ textAlign: 'left', padding: '8px 12px', background: 'var(--bg-2)', borderBottom: '1px solid var(--border)', color: 'var(--text-0)', fontWeight: 600, fontSize: 12 }}>{inline(h)}</th>)}</tr></thead>
          <tbody>{body.map((r, j) => <tr key={j}>{r.map((c, k) => <td key={k} style={{ padding: '8px 12px', borderTop: '1px solid var(--border)', color: 'var(--text-1)' }}>{inline(c)}</td>)}</tr>)}</tbody>
        </table>
      </div>);
      continue;
    }
    if (line.trim() === '') { i++; continue; }
    out.push(<p key={key++} style={{ margin: '10px 0', color: 'var(--text-1)', fontSize: 13.5, lineHeight: 1.7 }}>{inline(line)}</p>);
    i++;
  }
  return <div>{out}</div>;
}

function inline(text) {
  // **bold** and `code`
  const parts = [];
  let i = 0; let buf = ''; let k = 0;
  while (i < text.length) {
    if (text[i] === '`') {
      const e = text.indexOf('`', i + 1);
      if (e > i) { if (buf) { parts.push(<span key={k++}>{buf}</span>); buf = ''; }
        parts.push(<code key={k++} style={{ background: 'var(--bg-1)', border: '1px solid var(--border)', padding: '1px 5px', borderRadius: 4, fontFamily: 'var(--font-mono)', fontSize: 12, color: '#CE9178' }}>{text.slice(i+1, e)}</code>);
        i = e + 1; continue;
      }
    }
    if (text[i] === '*' && text[i+1] === '*') {
      const e = text.indexOf('**', i + 2);
      if (e > i) { if (buf) { parts.push(<span key={k++}>{buf}</span>); buf = ''; }
        parts.push(<strong key={k++} style={{ color: 'var(--text-0)', fontWeight: 600 }}>{text.slice(i+2, e)}</strong>);
        i = e + 2; continue;
      }
    }
    buf += text[i]; i++;
  }
  if (buf) parts.push(<span key={k++}>{buf}</span>);
  return parts;
}

function CodeBlock({ lang, src }) {
  const lines = src.split('\n');
  return (
    <div style={{ margin: '14px 0', background: 'var(--bg-1)', border: '1px solid var(--border)', borderRadius: 8, overflow: 'hidden' }}>
      <div style={{
        padding: '6px 12px', display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        background: 'var(--bg-2)', borderBottom: '1px solid var(--border)',
        fontSize: 11, color: 'var(--text-2)',
      }}>
        <span className="mono">{lang || 'text'}</span>
        <button className="btn-reset" style={{ color: 'var(--text-2)', display: 'flex', alignItems: 'center', gap: 4, fontSize: 11 }}>
          <Icon name="copy" size={11} /> copiar
        </button>
      </div>
      <pre style={{ margin: 0, padding: '12px 14px', fontFamily: 'var(--font-mono)', fontSize: 12.5, lineHeight: 1.7, color: 'var(--text-0)', overflow: 'auto' }}>
        {lines.map((l, i) => <div key={i}>{highlightCode(l, lang)}</div>)}
      </pre>
    </div>
  );
}

function highlightCode(line, lang) {
  // dead simple keyword/string/comment highlighter
  const KEYS_TS = /\b(interface|type|function|return|const|let|var|class|extends|new|if|else|for|while|export|import|from|as|number|string|boolean)\b/g;
  const KEYS_PML = /\b(type|fn|let|return|if|else|for|match|some|none|true|false|Int|Float|Str|Bool|Bytes|Some|None)\b/g;
  const KEYS = lang === 'pml' ? KEYS_PML : KEYS_TS;

  const segs = [];
  let i = 0; let buf = ''; let k = 0;
  const flush = () => { if (buf) { segs.push(<span key={k++}>{highlightWords(buf, KEYS)}</span>); buf = ''; } };
  while (i < line.length) {
    if (line[i] === '/' && line[i+1] === '/') {
      flush();
      segs.push(<span key={k++} className="tok-com">{line.slice(i)}</span>);
      return segs;
    }
    if (line[i] === '"' || line[i] === "'") {
      flush();
      const q = line[i]; let e = i + 1;
      while (e < line.length && line[e] !== q) e++;
      segs.push(<span key={k++} className="tok-str">{line.slice(i, e + 1)}</span>);
      i = e + 1; continue;
    }
    buf += line[i]; i++;
  }
  flush();
  return segs.length ? segs : '\u00A0';
}

function highlightWords(text, regex) {
  const parts = []; let last = 0; let m; let k = 0;
  regex.lastIndex = 0;
  while ((m = regex.exec(text))) {
    if (m.index > last) parts.push(<span key={k++}>{text.slice(last, m.index)}</span>);
    parts.push(<span key={k++} className="tok-key">{m[0]}</span>);
    last = m.index + m[0].length;
  }
  if (last < text.length) parts.push(<span key={k++}>{text.slice(last)}</span>);
  return parts;
}

// ── History drawer ─────────────────────────────────────────────────
function HistoryDrawer({ open, onClose, docPath }) {
  if (!open) return null;
  const [versions, setVersions] = React.useState([]);
  React.useEffect(() => {
    if (!docPath) return;
    let cancelled = false;
    window.fetchDocByPath(docPath)
      .then((d) => window.listDocVersions(d.id))
      .then((vs) => {
        if (cancelled) return;
        const minsAgo = (iso) => Math.max(0, Math.round((Date.now() - new Date(iso).getTime()) / 60000));
        setVersions(vs.map((v) => ({
          mins: minsAgo(v.createdAt),
          author: v.authorId,
          added: v.addedLines,
          removed: v.removedLines,
          summary: v.summary || '',
        })));
      })
      .catch((e) => console.error('history fetch failed', e));
    return () => { cancelled = true; };
  }, [docPath]);
  return (
    <>
      <div onClick={onClose} style={{ position: 'absolute', inset: 0, background: 'rgba(0,0,0,.4)', zIndex: 60 }} />
      <div style={{
        position: 'absolute', top: 0, right: 0, bottom: 0, width: 400,
        background: 'var(--bg-1)', borderLeft: '1px solid var(--border)',
        zIndex: 70, display: 'flex', flexDirection: 'column',
        animation: 'slide-in-right 200ms ease-out',
      }}>
        <div style={{ padding: '16px 18px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
          <div>
            <div style={{ fontSize: 14, fontWeight: 600 }}>Historial de versiones</div>
            <div className="mono" style={{ fontSize: 11, color: 'var(--text-2)', marginTop: 2 }}>{docPath || ''}</div>
          </div>
          <button className="btn-reset" onClick={onClose} style={{ width: 28, height: 28, display: 'flex', alignItems: 'center', justifyContent: 'center', borderRadius: 6, color: 'var(--text-2)' }}>
            <Icon name="x" size={15} />
          </button>
        </div>
        <div style={{ flex: 1, overflowY: 'auto' }}>
          {versions.map((v, i) => (
            <HistoryItem key={i} v={v} expanded={i === 0} />
          ))}
        </div>
      </div>
    </>
  );
}

function HistoryItem({ v, expanded }) {
  const [open, setOpen] = React.useState(expanded);
  return (
    <div style={{ borderBottom: '1px solid var(--border)' }}>
      <button className="btn-reset" onClick={() => setOpen(o => !o)} style={{
        display: 'flex', alignItems: 'center', gap: 12, width: '100%',
        padding: '14px 18px', textAlign: 'left',
      }}>
        <Avatar actor={v.author} size={28} />
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 12.5, fontWeight: 500, color: 'var(--text-0)' }}>{ACTORS[v.author].name}</div>
          <div style={{ fontSize: 11.5, color: 'var(--text-2)', marginTop: 2 }}>{relTime(v.mins)}</div>
        </div>
        <div style={{ display: 'flex', gap: 6, fontSize: 11 }} className="mono">
          <span style={{ color: 'var(--success)' }}>+{v.added}</span>
          <span style={{ color: 'var(--danger)' }}>−{v.removed}</span>
        </div>
      </button>
      {open && (
        <div style={{ padding: '0 18px 16px' }}>
          <div style={{ fontSize: 12, color: 'var(--text-1)', marginBottom: 10, padding: '0 0 10px', borderBottom: '1px dashed var(--border)' }}>
            {v.summary}
          </div>
          <div style={{
            background: 'var(--bg-0)', border: '1px solid var(--border)',
            borderRadius: 6, padding: 10,
            fontFamily: 'var(--font-mono)', fontSize: 11.5, lineHeight: 1.6,
          }}>
            <div style={{ background: 'rgba(16,185,129,.10)', color: '#7EE6BB', padding: '1px 6px', margin: '0 -10px', borderLeft: '2px solid var(--success)' }}>+ | \`Int\`    | Entero 64-bit con signo              |</div>
            <div style={{ background: 'rgba(16,185,129,.10)', color: '#7EE6BB', padding: '1px 6px', margin: '0 -10px', borderLeft: '2px solid var(--success)' }}>+ | \`Float\`  | IEEE 754 double                      |</div>
            <div style={{ background: 'rgba(239,68,68,.10)', color: '#FCA5A5', padding: '1px 6px', margin: '0 -10px', borderLeft: '2px solid var(--danger)' }}>- | Number  | Entero o flotante                    |</div>
          </div>
          <div style={{ display: 'flex', gap: 6, marginTop: 12 }}>
            <Button variant="secondary" size="sm" icon="eye">Ver</Button>
            <Button variant="ghost" size="sm" icon="rotate-ccw">Restaurar</Button>
          </div>
        </div>
      )}
    </div>
  );
}

Object.assign(window, { VaultEditor, HistoryDrawer, RenderedMd, CodeBlock });
