// ============================================================================
// AGREEMENT-PAPERGEN — paper-contract generator (PDF + DOCX export)
//
// Picks a template from window.AGREEMENT_TEMPLATES, fills variables either
// from an existing agreement or from a guided form, renders a print-ready
// HTML preview, and offers three export paths:
//
//   • PRINT / SAVE-AS-PDF  — opens browser print dialog with a styled @page
//   • DOWNLOAD .DOCX        — emits a minimal Word-compatible Open-XML zip
//   • DOWNLOAD .HTML        — standalone HTML for archive
//
// Everything happens client-side: variables are interpolated into
// template_body / sections, mustache-style {{key}} tokens are replaced,
// missing variables are highlighted in yellow.
//
// EXPORT: window.AgreementPaperGen — { renderHtml, downloadDocx, downloadHtml,
//                                       printPdf, fillFromAgreement }
//          window.AgreementPaperPanel — React component for in-screen use
// ============================================================================
(function () {
  const { useState, useMemo, useEffect } = React;

  // ── Variable resolution ────────────────────────────────────────────────
  function fillFromAgreement(tpl, ag) {
    const out = {};
    if (!tpl || !tpl.variables) return out;
    const today = new Date().toISOString().slice(0, 10);
    const writer = (ag.parties || []).find(p => p.role === 'Assignor' || p.kind === 'writer') || {};
    const pub = (ag.parties || []).find(p => p.role === 'Assignee' || p.kind === 'publisher') || {};
    tpl.variables.forEach(v => {
      switch (v.name) {
        case 'writer_name':    out[v.name] = writer.name || ''; break;
        case 'writer_pro':     out[v.name] = writer.pro  || ''; break;
        case 'writer_ipi':     out[v.name] = writer.ipi  || ''; break;
        case 'pub_share':      out[v.name] = pub.share != null ? String(pub.share) : ''; break;
        case 'writer_share':   out[v.name] = writer.share != null ? String(writer.share) : ''; break;
        case 'territory':      out[v.name] = ag.territory || (ag.territories && ag.territories[0] && ag.territories[0].label) || ''; break;
        case 'term_years':     out[v.name] = (ag.term || '').replace(/[^0-9]/g, ''); break;
        case 'effective_date': out[v.name] = ag.start || today; break;
        case 'jurisdiction':   out[v.name] = ag.jurisdiction || 'NY'; break;
        case 'advance_amount': out[v.name] = ag.value || ''; break;
        case 'audit_window_months': out[v.name] = '24'; break;
        case 'reversion_after_years': out[v.name] = ag.retentionYears || ''; break;
        default: out[v.name] = (v.ex != null ? String(v.ex) : '');
      }
    });
    return out;
  }

  function interp(s, vars, missing) {
    return String(s || '').replace(/\{\{\s*([\w.-]+)\s*\}\}/g, (_, k) => {
      const val = vars[k];
      if (val == null || val === '') {
        if (missing) missing.add(k);
        return `<<${k}>>`;
      }
      return val;
    });
  }

  // ── HTML render ────────────────────────────────────────────────────────
  function renderHtml(tpl, vars, opts) {
    const o = Object.assign({ ifMissingHighlight: true, parties: [], title: tpl.name }, opts || {});
    const missing = new Set();
    const sections = (tpl.sections || []).map(s => ({
      n: s.n, title: s.title,
      body: interp(s.body, vars, missing),
      critical: !!s.critical,
    }));
    const css = `
      body { font: 11.5pt/1.55 Georgia, "Times New Roman", serif; color: #111; max-width: 720px; margin: 0 auto; padding: 48px 56px; background: #fff; }
      h1 { font-size: 22pt; margin: 0 0 4pt; letter-spacing: .02em; }
      .deck { font-size: 10pt; letter-spacing: .12em; text-transform: uppercase; color: #666; margin-bottom: 28pt; }
      h2 { font-size: 12.5pt; margin: 24pt 0 6pt; text-transform: uppercase; letter-spacing: .06em; border-bottom: 1px solid #000; padding-bottom: 3pt; }
      h2 .num { color: #888; font-size: 10pt; margin-right: 8pt; letter-spacing: .1em; }
      p, .sec-body { margin: 8pt 0; text-align: justify; }
      .sigblock { margin-top: 36pt; border-top: 1px solid #000; padding-top: 14pt; }
      .sig { display: flex; justify-content: space-between; margin: 18pt 0; gap: 24pt; }
      .sig > div { flex: 1; border-bottom: 1px solid #000; padding-bottom: 28pt; font-size: 10pt; color: #444; }
      .miss { background: #fff7a8; padding: 1pt 4pt; border: 1px dashed #c89400; }
      .pre { font-family: "IBM Plex Mono", monospace; font-size: 9.5pt; }
      @page { size: Letter; margin: 0.75in; }
      @media print { body { max-width: none; padding: 0; } .nopr { display: none; } }
    `;
    const partiesBlock = (o.parties && o.parties.length) ? `
      <h2><span class="num">PARTIES</span></h2>
      ${o.parties.map(p => `<p><strong>${p.role || ''}</strong> · ${p.name || ''}${p.ipi ? ` · IPI ${p.ipi}` : ''}</p>`).join('')}
    ` : '';
    const body = sections.map(s => `
      <h2><span class="num">${s.n}</span>${s.title}${s.critical ? ' *' : ''}</h2>
      <div class="sec-body">${o.ifMissingHighlight ? s.body.replace(/&lt;&lt;([\w.-]+)&gt;&gt;|<<([\w.-]+)>>/g, (_, a, b) => `<span class="miss">{{${a || b}}}</span>`) : s.body}</div>
    `).join('\n');
    const sigParties = (o.parties && o.parties.length) ? o.parties : [{ name: '__________' }, { name: '__________' }];
    const sig = `
      <div class="sigblock">
        <h2><span class="num">SIGNATURES</span></h2>
        <div class="sig">
          ${sigParties.slice(0, 2).map(p => `<div><div style="font-size:10pt;color:#000;text-transform:uppercase;letter-spacing:.08em;margin-bottom:32pt;">${p.role || 'Party'}</div>By: <em>${p.name || ''}</em><br/>Title: ____________________________<br/>Date: ____________________________</div>`).join('')}
        </div>
      </div>`;
    const html = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>${o.title || tpl.name}</title><style>${css}</style></head>
      <body>
        <h1>${o.title || tpl.name}</h1>
        <div class="deck">${tpl.kindLabel || ''} · v${tpl.version || 1} · Generated ${new Date().toISOString().slice(0,10)}</div>
        ${partiesBlock}
        ${body}
        ${sig}
        <p class="pre" style="margin-top:48pt;color:#888;font-size:8.5pt;">— End of agreement · template ${tpl.id || ''} · ${missing.size ? missing.size + ' unfilled token(s): ' + Array.from(missing).slice(0,12).join(', ') : 'all tokens filled'}</p>
      </body></html>`;
    return { html, missing: Array.from(missing) };
  }

  // ── PDF (print) ────────────────────────────────────────────────────────
  function printPdf(tpl, vars, opts) {
    const { html } = renderHtml(tpl, vars, Object.assign({ ifMissingHighlight: false }, opts || {}));
    const w = window.open('', '_blank');
    w.document.write(html);
    w.document.close();
    w.focus();
    setTimeout(() => { try { w.print(); } catch {} }, 250);
  }

  // ── HTML download ──────────────────────────────────────────────────────
  function downloadHtml(tpl, vars, filename, opts) {
    const { html } = renderHtml(tpl, vars, Object.assign({ ifMissingHighlight: false }, opts || {}));
    const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
    triggerDownload(blob, filename || (tpl.name.replace(/\W+/g, '_') + '.html'));
  }

  // ── DOCX download — minimal Open-XML, single zip ───────────────────────
  // Uses a tiny inline ZIP (uncompressed STORE) so we can keep this dep-free.
  // Word will read it as long as content_types.xml + word/document.xml +
  // word/_rels/document.xml.rels + _rels/.rels are present.
  function downloadDocx(tpl, vars, filename, opts) {
    const o = Object.assign({ parties: [] }, opts || {});
    const missing = new Set();
    const docXml = buildDocXml(tpl, vars, o.parties, missing);
    const files = [
      ['[Content_Types].xml', CONTENT_TYPES],
      ['_rels/.rels', RELS_ROOT],
      ['word/_rels/document.xml.rels', RELS_DOC],
      ['word/document.xml', docXml],
      ['word/styles.xml', STYLES_XML],
    ];
    const zip = makeZip(files);
    triggerDownload(new Blob([zip], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }),
      filename || (tpl.name.replace(/\W+/g, '_') + '.docx'));
  }

  function buildDocXml(tpl, vars, parties, missing) {
    const xmlEsc = s => String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    const para = (text, style) => `<w:p>${style ? `<w:pPr><w:pStyle w:val="${style}"/></w:pPr>` : ''}<w:r><w:t xml:space="preserve">${xmlEsc(text)}</w:t></w:r></w:p>`;
    const head = (text) => `<w:p><w:pPr><w:pStyle w:val="Heading1"/></w:pPr><w:r><w:t xml:space="preserve">${xmlEsc(text)}</w:t></w:r></w:p>`;
    const sub  = (text) => `<w:p><w:pPr><w:pStyle w:val="Heading2"/></w:pPr><w:r><w:t xml:space="preserve">${xmlEsc(text)}</w:t></w:r></w:p>`;

    const partyParas = (parties || []).map(p => para(`${p.role || ''} · ${p.name || ''}${p.ipi ? ' · IPI ' + p.ipi : ''}`)).join('');
    const sectionParas = (tpl.sections || []).map(s => sub(`${s.n}  ${s.title}${s.critical ? ' *' : ''}`) + para(interp(s.body, vars, missing).replace(/<<([\w.-]+)>>/g, '«$1»'))).join('');

    return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
${head(tpl.name)}
${para(`${tpl.kindLabel || ''} · v${tpl.version || 1} · Generated ${new Date().toISOString().slice(0,10)}`, 'Subtitle')}
${parties && parties.length ? sub('PARTIES') + partyParas : ''}
${sectionParas}
${sub('SIGNATURES')}
${(parties || []).slice(0, 2).map(p => para(`${(p.role || '').toUpperCase()}\nBy: ${p.name || '________'}\nTitle: ________________\nDate: ________________`)).join('')}
${para(`— End of agreement · template ${tpl.id || ''} · ${missing.size ? missing.size + ' unfilled token(s)' : 'all tokens filled'}`)}
</w:body>
</w:document>`;
  }

  const CONTENT_TYPES = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml"  ContentType="application/xml"/>
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
<Override PartName="/word/styles.xml"   ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"/>
</Types>`;
  const RELS_ROOT = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>`;
  const RELS_DOC = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
</Relationships>`;
  const STYLES_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:style w:type="paragraph" w:styleId="Heading1"><w:name w:val="heading 1"/><w:rPr><w:b/><w:sz w:val="36"/></w:rPr></w:style>
<w:style w:type="paragraph" w:styleId="Heading2"><w:name w:val="heading 2"/><w:rPr><w:b/><w:sz w:val="26"/><w:caps/></w:rPr></w:style>
<w:style w:type="paragraph" w:styleId="Subtitle"><w:name w:val="Subtitle"/><w:rPr><w:i/><w:color w:val="666666"/></w:rPr></w:style>
</w:styles>`;

  // ── Tiny ZIP (STORE only — uncompressed) ────────────────────────────────
  function makeZip(files) {
    const enc = new TextEncoder();
    const localParts = []; const central = []; let offset = 0;
    function crc32(buf) {
      // Standard CRC-32. Cached table.
      let table = makeZip._t;
      if (!table) {
        table = new Uint32Array(256);
        for (let i = 0; i < 256; i++) {
          let c = i;
          for (let k = 0; k < 8; k++) c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
          table[i] = c;
        }
        makeZip._t = table;
      }
      let c = 0xFFFFFFFF;
      for (let i = 0; i < buf.length; i++) c = table[(c ^ buf[i]) & 0xFF] ^ (c >>> 8);
      return (c ^ 0xFFFFFFFF) >>> 0;
    }
    files.forEach(([name, body]) => {
      const data = typeof body === 'string' ? enc.encode(body) : body;
      const nameBytes = enc.encode(name);
      const crc = crc32(data);
      // Local header
      const lh = new Uint8Array(30 + nameBytes.length);
      const dv = new DataView(lh.buffer);
      dv.setUint32(0, 0x04034b50, true); // PK\003\004
      dv.setUint16(4, 20, true);          // version
      dv.setUint16(6, 0, true);           // flags
      dv.setUint16(8, 0, true);           // method = store
      dv.setUint16(10, 0, true); dv.setUint16(12, 0x21, true); // time/date placeholder
      dv.setUint32(14, crc, true);
      dv.setUint32(18, data.length, true);
      dv.setUint32(22, data.length, true);
      dv.setUint16(26, nameBytes.length, true);
      dv.setUint16(28, 0, true);
      lh.set(nameBytes, 30);
      localParts.push(lh, data);
      // Central dir entry
      const cd = new Uint8Array(46 + nameBytes.length);
      const cv = new DataView(cd.buffer);
      cv.setUint32(0, 0x02014b50, true);
      cv.setUint16(4, 20, true); cv.setUint16(6, 20, true);
      cv.setUint16(8, 0, true);  cv.setUint16(10, 0, true);
      cv.setUint16(12, 0, true); cv.setUint16(14, 0x21, true);
      cv.setUint32(16, crc, true);
      cv.setUint32(20, data.length, true); cv.setUint32(24, data.length, true);
      cv.setUint16(28, nameBytes.length, true);
      cv.setUint32(42, offset, true);
      cd.set(nameBytes, 46);
      central.push(cd);
      offset += lh.length + data.length;
    });
    const cdStart = offset;
    let cdLen = 0; central.forEach(c => cdLen += c.length);
    const eocd = new Uint8Array(22);
    const ev = new DataView(eocd.buffer);
    ev.setUint32(0, 0x06054b50, true);
    ev.setUint16(8, files.length, true); ev.setUint16(10, files.length, true);
    ev.setUint32(12, cdLen, true); ev.setUint32(16, cdStart, true);
    // Concat all
    const total = offset + cdLen + 22;
    const out = new Uint8Array(total);
    let p = 0;
    localParts.forEach(part => { out.set(part, p); p += part.length; });
    central.forEach(c => { out.set(c, p); p += c.length; });
    out.set(eocd, p);
    return out;
  }

  function triggerDownload(blob, name) {
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url; a.download = name; document.body.appendChild(a); a.click();
    setTimeout(() => { URL.revokeObjectURL(url); a.remove(); }, 100);
  }

  // ── Embeddable React panel ─────────────────────────────────────────────
  function AgreementPaperPanel({ agreement }) {
    const tpls = window.AGREEMENT_TEMPLATES || [];
    const [tplId, setTplId] = useState(tpls[0] ? tpls[0].id : '');
    const tpl = useMemo(() => tpls.find(t => t.id === tplId) || tpls[0], [tplId, tpls]);
    const [vars, setVars] = useState(() => agreement && tpl ? fillFromAgreement(tpl, agreement) : {});
    useEffect(() => { if (agreement && tpl) setVars(fillFromAgreement(tpl, agreement)); }, [tplId, agreement && agreement.id]);

    if (!tpl) return <div style={{ padding: 18, color: 'var(--ink-3)' }}>No templates loaded.</div>;
    const { html, missing } = renderHtml(tpl, vars);
    const parties = (agreement && agreement.parties) || [];

    return (
      <div style={{ display: 'grid', gridTemplateColumns: '320px 1fr', gap: 0, border: '1px solid var(--rule)', background: 'var(--bg)' }}>
        {/* Variable form */}
        <div style={{ padding: 14, borderRight: '1px solid var(--rule)', maxHeight: 720, overflow: 'auto' }}>
          <div className="ff-mono upper" style={{ fontSize: 9, color: 'var(--ink-3)', letterSpacing: '.12em', marginBottom: 6 }}>TEMPLATE</div>
          <select value={tplId} onChange={e => setTplId(e.target.value)} style={{ width: '100%', padding: '6px 8px', fontSize: 12, background: 'var(--bg)', border: '1px solid var(--rule)', marginBottom: 14 }}>
            {tpls.map(t => <option key={t.id} value={t.id}>{t.name}</option>)}
          </select>
          <div className="ff-mono upper" style={{ fontSize: 9, color: 'var(--ink-3)', letterSpacing: '.12em', marginBottom: 6 }}>VARIABLES</div>
          {(tpl.variables || []).map(v => {
            const val = vars[v.name] || '';
            const blank = !val;
            return (
              <div key={v.name} style={{ marginBottom: 8 }}>
                <label className="ff-mono" style={{ fontSize: 10, color: blank ? 'var(--danger)' : 'var(--ink-3)', display: 'block', marginBottom: 2 }}>
                  {v.label} {v.required && <span style={{ color: 'var(--danger)' }}>*</span>}
                </label>
                {v.kind === 'enum' ? (
                  <select value={val} onChange={e => setVars({ ...vars, [v.name]: e.target.value })} style={{ width: '100%', padding: '4px 6px', fontSize: 11, background: 'var(--bg)', border: '1px solid ' + (blank && v.required ? 'var(--danger)' : 'var(--rule)') }}>
                    <option value="">— pick —</option>
                    {(v.options || []).map(o => <option key={o} value={o}>{o}</option>)}
                  </select>
                ) : (
                  <input value={val} onChange={e => setVars({ ...vars, [v.name]: e.target.value })} placeholder={v.ex || ''}
                    style={{ width: '100%', padding: '4px 6px', fontSize: 11, fontFamily: v.kind === 'numeric' || v.kind === 'percent' || v.kind === 'currency' ? 'IBM Plex Mono, monospace' : 'inherit', background: 'var(--bg)', border: '1px solid ' + (blank && v.required ? 'var(--danger)' : 'var(--rule)'), boxSizing: 'border-box' }} />
                )}
              </div>
            );
          })}

          <div style={{ marginTop: 16, paddingTop: 12, borderTop: '1px solid var(--rule)' }}>
            <div className="ff-mono upper" style={{ fontSize: 9, color: 'var(--ink-3)', letterSpacing: '.12em', marginBottom: 6 }}>EXPORT</div>
            <button onClick={() => printPdf(tpl, vars, { parties })} className="ff-mono upper" style={btn('var(--ink)', 'var(--bg)')}>Print / Save PDF</button>
            <button onClick={() => downloadDocx(tpl, vars, null, { parties })} className="ff-mono upper" style={btn('transparent', 'var(--ink)')}>Download .docx</button>
            <button onClick={() => downloadHtml(tpl, vars, null, { parties })} className="ff-mono upper" style={btn('transparent', 'var(--ink)')}>Download .html</button>
            {window.dispatchEvent && (
              <button onClick={() => window.dispatchEvent(new CustomEvent('astro-toast', { detail: { kind: 'ok', text: 'Sent for e-signature (mock)' } }))}
                className="ff-mono upper" style={btn('var(--accent)', 'var(--bg)')}>Send for e-sign</button>
            )}
            {missing.length > 0 && (
              <div style={{ marginTop: 10, padding: 8, background: 'oklch(96% 0.04 30)', border: '1px solid var(--danger)' }}>
                <div className="ff-mono upper" style={{ fontSize: 9, color: 'var(--danger)', letterSpacing: '.1em', marginBottom: 4 }}>{missing.length} TOKENS UNFILLED</div>
                <div className="ff-mono" style={{ fontSize: 10, color: 'var(--ink-2)' }}>{missing.slice(0, 8).join(', ')}{missing.length > 8 ? '…' : ''}</div>
              </div>
            )}
          </div>
        </div>

        {/* Live preview */}
        <div style={{ background: '#e8e8e8', padding: 18, maxHeight: 720, overflow: 'auto' }}>
          <iframe srcDoc={html} title="contract-preview" style={{ width: '100%', minHeight: 680, border: '1px solid #ccc', background: '#fff', boxShadow: '0 2px 12px rgba(0,0,0,.08)' }} />
        </div>
      </div>
    );
  }
  function btn(bg, fg) {
    return { width: '100%', padding: '7px 10px', fontSize: 10, letterSpacing: '.12em', background: bg, color: fg, border: '1px solid ' + (bg === 'transparent' ? 'var(--ink)' : bg), cursor: 'pointer', marginBottom: 6 };
  }

  // ── Public API ─────────────────────────────────────────────────────────
  window.AgreementPaperGen = { renderHtml, printPdf, downloadHtml, downloadDocx, fillFromAgreement };
  window.AgreementPaperPanel = AgreementPaperPanel;
})();
