// views/compliance.jsx — period lock, audit trail, credit notes, GSTR-1 export

/* ---------- shared ---------- */
const GST_STATES = [
  ['01','Jammu and Kashmir'],['02','Himachal Pradesh'],['03','Punjab'],['04','Chandigarh'],['05','Uttarakhand'],
  ['06','Haryana'],['07','Delhi'],['08','Rajasthan'],['09','Uttar Pradesh'],['10','Bihar'],['11','Sikkim'],
  ['12','Arunachal Pradesh'],['13','Nagaland'],['14','Manipur'],['15','Mizoram'],['16','Tripura'],['17','Meghalaya'],
  ['18','Assam'],['19','West Bengal'],['20','Jharkhand'],['21','Odisha'],['22','Chhattisgarh'],['23','Madhya Pradesh'],
  ['24','Gujarat'],['26','Dadra and Nagar Haveli and Daman and Diu'],['27','Maharashtra'],['29','Karnataka'],['30','Goa'],
  ['31','Lakshadweep'],['32','Kerala'],['33','Tamil Nadu'],['34','Puducherry'],['35','Andaman and Nicobar Islands'],
  ['36','Telangana'],['37','Andhra Pradesh'],['38','Ladakh'],['97','Other Territory'],['96','Foreign Country'],
];
const GSTIN_RE = /^\d{2}[A-Z]{5}\d{4}[A-Z][1-9A-Z]Z[0-9A-Z]$/;
const PAN_RE = /^[A-Z]{5}\d{4}[A-Z]$/;
const SAC_TOUR = '998552';

function stateByCode(c) { const s = GST_STATES.find(x => x[0] === c); return s ? { code: s[0], name: s[1] } : null; }
// Place of supply: what's typed on the invoice, else the client's GSTIN state,
// else the firm's own state (flagged — it's a guess).
function posOf(inv, home) {
  const raw = String(inv.place_of_supply || '').trim();
  if (raw) {
    const code = raw.match(/^\d{2}/);
    if (code && stateByCode(code[0])) return { ...stateByCode(code[0]), guessed: false };
    const low = raw.toLowerCase().replace(/[^a-z ]/g, '').trim();
    const hit = GST_STATES.find(s => s[1].toLowerCase() === low) || GST_STATES.find(s => s[1].toLowerCase().startsWith(low) || low.startsWith(s[1].toLowerCase()));
    if (hit) return { code: hit[0], name: hit[1], guessed: false };
  }
  const g = String(inv.customer_gstin || '').toUpperCase();
  if (GSTIN_RE.test(g) && stateByCode(g.slice(0, 2))) return { ...stateByCode(g.slice(0, 2)), guessed: false };
  return { ...(stateByCode(home) || { code: home, name: '' }), guessed: true };
}
function posLabel(p) { return p.code + '-' + p.name; }
function isInter(inv, firm) { const home = String(firm.gstin || '').slice(0, 2); return posOf(inv, home).code !== home; }

// Overseas tour packages: flat 2% from 1 Apr 2026 (s.394, Income-tax Act 2025).
const TCS_NEW_FROM = '2026-04-01';
function tcsSection(date) { return String(date || '') >= TCS_NEW_FROM ? 's.394' : '206C(1G)'; }

function booksLock() { return (BooksStore.get().lockedUntil || ''); }
function isLockedDate(d) { const L = booksLock(); return !!(L && d && String(d).slice(0, 10) <= L); }

function fyCode(date) {
  const d = new Date(String(date).slice(0, 10) + 'T00:00:00');
  const y = d.getMonth() >= 3 ? d.getFullYear() : d.getFullYear() - 1;
  return String(y % 100).padStart(2, '0') + '-' + String((y + 1) % 100).padStart(2, '0');
}
function nextCreditNoteNumber(date) {
  const st = Store.get();
  const prefix = (st.invoicePrefix || 'LU') + '/CN/' + fyCode(date) + '/';
  let max = 0;
  (st.invoices || []).forEach(i => (i.credit_notes || []).forEach(c => {
    if (c.number && c.number.indexOf(prefix) === 0) max = Math.max(max, parseInt(c.number.slice(prefix.length), 10) || 0);
  }));
  return prefix + String(max + 1).padStart(3, '0');
}
// A credit note is entered as what the client is credited, and split back
// into the same taxable / GST / TCS proportions as the invoice it reverses.
function cnSplit(inv, gross) {
  const g = inv.gst_enabled ? (Number(inv.gst_rate) || 0) : 0;
  const tc = Number(inv.tcs_rate) || 0;
  const taxable = round2(gross / ((1 + g / 100) * (1 + tc / 100)));
  const gst = round2(taxable * g / 100);
  return { gross: round2(gross), taxable, gst, tcs: round2(gross - taxable - gst) };
}
function creditedOf(inv) { return round2((inv.credit_notes || []).reduce((a, c) => a + (Number(c.gross) || 0), 0)); }

const gstrDate = d => { const x = new Date(String(d).slice(0, 10) + 'T00:00:00'); return String(x.getDate()).padStart(2, '0') + '-' + ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][x.getMonth()] + '-' + x.getFullYear(); };

/* ---------- locked-period notice ---------- */
function LockedNotice({ date, what }) {
  const L = booksLock();
  if (!L || !date || String(date).slice(0, 10) > L) return null;
  return (
    <div className="ca-banner warn" style={{ marginBottom: 14 }}>
      <div><strong>Locked period.</strong> The books are locked up to {fmtDateFull(L)}. {what || 'Amounts, GST and client details on this invoice can no longer be changed — issue a credit note instead.'}</div>
    </div>
  );
}

/* ---------- credit notes (invoice editor) ---------- */
const CN_REASONS = ['Booking cancelled', 'Price revised', 'Service not provided', 'Excess billed', 'Other'];

