// crm-proposal-template.jsx — Admin-editable proposal template text
// Default copy for the catalog proposal preview (crm-proposal-catalog.jsx
// ProposalPreview). Admins manage one or more named templates via
// Settings → Proposal Template; templates persist as a JSON array
// [{ key, name, tpl }] under config key 'proposal_templates' (the first
// template is also mirrored to the legacy 'proposal_template' key for
// backward compatibility). Each tpl is a partial override merged over
// these defaults at render time.
const { useState, useEffect } = React;

// ── DEFAULT TEMPLATE TEXT ─────────────────────────────────
// Tokens: {client} {industry} {employeeCount} {proposalDate} {validUntil}
// {repName}. {industryClause}/{employeeClause} expand to " — a **X**
// organization" / " with **X** employees" only when the value exists.
// **text** renders bold. \n renders as a line break.
const PT_DEFAULT_TEXT = {
  // Clause variables: what {industryClause}/{employeeClause} expand to when
  // the proposal has an industry / employee count (empty otherwise). Clause
  // text may use any base token; industryOverrides gives a specific industry
  // its own wording.
  clauses: {
    industryClause: ' — a **{industry}** organization',
    employeeClause: ' with **{employeeCount}** employees',
    industryOverrides: [],   // [{ industry:'Healthcare', clause:' — a **healthcare** provider' }]
  },
  cover: {
    eyebrow:  'Workforce Solutions Proposal',
    title:    'Modern Time &\nAttendance for\nYour Team',
    subtitle: 'A tailored proposal for streamlining labor management, ensuring compliance, and reducing administrative overhead.',
  },
  exec: {
    lead: 'CTR/NY is pleased to present this proposal to **{client}**{industryClause}{employeeClause} for the implementation of **Attendance on Demand (AOD)** — a modern, cloud-based workforce management platform purpose-built to simplify time tracking, automate compliance, and deliver actionable labor insights.',
    challenge: '**The challenge most businesses face:** manual timekeeping, inconsistent compliance with state and federal regulations, and fragmented data that makes labor cost decisions difficult. AOD solves all three — from day one.',
    stats: [
      { num:'100%', label:'Cloud-Based' },
      { num:'4+',   label:'Payroll Integrations' },
      { num:'$99',  label:'Starting / Month' },
      { num:'1+',   label:'Days to Deploy' },
    ],
    features: [
      { title:'Time & Attendance Tracking', text:'Accurate, real-time punch data collected from mobile, kiosk, or biometric hardware — your choice.' },
      { title:'Regulatory Compliance',      text:'Built-in ACA tracking, NYS Spread of Hours automation, and state-mandated sick leave accruals.' },
      { title:'Payroll Integration',        text:'Certified integrations with ADP, Paylocity, Paychex, and most leading payroll providers via REST/SOAP APIs.' },
      { title:'Executive Dashboards',       text:'Real-time exception alerts and Excel-based reporting with OLE automation for deeper labor analysis.' },
    ],
  },
  capabilities: {
    lead: 'AOD is a comprehensive platform designed to handle every facet of workforce time management. Below is a complete overview of core system capabilities included with your subscription.',
    groups: [
      { heading:'Compliance & Regulatory', bullets: [
        'Affordable Care Act (ACA) tracking and reporting',
        'Automation of NYS Spread of Hours compliance',
        'Paid Time Off (PTO) and sick leave accrual automation',
        'Built-in state-mandated sick leave accrual compliance',
      ]},
      { heading:'Platform & Infrastructure', bullets: [
        'Secure cloud infrastructure — no on-premise footprint',
        'Advanced labor distribution by department, job, work orders and pay rate',
        'Configurable employee and operator preferences',
        'Full SOAP and REST API availability',
        'Certified integrations with ADP, Paylocity, Paychex and leading payroll providers',
      ]},
      { heading:'Reporting & Analytics', bullets: [
        'Executive dashboards with real-time exception alerts',
        'Excel-based reporting with OLE automation',
        'Scheduling and incident & point tracking',
        'Leave management workflows',
      ]},
      { heading:'Employee Self-Service', bullets: [
        'Mobile clock-in via iPhone/Android with geofencing',
        'ESS Kiosk — turn any tablet or PC into a time clock',
        'Timecard review and schedule visibility',
        'Geo-fencing to prevent unauthorized location punches',
      ]},
    ],
  },
  pricing: {
    lead: 'AOD pricing is designed to scale with your business — starting as low as **$99/month flat** for small teams and becoming even more cost-effective as your workforce grows.',
    tiers: [
      { match:'1-25',   label:'1–25 Employees',   model:'Flat monthly rate',    cost:'$99.00' },
      { match:'26-50',  label:'26–50 Employees',  model:'Per employee / month', cost:'$4.00 / emp' },
      { match:'51-100', label:'51–100 Employees', model:'Per employee / month', cost:'$3.50 / emp' },
      { match:'100+',   label:'100+ Employees',   model:'Per employee / month', cost:'$2.50 / emp' },
    ],
    priceNote: 'Minimum monthly platform fee of $99 applies. First and last month software fees billed at implementation.',
    includedHeading: 'Included at No Additional Cost',
    included: [
      { item:'ESS Kiosk',                description:'iPad / Android / PC — BYOD time clock',        price:'Included' },
      { item:'ESS Mobile',               description:'iPhone/Android clock-in with geofencing',       price:'Included' },
      { item:'Extended Online Training', description:'Instructor-led onboarding session',             price:'Included' },
    ],
  },
  hardware: {
    lead: 'For organizations that prefer dedicated hardware, AOD supports a range of biometric terminals. All GT Clocks include **GT Protect** — advance replacement and extended warranty 2 years past manufacturer retirement.',
    installNote: 'On-site hardware installation available — $250/terminal flat fee (NY/NJ/CT and select markets, travel not included). GT Protect ($25/mo per device) required for all purchased GT terminals.',
  },
  terms: {
    bullets: [
      'This proposal is valid for **30 days** from the issue date of {proposalDate}.',
      'First and last month software fees are billed at implementation.',
      'Monthly fees are processed via **ACH auto-debit**.',
      'A minimum monthly platform fee of **$99** applies regardless of employee count.',
      'A **30-day written notice** is required for cancellation.',
      'GT Protect ($25/mo per device) is required for all purchased GT terminal hardware.',
      'Rental terminals do not require GT Protect. Additional rates may apply.',
    ],
  },
  cta: {
    eyebrow: "Let's Get Started",
    heading: 'Ready to modernize\nyour workforce?',
    body: 'We appreciate the opportunity to support your workforce management initiatives. Our team is available to review this proposal, demonstrate the platform, and finalize implementation timelines at your convenience.',
    steps: [
      { title:'Review & Sign',    desc:'Review this proposal and sign the corresponding order forms to proceed.' },
      { title:'Discovery Call',   desc:'Schedule a policy review so our technicians can custom tailor AOD to your specific requirements.' },
      { title:'Setup & Go Live',  desc:'Configuration, training, and launch — typically completed in days.' },
    ],
  },
};

