import { entityRights, rightsFor, MESSAGES } from '../config/page-rights.mjs';
import { ReasonDialog } from '../views/spec/ui';
import { useCallback, useEffect, useState } from 'react';
import { useApp } from '../context/AppContext';
import { statusLabel } from '../views/RoleDashboard';

// One form pattern for create, edit and archive across HR, Finance and Device Services.
// The server decides what each role may do; the buttons shown come from row.actions.
const call = async (path, role, method = 'GET', body) => {
  const r = await fetch(`/api/${path}`, { method, headers: { 'x-saverbro-role': role, ...(body ? { 'Content-Type': 'application/json' } : {}) }, body: body ? JSON.stringify(body) : undefined });
  const j = await r.json().catch(() => ({}));
  if (!r.ok) throw Object.assign(new Error(j?.error?.message || 'Something went wrong. Try again.'), { status: r.status });
  return j.data;
};
const money = n => `RM ${Number(n).toLocaleString('en-MY', { minimumFractionDigits: Number(n) % 1 ? 2 : 0, maximumFractionDigits: 2 })}`;
const lookupCache = new Map();
function useLookup(role) {
  const [data, setData] = useState(lookupCache.get(role) || null);
  useEffect(() => { if (lookupCache.has(role)) return; call('crud-lookup', role).then(d => { lookupCache.set(role, d); setData(d); }).catch(() => {}); }, [role]);
  return data;
}