function CreditNotesPanel({ inv, firm, s }) {
  const t = invTotals(inv);
  const cns = inv.credit_notes || [];
  const credited = creditedOf(inv);
  const room = round2(t.total - credited);
  const today = new Date().toISOString().slice(0, 10);
  const [open, setOpen] = React.useState(false);
  const [f, setF] = React.useState({ date: today, gross: '', reason: CN_REASONS[0], note: '', refund_tx_id: '' });
  const [err, setErr] = React.useState('');
  const refunds = (s.transactions || []).filter(x => x.invoiceId === inv.id && x.direction === 'debit');

  function save() {
    setErr('');
    const gross = Number(f.gross);
    if (!inv.number) return setErr('Give the invoice a number first.');
    if (!(gross > 0)) return setErr('Enter the amount to credit.');
    if (gross > room + 0.5) return setErr('That is more than the ' + fmtINR(room) + ' left on this invoice.');
    if (!f.date) return setErr('Pick a date.');
    if (inv.date && f.date < inv.date) return setErr('A credit note can\u2019t be dated before its invoice.');
    if (isLockedDate(f.date)) return setErr('That date is in a locked period (up to ' + fmtDateFull(booksLock()) + ').');
    const cn = { id: 'cn' + Date.now().toString(36), number: invIsTest(inv) ? 'TEST/CN/' + Date.now().toString(36).slice(-4).toUpperCase() : nextCreditNoteNumber(f.date), date: f.date, ...cnSplit(inv, gross),
      reason: f.reason, note: f.note.trim(), refund_tx_id: f.refund_tx_id || null };
    Store.updateInvoice(inv.id, { credit_notes: [...cns, cn] });
    setOpen(false); setF({ date: today, gross: '', reason: CN_REASONS[0], note: '', refund_tx_id: '' });
  }
  function remove(cn) {
    if (isLockedDate(cn.date)) { alert('This credit note is in a locked period and can\u2019t be removed.'); return; }
    if (!confirm('Delete credit note ' + cn.number + '? The deletion is recorded in the audit trail.')) return;
    Store.updateInvoice(inv.id, { credit_notes: cns.filter(c => c.id !== cn.id) });
  }

  return (
    <div className="panel-section">
      <label className="field-label">Credit notes</label>
      {cns.map(cn => (
        <div key={cn.id} className="row gap-2" style={{ alignItems: 'center', fontSize: 12, padding: '6px 0', borderBottom: '1px solid var(--rule)' }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div className="mono" style={{ fontSize: 11 }}>{cn.number}</div>
            <div className="muted" style={{ fontSize: 10.5 }}>{fmtDate(cn.date)} · {cn.reason}</div>
          </div>
          <b className="num">−{fmtINR(cn.gross)}</b>
          <button className="btn btn-sm btn-ghost" onClick={() => cnPrint(inv, cn, firm)}>Print</button>
          {inv.share_token && <button className="btn btn-sm btn-ghost" title="Copy the client link to this credit note" onClick={() => {
            const url = location.origin + '/i/' + inv.share_token + '?cn=' + cn.id;
            (navigator.clipboard ? navigator.clipboard.writeText(url) : Promise.reject()).then(() => alert('Link copied:\n' + url), () => prompt('Copy this link:', url));
          }}>Link</button>}
          {!isLockedDate(cn.date) && <button className="btn btn-sm btn-ghost" title="Delete" onClick={() => remove(cn)}>×</button>}
        </div>
      ))}
      {!open && room > 0.5 && inv.status !== 'draft' && (
        <button className="btn btn-sm" style={{ marginTop: 8 }} onClick={() => { setOpen(true); setF(x => ({ ...x, gross: String(room) })); }}>+ Issue credit note</button>
      )}
      {!open && inv.status === 'draft' && <div className="muted" style={{ fontSize: 10.5 }}>Drafts can just be edited. Credit notes are for issued invoices.</div>}
      {open && (
        <div style={{ display: 'grid', gap: 8, marginTop: 8 }}>
          <div className="row gap-2">
            <input className="input" type="date" value={f.date} onChange={e => setF({ ...f, date: e.target.value })} />
            <input className="input num" type="number" placeholder="Amount incl. taxes" value={f.gross} onChange={e => setF({ ...f, gross: e.target.value })} />
          </div>
          <select className="select" value={f.reason} onChange={e => setF({ ...f, reason: e.target.value })}>
            {CN_REASONS.map(r => <option key={r}>{r}</option>)}
          </select>
          <input className="input" placeholder="Note (optional)" value={f.note} onChange={e => setF({ ...f, note: e.target.value })} />
          {refunds.length > 0 && (
            <select className="select" value={f.refund_tx_id} onChange={e => setF({ ...f, refund_tx_id: e.target.value })}>
              <option value="">Not linked to a refund</option>
              {refunds.map(r => <option key={r.id} value={r.id}>Refund {fmtDate(r.date)} · {fmtINR(r.amount)}</option>)}
            </select>
          )}
          {Number(f.gross) > 0 && (() => { const x = cnSplit(inv, Number(f.gross)); return (
            <div className="muted" style={{ fontSize: 10.5, lineHeight: 1.5 }}>Taxable {fmtINR(x.taxable)} · GST {fmtINR(x.gst)}{x.tcs ? ' · TCS ' + fmtINR(x.tcs) : ''}</div>
          ); })()}
          {err && <div className="t-debit" style={{ fontSize: 11.5 }}>{err}</div>}
          <div className="row gap-2">
            <button className="btn btn-sm btn-ghost" onClick={() => { setOpen(false); setErr(''); }}>Cancel</button>
            <button className="btn btn-sm btn-primary" onClick={save}>Issue</button>
          </div>
          <div className="muted" style={{ fontSize: 10, lineHeight: 1.5 }}>A credit note reduces what the client owes and the GST on this invoice. If money goes back to them, record the refund in the ledger too.</div>
        </div>
      )}
    </div>
  );
}

function cnPrint(inv, cn, firm) {
  const f = firm || {};
  const e = x => String(x == null ? '' : x).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[c]);
  const home = String(f.gstin || '').slice(0, 2);
  const pos = posOf(inv, home);
  const inter = pos.code !== home;
  const rate = inv.gst_enabled ? Number(inv.gst_rate) || 0 : 0;
  const logo = /^https?:|^data:/.test(f.logo || '') ? f.logo : location.origin + '/assets/luxury-unlocked-logo.png';
  const taxRows = rate ? (inter
    ? `<tr><td>IGST @ ${rate}%</td><td class="n">${invINR(cn.gst)}</td></tr>`
    : `<tr><td>CGST @ ${rate / 2}%</td><td class="n">${invINR(cn.gst / 2)}</td></tr><tr><td>SGST @ ${rate / 2}%</td><td class="n">${invINR(cn.gst - round2(cn.gst / 2))}</td></tr>`) : '';
  const html = `<!doctype html><html><head><meta charset="utf-8"><title>Credit Note ${e(cn.number).replace(/\//g, '-')} - ${e(inv.customer_name)}</title>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&display=swap" rel="stylesheet">
<style>@page{size:A4;margin:14mm}body{font:12px/1.55 Helvetica,Arial,sans-serif;color:#1C1A17;margin:0}.w{max-width:760px;margin:0 auto;padding:24px}
.h{display:flex;justify-content:space-between;gap:24px;border-bottom:1px solid #B08D57;padding-bottom:16px}.lg{width:140px;display:block;margin-bottom:8px}
.m{color:#5A5347;font-size:11px;white-space:pre-line}.t{font:italic 32px 'Instrument Serif',Georgia,serif;color:#B08D57;text-align:right}
.meta{margin-left:auto;font-size:11.5px;border-collapse:collapse}.meta td{padding:2px 0 2px 14px;text-align:right}.meta td:first-child{color:#8A8378;font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;font-weight:700}
.lb{font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;font-weight:700;color:#8A8378;margin:18px 0 4px}.pn{font:20px 'Instrument Serif',Georgia,serif}
table.it{width:100%;border-collapse:collapse;margin-top:18px}table.it th{text-align:left;font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;color:#8A8378;border-bottom:1px solid #1C1A17;padding:6px 4px}
table.it td{padding:8px 4px;border-bottom:1px solid #E2D9C8}.n{text-align:right;font-variant-numeric:tabular-nums}
.sum{width:300px;margin:14px 0 0 auto;border-collapse:collapse}.sum td{padding:4px 0}.sum .tot td{border-top:1px solid #1C1A17;font-weight:700;font-size:14px;padding-top:8px}
.wd{text-align:right;font-size:11px;color:#5A5347;margin-top:6px}.sg{margin-top:48px;text-align:right}.sg div{display:inline-block;min-width:240px;border-top:1px solid #B08D57;padding-top:6px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:#8A8378}</style></head>
<body><div class="w"><div class="h"><div><img class="lg" src="${e(logo)}" alt="">${f.address ? `<div class="m">${e(f.address)}</div>` : ''}<div class="m">${f.gstin ? 'GSTIN ' + e(f.gstin) + '\n' : ''}${f.pan ? 'PAN ' + e(f.pan) : ''}</div></div>
<div><div class="t">Credit Note</div><table class="meta"><tr><td>Number</td><td>${e(cn.number)}</td></tr><tr><td>Date</td><td>${e(invDate(cn.date))}</td></tr><tr><td>Against invoice</td><td>${e(inv.number)}</td></tr><tr><td>Invoice date</td><td>${e(invDate(inv.date))}</td></tr>${rate ? `<tr><td>Place of supply</td><td>${e(posLabel(pos))}</td></tr>` : ''}</table></div></div>
<div class="lb">Issued to</div><div class="pn">${e(inv.customer_name)}</div>${inv.customer_address ? `<div class="m">${e(inv.customer_address)}</div>` : ''}${inv.customer_gstin ? `<div class="m">GSTIN ${e(inv.customer_gstin)}</div>` : ''}
<table class="it"><thead><tr><th>Description</th><th>SAC</th><th class="n">Taxable value</th></tr></thead><tbody><tr><td>${e(cn.reason)}${cn.note ? ' — ' + e(cn.note) : ''}<div class="m">${e(inv.title || 'Travel arrangements')}</div></td><td>${e(inv.sac_code || (rate ? SAC_TOUR : ''))}</td><td class="n">${invINR(cn.taxable)}</td></tr></tbody></table>
<table class="sum"><tr><td>Taxable value</td><td class="n">${invINR(cn.taxable)}</td></tr>${taxRows}${cn.tcs ? `<tr><td>TCS @ ${Number(inv.tcs_rate)}% reversed</td><td class="n">${invINR(cn.tcs)}</td></tr>` : ''}<tr class="tot"><td>Total credit</td><td class="n">${invINR(cn.gross)}</td></tr></table>
<div class="wd">${e(invAmountWords(cn.gross))}</div>
${(() => { const sg = invSigner(inv, f); const src = sg.signature ? (/^data:|^https?:/.test(sg.signature) ? sg.signature : location.origin + sg.signature) : ''; return `<div class="sg"><p style="font:15px 'Instrument Serif',Georgia,serif;margin:0 0 6px">For ${e(f.name || 'Luxury Unlocked')}</p>${src ? `<img src="${e(src)}" style="display:block;margin:0 0 4px auto;max-height:80px;max-width:240px">` : '<div style="height:50px"></div>'}<div><b style="display:block;font-size:12px;letter-spacing:0;text-transform:none;color:#1C1A17">${e(sg.name)}</b>${e(sg.title || 'Authorised signatory')}</div></div>`; })()}</div>
<script>window.onload=function(){setTimeout(function(){window.print()},400)}<\/script></body></html>`;
  const w = window.open('', '_blank');
  if (!w) { alert('Allow pop-ups for this site to print the credit note.'); return; }
  w.document.open(); w.document.write(html); w.document.close();
}