// ── RENDER HELPERS ────────────────────────────────────────
function ptEsc(s) {
  return String(s == null ? '' : s)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;');
}

// Substitute {tokens}. When `esc` is true, substituted values are
// HTML-escaped (the surrounding string must already be escaped) and the
// conditional clauses carry **bold** markers for the later pass.
function ptFillTokens(str, ctx, esc) {
  const c = ctx || {};
  const v = k => esc ? ptEsc(c[k] || '') : String(c[k] || '');
  // Clause wording comes from the editable template (ctx.clauses); a
  // per-industry override beats the default. The clause TEMPLATE is inserted
  // here and its inner tokens ({industry} etc.) fill in the passes below.
  const cls = c.clauses || PT_DEFAULT_TEXT.clauses;
  const insertClause = t => esc ? ptEsc(t || '') : String(t || '');
  const indOverride = c.industry
    ? (cls.industryOverrides || []).find(o =>
        o && o.industry && o.clause &&
        String(o.industry).toLowerCase() === String(c.industry).toLowerCase())
    : null;
  const industryClause = c.industry
    ? insertClause(indOverride ? indOverride.clause : cls.industryClause)
    : '';
  const employeeClause = c.employeeCount ? insertClause(cls.employeeClause) : '';
  return String(str == null ? '' : str)
    .replace(/\{industryClause\}/g, () => industryClause)
    .replace(/\{employeeClause\}/g, () => employeeClause)
    .replace(/\{client\}/g,        () => v('client'))
    .replace(/\{industry\}/g,      () => v('industry'))
    .replace(/\{employeeCount\}/g, () => v('employeeCount'))
    .replace(/\{proposalDate\}/g,  () => v('proposalDate'))
    .replace(/\{validUntil\}/g,    () => v('validUntil'))
    .replace(/\{repName\}/g,       () => v('repName'));
}

// Template string → React node. Escapes HTML, fills tokens, converts
// **text** to <strong> and \n to <br/>.
function ptRender(str, ctx) {
  let h = ptEsc(str);
  h = ptFillTokens(h, ctx, true);
  h = h.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
  h = h.replace(/\n/g, '<br/>');
  return <span dangerouslySetInnerHTML={{ __html: h }} />;
}

// Plain-string variant — tokens only, bold markers stripped, no HTML.
function ptFillPlain(str, ctx) {
  return ptFillTokens(str, ctx, false).replace(/\*\*(.+?)\*\*/g, '$1');
}

