// Invoice generator — paper-first WYSIWYG editor
const { useState, useEffect, useRef, useMemo, useCallback } = React;

/* ============ Auto-grow textarea ============ */
function AutoTextarea({ value, onChange, className, placeholder, rows = 1, onKeyDown, ...rest }) {
  return (
    <textarea
      className={className}
      value={value || ''}
      onChange={e => onChange(e.target.value)}
      placeholder={placeholder}
      rows={rows}
      onKeyDown={onKeyDown}
      {...rest}
    />
  );
}

/* ============ Logo slot ============ */
function LogoSlot({ logo, onChange }) {
  const fileRef = useRef();
  const [over, setOver] = useState(false);

  const onPick = () => fileRef.current?.click();
  const onFile = (file) => {
    if (!file || !file.type.startsWith('image/')) return;
    const reader = new FileReader();
    reader.onload = (e) => onChange(e.target.result);
    reader.readAsDataURL(file);
  };

  return (
    <div
      className={`paper-logo-slot ${logo ? 'has-img' : ''} ${over ? 'file-over' : ''}`}
      onClick={logo ? undefined : onPick}
      onDragOver={(e) => { e.preventDefault(); setOver(true); }}
      onDragLeave={() => setOver(false)}
      onDrop={(e) => {
        e.preventDefault(); setOver(false);
        onFile(e.dataTransfer.files?.[0]);
      }}
    >
      {logo ? (
        <>
          <img src={logo} alt="Logo" />
          <button className="paper-logo-clear" onClick={(e) => { e.stopPropagation(); onChange(null); }} title="Remove">
            <IconX />
          </button>
        </>
      ) : (
        <div className="paper-logo-hint">
          <IconUpload />
          <span>Add your logo</span>
        </div>
      )}
      <input
        ref={fileRef}
        type="file"
        accept="image/*"
        style={{ display: 'none' }}
        onChange={(e) => onFile(e.target.files?.[0])}
      />
    </div>
  );
}

/* ============ Paper Head ============ */
function PaperHead({ data, upd }) {
  return (
    <div className="paper-head">
      <LogoSlot logo={data.logo} onChange={(v) => upd({ logo: v })} />
      <div className="paper-title-block">
        <input
          className="paper-title-input"
          value={data.title}
          onChange={e => upd({ title: e.target.value })}
          maxLength={24}
        />
        <div className="paper-num-row">
          <span className="lbl">#</span>
          <input
            className="p-input mono right"
            style={{ width: 180 }}
            value={data.number || ''}
            onChange={e => upd({ number: e.target.value })}
            placeholder="001"
          />
        </div>
        {/* Print-only — sits below the number in the PDF and replaces the
            standalone meta row to save vertical space. Hidden on screen. */}
        <MetaCompact data={data} />
      </div>
    </div>
  );
}

function MetaCompact({ data }) {
  const isQuote = data.mode === 'quote';
  const rows = [
    { label: 'Date',            value: data.date    ? fmtDate(data.date,    data.dateFormat) : '' },
    { label: 'Payment Terms',   value: data.paymentTerms || '' },
    { label: isQuote ? 'Valid Until' : 'Due Date',
                                value: data.dueDate ? fmtDate(data.dueDate, data.dateFormat) : '' },
    { label: 'PO Number',       value: data.poNumber || '' },
  ].filter(r => String(r.value).trim() !== '');
  if (rows.length === 0) return null;
  return (
    <div className="paper-meta-compact">
      {rows.map(r => (
        <div className="pmc-row" key={r.label}>
          <span className="pmc-lbl">{r.label}</span>
          <span className="pmc-val">{r.value}</span>
        </div>
      ))}
    </div>
  );
}

/* ============ Parties (From / Bill To / Ship To) ============ */
function PartyBlock({ label, value, onChange, placeholder, onRemove, accent }) {
  return (
    <div className="party">
      <div className="party-label">
        {accent && <span style={{
          width: 6, height: 6, borderRadius: '50%',
          background: 'var(--paper-accent)', boxShadow: '0 0 6px var(--paper-accent)',
        }}/>}
        {label}
      </div>
      <AutoTextarea
        className="p-textarea party-textarea"
        value={value}
        onChange={onChange}
        placeholder={placeholder}
      />
      {onRemove && (
        <button className="party-close" onClick={onRemove} title="Remove">
          <IconX />
        </button>
      )}
    </div>
  );
}

function Parties({ data, upd }) {
  const hasShip = data.shipTo !== null;
  const empty = (v) => !v || !String(v).trim();
  return (
    <div className="paper-parties">
      <div className="party" data-empty={empty(data.from)}>
        <div className="party-label">
          <span style={{
            width: 6, height: 6, borderRadius: '50%',
            background: 'var(--paper-accent)', boxShadow: '0 0 6px var(--paper-accent)',
          }}/>
          From
        </div>
        <AutoTextarea
          className="p-textarea party-textarea"
          value={data.from}
          onChange={(v) => upd({ from: v })}
          placeholder={"Your business name\nStreet address\nCity, State ZIP\nemail@business.com"}
        />
      </div>
      <div className="party" data-empty={empty(data.billTo)}>
        <div className="party-label">Bill to</div>
        <AutoTextarea
          className="p-textarea party-textarea"
          value={data.billTo}
          onChange={(v) => upd({ billTo: v })}
          placeholder={"Client name\nCompany\nAddress\nemail@client.com"}
        />
      </div>
      {hasShip ? (
        <div className="party" data-empty={empty(data.shipTo)}>
          <div className="party-label">Ship to</div>
          <AutoTextarea
            className="p-textarea party-textarea"
            value={data.shipTo}
            onChange={(v) => upd({ shipTo: v })}
            placeholder={"Shipping address\nif different from billing"}
          />
          <button className="party-close print-hide" onClick={() => upd({ shipTo: null })} title="Remove">
            <IconX />
          </button>
        </div>
      ) : (
        <div className="party party-add-slot">
          <button className="party-toggle print-hide" onClick={() => upd({ shipTo: '' })}>
            <IconPlus /> Add ship to
          </button>
        </div>
      )}
    </div>
  );
}