/* ---------- GSTR-1 ---------- */
const GSTR_H = {
  b2b: ['GSTIN/UIN of Recipient', 'Receiver Name', 'Invoice Number', 'Invoice date', 'Invoice Value', 'Place Of Supply', 'Reverse Charge', 'Applicable % of Tax Rate', 'Invoice Type', 'E-Commerce GSTIN', 'Rate', 'Taxable Value', 'Cess Amount'],
  b2cl: ['Invoice Number', 'Invoice date', 'Invoice Value', 'Place Of Supply', 'Applicable % of Tax Rate', 'Rate', 'Taxable Value', 'Cess Amount', 'E-Commerce GSTIN'],
  b2cs: ['Type', 'Place Of Supply', 'Applicable % of Tax Rate', 'Rate', 'Taxable Value', 'Cess Amount', 'E-Commerce GSTIN'],
  cdnr: ['GSTIN/UIN of Recipient', 'Receiver Name', 'Note Number', 'Note Date', 'Note Type', 'Place Of Supply', 'Reverse Charge', 'Note Supply Type', 'Note Value', 'Applicable % of Tax Rate', 'Rate', 'Taxable Value', 'Cess Amount'],
  cdnur: ['UR Type', 'Note Number', 'Note Date', 'Note Type', 'Place Of Supply', 'Note Value', 'Applicable % of Tax Rate', 'Rate', 'Taxable Value', 'Cess Amount'],
  hsn: ['HSN', 'Description', 'UQC', 'Total Quantity', 'Total Value', 'Rate', 'Taxable Value', 'Integrated Tax Amount', 'Central Tax Amount', 'State/UT Tax Amount', 'Cess Amount'],
  docs: ['Nature of Document', 'Sr. No. From', 'Sr. No. To', 'Total Number', 'Cancelled'],
};
const B2CL_LIMIT = 100000;