export const CRUD = {
  adjustments: {
    title: 'Adjustments to paid records', noun: 'adjustment', add: 'Add adjustment', noAdd: true,
    columns: [['entity','Record', v => v === 'claims' ? 'Claim' : 'Spend request'], ['recordId','No.'], ['memberName','For'], ['amountRm','Amount', v => `${Number(v) > 0 ? '+' : '−'}${money(Math.abs(Number(v)))}`], ['reason','Reason'], ['status','Status', v => v === 'SUBMITTED' ? 'Waiting for approval' : statusLabel(v)]],
    form: () => [], blank: {}, name: r => `adjustment ${r.id}`,
  },
  employees: {
    title: 'Staff', noun: 'staff member', add: 'Add staff',
    columns: [['displayName','Name'], ['roleLabel','Role'], ['unitId','Branch'], ['employmentType','Contract', v => statusLabel(v)], ['startDate','Start date', v => v ? new Date(v + 'T00:00:00').toLocaleDateString('en-GB', { day:'numeric', month:'short', year:'numeric' }) : '']],
    form: L => [['displayName','Name','text'], ['roleKey','Role','select', L?.roles], ['unitId','Branch','select', [{id:'HQ',name:'HQ'},{id:'KK',name:'Kota Kinabalu'}]], ['employmentType','Contract','select', ['PERMANENT','CONTRACT','PROBATION'].map(v => ({ id:v, name:statusLabel(v) }))], ['startDate','Start date','date']],
    blank: { displayName:'', roleKey:'technician', unitId:'KK', employmentType:'PROBATION', startDate:'2026-10-19' },
    name: r => r.displayName,
  },
  onboarding: {
    title: 'Joining steps', noun: 'joining step', add: 'Add step',
    columns: [['memberName','Staff member'], ['itemKey','Step', v => ({ ROLE_BRIEF:'Role briefing', LOCAL_SAFETY:'Branch safety walk', FIRST_WEEK_PLAN:'First week plan' })[v] || v], ['status','Status', v => statusLabel(v)]],
    form: L => [['memberId','Staff member','select', L?.members], ['itemKey','Step','select', [{id:'ROLE_BRIEF',name:'Role briefing'},{id:'LOCAL_SAFETY',name:'Branch safety walk'},{id:'FIRST_WEEK_PLAN',name:'First week plan'}]], ['status','Status','select', [{id:'OPEN',name:'Open'},{id:'COMPLETE',name:'Done'}]]],
    blank: { memberId:'', itemKey:'ROLE_BRIEF', status:'OPEN' },
    name: r => `${r.memberName}'s step`,
  },
  claims: {
    title: 'Claims', noun: 'claim', add: 'Add claim',
    columns: [['memberName','Staff member'], ['unitId','Branch'], ['category','Category', v => statusLabel(v)], ['amountRm','Amount', money], ['status','Status', v => v === 'FIRST_APPROVED' ? 'Waiting for Finance' : v === 'SUBMITTED' ? 'Waiting for manager' : statusLabel(v)]],
    maxAmount: 500,
    form: L => [['memberId','Staff member','select', L?.members], ['category','Category','select', ['TRAVEL','MEAL','MEDICAL','OTHER'].map(v => ({ id:v, name:statusLabel(v) }))], ['amountRm','Amount (RM)','number']],
    blank: { memberId:'', category:'TRAVEL', amountRm:50 },
    name: r => `${r.memberName}'s claim`,
  },
  'finance-requests': {
    title: 'Spend requests', noun: 'spend request', add: 'Add request',
    columns: [['purpose','Purpose'], ['memberName','Requested by'], ['unitId','Branch'], ['amountRm','Amount', money], ['status','Status', (v, r) => v === 'SUBMITTED' ? (r?.needsHqApproval ? 'Waiting for HQ approval' : 'Waiting for Finance') : statusLabel(v)]],
    maxAmount: 100000,
    form: L => [['memberId','Requested by','select', L?.members], ['purpose','Purpose','text'], ['amountRm','Amount (RM)','number']],
    blank: { memberId:'', purpose:'', amountRm:100 },
    name: r => r.purpose,
  },
  jobs: {
    title: 'Service jobs', noun: 'job', add: 'Add job',
    columns: [['jobAlias','Job'], ['itemAlias','Device'], ['ownerName','Assigned to'], ['status','Status', v => statusLabel(v)], ['qcState','QC', v => statusLabel(v)]],
    form: L => [['jobAlias','Job','text'], ['itemId','Device','select', L?.items], ['ownerId','Assigned to','select', L?.members], ['status','Status','select', ['READY','IN_PROGRESS','QC_READY','COMPLETE'].map(v => ({ id:v, name:statusLabel(v) }))], ['partsState','Parts','select', [{id:'NOT_REQUIRED',name:'Not needed'},{id:'AVAILABLE',name:'In stock'},{id:'WAITING_SYNTHETIC',name:'Waiting for parts'}]], ['qcState','QC','select', [{id:'NOT_STARTED',name:'Not started'},{id:'PENDING',name:'Waiting'},{id:'PASSED',name:'Passed'}]]],
    blank: { jobAlias:'', itemId:'SI-001', ownerId:'', status:'READY', partsState:'NOT_REQUIRED', qcState:'NOT_STARTED' },
    name: r => r.jobAlias,
  },
  customers: {
    title: 'Customers', noun: 'customer', add: 'Add customer',
    columns: [['alias','Customer'], ['purpose','Reason'], ['stage','Stage', v => v === 'Proposal draft' ? 'Quote drafted' : v === 'Hold' ? 'On hold' : v], ['contactAllowed','Contact', v => v ? 'Allowed' : 'Not yet']],
    form: () => [['alias','Customer name','text'], ['purpose','Reason','select', ['Quotation follow-up','Service follow-up','Campaign enquiry'].map(v => ({ id:v, name:v }))], ['stage','Stage','select', [{id:'New',name:'New'},{id:'Qualified',name:'Qualified'},{id:'Proposal draft',name:'Quote drafted'},{id:'Hold',name:'On hold'},{id:'Won',name:'Closed: won'},{id:'Lost',name:'Closed: lost'}]], ['contactAllowed','Contact allowed','checkbox']],
    blank: { alias:'', purpose:'Service follow-up', stage:'New', contactAllowed:true },
    board: { field: 'stage', columns: [['New','New'], ['Qualified','Qualified'], ['Proposal draft','Quote drafted'], ['Hold','On hold']], next: { New: 'Qualified', Qualified: 'Proposal draft' } },
    name: r => r.alias,
  },
};