// Deep-merge a saved partial template over the defaults. Saved values
// win; arrays replace wholesale (no per-index merging). Always returns
// fresh objects/arrays so callers can safely use it as React state.
function mergeTemplate(saved) {
  const clone = v => JSON.parse(JSON.stringify(v));
  const merge = (def, sv) => {
    if (Array.isArray(def)) return Array.isArray(sv) ? clone(sv) : clone(def);
    if (def && typeof def === 'object') {
      const out = {};
      Object.keys(def).forEach(k => {
        const has = sv && typeof sv === 'object' && !Array.isArray(sv) && sv[k] !== undefined && sv[k] !== null;
        out[k] = has ? merge(def[k], sv[k]) : clone(def[k]);
      });
      return out;
    }
    return typeof sv === typeof def ? sv : def;
  };
  return merge(PT_DEFAULT_TEXT, saved || null);
}

// ── NAMED TEMPLATE STORAGE ────────────────────────────────
// Config key 'proposal_templates' = JSON array [{ key, name, tpl }] where
// tpl is a (possibly partial) template object. Legacy single-template key
// 'proposal_template' is migrated on read and mirrored on save.

// cfgMap (key → string, from CRM.ConfigAPI.getAll()) → array of
// { key, name, tpl } with tpl FULLY MERGED over the defaults.
// Always returns ≥1 entry; the first entry is the default template.
function loadProposalTemplates(cfgMap) {
  const cfg = cfgMap || {};
  let list = null;
  if (cfg['proposal_templates']) {
    try {
      const parsed = JSON.parse(cfg['proposal_templates']);
      if (Array.isArray(parsed) && parsed.length) list = parsed;
    } catch (e) { /* fall through to legacy/defaults */ }
  }
  if (!list && cfg['proposal_template']) {
    try {
      list = [{ key: 'default', name: 'Standard', tpl: JSON.parse(cfg['proposal_template']) }];
    } catch (e) { /* fall through to defaults */ }
  }
  if (!list || !list.length) list = [{ key: 'default', name: 'Standard', tpl: null }];
  return list.map((t, i) => ({
    key:  String((t && t.key)  || 'tpl-' + (i + 1)),
    name: String((t && t.name) || 'Template ' + (i + 1)),
    tpl:  mergeTemplate(t && t.tpl),
  }));
}

// cfgMap + template key → that template's fully-merged tpl. Falls back to
// the FIRST template when key is null/unknown.
function resolveProposalTemplate(cfgMap, key) {
  const list = loadProposalTemplates(cfgMap);
  const found = key != null ? list.find(t => t.key === key) : null;
  return (found || list[0]).tpl;
}