function buildGSTR1(st, firm, from, to) {
  const home = String(firm.gstin || '').slice(0, 2);
  const issued = (st.invoices || []).filter(i => i.number && i.status !== 'draft' && !invIsTest(i));
  const taxed = issued.filter(i => i.status !== 'cancelled' && i.gst_enabled && Number(i.gst_rate));
  const inP = d => d && d >= from && d <= to;
  const warn = [];
  const b2b = [], b2cl = [], cdnr = [], cdnur = [];
  const b2cs = {}, hsnB2B = {}, hsnB2C = {};
  const sum = { taxable: 0, igst: 0, cgst: 0, sgst: 0 };

  function addTax(bucket, key, sac, rate, value, taxable, tax, inter) {
    const k = sac + '|' + rate;
    const h = bucket[k] = bucket[k] || { HSN: sac, Description: 'Tour operator services', UQC: 'NA', 'Total Quantity': 0, 'Total Value': 0, Rate: rate, 'Taxable Value': 0, 'Integrated Tax Amount': 0, 'Central Tax Amount': 0, 'State/UT Tax Amount': 0, 'Cess Amount': 0 };
    h['Total Value'] = round2(h['Total Value'] + value);
    h['Taxable Value'] = round2(h['Taxable Value'] + taxable);
    if (inter) h['Integrated Tax Amount'] = round2(h['Integrated Tax Amount'] + tax);
    else { h['Central Tax Amount'] = round2(h['Central Tax Amount'] + tax / 2); h['State/UT Tax Amount'] = round2(h['State/UT Tax Amount'] + tax / 2); }
    sum.taxable += taxable;
    if (inter) sum.igst += tax; else { sum.cgst += tax / 2; sum.sgst += tax / 2; }
  }
  function addB2CS(pos, rate, taxable) {
    const k = pos.code + '|' + rate;
    const r = b2cs[k] = b2cs[k] || { Type: 'OE', 'Place Of Supply': posLabel(pos), 'Applicable % of Tax Rate': '', Rate: rate, 'Taxable Value': 0, 'Cess Amount': 0, 'E-Commerce GSTIN': '' };
    r['Taxable Value'] = round2(r['Taxable Value'] + taxable);
  }

  taxed.forEach(inv => {
    const t = invTotals(inv);
    const rate = Number(inv.gst_rate);
    const pos = posOf(inv, home);
    const inter = pos.code !== home;
    const gstin = String(inv.customer_gstin || '').toUpperCase().trim();
    const reg = GSTIN_RE.test(gstin);
    const value = round2(t.sub + t.gst);
    const sac = inv.sac_code || SAC_TOUR;
    if (inP(inv.date)) {
      if (pos.guessed) warn.push(inv.number + ': no place of supply — assumed ' + (pos.name || 'your state'));
      if (gstin && !reg) warn.push(inv.number + ': GSTIN "' + gstin + '" is not valid — treated as B2C');
      if (reg) b2b.push({ 'GSTIN/UIN of Recipient': gstin, 'Receiver Name': inv.customer_name, 'Invoice Number': inv.number, 'Invoice date': gstrDate(inv.date), 'Invoice Value': value, 'Place Of Supply': posLabel(pos), 'Reverse Charge': 'N', 'Applicable % of Tax Rate': '', 'Invoice Type': 'Regular B2B', 'E-Commerce GSTIN': '', Rate: rate, 'Taxable Value': t.sub, 'Cess Amount': 0 });
      else if (inter && value > B2CL_LIMIT) b2cl.push({ 'Invoice Number': inv.number, 'Invoice date': gstrDate(inv.date), 'Invoice Value': value, 'Place Of Supply': posLabel(pos), 'Applicable % of Tax Rate': '', Rate: rate, 'Taxable Value': t.sub, 'Cess Amount': 0, 'E-Commerce GSTIN': '' });
      else addB2CS(pos, rate, t.sub);
      addTax(reg ? hsnB2B : hsnB2C, null, sac, rate, value, t.sub, t.gst, inter);
    }
    (inv.credit_notes || []).filter(c => inP(c.date)).forEach(c => {
      const nv = round2(c.taxable + c.gst);
      if (reg) cdnr.push({ 'GSTIN/UIN of Recipient': gstin, 'Receiver Name': inv.customer_name, 'Note Number': c.number, 'Note Date': gstrDate(c.date), 'Note Type': 'C', 'Place Of Supply': posLabel(pos), 'Reverse Charge': 'N', 'Note Supply Type': 'Regular B2B', 'Note Value': nv, 'Applicable % of Tax Rate': '', Rate: rate, 'Taxable Value': c.taxable, 'Cess Amount': 0 });
      else if (inter && value > B2CL_LIMIT) cdnur.push({ 'UR Type': 'B2CL', 'Note Number': c.number, 'Note Date': gstrDate(c.date), 'Note Type': 'C', 'Place Of Supply': posLabel(pos), 'Note Value': nv, 'Applicable % of Tax Rate': '', Rate: rate, 'Taxable Value': c.taxable, 'Cess Amount': 0 });
      else addB2CS(pos, rate, -c.taxable);
      addTax(reg ? hsnB2B : hsnB2C, null, sac, rate, -nv, -c.taxable, -c.gst, inter);
    });
  });

  const noGst = issued.filter(i => inP(i.date) && i.status !== 'cancelled' && !(i.gst_enabled && Number(i.gst_rate)));
  if (noGst.length) warn.push(noGst.length + ' issued invoice' + (noGst.length > 1 ? 's have' : ' has') + ' no GST rate and ' + (noGst.length > 1 ? 'are' : 'is') + ' left out: ' + noGst.map(i => i.number).join(', '));

  const series = (list, label) => {
    if (!list.length) return null;
    const nums = list.map(x => x.number).sort();
    return { 'Nature of Document': label, 'Sr. No. From': nums[0], 'Sr. No. To': nums[nums.length - 1], 'Total Number': list.length, Cancelled: list.filter(x => x.cancelled).length };
  };
  const invDocs = issued.filter(i => inP(i.date)).map(i => ({ number: i.number, cancelled: i.status === 'cancelled' }));
  const cnDocs = [];
  issued.forEach(i => (i.credit_notes || []).filter(c => inP(c.date)).forEach(c => cnDocs.push({ number: c.number, cancelled: false })));
  const docs = [series(invDocs, 'Invoices for outward supply'), series(cnDocs, 'Credit Note')].filter(Boolean);

  return {
    b2b, b2cl, b2cs: Object.values(b2cs).filter(r => Math.abs(r['Taxable Value']) > 0.004), cdnr, cdnur,
    hsnB2B: Object.values(hsnB2B), hsnB2C: Object.values(hsnB2C), docs, warn,
    sum: { taxable: round2(sum.taxable), igst: round2(sum.igst), cgst: round2(sum.cgst), sgst: round2(sum.sgst) },
  };
}

