// Reads the plain text of a rich-text node in the document model.
function textOf(rich) {
  if (!rich || !Array.isArray(rich.runs)) return '';
  return rich.runs.map((r) => r.text || '').join('');
}

// Replaces the text of a rich-text node, keeping the node shape.
function setText(rich, value) {
  return { runs: [{ ...(rich && rich.runs && rich.runs[0] ? rich.runs[0] : {}), text: value }] };
}

// Renders the reconstructed blocks as an editable, reflowable HTML document.
function structureHtml(doc) {
  const esc = (s) => String(s ?? '').replace(/[&<>]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]));
  const block = (b) => {
    if (b.type === 'section') {
      return `<section><h2>${esc(textOf(b.title))}</h2>\n${(b.body || []).map(block).join('\n')}</section>`;
    }
    if (b.type === 'bullets') {
      return `<ul>\n${b.items.map((i) => `  <li>${esc(textOf(i))}</li>`).join('\n')}\n</ul>`;
    }
    return `<p>${esc(textOf(b.content))}</p>`;
  };
  const pages = doc.pages.map((p) => `<article>\n${p.blocks.map(block).join('\n')}\n</article>`).join('\n');
  return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${esc(doc.title)}</title>
<style>
  body{margin:0 auto;max-width:72ch;padding:48px 20px 80px;
    font:17px/1.6 system-ui,-apple-system,"Segoe UI",sans-serif;color:#161c26;background:#fff}
  article + article{margin-top:48px;border-top:1px solid #dde1e9;padding-top:32px}
  h2{font-size:1.3rem;margin:1.6em 0 .4em}
  p{margin:0 0 .9em}
  ul{margin:0 0 1em 1.2em;padding:0}
  li{margin:0 0 .35em}
</style>
</head>
<body>
${pages}
</body>
</html>
`;
}

// Saves a string to a file the browser downloads.
function saveAs(text, name, type) {
  const blob = new Blob([text], { type: type || 'text/html' });
  const a = document.createElement('a');
  a.href = URL.createObjectURL(blob);
  a.download = name;
  a.click();
  setTimeout(() => URL.revokeObjectURL(a.href), 1000);
}

// Edits one block of the structure in place.
function BlockEditor({ block, onChange }) {
  if (block.type === 'section') {
    return (
      <div className="block">
        <div className="label">Heading</div>
        <div className="h2 edit" contentEditable suppressContentEditableWarning
          onBlur={(e) => onChange({ ...block, title: setText(block.title, e.target.textContent) })}>
          {textOf(block.title)}
        </div>
        {!!(block.body || []).length && (
          <div className="blocklist" style={{ marginTop: 'var(--s-3)' }}>
            {block.body.map((child, i) => (
              <BlockEditor key={child.id} block={child}
                onChange={(next) => {
                  const body = block.body.slice();
                  body[i] = next;
                  onChange({ ...block, body });
                }} />
            ))}
          </div>
        )}
      </div>
    );
  }
  if (block.type === 'bullets') {
    return (
      <div className="block">
        <div className="label">List</div>
        <ul style={{ margin: 0, paddingLeft: 'var(--s-5)' }}>
          {block.items.map((item, i) => (
            <li key={i} className="edit" contentEditable suppressContentEditableWarning
              style={{ marginBottom: 4 }}
              onBlur={(e) => {
                const items = block.items.slice();
                items[i] = setText(item, e.target.textContent);
                onChange({ ...block, items });
              }}>
              {textOf(item)}
            </li>
          ))}
        </ul>
      </div>
    );
  }
  return (
    <div className="block">
      <div className="edit" contentEditable suppressContentEditableWarning
        onBlur={(e) => onChange({ ...block, content: setText(block.content, e.target.textContent) })}>
        {textOf(block.content)}
      </div>
    </div>
  );
}

// Shows one converted document as a replica, as editable structure, or both.
function DocumentPage({ record, onSave, toast }) {
  const [view, setView] = React.useState('replica');
  const [doc, setDoc] = React.useState(record.doc);
  const [dirty, setDirty] = React.useState(false);

  React.useEffect(() => { setDoc(record.doc); setDirty(false); setView('replica'); }, [record.id]);

  const s = record.stats || {};
  const measured = s.runs ? Math.round((s.measuredRuns / s.runs) * 100) : 0;

  function editBlock(pageIndex, blockIndex, next) {
    const pages = doc.pages.slice();
    const blocks = pages[pageIndex].blocks.slice();
    blocks[blockIndex] = next;
    pages[pageIndex] = { ...pages[pageIndex], blocks };
    const updated = { ...doc, pages };
    setDoc(updated);
    setDirty(true);
  }

  function save() {
    onSave({ ...record, doc });
    setDirty(false);
    toast('Saved.');
  }

  return (
    <div className="stage-in">
      <Head
        kicker={`Converted ${when(record.importedAt)}`}
        title={record.title}
        right={
          <div style={{ display: 'flex', gap: 'var(--s-2)' }}>
            <Btn icon="download" onClick={() => saveAs(record.html, record.title + '.html')}>
              Replica .html
            </Btn>
            <Btn icon="download" onClick={() => saveAs(structureHtml(doc), record.title + '-structure.html')}>
              Structure .html
            </Btn>
            <Btn kind="primary" disabled={!dirty} onClick={save}>{dirty ? 'Save changes' : 'Saved'}</Btn>
          </div>
        }
      />

      <div className="card" style={{ padding: 'var(--s-4) var(--s-5)', marginBottom: 'var(--s-5)' }}>
        <div className="statrow">
          <Stat value={s.pages} label="pages" />
          <Stat value={s.runs} label="text runs" />
          <Stat value={s.blocks} label="editable blocks" />
          <Stat value={s.fonts} label="fonts kept" />
          <Stat value={measured + '%'} label="runs placed from a real face"
            tone={measured >= 90 ? 'success' : measured >= 50 ? 'warning' : 'danger'} />
          <Stat value={bytes(s.htmlBytes)} label="output" />
        </div>
        {measured < 100 && (
          <div className="notice notice-info" style={{ marginTop: 'var(--s-3)' }}>
            {s.runs - s.measuredRuns} of {s.runs} runs were placed from a fallback ascent rather than
            the embedded face. Those baselines are approximate.
          </div>
        )}
        {(record.warnings || []).map((w, i) => (
          <div key={i} className="notice notice-warn" style={{ marginTop: 'var(--s-2)' }}>{w}</div>
        ))}
      </div>

      <div className="tabs" role="tablist" style={{ marginBottom: 'var(--s-4)' }}>
        {[['replica', 'Replica'], ['structure', 'Structure']].map(([id, name]) => (
          <button key={id} className="tab" role="tab" aria-selected={view === id}
            onClick={() => setView(id)}>{name}</button>
        ))}
      </div>

      {view === 'replica' && (
        <div>
          <p className="sub" style={{ fontSize: 'var(--fs-300)', marginBottom: 'var(--s-3)' }}>
            The page as it prints, with the text lifted onto it. Press Onionskin inside the frame to
            fade the picture back and read what was actually recovered.
          </p>
          <iframe className="frame" title="Replica" srcDoc={record.html} />
        </div>
      )}

      {view === 'structure' && (
        <div>
          <p className="sub" style={{ fontSize: 'var(--fs-300)', marginBottom: 'var(--s-3)' }}>
            Click any block to edit it. This is a reconstruction, so it carries reading order and not
            layout; the replica is unaffected by what you change here.
          </p>
          {doc.pages.map((page, pi) => (
            <section key={page.id} style={{ marginBottom: 'var(--s-6)' }}>
              <div className="label" style={{ marginBottom: 'var(--s-2)' }}>Page {page.n}</div>
              <div className="blocklist">
                {page.blocks.map((b, bi) => (
                  <BlockEditor key={b.id} block={b} onChange={(next) => editBlock(pi, bi, next)} />
                ))}
                {!page.blocks.length && (
                  <div className="notice notice-warn">
                    No block was reconstructed from this page. That happens when the page carries no
                    text layer, which is what a scan looks like.
                  </div>
                )}
              </div>
            </section>
          ))}
        </div>
      )}
    </div>
  );
}

Object.assign(window, { DocumentPage, structureHtml, saveAs });