// ── ADMIN EDITOR ──────────────────────────────────────────
function ProposalTemplateEditor({ showToast }) {
  const [tpls, setTpls]     = useState(null);   // [{ key, name, tpl }] — whole array in state
  const [selKey, setSelKey] = useState(null);   // key of the template being edited
  const [saving, setSaving] = useState(false);

  useEffect(() => {
    const applyList = list => { setTpls(list); setSelKey(list[0].key); };
    CRM.ConfigAPI.getAll()
      .then(cfg => applyList(loadProposalTemplates(cfg)))
      .catch(() => applyList(loadProposalTemplates(null)));
  }, []);

  const selIdx = tpls ? Math.max(0, tpls.findIndex(t => t.key === selKey)) : 0;
  const tpl    = tpls ? tpls[selIdx].tpl : null;

  // Update the SELECTED template's tpl (same updater signature the
  // section-field helpers below rely on).
  const setTpl = fn =>
    setTpls(list => list.map((t, i) => i !== selIdx ? t : { ...t, tpl: fn(t.tpl) }));

  // ── Template management (array-level) ──
  const slugify = s =>
    String(s || '').toLowerCase().trim().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'template';
  const uniqueKey = (base, list) => {
    let k = base, n = 2;
    while (list.some(t => t.key === k)) k = base + '-' + (n++);
    return k;
  };

  const copyTemplate = () => {
    const cur = tpls[selIdx];
    const name = prompt('Name for the new template:', cur.name + ' (Copy)');
    if (!name || !name.trim()) return;
    const key = uniqueKey(slugify(name), tpls);
    setTpls(list => [...list, { key, name: name.trim(), tpl: JSON.parse(JSON.stringify(cur.tpl)) }]);
    setSelKey(key);
  };

  const renameTemplate = () => {
    const name = prompt('Rename template:', tpls[selIdx].name);
    if (!name || !name.trim()) return;
    setTpls(list => list.map((t, i) => i !== selIdx ? t : { ...t, name: name.trim() }));
  };

  const deleteTemplate = () => {
    if (tpls.length <= 1) { showToast('At least one template is required — cannot delete the last one.'); return; }
    if (!confirm('Delete template "' + tpls[selIdx].name + '"? Click Save Template to make it permanent.')) return;
    const next = tpls.filter((_, i) => i !== selIdx);
    setTpls(next);
    setSelKey(next[0].key);
  };

  const setField = (section, key, v) =>
    setTpl(t => ({ ...t, [section]: { ...t[section], [key]: v } }));

  // Array-of-objects item update; pass field=null for string arrays.
  const setItem = (section, key, idx, field, v) =>
    setTpl(t => ({ ...t, [section]: { ...t[section],
      [key]: t[section][key].map((it, i) => i !== idx ? it : (field == null ? v : { ...it, [field]: v })) } }));

  const addItem = (section, key, blank) =>
    setTpl(t => ({ ...t, [section]: { ...t[section], [key]: [...t[section][key], blank] } }));

  const removeItem = (section, key, idx) =>
    setTpl(t => ({ ...t, [section]: { ...t[section], [key]: t[section][key].filter((_, i) => i !== idx) } }));

  const save = async () => {
    setSaving(true);
    try {
      // Drop empty rows/lines before persisting — every template in the array
      const cleaned = tpls.map(entry => {
        const clean = JSON.parse(JSON.stringify(entry.tpl));
        clean.capabilities.groups = clean.capabilities.groups.map(g =>
          ({ ...g, bullets: (g.bullets || []).filter(b => String(b).trim() !== '') }));
        clean.terms.bullets = (clean.terms.bullets || []).filter(b => String(b).trim() !== '');
        clean.clauses.industryOverrides = (clean.clauses.industryOverrides || [])
          .filter(o => o && String(o.industry || '').trim() && String(o.clause || '').trim());
        return { key: entry.key, name: entry.name, tpl: clean };
      });
      await CRM.ConfigAPI.set('proposal_templates', JSON.stringify(cleaned));
      // Legacy single-template key: mirror the FIRST (default) template so
      // cached clients still reading 'proposal_template' keep working.
      await CRM.ConfigAPI.set('proposal_template', JSON.stringify(cleaned[0].tpl));
      setTpls(cleaned);
      showToast('Proposal templates saved!', 'success');
    } catch (e) {
      showToast('Error saving templates.');
    } finally {
      setSaving(false);
    }
  };

  const reset = () => {
    if (!confirm('Reset "' + tpls[selIdx].name + '" template text to defaults?')) return;
    setTpl(() => mergeTemplate(null));
    showToast('Reset to defaults — click Save Template to apply.', 'success');
  };

  if (!tpl) return <div style={{ padding:40, color:'var(--g400)', textAlign:'center' }}>Loading proposal template…</div>;

  // Shared styles (match the setup-items table editing pattern)
  const inp   = { width:'100%', fontSize:12, padding:'5px 7px', border:'1.5px solid var(--g200)', borderRadius:4 };
  const ta    = { ...inp, resize:'vertical', fontFamily:'inherit', lineHeight:1.5 };
  const thS   = { textAlign:'left', padding:'6px 8px', color:'var(--g400)', fontWeight:600 };
  const tdS   = { padding:'4px 4px', verticalAlign:'top' };
  const xBtn  = { background:'none', border:'none', cursor:'pointer', color:'var(--g400)', fontSize:16, lineHeight:1, padding:2 };
  const sectionHd = label => (
    <div style={{ fontSize:11, fontWeight:700, color:'var(--g600)', textTransform:'uppercase', letterSpacing:'0.06em', marginBottom:8 }}>{label}</div>
  );
  const fieldRow = (label, node) => (
    <div className="field" style={{ marginBottom:10 }}>
      <label>{label}</label>
      {node}
    </div>
  );
  const listTable = (cols, rows) => (
    <table style={{ width:'100%', borderCollapse:'collapse', fontSize:13 }}>
      <thead>
        <tr style={{ background:'var(--off)' }}>
          {cols.map((c, i) => <th key={i} style={{ ...thS, width: c.width }}>{c.label}</th>)}
          <th style={{ width:28 }}></th>
        </tr>
      </thead>
      <tbody>{rows}</tbody>
    </table>
  );

  // ── Industry clause table helpers ──
  // Overrides keep the [{ industry, clause }] shape (ptFillTokens depends
  // on it); lookups are case-insensitive to match render-time behavior.
  const findOverride = ind => (tpl.clauses.industryOverrides || []).find(o =>
    o && String(o.industry || '').toLowerCase() === String(ind).toLowerCase());
  // The default clause RESOLVED for one industry — i.e. the actual text
  // that gets inserted into the proposal. Shown (and compared) per row so
  // users edit real sentences, not token templates.
  const resolvedDefault = ind =>
    String(tpl.clauses.industryClause || '').replace(/\{industry\}/g, ind);
  const setIndustryClause = (industry, value) => setTpl(t => {
    const rest = (t.clauses.industryOverrides || []).filter(o =>
      !(o && String(o.industry || '').toLowerCase() === String(industry).toLowerCase()));
    // Typing the text back to EXACTLY the resolved default removes the
    // override instead of storing a redundant copy.
    const def = String(t.clauses.industryClause || '').replace(/\{industry\}/g, industry);
    const next = value === def ? rest : [...rest, { industry, clause: value }];
    return { ...t, clauses: { ...t.clauses, industryOverrides: next } };
  });
  const clearIndustryClause = industry => setTpl(t => ({ ...t, clauses: { ...t.clauses,
    industryOverrides: (t.clauses.industryOverrides || []).filter(o =>
      !(o && String(o.industry || '').toLowerCase() === String(industry).toLowerCase())) } }));

  return (
    <div>
      {/* Header actions */}
      <div style={{ fontSize:13, color:'var(--g600)', marginBottom:10 }}>Edit the standard text shown on generated proposals. Keep multiple named templates and pick one per proposal.</div>
      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:16, gap:12, flexWrap:'wrap' }}>
        <div style={{ display:'flex', alignItems:'center', gap:8 }}>
          <select style={{ ...inp, width:'auto', minWidth:180, fontWeight:600 }} value={tpls[selIdx].key} onChange={e => setSelKey(e.target.value)}>
            {tpls.map(t => <option key={t.key} value={t.key}>{t.name}</option>)}
          </select>
          <button className="btn btn-ghost btn-sm" onClick={copyTemplate}>＋ Copy</button>
          <button className="btn btn-ghost btn-sm" onClick={renameTemplate}>Rename</button>
          <button className="btn btn-ghost btn-sm" onClick={deleteTemplate}>Delete</button>
        </div>
        <div style={{ display:'flex', gap:8 }}>
          <button className="btn btn-ghost btn-sm" onClick={reset}>Reset to Defaults</button>
          <button className="btn btn-primary btn-sm" onClick={save} disabled={saving}>{saving ? 'Saving…' : 'Save Template'}</button>
        </div>
      </div>

      {/* Help note */}
      <div className="card card-pad" style={{ marginBottom:16, background:'var(--off)' }}>
        <div style={{ fontSize:12, color:'var(--g600)', lineHeight:1.7 }}>
          <strong>Placeholders:</strong> use <code>{'{client}'}</code> <code>{'{industry}'}</code> <code>{'{employeeCount}'}</code> <code>{'{proposalDate}'}</code> <code>{'{validUntil}'}</code> <code>{'{repName}'}</code> anywhere in the text — they fill in per-proposal at view/print time.
          Wrap text in <code>**double asterisks**</code> to render it <strong>bold</strong>.
          <br /><code>{'{industryClause}'}</code> and <code>{'{employeeClause}'}</code> expand to the clause wording below (empty when the proposal has no industry / employee count).
          <br />Edits apply to newly <strong>rendered</strong> proposals — the template is applied when a proposal is viewed or printed, so existing proposals pick up changes on their next print.
        </div>
      </div>

      {/* ── Clause variables ── */}
      {sectionHd('Clause Variables')}
      <div className="card card-pad" style={{ marginBottom:16 }}>
        {fieldRow(<>Industry Clause <span style={{ fontWeight:400, color:'var(--g400)', fontSize:11 }}>— inserted for {'{industryClause}'}; use {'{industry}'} inside it</span></>,
          <input style={inp} value={tpl.clauses.industryClause} onChange={e => setField('clauses', 'industryClause', e.target.value)} />)}
        {fieldRow(<>Employee Clause <span style={{ fontWeight:400, color:'var(--g400)', fontSize:11 }}>— inserted for {'{employeeClause}'}; use {'{employeeCount}'} inside it</span></>,
          <input style={inp} value={tpl.clauses.employeeClause} onChange={e => setField('clauses', 'employeeClause', e.target.value)} />)}

        <div style={{ fontSize:12, fontWeight:700, color:'var(--g600)', margin:'14px 0 6px' }}>Industry Clauses</div>
        <div style={{ fontSize:12, color:'var(--g400)', marginBottom:8 }}>
          Each industry uses the default Industry Clause above unless you customize its wording here.
        </div>
        <table style={{ width:'100%', borderCollapse:'collapse', fontSize:13 }}>
          <thead>
            <tr style={{ background:'var(--off)' }}>
              <th style={{ ...thS, width:220 }}>Industry</th>
              <th style={thS}>Clause</th>
              <th style={{ ...thS, width:120 }}></th>
            </tr>
          </thead>
          <tbody>
            {(CRM.INDUSTRIES || []).map(ind => {
              const ov = findOverride(ind);
              return (
                <tr key={ind}>
                  <td style={{ ...tdS, paddingTop:9, fontWeight:600, color:'var(--navy)' }}>{ind}</td>
                  <td style={tdS}>
                    <input style={ov ? { ...inp, border:'1.5px solid var(--navy)' } : inp}
                      value={ov ? ov.clause : resolvedDefault(ind)}
                      onChange={e => setIndustryClause(ind, e.target.value)} />
                  </td>
                  <td style={{ ...tdS, whiteSpace:'nowrap', paddingTop:7 }}>
                    {ov ? (
                      <>
                        <span style={{ fontSize:10, fontWeight:700, color:'var(--navy)', textTransform:'uppercase', letterSpacing:'0.05em', marginRight:6 }}>custom</span>
                        <button className="btn btn-ghost btn-sm" title="Revert to the default clause" onClick={() => clearIndustryClause(ind)}>↺ Default</button>
                      </>
                    ) : (
                      <span style={{ fontSize:11, color:'var(--g400)' }}>default</span>
                    )}
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
        <div style={{ fontSize:11, color:'var(--g400)', marginTop:8 }}>
          Industries are managed in <strong>Settings → Field Options → Industries</strong>.
        </div>
      </div>

      {/* ── Cover ── */}
      {sectionHd('Cover Page')}
      <div className="card card-pad" style={{ marginBottom:16 }}>
        {fieldRow('Eyebrow', <input style={inp} value={tpl.cover.eyebrow} onChange={e => setField('cover', 'eyebrow', e.target.value)} />)}
        {fieldRow('Title (line breaks preserved)',
          <textarea style={ta} rows={3} value={tpl.cover.title} onChange={e => setField('cover', 'title', e.target.value)} />)}
        {fieldRow('Subtitle',
          <textarea style={ta} rows={2} value={tpl.cover.subtitle} onChange={e => setField('cover', 'subtitle', e.target.value)} />)}
      </div>

      {/* ── Executive Summary ── */}
      {sectionHd('Executive Summary')}
      <div className="card card-pad" style={{ marginBottom:16 }}>
        {fieldRow('Lead Paragraph',
          <textarea style={ta} rows={4} value={tpl.exec.lead} onChange={e => setField('exec', 'lead', e.target.value)} />)}
        {fieldRow('Challenge Callout',
          <textarea style={ta} rows={3} value={tpl.exec.challenge} onChange={e => setField('exec', 'challenge', e.target.value)} />)}

        <div style={{ fontSize:12, fontWeight:700, color:'var(--g600)', margin:'14px 0 6px' }}>Stat Strip</div>
        {listTable([{ label:'Number', width:140 }, { label:'Label' }],
          tpl.exec.stats.map((s, i) => (
            <tr key={i}>
              <td style={tdS}><input style={inp} value={s.num} onChange={e => setItem('exec', 'stats', i, 'num', e.target.value)} placeholder="100%" /></td>
              <td style={tdS}><input style={inp} value={s.label} onChange={e => setItem('exec', 'stats', i, 'label', e.target.value)} placeholder="Label" /></td>
              <td style={{ ...tdS, textAlign:'center' }}><button style={xBtn} title="Remove" onClick={() => removeItem('exec', 'stats', i)}>×</button></td>
            </tr>
          )))}
        <button className="btn btn-ghost btn-sm" style={{ marginTop:8 }} onClick={() => addItem('exec', 'stats', { num:'', label:'' })}>+ Add Stat</button>

        <div style={{ fontSize:12, fontWeight:700, color:'var(--g600)', margin:'18px 0 6px' }}>Feature Boxes</div>
        {listTable([{ label:'Title', width:220 }, { label:'Text' }],
          tpl.exec.features.map((f, i) => (
            <tr key={i}>
              <td style={tdS}><input style={inp} value={f.title} onChange={e => setItem('exec', 'features', i, 'title', e.target.value)} placeholder="Feature title" /></td>
              <td style={tdS}><textarea style={ta} rows={2} value={f.text} onChange={e => setItem('exec', 'features', i, 'text', e.target.value)} placeholder="Feature description" /></td>
              <td style={{ ...tdS, textAlign:'center' }}><button style={xBtn} title="Remove" onClick={() => removeItem('exec', 'features', i)}>×</button></td>
            </tr>
          )))}
        <button className="btn btn-ghost btn-sm" style={{ marginTop:8 }} onClick={() => addItem('exec', 'features', { title:'', text:'' })}>+ Add Feature</button>
      </div>

      {/* ── Key Capabilities ── */}
      {sectionHd('Key Capabilities')}
      <div className="card card-pad" style={{ marginBottom:16 }}>
        {fieldRow('Lead Paragraph',
          <textarea style={ta} rows={2} value={tpl.capabilities.lead} onChange={e => setField('capabilities', 'lead', e.target.value)} />)}
        {tpl.capabilities.groups.map((g, gi) => (
          <div key={gi} style={{ border:'1px solid var(--g200)', borderRadius:6, padding:'10px 12px', marginBottom:10 }}>
            <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:8 }}>
              <span style={{ fontSize:11, color:'var(--g400)', width:90, flexShrink:0 }}>Heading</span>
              <input style={{ ...inp, fontWeight:600 }} value={g.heading}
                onChange={e => setItem('capabilities', 'groups', gi, 'heading', e.target.value)} placeholder="Group heading" />
              <button style={xBtn} title="Remove group" onClick={() => removeItem('capabilities', 'groups', gi)}>×</button>
            </div>
            <div style={{ display:'flex', alignItems:'flex-start', gap:8 }}>
              <span style={{ fontSize:11, color:'var(--g400)', width:90, flexShrink:0, marginTop:5 }}>Bullets</span>
              <textarea style={ta} placeholder="One bullet point per line"
                rows={Math.max(3, (g.bullets || []).length)}
                value={(g.bullets || []).join('\n')}
                onChange={e => setItem('capabilities', 'groups', gi, 'bullets', e.target.value.split('\n'))} />
            </div>
          </div>
        ))}
        <button className="btn btn-ghost btn-sm" onClick={() => addItem('capabilities', 'groups', { heading:'', bullets:[] })}>+ Add Group</button>
      </div>

      {/* ── Pricing ── */}
      {sectionHd('Pricing')}
      <div className="card card-pad" style={{ marginBottom:16 }}>
        {fieldRow('Lead Paragraph',
          <textarea style={ta} rows={2} value={tpl.pricing.lead} onChange={e => setField('pricing', 'lead', e.target.value)} />)}

        <div style={{ fontSize:12, fontWeight:700, color:'var(--g600)', margin:'14px 0 6px' }}>Subscription Tiers</div>
        <div style={{ fontSize:11, color:'var(--g400)', marginBottom:6 }}>
          "Match" is compared against the proposal's Employee Count selection to highlight the client's tier (e.g. <em>1-25</em>, <em>26-50</em>, <em>100+</em>).
        </div>
        {listTable([{ label:'Match', width:100 }, { label:'Employee Count' }, { label:'Pricing Model' }, { label:'Monthly Cost', width:130 }],
          tpl.pricing.tiers.map((t, i) => (
            <tr key={i}>
              <td style={tdS}><input style={inp} value={t.match} onChange={e => setItem('pricing', 'tiers', i, 'match', e.target.value)} placeholder="1-25" /></td>
              <td style={tdS}><input style={inp} value={t.label} onChange={e => setItem('pricing', 'tiers', i, 'label', e.target.value)} placeholder="1–25 Employees" /></td>
              <td style={tdS}><input style={inp} value={t.model} onChange={e => setItem('pricing', 'tiers', i, 'model', e.target.value)} placeholder="Flat monthly rate" /></td>
              <td style={tdS}><input style={{ ...inp, textAlign:'right' }} value={t.cost} onChange={e => setItem('pricing', 'tiers', i, 'cost', e.target.value)} placeholder="$99.00" /></td>
              <td style={{ ...tdS, textAlign:'center' }}><button style={xBtn} title="Remove" onClick={() => removeItem('pricing', 'tiers', i)}>×</button></td>
            </tr>
          )))}
        <button className="btn btn-ghost btn-sm" style={{ marginTop:8 }} onClick={() => addItem('pricing', 'tiers', { match:'', label:'', model:'', cost:'' })}>+ Add Tier</button>

        <div style={{ marginTop:14 }}>
          {fieldRow('Price Note', <input style={inp} value={tpl.pricing.priceNote} onChange={e => setField('pricing', 'priceNote', e.target.value)} />)}
          {fieldRow('Included Section Heading', <input style={inp} value={tpl.pricing.includedHeading} onChange={e => setField('pricing', 'includedHeading', e.target.value)} />)}
        </div>

        <div style={{ fontSize:12, fontWeight:700, color:'var(--g600)', margin:'4px 0 6px' }}>Included Items</div>
        {listTable([{ label:'Item', width:200 }, { label:'Description' }, { label:'Price', width:110 }],
          tpl.pricing.included.map((it, i) => (
            <tr key={i}>
              <td style={tdS}><input style={inp} value={it.item} onChange={e => setItem('pricing', 'included', i, 'item', e.target.value)} placeholder="Item" /></td>
              <td style={tdS}><input style={inp} value={it.description} onChange={e => setItem('pricing', 'included', i, 'description', e.target.value)} placeholder="Description" /></td>
              <td style={tdS}><input style={{ ...inp, textAlign:'right' }} value={it.price} onChange={e => setItem('pricing', 'included', i, 'price', e.target.value)} placeholder="Included" /></td>
              <td style={{ ...tdS, textAlign:'center' }}><button style={xBtn} title="Remove" onClick={() => removeItem('pricing', 'included', i)}>×</button></td>
            </tr>
          )))}
        <button className="btn btn-ghost btn-sm" style={{ marginTop:8 }} onClick={() => addItem('pricing', 'included', { item:'', description:'', price:'' })}>+ Add Item</button>
      </div>

      {/* ── Hardware ── */}
      {sectionHd('Hardware Options')}
      <div className="card card-pad" style={{ marginBottom:16 }}>
        {fieldRow('Lead Paragraph',
          <textarea style={ta} rows={3} value={tpl.hardware.lead} onChange={e => setField('hardware', 'lead', e.target.value)} />)}
        {fieldRow('Installation Note (shown under hardware cards)',
          <textarea style={ta} rows={2} value={tpl.hardware.installNote} onChange={e => setField('hardware', 'installNote', e.target.value)} />)}
      </div>

      {/* ── Terms & Conditions ── */}
      {sectionHd('Terms & Conditions')}
      <div className="card card-pad" style={{ marginBottom:16 }}>
        <div style={{ fontSize:11, color:'var(--g400)', marginBottom:6 }}>Items are numbered automatically (01, 02, …) in the order listed.</div>
        {listTable([{ label:'#', width:36 }, { label:'Term' }],
          tpl.terms.bullets.map((b, i) => (
            <tr key={i}>
              <td style={{ ...tdS, fontSize:11, fontWeight:700, color:'var(--g400)', paddingTop:10 }}>{String(i + 1).padStart(2, '0')}</td>
              <td style={tdS}><textarea style={ta} rows={2} value={b} onChange={e => setItem('terms', 'bullets', i, null, e.target.value)} placeholder="Term text" /></td>
              <td style={{ ...tdS, textAlign:'center' }}><button style={xBtn} title="Remove" onClick={() => removeItem('terms', 'bullets', i)}>×</button></td>
            </tr>
          )))}
        <button className="btn btn-ghost btn-sm" style={{ marginTop:8 }} onClick={() => addItem('terms', 'bullets', '')}>+ Add Term</button>
      </div>

      {/* ── Closing CTA ── */}
      {sectionHd('Closing CTA Page')}
      <div className="card card-pad" style={{ marginBottom:16 }}>
        {fieldRow('Eyebrow', <input style={inp} value={tpl.cta.eyebrow} onChange={e => setField('cta', 'eyebrow', e.target.value)} />)}
        {fieldRow('Heading (line breaks preserved)',
          <textarea style={ta} rows={2} value={tpl.cta.heading} onChange={e => setField('cta', 'heading', e.target.value)} />)}
        {fieldRow('Body',
          <textarea style={ta} rows={3} value={tpl.cta.body} onChange={e => setField('cta', 'body', e.target.value)} />)}

        <div style={{ fontSize:12, fontWeight:700, color:'var(--g600)', margin:'14px 0 6px' }}>Next Steps</div>
        <div style={{ fontSize:11, color:'var(--g400)', marginBottom:6 }}>"Step 01 / 02 / 03" numbers are added automatically.</div>
        {listTable([{ label:'Title', width:200 }, { label:'Description' }],
          tpl.cta.steps.map((s, i) => (
            <tr key={i}>
              <td style={tdS}><input style={inp} value={s.title} onChange={e => setItem('cta', 'steps', i, 'title', e.target.value)} placeholder="Step title" /></td>
              <td style={tdS}><textarea style={ta} rows={2} value={s.desc} onChange={e => setItem('cta', 'steps', i, 'desc', e.target.value)} placeholder="Step description" /></td>
              <td style={{ ...tdS, textAlign:'center' }}><button style={xBtn} title="Remove" onClick={() => removeItem('cta', 'steps', i)}>×</button></td>
            </tr>
          )))}
        <button className="btn btn-ghost btn-sm" style={{ marginTop:8 }} onClick={() => addItem('cta', 'steps', { title:'', desc:'' })}>+ Add Step</button>
      </div>

      {/* Bottom actions */}
      <div style={{ display:'flex', gap:10 }}>
        <button className="btn btn-primary" onClick={save} disabled={saving}>{saving ? 'Saving…' : 'Save Template'}</button>
        <button className="btn btn-ghost" onClick={reset}>Reset to Defaults</button>
      </div>
    </div>
  );
}

Object.assign(window, { ProposalTemplateEditor, PT_DEFAULT_TEXT, ptRender, ptFillPlain, mergeTemplate, loadProposalTemplates, resolveProposalTemplate });