function GSTR1Export({ books }) {
  const firm = books.firm || {};
  const st = useStore();
  const now = new Date();
  const lastM = new Date(now.getFullYear(), now.getMonth() - 1, 1);
  const [mode, setMode] = React.useState('month');
  const [month, setMonth] = React.useState(lastM.getFullYear() + '-' + String(lastM.getMonth() + 1).padStart(2, '0'));
  const win = React.useMemo(() => {
    const [y, m] = month.split('-').map(Number);
    if (mode === 'month') return { from: month + '-01', to: month + '-' + String(new Date(y, m, 0).getDate()).padStart(2, '0'), label: new Date(y, m - 1, 1).toLocaleString('en-IN', { month: 'long', year: 'numeric' }) };
    const qStart = Math.floor((m - 1) / 3) * 3 + 1;
    const end = new Date(y, qStart + 2, 0);
    return { from: y + '-' + String(qStart).padStart(2, '0') + '-01', to: y + '-' + String(qStart + 2).padStart(2, '0') + '-' + String(end.getDate()).padStart(2, '0'), label: 'Quarter ' + new Date(y, qStart - 1, 1).toLocaleString('en-IN', { month: 'short' }) + '–' + end.toLocaleString('en-IN', { month: 'short', year: 'numeric' }) };
  }, [mode, month]);
  const r = React.useMemo(() => buildGSTR1(st, firm, win.from, win.to), [st.invoices, firm.gstin, win.from, win.to]);

  function download() {
    const wb = XLSX.utils.book_new();
    const add = (rows, h, name) => {
      const ws = rows.length ? XLSX.utils.json_to_sheet(rows, { header: h }) : XLSX.utils.aoa_to_sheet([h]);
      XLSX.utils.book_append_sheet(wb, ws, name);
    };
    XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet([
      ['GSTR-1 workings', firm.name || 'Luxury Unlocked'], ['GSTIN', firm.gstin || ''], ['Period', win.label + ' (' + win.from + ' to ' + win.to + ')'], [],
      ['GSTR-3B · 3.1(a) Outward taxable supplies (net of credit notes)'],
      ['Taxable value', r.sum.taxable], ['Integrated tax', r.sum.igst], ['Central tax', r.sum.cgst], ['State/UT tax', r.sum.sgst], [],
      ['GSTR-3B · 4 Eligible ITC', books.itc ? 'See purchase register' : 'Nil — tour operator services at 5% without ITC'], [],
      ['Invoice value = taxable value + GST. TCS under the Income-tax Act is not part of the value of supply.'],
      ['Workings only. Have your CA review before filing.'],
      ...(r.warn.length ? [[], ['Check before filing'], ...r.warn.map(w => [w])] : []),
    ]), 'Summary');
    add(r.b2b, GSTR_H.b2b, 'b2b'); add(r.b2cl, GSTR_H.b2cl, 'b2cl'); add(r.b2cs, GSTR_H.b2cs, 'b2cs');
    add(r.cdnr, GSTR_H.cdnr, 'cdnr'); add(r.cdnur, GSTR_H.cdnur, 'cdnur');
    add(r.hsnB2B, GSTR_H.hsn, 'hsn(b2b)'); add(r.hsnB2C, GSTR_H.hsn, 'hsn(b2c)'); add(r.docs, GSTR_H.docs, 'docs');
    XLSX.writeFile(wb, 'GSTR1_' + (firm.gstin || 'workings') + '_' + win.from.slice(0, 7) + (mode === 'quarter' ? '_Q' : '') + '.xlsx');
  }

  const n = r.b2b.length + r.b2cl.length + r.b2cs.length;
  return (
    <div className="card" style={{ marginBottom: 16 }}>
      <div className="row gap-2" style={{ alignItems: 'flex-end', flexWrap: 'wrap', justifyContent: 'space-between' }}>
        <div>
          <div className="serif" style={{ fontSize: 22 }}>GSTR-1 &amp; 3B export</div>
          <div className="muted" style={{ fontSize: 12 }}>Built from your issued invoices and credit notes, laid out like the GST offline tool's sheets.</div>
        </div>
        <div className="row gap-2" style={{ alignItems: 'center' }}>
          <div className="seg">{['month', 'quarter'].map(m => <div key={m} className={'seg-btn ' + (mode === m ? 'active' : '')} onClick={() => setMode(m)}>{m}</div>)}</div>
          <input className="input" type="month" value={month} onChange={e => e.target.value && setMonth(e.target.value)} style={{ width: 150 }} />
          <button className="btn btn-primary" disabled={!firm.gstin} onClick={download}>Download GSTR-1.xlsx</button>
        </div>
      </div>
      {!firm.gstin && <div className="t-debit" style={{ fontSize: 12, marginTop: 10 }}>Add your GSTIN in Invoices → Firm settings first.</div>}
      <div className="books-kpi-row" style={{ marginTop: 14 }}>
        <div className="card"><div className="bk-label">{win.label}</div><div className="bk-val">{n} line{n === 1 ? '' : 's'}</div><div className="bk-note">B2B {r.b2b.length} · B2CL {r.b2cl.length} · B2CS {r.b2cs.length}</div></div>
        <div className="card"><div className="bk-label">Credit notes</div><div className="bk-val">{(r.docs.find(d => d['Nature of Document'] === 'Credit Note') || {})['Total Number'] || 0}</div><div className="bk-note">CDNR {r.cdnr.length} · CDNUR {r.cdnur.length}</div></div>
        <div className="card"><div className="bk-label">Taxable (net)</div><div className="bk-val">{fmtINR(r.sum.taxable)}</div></div>
        <div className="card"><div className="bk-label">Tax · IGST / CGST / SGST</div><div className="bk-val" style={{ fontSize: 16 }}>{fmtINR(r.sum.igst)} / {fmtINR(r.sum.cgst)} / {fmtINR(r.sum.sgst)}</div></div>
      </div>
      {r.warn.length > 0 && (
        <div className="ca-banner warn" style={{ marginTop: 12, marginBottom: 0 }}>
          <div><strong>Check before filing</strong><ul style={{ margin: '6px 0 0', paddingLeft: 18 }}>{r.warn.slice(0, 8).map((w, i) => <li key={i}>{w}</li>)}</ul>{r.warn.length > 8 && <div>…and {r.warn.length - 8} more (listed in the file).</div>}</div>
        </div>
      )}
    </div>
  );
}