/* ============ Meta row ============ */
function MetaCell({ label, empty, children, onRemove }) {
  return (
    <div className="meta-cell" data-empty={empty}>
      <span className="meta-label">
        {label}
        {onRemove && (
          <button className="po-remove print-hide" onClick={onRemove} title="Remove">×</button>
        )}
      </span>
      {children}
    </div>
  );
}

function MetaRow({ data, upd }) {
  const showPO = data.poNumber !== null;
  const empty = (v) => !v || !String(v).trim();
  return (
    <div className="paper-meta">
      <MetaCell label="Date" empty={empty(data.date)}>
        <input
          type="date"
          className="p-input mono"
          value={data.date || ''}
          onChange={e => upd({ date: e.target.value })}
        />
      </MetaCell>
      <MetaCell label="Payment Terms" empty={empty(data.paymentTerms)}>
        <input
          className="p-input"
          value={data.paymentTerms || ''}
          onChange={e => upd({ paymentTerms: e.target.value })}
          placeholder="Net 30"
        />
      </MetaCell>
      <MetaCell label={data.mode === 'quote' ? 'Valid Until' : 'Due Date'} empty={empty(data.dueDate)}>
        <input
          type="date"
          className="p-input mono"
          value={data.dueDate || ''}
          onChange={e => upd({ dueDate: e.target.value })}
        />
      </MetaCell>
      {showPO ? (
        <MetaCell label="PO Number" empty={empty(data.poNumber)} onRemove={() => upd({ poNumber: null })}>
          <input
            className="p-input"
            value={data.poNumber || ''}
            onChange={e => upd({ poNumber: e.target.value })}
            placeholder="PO-2026-01"
          />
        </MetaCell>
      ) : (
        <div className="meta-cell meta-cell-add print-hide">
          <button
            className="party-toggle"
            style={{ marginTop: 'auto', padding: '6px 10px', fontSize: 10.5 }}
            onClick={() => upd({ poNumber: '' })}
          >
            <IconPlus /> PO Number
          </button>
        </div>
      )}
    </div>
  );
}