function Modal({ title, children, onClose, labelledBy }) {
  useEffect(() => { const k = e => e.key === 'Escape' && onClose(); window.addEventListener('keydown', k); return () => window.removeEventListener('keydown', k); }, [onClose]);
  return <div className="sb-modal-backdrop" onMouseDown={e => e.target === e.currentTarget && onClose()}>
    <div className="sb-modal" role="dialog" aria-modal="true" aria-labelledby={labelledBy}><h2 id={labelledBy}>{title}</h2>{children}</div>
  </div>;
}

// Spec pages use `bare` (no title row; search, Show archived and a record count instead), `addSignal`
// (the page header's primary button opens the add form), `onRows` (page tiles) and `columns`/`emptyText` overrides.
export default function CrudPanel({ entity, layout = 'table', title, addLabel, bare, addSignal, onRows, columns, emptyText, filterRows, rowMenu, branch, page }) {
  const base = CRUD[entity];
  const cfg = { ...base, ...(title ? { title } : {}), ...(addLabel ? { add: addLabel } : {}), ...(columns ? { columns } : {}) };
  const [search, setSearch] = useState('');
  const [menuFor, setMenuFor] = useState(null);
  const { state, dispatch } = useApp();
  const role = state.currentRole;
  const lookup = useLookup(role);
  const [rows, setRows] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');
  const [archived, setArchived] = useState(false);
  const [form, setForm] = useState(null); // { mode:'add'|'edit', id, values }
  const [formError, setFormError] = useState('');
  const [confirm, setConfirm] = useState(null);
  const [busy, setBusy] = useState(false);
  const [canAdd, setCanAdd] = useState(true);
  const [adjust, setAdjust] = useState(null); // { row, amountRm, reason }
  const [sendBack, setSendBack] = useState(null); // spend request waiting for a "What needs changing?" reason

  const load = useCallback(async () => {
    setLoading(true); setError('');
    try { const qs = [archived ? 'archived=1' : '', branch && branch !== 'all' ? `branch=${encodeURIComponent(branch)}` : ''].filter(Boolean).join('&'); setRows(await call(`crud/${entity}${qs ? `?${qs}` : ''}`, role)); setCanAdd(entityRights(role, entity).add && (!page || rightsFor(role, page).add)); } // page: the Add button also follows that page's row (e.g. Finance can't add claims on HR › Claims)
    catch (e) { setError(e.status === 403 ? MESSAGES.forbidden : `Couldn't load ${cfg.noun}s.`); setCanAdd(false); setRows([]); }
    finally { setLoading(false); }
  }, [entity, archived, role, branch, page]);
  useEffect(() => { load(); }, [load]);
  useEffect(() => { onRows?.(rows); }, [rows]); // eslint-disable-line react-hooks/exhaustive-deps
  useEffect(() => { if (addSignal) openAdd(); }, [addSignal]); // eslint-disable-line react-hooks/exhaustive-deps
  const errText = err => err.status === 403 && err.message !== MESSAGES.selfApproval ? MESSAGES.forbidden : err.message;
  const q = search.trim().toLowerCase();
  const visible = rows.filter(r => (!filterRows || filterRows(r)) && (!q || cfg.columns.some(([k]) => String(r[k] ?? '').toLowerCase().includes(q))));

  const toast = (message, kind = 'info') => dispatch({ type: 'SHOW_TOAST', message, kind });
  async function save(e) {
    e.preventDefault(); setBusy(true); setFormError('');
    try {
      const v = { ...form.values, ...(form.values.amountRm !== undefined ? { amountRm: Number(form.values.amountRm) } : {}) };
      if (form.mode === 'add') await call(`crud/${entity}`, role, 'POST', v); else await call(`crud/${entity}/${form.id}`, role, 'PATCH', v);
      toast(form.mode === 'add' ? `${cfg.noun.charAt(0).toUpperCase() + cfg.noun.slice(1)} added.` : 'Changes saved.');
      setForm(null); load();
    } catch (err) { setFormError(errText(err)); } finally { setBusy(false); }
  }
  async function move(row, value) {
    setBusy(true);
    try { await call(`crud/${entity}/${row.id}`, role, 'PATCH', { [cfg.board.field]: value }); toast(`Moved to ${cfg.board.columns.find(c => c[0] === value)?.[1] || value}.`); load(); }
    catch (err) { toast(errText(err), 'error'); } finally { setBusy(false); }
  }
  async function act(row, verb, note) {
    setBusy(true);
    try { await call(`crud/${entity}/${row.id}/${verb}`, role, 'POST', note ? { note } : {}); toast({ archive:'Archived.', restore:'Restored.', approve:'Approved.', decline: entity === 'finance-requests' ? 'Sent back.' : 'Declined.', pay:'Marked as paid.' }[verb]); setConfirm(null); load(); }
    catch (err) { toast(errText(err), 'error'); setConfirm(null); } finally { setBusy(false); }
  }
  async function saveAdjustment(e) {
    e.preventDefault(); setBusy(true); setFormError('');
    try { await call('crud/adjustments', role, 'POST', { entity, recordId: String(adjust.row.id), amountRm: Number(adjust.amountRm), reason: adjust.reason }); toast('Adjustment added. It needs approval from someone else.'); setAdjust(null); load(); }
    catch (err) { setFormError(err.message); } finally { setBusy(false); }
  }
  const fields = cfg.form(lookup);
  const openAdd = () => { setFormError(''); setForm({ mode:'add', values: { ...cfg.blank, ...(cfg.blank.memberId === '' ? { memberId: lookup?.members?.[0]?.id || '' } : {}), ...(cfg.blank.ownerId === '' ? { ownerId: lookup?.members?.[0]?.id || '' } : {}) } }); };
  const openEdit = row => { setFormError(''); setForm({ mode:'edit', id: row.id, values: Object.fromEntries(Object.keys(cfg.blank).map(k => [k, row[k] ?? cfg.blank[k]])) }); };

  return <div className={bare ? 'sp-crud sb-crud' : 'sb-panel sb-crud'} data-crud={entity}>
    {bare ? <div className="sp-filters">
      <input className="sp-search" type="search" placeholder={`Search ${cfg.title.toLowerCase()}`} aria-label={`Search ${cfg.title.toLowerCase()}`} value={search} onChange={e => setSearch(e.target.value)} />
      <label className="sp-check"><input type="checkbox" checked={archived} onChange={e => setArchived(e.target.checked)} /> Show archived</label>
      <span className="count">{visible.length === 1 ? '1 record' : `${visible.length} records`}</span>
    </div> : <div className="sb-panel-head">
      <h2>{cfg.title}</h2>
      <div className="sb-crud-tools">
        <label className="sb-toggle"><input type="checkbox" checked={archived} onChange={e => setArchived(e.target.checked)} /> Show archived</label>
        {canAdd && !cfg.noAdd && !archived && <button type="button" className="sb-primary" onClick={openAdd}>{cfg.add}</button>}
      </div>
    </div>}
    {loading ? <div className="sb-empty">Loading…</div> : error ? <div className="sb-empty" role="alert">{error}{error.startsWith("Couldn't") && <> <button type="button" className="sp-btn link" onClick={load}>Try again</button></>}</div> : layout === 'board' && cfg.board && !archived ? <div className="sb-pipeline">
      {cfg.board.columns.map(([value, label]) => { const items = rows.filter(r => r[cfg.board.field] === value); return <section key={value} className="sb-pipeline-col" aria-label={label}>
        <header>{label}<span>{items.length}</span></header>
        {!items.length && <div className="sb-empty">None</div>}
        {items.map(r => <article key={r.id} className="sb-pipeline-card">
          <strong>{cfg.name(r)}</strong>
          <small>{r.purpose}</small>
          <small>Owner: {r.ownerName || 'Unassigned'} · {r.unitId}</small>
          <small>{r.contactAllowed ? 'Contact allowed' : "Don't contact yet"}</small>
          <div className="sb-crud-actions">
            {r.actions.includes('edit') && cfg.board.next[value] && <button type="button" className="sb-secondary" disabled={busy} onClick={() => move(r, cfg.board.next[value])}>Move to {cfg.board.columns.find(c => c[0] === cfg.board.next[value])[1]}</button>}
            {r.actions.includes('edit') && <button type="button" className="sb-secondary" onClick={() => openEdit(r)}>Edit</button>}
            {r.actions.includes('archive') && <button type="button" className="sb-secondary sb-danger-text" onClick={() => setConfirm(r)}>Archive</button>}
          </div>
        </article>)}
      </section>; })}
    </div> : !visible.length ? <div className="sb-empty">{archived ? 'Nothing archived.' : q ? 'No matches.' : (emptyText || `No ${cfg.noun}s yet.`)}</div> :
      <table className="sb-crud-table"><thead><tr>{cfg.columns.map(([k, l]) => <th key={k}>{l}</th>)}<th aria-label="Actions" /></tr></thead>
        <tbody>{visible.map(r => <tr key={r.id}>{cfg.columns.map(([k, , f]) => <td key={k} className={k === 'amountRm' ? 'num' : undefined}>{f ? f(r[k], r) : r[k]}{k === 'status' && r.paidAt && <span className="sb-tag-paid">Paid</span>}</td>)}
          <td className="sb-crud-actions">
            {r.actions.includes('approve') && <button type="button" className="sb-secondary" disabled={busy} onClick={() => act(r, 'approve')}>Approve</button>}
            {r.actions.includes('decline') && (entity === 'finance-requests'
              ? <button type="button" className="sb-secondary" disabled={busy} onClick={() => setSendBack(r)}>Send back</button>
              : <button type="button" className="sb-secondary" disabled={busy} onClick={() => act(r, 'decline')}>Decline</button>)}
            {r.actions.includes('pay') && <button type="button" className="sb-secondary" disabled={busy} onClick={() => act(r, 'pay')}>Mark as paid</button>}
            {r.actions.includes('adjust') && <button type="button" className="sb-secondary" onClick={() => { setFormError(''); setAdjust({ row: r, amountRm: '', reason: '' }); }}>Add adjustment</button>}
            {rowMenu ? ['edit','archive','restore'].some(a => r.actions.includes(a)) && <span className="sp-more">
              <button type="button" className="sp-btn small" aria-label={`More actions for ${cfg.name(r)}`} aria-expanded={menuFor === r.id} onClick={() => setMenuFor(m => m === r.id ? null : r.id)}>⋯</button>
              {menuFor === r.id && <span className="sp-menu" role="menu" style={{ top: 36 }}>
                {r.actions.includes('edit') && <button type="button" role="menuitem" onClick={() => { setMenuFor(null); openEdit(r); }}>Edit</button>}
                {r.actions.includes('archive') && <button type="button" role="menuitem" className="danger" onClick={() => { setMenuFor(null); setConfirm(r); }}>Archive</button>}
                {r.actions.includes('restore') && <button type="button" role="menuitem" onClick={() => { setMenuFor(null); act(r, 'restore'); }}>Restore</button>}
              </span>}
            </span> : <>
            {r.actions.includes('edit') && <button type="button" className="sb-secondary" onClick={() => openEdit(r)}>Edit</button>}
            {r.actions.includes('archive') && <button type="button" className="sb-secondary sb-danger-text" onClick={() => setConfirm(r)}>Archive</button>}
            {r.actions.includes('restore') && <button type="button" className="sb-secondary" disabled={busy} onClick={() => act(r, 'restore')}>Restore</button>}
            </>}
          </td></tr>)}</tbody></table>}

    {sendBack && <ReasonDialog title={`Send back ${cfg.name(sendBack)}?`} prompt="What needs changing?" confirmLabel="Send back" onClose={() => setSendBack(null)}
      onConfirm={async reason => { await call(`crud/${entity}/${sendBack.id}/decline`, role, 'POST', { note: reason }); toast('Sent back.'); load(); }} />}
    {form && <Modal title={form.mode === 'add' ? cfg.add : `Edit ${cfg.noun}`} labelledBy={`crud-${entity}-title`} onClose={() => setForm(null)}>
      <form onSubmit={save} className="sb-crud-form" noValidate>
        {fields.map(([k, label, type, options]) => <label key={k} className={type === 'checkbox' ? 'sb-crud-check' : ''}>
          {type !== 'checkbox' && <span>{label}</span>}
          {type === 'select' ? <select value={form.values[k] ?? ''} onChange={e => setForm(f => ({ ...f, values: { ...f.values, [k]: e.target.value } }))}>{(options || []).map(o => <option key={o.id} value={o.id}>{o.name}{o.roleLabel ? ` (${o.roleLabel})` : ''}</option>)}</select>
            : type === 'checkbox' ? <><input type="checkbox" checked={!!form.values[k]} onChange={e => setForm(f => ({ ...f, values: { ...f.values, [k]: e.target.checked } }))} /> {label}</>
            : <input type={type} value={form.values[k] ?? ''} min={type === 'number' ? 1 : undefined} max={type === 'number' ? (cfg.maxAmount || 500) : undefined} onChange={e => setForm(f => ({ ...f, values: { ...f.values, [k]: e.target.value } }))} />}
        </label>)}
        {formError && <p className="sb-crud-error" role="alert">{formError}</p>}
        <div className="sb-modal-actions"><button type="button" className="sb-secondary" onClick={() => setForm(null)}>Cancel</button><button type="submit" className="sb-primary" disabled={busy}>{form.mode === 'add' ? cfg.add : 'Save changes'}</button></div>
      </form>
    </Modal>}

    {adjust && <Modal title={`Add adjustment to ${cfg.name(adjust.row)}`} labelledBy={`crud-${entity}-adjust`} onClose={() => setAdjust(null)}>
      <form onSubmit={saveAdjustment} className="sb-crud-form" noValidate>
        <p className="sb-crud-note">This record is paid and locked. An adjustment is added alongside it and needs approval from someone else.</p>
        <label><span>Amount (RM, use minus to reduce)</span><input type="number" step="0.01" value={adjust.amountRm} onChange={e => setAdjust(a => ({ ...a, amountRm: e.target.value }))} /></label>
        <label><span>Reason</span><input type="text" value={adjust.reason} onChange={e => setAdjust(a => ({ ...a, reason: e.target.value }))} /></label>
        {formError && <p className="sb-crud-error" role="alert">{formError}</p>}
        <div className="sb-modal-actions"><button type="button" className="sb-secondary" onClick={() => setAdjust(null)}>Cancel</button><button type="submit" className="sb-primary" disabled={busy}>Add adjustment</button></div>
      </form>
    </Modal>}

    {confirm && <Modal title={`Archive ${cfg.name(confirm)}?`} labelledBy={`crud-${entity}-confirm`} onClose={() => setConfirm(null)}>
      <p className="sb-crud-note">It will be hidden from lists. You can restore it from Show archived.</p>
      <div className="sb-modal-actions"><button type="button" className="sb-secondary" onClick={() => setConfirm(null)}>Cancel</button><button type="button" className="sb-primary sb-danger-bg" disabled={busy} onClick={() => act(confirm, 'archive')}>Archive</button></div>
    </Modal>}
  </div>;
}