/* ---------- TCS quarterly pack (27EQ workings) ---------- */
const TCS_Q = [
  { q: 1, label: 'Q1 · Apr–Jun', from: '-04-01', to: '-06-30', due: ['', '-07-15'] },
  { q: 2, label: 'Q2 · Jul–Sep', from: '-07-01', to: '-09-30', due: ['', '-10-15'] },
  { q: 3, label: 'Q3 · Oct–Dec', from: '-10-01', to: '-12-31', due: ['+1', '-01-15'] },
  { q: 4, label: 'Q4 · Jan–Mar', from: '-01-01', to: '-03-31', due: ['+1', '-05-15'], next: true },
];
function tcsQuarterWindow(fyStart, q) {
  const d = TCS_Q[q - 1];
  const y = d.next ? fyStart + 1 : fyStart;
  return { from: y + d.from, to: y + d.to, label: d.label + ' ' + fyStart + '-' + String((fyStart + 1) % 100).padStart(2, '0'), returnDue: (d.due[0] ? fyStart + 1 : fyStart) + d.due[1] };
}
// TCS is due when the money is received or the client is debited, whichever
// comes first — so the collection date is the earlier of the invoice date and
// the first payment against it.
function buildTCSQuarter(st, fyStart, q) {
  const w = tcsQuarterWindow(fyStart, q);
  const inW = d => d && d >= w.from && d <= w.to;
  const rows = [], missing = [];
  (st.invoices || []).filter(i => Number(i.tcs_rate) > 0 && i.number && i.status !== 'draft' && i.status !== 'cancelled' && !invIsTest(i)).forEach(inv => {
    const t = invTotals(inv);
    const paidDates = [
      ...(st.transactions || []).filter(x => x.invoiceId === inv.id && x.direction === 'credit').map(x => x.date),
      ...(inv.offledger || []).map(p => p.date),
    ].filter(Boolean).sort();
    const coll = [inv.date, paidDates[0]].filter(Boolean).sort()[0];
    const pan = String(inv.customer_pan || '').toUpperCase();
    const base = { name: inv.customer_name || '', pan, panOk: PAN_RE.test(pan), number: inv.number, invDate: inv.date, rate: Number(inv.tcs_rate), section: tcsSection(coll) };
    if (inW(coll)) {
      rows.push({ ...base, date: coll, amount: round2(t.sub + t.gst), tcs: t.tcs, remark: coll < inv.date ? 'Advance received before invoice' : '' });
      if (!base.panOk) missing.push(inv);
    }
    (inv.credit_notes || []).filter(c => Number(c.tcs) > 0 && inW(c.date)).forEach(c => {
      rows.push({ ...base, date: c.date, amount: -round2(c.taxable + c.gst), tcs: -Number(c.tcs), remark: 'Credit note ' + c.number + ' — TCS reversed' });
    });
  });
  rows.sort((a, b) => a.date.localeCompare(b.date));
  const months = {};
  rows.forEach(r => { const m = r.date.slice(0, 7); months[m] = round2((months[m] || 0) + r.tcs); });
  const deposits = Object.keys(months).sort().map(m => {
    const [y, mo] = m.split('-').map(Number);
    const due = mo === 3 ? y + '-04-30' : (mo === 12 ? (y + 1) + '-01-07' : y + '-' + String(mo + 1).padStart(2, '0') + '-07');
    return { month: m, tcs: months[m], due };
  });
  return { w, rows, missing, deposits, total: round2(rows.reduce((a, r) => a + r.tcs, 0)), base: round2(rows.reduce((a, r) => a + r.amount, 0)) };
}

function TCSQuarterPack({ books }) {
  const st = useStore();
  const firm = books.firm || {};
  const today = new Date();
  const curFy = today.getMonth() >= 3 ? today.getFullYear() : today.getFullYear() - 1;
  const curQ = Math.floor(((today.getMonth() + 9) % 12) / 3) + 1;
  const [fy, setFy] = React.useState(curQ === 1 ? curFy - 1 : curFy);
  const [q, setQ] = React.useState(curQ === 1 ? 4 : curQ - 1);
  const r = React.useMemo(() => buildTCSQuarter(st, fy, q), [st.invoices, st.transactions, fy, q]);
  const bookTotal = React.useMemo(() => {
    const reg = tcsRegister(st, books);
    return round2(reg.rows.filter(x => x.date >= r.w.from && x.date <= r.w.to).reduce((a, x) => a + x.collected, 0));
  }, [st.transactions, books.tx, r.w.from, r.w.to]);
  const fys = [curFy - 2, curFy - 1, curFy];

  function download() {
    const wb = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet([
      ['TCS quarterly workings — Form 27EQ', firm.name || 'Luxury Unlocked'],
      ['PAN of collector', firm.pan || ''], ['TAN of collector', firm.tan || '(add in Firm settings)'],
      ['Quarter', r.w.label + ' (' + r.w.from + ' to ' + r.w.to + ')'], ['Usual return due date', r.w.returnDue], [],
      ['Collectees', r.rows.filter(x => x.tcs > 0).length], ['Amount on which TCS collected', r.base], ['TCS collected (net)', r.total],
      ['TCS in the books (tagged bank receipts)', bookTotal], ['Difference', round2(r.total - bookTotal)], [],
      ['Deposits by month', 'TCS', 'Usual challan due date'], ...r.deposits.map(d => [d.month, d.tcs, d.due]), [],
      ['Nature of collection: overseas tour programme package. ' + (r.w.from >= TCS_NEW_FROM ? 'Section 394, Income-tax Act 2025 — flat 2%.' : 'Section 206C(1G), Income-tax Act 1961.')],
      ['Collection date = earlier of invoice date and first receipt. Workings only — your CA files the return.'],
      ...(r.missing.length ? [[], ['Missing or invalid PAN — collect before filing'], ...r.missing.map(i => [i.number, i.customer_name, i.customer_pan || 'none'])] : []),
    ]), 'Summary');
    const h = ['Sr', 'Collectee name', 'PAN', 'PAN valid', 'Invoice no.', 'Invoice date', 'Date of collection', 'Amount received / debited', 'TCS rate %', 'TCS collected', 'Section', 'Remarks'];
    const rows = r.rows.map((x, i) => [i + 1, x.name, x.pan, x.panOk ? 'Yes' : 'No', x.number, x.invDate, x.date, x.amount, x.rate, x.tcs, x.section, x.remark]);
    XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet([h, ...rows]), 'Collectees');
    XLSX.writeFile(wb, 'TCS_27EQ_' + fy + '-' + String((fy + 1) % 100).padStart(2, '0') + '_Q' + q + '.xlsx');
  }

  return (
    <div className="card" style={{ marginBottom: 16 }}>
      <div className="row gap-2" style={{ alignItems: 'flex-end', flexWrap: 'wrap', justifyContent: 'space-between' }}>
        <div>
          <div className="serif" style={{ fontSize: 22 }}>Quarterly pack for your CA</div>
          <div className="muted" style={{ fontSize: 12 }}>Collectee-wise TCS from your invoices, ready for the 27EQ return.</div>
        </div>
        <div className="row gap-2" style={{ alignItems: 'center' }}>
          <select className="select" value={fy} onChange={e => setFy(Number(e.target.value))}>{fys.map(y => <option key={y} value={y}>FY {y}-{String((y + 1) % 100).padStart(2, '0')}</option>)}</select>
          <div className="seg">{[1, 2, 3, 4].map(n => <div key={n} className={'seg-btn ' + (q === n ? 'active' : '')} onClick={() => setQ(n)}>Q{n}</div>)}</div>
          <button className="btn btn-primary" disabled={!r.rows.length} onClick={download}>Download TCS pack.xlsx</button>
        </div>
      </div>
      <div className="books-kpi-row" style={{ marginTop: 14 }}>
        <div className="card"><div className="bk-label">{r.w.label}</div><div className="bk-val">{r.rows.filter(x => x.tcs > 0).length} collectee{r.rows.filter(x => x.tcs > 0).length === 1 ? '' : 's'}</div><div className="bk-note">Return usually due {fmtDateFull(r.w.returnDue)}</div></div>
        <div className="card"><div className="bk-label">TCS collected</div><div className="bk-val">{fmtINR(r.total)}</div><div className="bk-note">on {fmtINR(r.base)}</div></div>
        <div className="card"><div className="bk-label">In the books</div><div className="bk-val">{fmtINR(bookTotal)}</div><div className={'bk-note ' + (Math.abs(r.total - bookTotal) > 1 ? 't-debit' : '')}>{Math.abs(r.total - bookTotal) > 1 ? 'Differs by ' + fmtINR(Math.abs(r.total - bookTotal)) + ' — check TCS tags on receipts' : 'Matches the invoices'}</div></div>
        <div className="card"><div className="bk-label">Missing PAN</div><div className={'bk-val ' + (r.missing.length ? 't-debit' : '')}>{r.missing.length}</div></div>
      </div>
      {r.missing.length > 0 && (
        <div className="ca-banner warn" style={{ marginTop: 12, marginBottom: 0 }}>
          <div><strong>Collect these PANs before filing.</strong> Without one, the client can't claim the TCS back and a higher rate may apply. {r.missing.slice(0, 6).map(i => i.number + ' · ' + i.customer_name).join('; ')}{r.missing.length > 6 ? '…' : ''}</div>
        </div>
      )}
      {r.deposits.length > 0 && (
        <div className="muted" style={{ fontSize: 11.5, marginTop: 10 }}>
          Deposits: {r.deposits.map(d => new Date(d.month + '-01T00:00:00').toLocaleString('en-IN', { month: 'short' }) + ' ' + fmtINR(d.tcs) + ' by ' + fmtDate(d.due)).join(' · ')}
        </div>
      )}
    </div>
  );
}