/* ============ Line items ============ */
function LineItems({ data, upd }) {
  const [drag, setDrag] = useState({ from: null, overId: null, pos: null });
  const showQty = data.showQuantity !== false;

  const setLines = (fn) => upd({ lines: fn(data.lines) });
  const updLine = (id, patch) => setLines(L => L.map(l => l.id === id ? { ...l, ...patch } : l));
  const addLine = () => setLines(L => [...L, { id: uid(), description: '', quantity: 1, rate: 0 }]);
  const removeLine = (id) => setLines(L => L.length > 1 ? L.filter(l => l.id !== id) : L);

  const onDragStart = (i) => (e) => {
    setDrag({ from: i, overId: null, pos: null });
    e.dataTransfer.effectAllowed = 'move';
    try { e.dataTransfer.setData('text/plain', String(i)); } catch {}
  };
  const onDragOver = (i, id) => (e) => {
    e.preventDefault();
    const r = e.currentTarget.getBoundingClientRect();
    const pos = (e.clientY - r.top) < r.height / 2 ? 'before' : 'after';
    setDrag(d => ({ ...d, overId: id, pos }));
  };
  const onDrop = (i) => (e) => {
    e.preventDefault();
    if (drag.from === null) return;
    let to = i + (drag.pos === 'after' ? 1 : 0);
    if (to > drag.from) to -= 1;
    setLines(L => {
      const arr = [...L];
      const [moved] = arr.splice(drag.from, 1);
      arr.splice(to, 0, moved);
      return arr;
    });
    setDrag({ from: null, overId: null, pos: null });
  };
  const onDragEnd = () => setDrag({ from: null, overId: null, pos: null });

  return (
    <>
      <table className={`lines ${showQty ? '' : 'no-qty'}`}>
        <thead>
          <tr>
            <th className="col-grip" />
            <th className="col-desc">Item</th>
            {showQty && <th className="col-qty">Quantity</th>}
            {showQty && <th className="col-rate">Rate</th>}
            <th className="col-amt">Amount</th>
            <th className="col-actions" />
          </tr>
        </thead>
        <tbody>
          {data.lines.map((line, i) => {
            const qty = showQty ? parseNumber(line.quantity) : 1;
            const amount = qty * parseNumber(line.rate);
            const dragCls =
              drag.from === i ? 'dragging' :
              (drag.overId === line.id ? `drop-${drag.pos}` : '');
            return (
              <tr
                key={line.id}
                className={dragCls}
                onDragOver={onDragOver(i, line.id)}
                onDrop={onDrop(i)}
              >
                <td
                  className="col-grip"
                  draggable
                  onDragStart={onDragStart(i)}
                  onDragEnd={onDragEnd}
                  title="Drag to reorder"
                >
                  <IconGrip />
                </td>
                <td className="col-desc">
                  <AutoTextarea
                    className="p-textarea"
                    value={line.description}
                    onChange={(v) => updLine(line.id, { description: v })}
                    placeholder="Description of item or service…"
                  />
                </td>
                {showQty && (
                  <td className="col-qty">
                    <span className="li-print-val">{line.quantity || ''}</span>
                    <input
                      className="p-input num right print-hide"
                      value={line.quantity ?? ''}
                      onChange={(e) => updLine(line.id, { quantity: e.target.value })}
                      onBlur={(e) => updLine(line.id, { quantity: parseNumber(e.target.value) })}
                      placeholder="1"
                      inputMode="decimal"
                    />
                  </td>
                )}
                {showQty && (
                  <td className="col-rate">
                    <span className="li-print-val">{fmtMoney(parseNumber(line.rate), data.currency)}</span>
                    <div className="rate-input-wrap print-hide">
                      <span className="rate-symbol">{curInfo(data.currency).symbol}</span>
                      <input
                        className="p-input num right"
                        value={line.rate ?? ''}
                        size={Math.max(4, String(line.rate ?? '').length + 1)}
                        onChange={(e) => updLine(line.id, { rate: e.target.value })}
                        onBlur={(e) => updLine(line.id, { rate: parseNumber(e.target.value) })}
                        placeholder="0.00"
                        inputMode="decimal"
                      />
                    </div>
                  </td>
                )}
                <td className="col-amt">
                  {showQty ? (
                    // qty × rate is a derived display — read-only.
                    fmtMoney(amount, data.currency)
                  ) : (
                    // With no quantity column, the Amount column itself is the
                    // editable input — qty is implicitly 1, so amount ≡ rate.
                    <>
                      <span className="li-print-val">{fmtMoney(parseNumber(line.rate), data.currency)}</span>
                      <div className="rate-input-wrap print-hide">
                        <span className="rate-symbol">{curInfo(data.currency).symbol}</span>
                        <input
                          className="p-input num right"
                          value={line.rate ?? ''}
                          size={Math.max(4, String(line.rate ?? '').length + 1)}
                          onChange={(e) => updLine(line.id, { rate: e.target.value })}
                          onBlur={(e) => updLine(line.id, { rate: parseNumber(e.target.value) })}
                          placeholder="0.00"
                          inputMode="decimal"
                        />
                      </div>
                    </>
                  )}
                </td>
                <td className="col-actions print-hide">
                  <button
                    className="line-remove"
                    onClick={() => removeLine(line.id)}
                    title="Remove line"
                  >
                    <IconX />
                  </button>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
      <button className="add-line print-hide" onClick={addLine}>
        <IconPlus /> Add line item
      </button>
    </>
  );
}

/* ============ Totals (right column) ============ */
function ModSwitch({ kind, onChange }) {
  return (
    <span className="tt-mod-pct">
      <button className={kind === 'percent' ? 'active' : ''} onClick={() => onChange('percent')}>%</button>
      <button className={kind === 'amount' ? 'active' : ''} onClick={() => onChange('amount')}>$</button>
    </span>
  );
}

function Totals({ data, upd, totals }) {
  const cur = data.currency;
  const isQuote = data.mode === 'quote';

  const setMod = (key, kind) => {
    const current = data[key];
    if (!current) return upd({ [key]: { kind, value: 0 } });
    upd({ [key]: { ...current, kind } });
  };
  const setModValue = (key, value) => {
    const current = data[key] || { kind: 'percent', value: 0 };
    upd({ [key]: { ...current, value } });
  };
  const addMod = (key, kind = 'percent') => upd({ [key]: { kind, value: 0 } });
  const removeMod = (key) => upd({ [key]: null });

  // formatted print displays
  const fmtMod = (m) => {
    if (!m) return '';
    const v = parseNumber(m.value);
    return m.kind === 'percent' ? `${v}%` : fmtMoney(v, cur);
  };
  const paidAmt = parseNumber(data.amountPaid);

  return (
    <table className="totals-table">
      <tbody>
        <tr>
          <td className="tt-lbl">Subtotal</td>
          <td className="tt-val">{fmtMoney(totals.subtotal, cur)}</td>
        </tr>

        {/* Discount */}
        {data.discount ? (
          <tr>
            <td className="tt-lbl">
              Discount
              <ModSwitch kind={data.discount.kind} onChange={(k) => setMod('discount', k)} />
            </td>
            <td className="tt-val">
              <span className="tt-print-val">{fmtMod(data.discount)}</span>
              <input
                className="tt-pct-input print-hide"
                value={data.discount.value ?? ''}
                onChange={(e) => setModValue('discount', e.target.value)}
                onBlur={(e) => setModValue('discount', parseNumber(e.target.value))}
                placeholder="0"
                inputMode="decimal"
              />
              <button className="tt-remove print-hide" onClick={() => removeMod('discount')} title="Remove">
                <IconX />
              </button>
            </td>
          </tr>
        ) : null}

        {/* Tax */}
        {data.tax ? (
          <tr>
            <td className="tt-lbl">
              Tax
              <ModSwitch kind={data.tax.kind} onChange={(k) => setMod('tax', k)} />
            </td>
            <td className="tt-val">
              <span className="tt-print-val">{fmtMod(data.tax)}</span>
              <input
                className="tt-pct-input print-hide"
                value={data.tax.value ?? ''}
                onChange={(e) => setModValue('tax', e.target.value)}
                onBlur={(e) => setModValue('tax', parseNumber(e.target.value))}
                placeholder="0"
                inputMode="decimal"
              />
              <button className="tt-remove print-hide" onClick={() => removeMod('tax')} title="Remove">
                <IconX />
              </button>
            </td>
          </tr>
        ) : null}

        {/* Shipping */}
        {data.shipping != null ? (
          <tr>
            <td className="tt-lbl">Shipping</td>
            <td className="tt-val">
              <span className="tt-print-val">{fmtMoney(parseNumber(data.shipping), cur)}</span>
              <input
                className="tt-pct-input print-hide"
                value={data.shipping ?? ''}
                onChange={(e) => upd({ shipping: e.target.value })}
                onBlur={(e) => upd({ shipping: parseNumber(e.target.value) })}
                placeholder="0.00"
                inputMode="decimal"
              />
              <button className="tt-remove print-hide" onClick={() => upd({ shipping: null })} title="Remove">
                <IconX />
              </button>
            </td>
          </tr>
        ) : null}

        {/* Add-mod buttons */}
        {(!data.discount || !data.tax || data.shipping == null) && (
          <tr className="print-hide">
            <td colSpan={2} style={{ paddingTop: 4 }}>
              <div style={{ display: 'flex', gap: 14, justifyContent: 'flex-end', flexWrap: 'wrap' }}>
                {!data.discount && (
                  <button className="tt-add" onClick={() => addMod('discount', 'percent')}>
                    <IconPlus /> Discount
                  </button>
                )}
                {!data.tax && (
                  <button className="tt-add" onClick={() => addMod('tax', 'percent')}>
                    <IconPlus /> Tax
                  </button>
                )}
                {data.shipping == null && (
                  <button className="tt-add" onClick={() => upd({ shipping: 0 })}>
                    <IconPlus /> Shipping
                  </button>
                )}
              </div>
            </td>
          </tr>
        )}

        {isQuote ? (
          <tr className="tt-balance">
            <td className="tt-lbl">Total</td>
            <td className="tt-val">{fmtMoney(totals.total, cur)}</td>
          </tr>
        ) : (
          <>
            <tr className="tt-total">
              <td className="tt-lbl">Total</td>
              <td className="tt-val">{fmtMoney(totals.total, cur)}</td>
            </tr>
            <tr data-paid-zero={paidAmt === 0}>
              <td className="tt-lbl">Amount Paid</td>
              <td className="tt-val">
                <span className="tt-print-val">{fmtMoney(paidAmt, cur)}</span>
                <input
                  className="tt-pct-input print-hide"
                  style={{ width: 110 }}
                  value={data.amountPaid ?? ''}
                  onChange={(e) => upd({ amountPaid: e.target.value })}
                  onBlur={(e) => upd({ amountPaid: parseNumber(e.target.value) })}
                  placeholder="0.00"
                  inputMode="decimal"
                />
              </td>
            </tr>
            <tr className="tt-balance">
              <td className="tt-lbl">Balance Due</td>
              <td className="tt-val">{fmtMoney(totals.balance, cur)}</td>
            </tr>
          </>
        )}
      </tbody>
    </table>
  );
}

/* ============ Currency picker ============ */
function CurrencyPicker({ value, onChange }) {
  const [open, setOpen] = useState(false);
  const [q, setQ] = useState('');
  const wrapRef = useRef();
  const cur = curInfo(value);

  useEffect(() => {
    if (!open) return;
    const onDoc = (e) => {
      if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
    };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, [open]);

  const filtered = useMemo(() => {
    if (!q) return CURRENCIES;
    const s = q.toLowerCase();
    return CURRENCIES.filter(c =>
      c.code.toLowerCase().includes(s) ||
      c.name.toLowerCase().includes(s) ||
      c.symbol.toLowerCase().includes(s)
    );
  }, [q]);

  return (
    <div style={{ position: 'relative' }} ref={wrapRef}>
      <button className="cur-pick" onClick={() => setOpen(o => !o)}>
        <span className="cur-flag">{cur.flag}</span>
        <span className="cur-code">{cur.code}</span>
        <span className="cur-name">{cur.name}</span>
        <span className="cur-sym">{cur.symbol}</span>
        <IconChevDownSmall />
      </button>
      {open && (
        <div className="cur-popover">
          <div className="cur-search">
            <IconSearch />
            <input
              autoFocus
              placeholder="Search currency…"
              value={q}
              onChange={(e) => setQ(e.target.value)}
            />
          </div>
          <div className="cur-list">
            {filtered.map(c => (
              <button
                key={c.code}
                className={`cur-row ${c.code === value ? 'active' : ''}`}
                onClick={() => { onChange(c.code); setOpen(false); setQ(''); }}
              >
                <span className="cur-flag">{c.flag}</span>
                <span className="cur-code">{c.code}</span>
                <span className="cur-name">{c.name}</span>
                <span className="cur-sym">{c.symbol}</span>
              </button>
            ))}
            {filtered.length === 0 && (
              <div style={{ padding: 16, fontSize: 12.5, color: 'var(--text-3)', textAlign: 'center' }}>
                No currencies match.
              </div>
            )}
          </div>
        </div>
      )}
    </div>
  );
}

/* ============ Bank details (paper footer) ============ */
function BankFooter({ bank }) {
  if (!bank || !bank.region) return null;
  const region = BANK_REGIONS[bank.region];
  if (!region) return null;
  const rows = region.fields
    .filter(f => f.key !== 'extra' && (bank[f.key] || '').trim() !== '')
    .map(f => ({ ...f, value: bank[f.key] }));
  const extraField = region.fields.find(f => f.key === 'extra');
  const extraVal = extraField && (bank.extra || '').trim();
  if (rows.length === 0 && !extraVal) return null;

  return (
    <div className="bank-footer">
      <div className="bank-label">Payment details</div>
      <div className="bank-grid">
        {rows.map(r => (
          <div className="bank-row" key={r.key}>
            <div className="bank-row-lbl">{r.label}</div>
            <div className={`bank-row-val ${r.mono ? 'mono' : ''}`}>{r.value}</div>
          </div>
        ))}
      </div>
      {extraVal && (
        <div className="bank-extra">{extraVal}</div>
      )}
    </div>
  );
}

/* ============ Bank details modal ============ */
function BankDetailsModal({ open, initial, onClose, onSave }) {
  const [draft, setDraft] = useState(() => initial || { region: 'US' });
  useEffect(() => { if (open) setDraft(initial || { region: 'US' }); }, [open, initial]);

  if (!open) return null;
  const region = BANK_REGIONS[draft.region] || BANK_REGIONS.US;

  const setField = (k, v) => setDraft(d => ({ ...d, [k]: v }));

  const isEmpty = !Object.entries(draft).some(([k, v]) => k !== 'region' && String(v || '').trim() !== '');

  return (
    <div className="bank-modal-backdrop" onClick={onClose}>
      <div className="bank-modal" onClick={(e) => e.stopPropagation()}>
        <header className="bank-modal-head">
          <div>
            <div className="bank-modal-eyebrow">Payment details</div>
            <h3 className="bank-modal-ttl">Bank account</h3>
          </div>
          <button className="bank-modal-close" onClick={onClose} aria-label="Close">
            <IconX />
          </button>
        </header>

        <div className="bank-modal-body">
          <div className="bank-modal-section">
            <label className="bank-modal-lbl">Region</label>
            <div className="bank-region-grid">
              {Object.entries(BANK_REGIONS).map(([code, r]) => (
                <button
                  key={code}
                  className={`bank-region-card ${draft.region === code ? 'active' : ''}`}
                  onClick={() => setDraft(d => ({ ...d, region: code }))}
                >
                  <span className="bank-region-code">{code === 'OTHER' ? 'Intl' : code}</span>
                  <span className="bank-region-name">{r.label}</span>
                </button>
              ))}
            </div>
          </div>

          <div className="bank-modal-section">
            <div className="bank-fields">
              {region.fields.map(f => (
                <div className={`bank-field ${f.textarea ? 'full' : ''}`} key={f.key}>
                  <label className="bank-modal-lbl">
                    {f.label}
                    {f.optional && <span className="bank-opt">optional</span>}
                  </label>
                  {f.textarea ? (
                    <textarea
                      className={`bank-modal-input ${f.mono ? 'mono' : ''}`}
                      value={draft[f.key] || ''}
                      onChange={(e) => setField(f.key, e.target.value)}
                      placeholder={f.placeholder}
                      rows={2}
                    />
                  ) : (
                    <input
                      className={`bank-modal-input ${f.mono ? 'mono' : ''}`}
                      value={draft[f.key] || ''}
                      onChange={(e) => setField(f.key, e.target.value)}
                      placeholder={f.placeholder}
                    />
                  )}
                </div>
              ))}
            </div>
          </div>
        </div>

        <footer className="bank-modal-foot">
          {initial && (
            <button
              className="btn"
              style={{ color: '#d65656', marginRight: 'auto' }}
              onClick={() => { onSave(null); onClose(); }}
            >
              <IconTrash /> Remove
            </button>
          )}
          <button className="btn" onClick={onClose}>Cancel</button>
          <button
            className="btn btn-primary"
            disabled={isEmpty}
            onClick={() => { onSave(draft); onClose(); }}
          >
            Save details
          </button>
        </footer>
      </div>
    </div>
  );
}

/* ============ Segmented control ============ */
function SegBar({ options, value, onChange }) {
  return (
    <div className="seg-bar">
      {options.map(o => (
        <button
          key={o.value}
          className={value === o.value ? 'active' : ''}
          onClick={() => onChange(o.value)}
        >
          {o.label}
        </button>
      ))}
    </div>
  );
}

/* ============ Side panel ============ */
function SidePanel({ data, upd, totals, onDownload, onReset, savedAt, onOpenBank, downloading }) {
  const bankConfigured = data.bank && Object.entries(data.bank).some(([k, v]) => k !== 'region' && String(v || '').trim() !== '');
  const bankRegion = data.bank?.region && BANK_REGIONS[data.bank.region]?.label;

  const setMode = (m) => {
    // auto-sync title when it still matches the default
    const defaults = { invoice: 'INVOICE', quote: 'QUOTE' };
    const patch = { mode: m };
    if (Object.values(defaults).includes((data.title || '').trim().toUpperCase())) {
      patch.title = defaults[m];
    }
    upd(patch);
  };

  return (
    <aside className="invoice-side">
      <div className="side-card">
        <div className="side-card-head">
          <span className="ttl">Document type</span>
        </div>
        <div className="side-body">
          <SegBar
            options={[
              { value: 'invoice', label: 'Invoice' },
              { value: 'quote',   label: 'Quote' },
            ]}
            value={data.mode || 'invoice'}
            onChange={setMode}
          />
        </div>
      </div>

      <div className="side-card">
        <div className="side-card-head">
          <span className="ttl">Currency</span>
        </div>
        <div className="side-body">
          <CurrencyPicker value={data.currency} onChange={(c) => upd({ currency: c })} />
        </div>
      </div>

      <div className="side-card">
        <div className="side-card-head">
          <span className="ttl">Summary</span>
        </div>
        <div className="side-totals">
          <div className="side-totals-row"><span>Subtotal</span><strong>{fmtMoney(totals.subtotal, data.currency)}</strong></div>
          {data.discount && (
            <div className="side-totals-row"><span>Discount</span><strong>− {fmtMoney(totals.discountAmt, data.currency)}</strong></div>
          )}
          {data.tax && (
            <div className="side-totals-row"><span>Tax</span><strong>+ {fmtMoney(totals.taxAmt, data.currency)}</strong></div>
          )}
          {data.shipping != null && (
            <div className="side-totals-row"><span>Shipping</span><strong>+ {fmtMoney(totals.shippingAmt, data.currency)}</strong></div>
          )}
          {totals.paid > 0 && data.mode !== 'quote' && (
            <div className="side-totals-row"><span>Paid</span><strong>− {fmtMoney(totals.paid, data.currency)}</strong></div>
          )}
          <div className="side-totals-big">
            <span className="lbl">{data.mode === 'quote' ? 'Total' : 'Balance due'}</span>
            <span className="val">{fmtMoney(data.mode === 'quote' ? totals.total : totals.balance, data.currency)}</span>
          </div>
        </div>
      </div>

      <div className="side-card">
        <div className="side-card-head">
          <span className="ttl">Options</span>
        </div>
        <div className="side-body">
          <div className="opt-row">
            <div className="opt-lbl">
              <span>Quantity column</span>
              <span className="opt-sub">Show qty × rate on lines</span>
            </div>
            <button
              className={`opt-toggle ${data.showQuantity !== false ? 'on' : ''}`}
              onClick={() => upd({ showQuantity: !(data.showQuantity !== false) })}
              aria-pressed={data.showQuantity !== false}
            />
          </div>

          {data.mode !== 'quote' && (
            <div className="opt-row">
              <div className="opt-lbl">
                <span>Mark as paid</span>
                <span className="opt-sub">Adds a "PAID" stamp</span>
              </div>
              <button
                className={`opt-toggle ${data.paid ? 'on' : ''}`}
                onClick={() => upd({ paid: !data.paid, amountPaid: !data.paid ? totals.total : data.amountPaid })}
                aria-pressed={data.paid}
              />
            </div>
          )}

          <div className="opt-row bank-row-card">
            <div className="opt-lbl">
              <span>Bank details</span>
              <span className="opt-sub">
                {bankConfigured ? `Showing · ${bankRegion}` : 'Not set'}
              </span>
            </div>
            <button
              className={`btn btn-ghost ${bankConfigured ? 'btn-ghost-active' : ''}`}
              style={{ padding: '7px 11px', fontSize: 12, whiteSpace: 'nowrap' }}
              onClick={onOpenBank}
            >
              {bankConfigured ? 'Edit' : 'Add'}
            </button>
          </div>
        </div>
      </div>

      <div className="side-actions">
        <button className="btn btn-primary" onClick={onDownload} disabled={downloading}>
          {downloading ? (
            <>
              <span className="btn-spin" /> Preparing PDF…
            </>
          ) : (
            <>
              <IconDownload /> Download PDF
            </>
          )}
        </button>
        <button className="btn" onClick={onReset} style={{ color: 'var(--text-3)' }}>
          <IconTrash /> Reset {data.mode === 'quote' ? 'quote' : 'invoice'}
        </button>
      </div>

      <div className="side-status">
        <span className="dot" />
        {savedAt ? `Saved · ${savedAt}` : 'Autosaved'}
      </div>
    </aside>
  );
}

/* ============ Invoice paper (puts it all together) ============ */
function InvoicePaper({ data, upd, totals }) {
  const notesEmpty = !data.notes || !data.notes.trim();
  const termsEmpty = !data.terms || !data.terms.trim();
  return (
    <div className={`invoice-paper ${data.paid ? 'is-paid' : ''} body-font-${data.bodyFont || 'default'}`} id="invoice-paper">
      {data.paid && <div className="paid-stamp">PAID</div>}
      <PaperHead data={data} upd={upd} />
      <Parties data={data} upd={upd} />
      <MetaRow data={data} upd={upd} />
      <LineItems data={data} upd={upd} />

      <div className="paper-totals-row">
        <Totals data={data} upd={upd} totals={totals} />
      </div>

      <div className="paper-bottom">
        <div className="notes-block" data-empty={notesEmpty}>
          <div className="party-label">Notes</div>
          <AutoTextarea
            className="p-textarea notes-textarea"
            value={data.notes}
            onChange={(v) => upd({ notes: v })}
            placeholder={data.mode === 'quote'
              ? "Thanks for considering us. Project scope, assumptions, anything the client should know."
              : "Thanks for your business. Payment instructions, project context, anything you'd like the client to read."}
          />
        </div>
        <div className="notes-block" data-empty={termsEmpty}>
          <div className="party-label">{data.mode === 'quote' ? 'Validity' : 'Terms'}</div>
          <AutoTextarea
            className="p-textarea notes-textarea"
            value={data.terms}
            onChange={(v) => upd({ terms: v })}
            placeholder={data.mode === 'quote'
              ? "Quote valid for 30 days from issue date. Prices in stated currency, excl. taxes unless noted."
              : "Late payments are subject to a 1.5% monthly fee. Payable via bank transfer."}
          />
        </div>
      </div>

      <BankFooter bank={data.bank} />
    </div>
  );
}

/* ============ Tweaks panel (left side) ============ */
// Holds visual / typographic experiments. First control: body font for the
// invoice's data fields. Designed to grow — drop new opt-rows into the same
// card or add additional cards as new tweaks are introduced.
function TweaksPanel({ data, upd }) {
  return (
    <aside className="invoice-tweaks">
      <div className="side-card">
        <div className="side-card-head">
          <span className="ttl">Tweaks</span>
        </div>
        <div className="side-body">
          <div className="opt-row tweak-stack">
            <div className="opt-lbl">
              <span>Body font</span>
              <span className="opt-sub">Applies to data fields on the paper</span>
            </div>
            <SegBar
              options={[
                { value: 'default', label: 'Mixed' },
                { value: 'sans',    label: 'Sans' },
                { value: 'mono',    label: 'Mono' },
              ]}
              value={data.bodyFont || 'default'}
              onChange={(v) => upd({ bodyFont: v })}
            />
          </div>
        </div>
      </div>
    </aside>
  );
}

/* ============ App ============ */
// Per-invoice storage. The legacy single-key (pp-invoice-v1) is migrated
// to the first entry in the new index on first load (see invoice-storage.jsx).

function loadInvoiceFor(meta) {
  if (!meta || !meta.id) return defaultInvoice();
  try {
    const raw = localStorage.getItem(invoiceKey(meta.id));
    if (!raw) return { ...defaultInvoice(), number: meta.number || '' };
    const parsed = JSON.parse(raw);
    return { ...defaultInvoice(), ...parsed };
  } catch {
    return { ...defaultInvoice(), number: meta.number || '' };
  }
}

function App({ meta }) {
  const [theme, setTheme] = useState(() => document.documentElement.getAttribute('data-theme') || 'dark');
  const [railExpanded, setRailExpanded] = useState(false);
  const [data, setData] = useState(() => loadInvoiceFor(meta));
  const [savedAt, setSavedAt] = useState(null);
  const [bankOpen, setBankOpen] = useState(false);

  // theme
  useEffect(() => { document.documentElement.setAttribute('data-theme', theme); }, [theme]);

  // IP-based currency detection — runs once on mount, ONLY for fresh
  // invoices (no per-invoice state yet). The result is cached so the
  // synchronous seed in defaultInvoice() catches it on later visits.
  useEffect(() => {
    if (!meta || !meta.id) return;
    if (localStorage.getItem(invoiceKey(meta.id))) return;
    let cancelled = false;
    detectCurrencyByIP().then(ccy => {
      if (cancelled || !ccy) return;
      setData(d => d.currency === ccy ? d : { ...d, currency: ccy });
    });
    return () => { cancelled = true; };
  }, [meta && meta.id]);

  // document title — shows in print headers if browser headers are enabled
  useEffect(() => {
    const noun = data.mode === 'quote' ? 'Quote' : 'Invoice';
    document.title = `${noun} ${data.number || ''} — Pixel Portal`.trim();
  }, [data.mode, data.number]);

  // autosave — writes per-invoice state AND syncs metadata in the index so
  // the manager page reflects current number/client/total/etc.
  const firstSave = useRef(true);
  useEffect(() => {
    if (firstSave.current) { firstSave.current = false; return; }
    if (!meta || !meta.id) return;
    try {
      localStorage.setItem(invoiceKey(meta.id), JSON.stringify(data));
      updateInvoiceMeta(meta.id, deriveMetaFromData(data));
      const t = new Date();
      setSavedAt(t.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }));
    } catch {}
  }, [data, meta && meta.id]);

  const upd = useCallback((patch) => setData(d => ({ ...d, ...patch })), []);

  const totals = useMemo(() => calcTotals(data), [data]);

  const onReset = () => {
    if (confirm(`Clear this ${data.mode === 'quote' ? 'quote' : 'invoice'} and start fresh?`)) {
      setData({ ...defaultInvoice(), mode: data.mode, title: data.mode === 'quote' ? 'QUOTE' : 'INVOICE' });
    }
  };

  const [downloading, setDownloading] = useState(false);
  const onDownload = useCallback(async () => {
    if (downloading) return;
    setDownloading(true);
    const noun = data.mode === 'quote' ? 'Quote' : 'Invoice';
    const filename = `${noun} ${(data.number || '001').toString().replace(/[/\\?%*:|"<>]/g, '-')}.pdf`;

    let wrapper = null;
    let scopedStyle = null;
    try {
      // 1) Ensure html2pdf is loaded
      if (!window.html2pdf) {
        await new Promise((res, rej) => {
          const s = document.createElement('script');
          s.src = 'https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js';
          s.crossOrigin = 'anonymous';
          s.onload = res; s.onerror = rej;
          document.head.appendChild(s);
        });
      }
      if (document.fonts && document.fonts.ready) await document.fonts.ready;

      // 2) Clone the live paper, place it OFFSCREEN, replace its inputs with spans
      const paper = document.getElementById('invoice-paper');
      wrapper = document.createElement('div');
      wrapper.id = 'pdf-render-wrapper';
      wrapper.style.cssText =
        'position:fixed;left:-10000px;top:0;width:794px;background:#ffffff;z-index:-1;';
      const clone = paper.cloneNode(true);
      clone.id = 'pdf-render-paper';
      clone.querySelectorAll('input, textarea').forEach((el) => {
        const isTextarea = el.tagName === 'TEXTAREA';
        const replacement = document.createElement(isTextarea ? 'div' : 'span');
        replacement.textContent = el.value || '';
        replacement.className = el.className;
        if (el.style.cssText) replacement.style.cssText = el.style.cssText;
        if (isTextarea) {
          replacement.style.whiteSpace = 'pre-wrap';
          replacement.style.display = 'block';
        } else {
          replacement.style.display = 'inline-block';
          replacement.style.minWidth = '1px';
        }
        if (el.classList.contains('right')) replacement.style.textAlign = 'right';
        el.parentNode.replaceChild(replacement, el);
      });
      wrapper.appendChild(clone);
      document.body.appendChild(wrapper);

      // 3) Build a <style> tag scoping @media print rules to #pdf-render-paper
      //    so we don't affect the visible editor.
      let scopedCss = '';
      const HOST = '#pdf-render-paper';
      for (const sheet of document.styleSheets) {
        try {
          for (const mediaRule of sheet.cssRules) {
            if (!(mediaRule instanceof CSSMediaRule)) continue;
            if (![...mediaRule.media].some(m => m.includes('print'))) continue;
            for (const r of mediaRule.cssRules) {
              if (!r.selectorText) continue;
              const sel = r.selectorText.split(',').map(s => {
                const t = s.trim();
                // Map html/body rules onto the wrapper itself.
                if (/^(html|body)\b/.test(t)) return '#pdf-render-wrapper';
                // If selector already starts with .invoice-paper / #invoice-paper, rewrite.
                if (/^(\.invoice-paper|#invoice-paper)\b/.test(t)) {
                  return HOST + t.replace(/^(\.invoice-paper|#invoice-paper)/, '');
                }
                return HOST + ' ' + t;
              }).join(', ');
              scopedCss += sel + ' { ' + r.style.cssText + ' }\n';
            }
          }
        } catch (e) { /* cross-origin */ }
      }
      // Extra lock-down for the PDF target
      scopedCss += `
        ${HOST} {
          width: 794px !important;
          max-width: 794px !important;
          padding: 48px 48px 56px !important;
          margin: 0 !important;
          box-shadow: none !important;
          border-radius: 0 !important;
          background: #ffffff !important;
          color: #14191a !important;
        }
        ${HOST}::before { display: none !important; }
        #pdf-render-wrapper { background: #ffffff !important; }
      `;
      scopedStyle = document.createElement('style');
      scopedStyle.id = 'pdf-render-styles';
      scopedStyle.textContent = scopedCss;
      document.head.appendChild(scopedStyle);

      await new Promise(r => setTimeout(r, 100)); // let layout settle

      // 4) Capture and save
      await window.html2pdf()
        .from(clone)
        .set({
          margin: 0,
          filename,
          image: { type: 'jpeg', quality: 0.98 },
          html2canvas: {
            scale: 2,
            backgroundColor: '#ffffff',
            useCORS: true,
            letterRendering: true,
          },
          jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
          pagebreak: { mode: ['css', 'legacy'] },
        })
        .save();
    } catch (err) {
      console.error('PDF download failed:', err);
      alert('PDF download failed. Please try again.');
    } finally {
      if (wrapper && wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
      if (scopedStyle && scopedStyle.parentNode) scopedStyle.parentNode.removeChild(scopedStyle);
      setDownloading(false);
    }
  }, [data.mode, data.number, downloading]);

  // Replaces the breadcrumb in the header — the "View Invoices" button is
  // the only top-left affordance on the editor page.
  const headerLeft = (
    <a className="btn invoice-back-btn" href="/invoice/">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" width="14" height="14">
        <path d="M15 6l-6 6 6 6" />
      </svg>
      View Invoices
    </a>
  );

  return (
    <div className={`app invoice-app ${railExpanded ? 'rail-expanded' : ''}`}>
      <Rail page="invoice" expanded={railExpanded} setExpanded={setRailExpanded} />
      <main className="main">
        <Header theme={theme} setTheme={setTheme} headerLeft={headerLeft} />
        <div className="invoice-stage">
          <TweaksPanel data={data} upd={upd} />
          <div className="invoice-canvas">
            <InvoicePaper data={data} upd={upd} totals={totals} />
          </div>
          <SidePanel
            data={data}
            upd={upd}
            totals={totals}
            onDownload={onDownload}
            onReset={onReset}
            savedAt={savedAt}
            onOpenBank={() => setBankOpen(true)}
            downloading={downloading}
          />
        </div>
      </main>
      <BankDetailsModal
        open={bankOpen}
        initial={data.bank}
        onClose={() => setBankOpen(false)}
        onSave={(b) => upd({ bank: b })}
      />
    </div>
  );
}

// Mounted by root.jsx — exposed globally so the entry can render us.
window.InvoiceEditor = App;