/* ---------- audit & lock tab ---------- */
function monthEnd(offset) { const d = new Date(); const x = new Date(d.getFullYear(), d.getMonth() + offset, 0); return x.getFullYear() + '-' + String(x.getMonth() + 1).padStart(2, '0') + '-' + String(x.getDate()).padStart(2, '0'); }
function quarterEnd() { const d = new Date(); const q = Math.floor(d.getMonth() / 3) * 3; const x = new Date(d.getFullYear(), q, 0); return x.getFullYear() + '-' + String(x.getMonth() + 1).padStart(2, '0') + '-' + String(x.getDate()).padStart(2, '0'); }

function LockPanel({ books }) {
  const L = books.lockedUntil || '';
  const [d, setD] = React.useState(L || monthEnd(0));
  function apply(next) {
    if (!next) return;
    if (L && next < L && !confirm('Unlock ' + fmtDateFull(next) + ' to ' + fmtDateFull(L) + '?\n\nEntries in that window become editable again. The unlock is recorded in the audit trail.')) return;
    if (next > new Date().toISOString().slice(0, 10) && !confirm('That date is in the future — today\u2019s entries would be locked too. Continue?')) return;
    BooksStore.set({ lockedUntil: next });
  }
  return (
    <div className="card" style={{ marginBottom: 16 }}>
      <div className="serif" style={{ fontSize: 22 }}>Period lock</div>
      <div className="muted" style={{ fontSize: 12.5, maxWidth: 680, marginTop: 2 }}>
        Lock a month once its GST return is filed. Nothing dated on or before the lock can be added, changed or deleted — by anyone — until you move it back. Invoices in a locked month are corrected with a credit note.
      </div>
      <div className="row gap-2" style={{ alignItems: 'center', marginTop: 14, flexWrap: 'wrap' }}>
        <span className="field-label" style={{ margin: 0 }}>Locked up to</span>
        <b>{L ? fmtDateFull(L) : 'Nothing locked'}</b>
        <div style={{ flex: 1 }}></div>
        <input className="input" type="date" value={d} onChange={e => setD(e.target.value)} style={{ width: 160 }} />
        <button className="btn btn-primary" onClick={() => apply(d)}>{L && d < L ? 'Unlock to this date' : 'Lock up to this date'}</button>
      </div>
      <div className="row gap-2" style={{ marginTop: 10, flexWrap: 'wrap' }}>
        <button className="btn btn-sm btn-ghost" onClick={() => { setD(monthEnd(0)); apply(monthEnd(0)); }}>Lock through {fmtDateFull(monthEnd(0))}</button>
        <button className="btn btn-sm btn-ghost" onClick={() => { setD(quarterEnd()); apply(quarterEnd()); }}>Lock through last quarter</button>
        {L && <button className="btn btn-sm btn-ghost" onClick={() => { if (confirm('Remove the lock entirely? This is recorded in the audit trail.')) BooksStore.set({ lockedUntil: '' }); }}>Remove lock</button>}
      </div>
    </div>
  );
}

const AUDIT_TABLES = [['', 'Everything'], ['transactions', 'Transactions'], ['invoices', 'Invoices'], ['books_tx', 'Books tags'], ['books_meta', 'Books settings'], ['bookings', 'Bookings'], ['customers', 'Customers'], ['vendors', 'Vendors'], ['bank_accounts', 'Bank accounts'], ['upcoming', 'Upcoming'], ['books_assets', 'Fixed assets'], ['categories', 'Categories']];

function auditLabel(r) {
  const x = r.new_row || r.old_row || {};
  switch (r.table_name) {
    case 'transactions': return (x.date || '') + ' · ' + String(x.particulars || '').slice(0, 46) + ' · ' + fmtINR(x.amount);
    case 'invoices': return (x.number || 'invoice') + ' · ' + (x.customer_name || '');
    case 'books_tx': return 'Tags on transaction ' + x.tx_id;
    case 'books_meta': return 'Setting: ' + x.key;
    case 'categories': return x.kind + ' category: ' + x.name;
    default: return x.name || x.tour || x.particulars || r.row_key;
  }
}
function auditVal(v) {
  if (v == null || v === '') return '—';
  if (typeof v === 'object') { const s = JSON.stringify(v); return s.length > 70 ? s.slice(0, 67) + '…' : s; }
  const s = String(v); return s.length > 70 ? s.slice(0, 67) + '…' : s;
}

function AuditTrail() {
  const cs = useCloud();
  const [f, setF] = React.useState({ table: '', q: '', from: '', to: '' });
  const [rows, setRows] = React.useState([]);
  const [st, setSt] = React.useState({ loading: false, error: '', more: false });
  const PAGE = 100;

  async function load(append) {
    if (!cs.session) return;
    setSt(x => ({ ...x, loading: true, error: '' }));
    let q = Cloud.client.from('audit_log').select('*').order('at', { ascending: false });
    if (f.table) q = q.eq('table_name', f.table);
    if (f.q.trim()) q = q.ilike('row_key', '%' + f.q.trim() + '%');
    if (f.from) q = q.gte('at', f.from + 'T00:00:00');
    if (f.to) q = q.lte('at', f.to + 'T23:59:59');
    const start = append ? rows.length : 0;
    const { data, error } = await q.range(start, start + PAGE - 1);
    if (error) { setSt({ loading: false, error: /audit_log|relation|schema cache/i.test(error.message) ? 'missing' : error.message, more: false }); return; }
    setRows(append ? [...rows, ...data] : data);
    setSt({ loading: false, error: '', more: data.length === PAGE });
  }
  React.useEffect(() => { load(false); }, [cs.session, f.table, f.from, f.to]);

  function csv() {
    const esc = v => '"' + String(v == null ? '' : v).replace(/"/g, '""') + '"';
    const lines = [['When', 'User', 'Action', 'Table', 'Record', 'Field', 'Before', 'After'].map(esc).join(',')];
    rows.forEach(r => {
      const fields = r.action === 'update' ? (r.changed || []) : [''];
      fields.forEach(k => lines.push([r.at, r.user_email, r.action, r.table_name, auditLabel(r), k,
        k ? JSON.stringify((r.old_row || {})[k]) : '', k ? JSON.stringify((r.new_row || {})[k]) : ''].map(esc).join(',')));
    });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(new Blob([lines.join('\n')], { type: 'text/csv' }));
    a.download = 'audit-trail-' + new Date().toISOString().slice(0, 10) + '.csv';
    a.click();
  }

  if (!cs.session) return <EmptyNote>Sign in to the cloud to see the audit trail. Edits made while signed out aren't recorded.</EmptyNote>;
  if (st.error === 'missing') return (
    <div className="ca-banner warn"><div><strong>Audit trail isn't switched on yet.</strong> Run <span className="mono">supabase/compliance.sql</span> once in Supabase → SQL Editor. From then on every change is logged by the database itself, and the log can't be edited or deleted.</div></div>
  );

  return (
    <div className="card" style={{ padding: 0 }}>
      <div className="stmt-head" style={{ flexWrap: 'wrap', gap: 8 }}>
        <div>
          <div className="stmt-title">Audit trail</div>
          <div className="muted" style={{ fontSize: 12 }}>Every add, edit and delete, recorded by the database. Read-only — it can't be switched off or altered.</div>
        </div>
        <div className="row gap-2" style={{ flexWrap: 'wrap' }}>
          <select className="select" value={f.table} onChange={e => setF({ ...f, table: e.target.value })}>{AUDIT_TABLES.map(([v, l]) => <option key={v} value={v}>{l}</option>)}</select>
          <input className="input" placeholder="Record id" value={f.q} onChange={e => setF({ ...f, q: e.target.value })} onKeyDown={e => e.key === 'Enter' && load(false)} style={{ width: 120 }} />
          <input className="input" type="date" value={f.from} onChange={e => setF({ ...f, from: e.target.value })} style={{ width: 140 }} />
          <input className="input" type="date" value={f.to} onChange={e => setF({ ...f, to: e.target.value })} style={{ width: 140 }} />
          <button className="btn btn-ghost" disabled={!rows.length} onClick={csv}>Export CSV</button>
        </div>
      </div>
      <table className="tbl">
        <thead><tr><th style={{ width: 132 }}>When</th><th style={{ width: 150 }}>Who</th><th style={{ width: 70 }}>Action</th><th>Record</th><th>Change</th></tr></thead>
        <tbody>
          {rows.map(r => (
            <tr key={r.id}>
              <td className="num" style={{ fontSize: 11.5 }}>{new Date(r.at).toLocaleString('en-IN', { day: '2-digit', month: 'short', year: '2-digit', hour: '2-digit', minute: '2-digit' })}</td>
              <td style={{ fontSize: 11.5 }}>{r.user_email || <span className="muted">system</span>}</td>
              <td><span className={'tag ' + (r.action === 'delete' ? 'tag-expense' : r.action === 'insert' ? 'tag-income' : 'tag-transfer')}>{r.action === 'insert' ? 'added' : r.action === 'delete' ? 'deleted' : 'edited'}</span></td>
              <td style={{ fontSize: 12 }}><div>{auditLabel(r)}</div><div className="muted mono" style={{ fontSize: 10 }}>{r.table_name} · {r.row_key}</div></td>
              <td style={{ fontSize: 11.5 }}>
                {r.action === 'update'
                  ? (r.changed || []).map(k => <div key={k}><span className="mono" style={{ fontSize: 10.5 }}>{k}</span> <span className="muted">{auditVal((r.old_row || {})[k])}</span> → {auditVal((r.new_row || {})[k])}</div>)
                  : <span className="muted">{r.action === 'insert' ? 'New record' : 'Removed — last values kept in the log'}</span>}
              </td>
            </tr>
          ))}
          {!rows.length && !st.loading && <tr><td colSpan={5} style={{ textAlign: 'center', padding: 32, color: 'var(--ink-3)' }}>{st.error || 'No changes recorded for this filter yet.'}</td></tr>}
        </tbody>
      </table>
      {(st.more || st.loading) && <div style={{ padding: 12, textAlign: 'center' }}><button className="btn btn-sm" disabled={st.loading} onClick={() => load(true)}>{st.loading ? 'Loading…' : 'Load more'}</button></div>}
    </div>
  );
}

function AuditLockTab({ books }) {
  return <div><LockPanel books={books} /><AuditTrail /></div>;
}

Object.assign(window, {
  GST_STATES, GSTIN_RE, PAN_RE, SAC_TOUR, posOf, posLabel, isInter, tcsSection, TCS_NEW_FROM, booksLock, isLockedDate,
  nextCreditNoteNumber, cnSplit, creditedOf, cnPrint, CreditNotesPanel, LockedNotice, buildGSTR1, GSTR1Export,
  AuditLockTab, AuditTrail, LockPanel, buildTCSQuarter, TCSQuarterPack,
});
