/* Ekonomi, fliken Reskontra (redesign 2026-09).
 *
 * Kundreskontra och leverantörsreskontra från Fortnox, belopp inkl. moms.
 * Bara läsande anrop:
 *   GET /v1/admin/fortnox/summary              lastSync, netPosition
 *   GET /v1/admin/fortnox/ar?status=open       öppna kundfakturor
 *   GET /v1/admin/fortnox/ap?status=open       öppna leverantörsfakturor
 *   GET /v1/admin/fortnox/ap?status=paid&from= betalda leverantörsfakturor (betalningsmönster)
 *   GET /v1/admin/fortnox/intelligence         dso, dpo, ccc, collectionRate
 *   GET /v1/admin/finance/customer-dso         kredittid och betalningsmönster per kund
 *   GET /v1/admin/clients/credit-utilization   kreditgränser
 *   GET /v1/admin/finance/credit-facilities    registrerade krediter (typ factoring = fakturaköp)
 * Åldersanalys, förfallet och grupperingen per kund räknas här från de öppna
 * fakturorna, med dagens lokala datum.
 */

const rxFinBuckets = [
  { key: 'current', label: 'Ej förfallet', color: '#1F8A55' },
  { key: '1-30', label: '1 till 30 dagar', color: '#E0A33B' },
  { key: '31-60', label: '31 till 60 dagar', color: '#D0782A' },
  { key: '61-90', label: '61 till 90 dagar', color: '#B9521F' },
  { key: '90+', label: 'Över 90 dagar', color: '#A12C1E' },
];

const rxFinModes = {
  ar: {
    party: 'Kund', parties: 'kunder', ledger: 'Kundreskontra',
    openLabel: 'Öppna kundfordringar', invoiceWord: 'kundfakturor',
    creditLabel: 'Kredittid kunder', patternLabel: 'Betalningsmönster',
    csvName: 'forfallna-kundfakturor',
  },
  ap: {
    party: 'Leverantör', parties: 'leverantörer', ledger: 'Leverantörsreskontra',
    openLabel: 'Öppna leverantörsskulder', invoiceWord: 'leverantörsfakturor',
    creditLabel: 'Kredittid leverantörer', patternLabel: 'Vår betalning',
    csvName: 'forfallna-leverantorsfakturor',
  },
};

function rxFinIsoDaysAgo(days) {
  const d = rxToday();
  d.setDate(d.getDate() - days);
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
}

/** '23/9', med år om det inte är innevarande år: '23/9 2025'. */
function rxFinDate(v) {
  const d = rxParseDate(v);
  if (!d) return '';
  const base = `${d.getDate()}/${d.getMonth() + 1}`;
  return d.getFullYear() === new Date().getFullYear() ? base : `${base} ${d.getFullYear()}`;
}

/** Fortnox synced_at är SQLite datetime('now'), dvs UTC utan zon. */
function rxFinSyncDate(v) {
  if (!v) return null;
  const s = String(v);
  const iso = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}/.test(s) ? s.replace(' ', 'T') + 'Z' : s;
  const d = new Date(iso);
  return isNaN(d) ? null : d;
}

function rxFinBucketOf(daysLate) {
  if (daysLate == null) return 'unknown';
  if (daysLate <= 0) return 'current';
  if (daysLate <= 30) return '1-30';
  if (daysLate <= 60) return '31-60';
  if (daysLate <= 90) return '61-90';
  return '90+';
}

function rxFinNormalize(inv, mode) {
  const today = rxToday();
  const daysLate = inv.due_date ? rxDayDiff(inv.due_date, today) : null;
  return {
    id: mode === 'ar' ? inv.document_number : inv.givenumber,
    party: (mode === 'ar' ? inv.customer_name : inv.supplier_name) || 'Okänd',
    partyNo: mode === 'ar' ? inv.customer_number : inv.supplier_number,
    invoiceDate: inv.invoice_date,
    dueDate: inv.due_date,
    total: Number(inv.total) || 0,
    balance: Number(inv.balance) || 0,
    currency: inv.currency || 'SEK',
    ocr: inv.ocr || '',
    daysLate,
    bucket: rxFinBucketOf(daysLate),
  };
}

function rxFinPct(part, whole) {
  if (!whole) return 0;
  return Math.round((part / whole) * 100);
}

function rxFinDays(n) {
  if (n == null) return '';
  const r = Math.round(n * 10) / 10;
  return `${r.toLocaleString('sv-SE')} ${r === 1 ? 'dag' : 'dagar'}`;
}

/** Betalningsmönster: snitt dagar efter förfallodag vid betalning. */
function rxFinPattern(avgLate) {
  if (avgLate == null) return { text: 'Ingen historik', tone: 'neutral' };
  const n = Math.round(avgLate);
  if (n < 0) return { text: `${-n} d före förfall`, tone: 'ok' };
  if (n === 0) return { text: 'På förfallodagen', tone: 'ok' };
  return { text: `${n} d sent`, tone: n > 20 ? 'danger' : 'warn' };
}

/** Kredittid och betalningsmönster per leverantör från betalda fakturor. */
function rxFinSupplierStats(paidInvoices) {
  const acc = {};
  (paidInvoices || []).forEach(inv => {
    if (!inv.final_pay_date) return;
    const key = inv.supplier_number || inv.supplier_name;
    const a = acc[key] || (acc[key] = { n: 0, credit: 0, lateN: 0, late: 0 });
    const credit = rxDayDiff(inv.invoice_date, inv.final_pay_date);
    if (credit != null) { a.n++; a.credit += credit; }
    const late = inv.due_date ? rxDayDiff(inv.due_date, inv.final_pay_date) : null;
    if (late != null) { a.lateN++; a.late += late; }
  });
  const out = {};
  Object.keys(acc).forEach(k => {
    const a = acc[k];
    out[k] = {
      paidCount: a.n,
      avgCredit: a.n ? Math.round(a.credit / a.n) : null,
      avgLate: a.lateN ? Math.round(a.late / a.lateN) : null,
    };
  });
  return out;
}

function rxFinGroup(invoices) {
  const map = {};
  invoices.forEach(inv => {
    const key = inv.partyNo || inv.party;
    const g = map[key] || (map[key] = {
      key, name: inv.party, partyNo: inv.partyNo, open: 0, count: 0,
      overdue: 0, overdueCount: 0, oldestDue: null, maxLate: null, invoices: [],
    });
    g.open += inv.balance;
    g.count++;
    if (inv.daysLate != null && inv.daysLate > 0) {
      g.overdue += inv.balance;
      g.overdueCount++;
    }
    if (inv.dueDate && (!g.oldestDue || inv.dueDate < g.oldestDue)) g.oldestDue = inv.dueDate;
    if (inv.daysLate != null && (g.maxLate == null || inv.daysLate > g.maxLate)) g.maxLate = inv.daysLate;
    g.invoices.push(inv);
  });
  return Object.values(map).map(g => {
    g.invoices.sort((a, b) => String(a.dueDate || '').localeCompare(String(b.dueDate || '')));
    return g;
  });
}

function rxFinCsvNum(v) {
  return String(Math.round((Number(v) || 0) * 100) / 100).replace('.', ',');
}

function rxFinExportOverdue(mode, invoices) {
  const rows = invoices
    .filter(i => i.daysLate != null && i.daysLate > 0)
    .sort((a, b) => b.daysLate - a.daysLate)
    .map(i => ({
      party: i.party, partyNo: i.partyNo || '', id: i.id,
      invoiceDate: i.invoiceDate || '', dueDate: i.dueDate || '', daysLate: i.daysLate,
      total: rxFinCsvNum(i.total), balance: rxFinCsvNum(i.balance), currency: i.currency, ocr: i.ocr,
    }));
  if (!rows.length || typeof downloadCsv !== 'function') return;
  const cfg = rxFinModes[mode];
  const cols = [
    { key: 'party', label: cfg.party },
    { key: 'partyNo', label: mode === 'ar' ? 'Kundnummer' : 'Leverantörsnummer' },
    { key: 'id', label: mode === 'ar' ? 'Fakturanummer' : 'Löpnummer' },
    { key: 'invoiceDate', label: 'Fakturadatum' },
    { key: 'dueDate', label: 'Förfallodag' },
    { key: 'daysLate', label: 'Dagar sen' },
    { key: 'total', label: 'Totalt inkl. moms' },
    { key: 'balance', label: 'Saldo inkl. moms' },
    { key: 'currency', label: 'Valuta' },
  ];
  if (mode === 'ar') cols.push({ key: 'ocr', label: 'OCR' });
  downloadCsv(`${cfg.csvName}-${rxFinIsoDaysAgo(0)}.csv`, rows, cols);
}

/* ── Delkomponenter ─────────────────────────────────────── */

function RxFinModeToggle({ mode, onChange }) {
  const btn = (key, label) => <button key={key} type="button" aria-pressed={mode === key} onClick={() => onChange(key)}
    style={{
      height: 32, padding: '0 14px', border: 0, borderRadius: 8, font: 'inherit', fontSize: 13.5, cursor: 'pointer',
      background: mode === key ? '#fff' : 'transparent',
      color: mode === key ? 'var(--rx-ink)' : 'var(--rx-ink-3)',
      fontWeight: mode === key ? 600 : 500,
      boxShadow: mode === key ? '0 0 0 1px var(--rx-border), 0 1px 2px rgba(23,22,28,.06)' : 'none',
    }}>{label}</button>;
  return <div role="group" aria-label="Välj reskontra"
    style={{ display: 'inline-flex', gap: 2, padding: 3, borderRadius: 10, background: 'var(--rx-neutral-bg)' }}>
    {btn('ar', 'Kundreskontra')}
    {btn('ap', 'Leverantörsreskontra')}
  </div>;
}

function RxFinSyncLine({ summary, error }) {
  if (error) return <span className="rx-muted" style={{ fontSize: 13 }}>Senaste uppdatering från Fortnox kunde inte hämtas.</span>;
  if (!summary) return null;
  const d = rxFinSyncDate(summary.lastSync);
  const stale = d && (Date.now() - d.getTime()) > 24 * 3600 * 1000;
  return <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8, fontSize: 13, color: 'var(--rx-ink-3)', flexWrap: 'wrap' }}>
    <span className={`rx-dot${!d ? ' rx-dot--neutral' : stale ? ' rx-dot--warn' : ''}`} />
    {d ? <span>Uppdaterad från Fortnox {rxDayTime(d)}{d.getFullYear() !== new Date().getFullYear() ? ` ${d.getFullYear()}` : ''}</span>
      : <span>Ingen uppdatering från Fortnox registrerad</span>}
    {stale && <RxPill tone="warn">Över ett dygn gammal</RxPill>}
    <span aria-hidden="true">·</span>
    <span>Belopp inkl. moms</span>
  </span>;
}

function RxFinAging({ invoices, bucket, onBucket, total }) {
  const sums = rxFinBuckets.map(b => {
    const list = invoices.filter(i => i.bucket === b.key);
    return { ...b, amount: list.reduce((s, i) => s + i.balance, 0), count: list.length };
  });
  const unknown = invoices.filter(i => i.bucket === 'unknown');
  return <RxPanel title="Åldersanalys" sub="Öppet belopp per dagar efter förfallodag"
    actions={bucket !== 'all' && <button type="button" className="rx-link" onClick={() => onBucket('all')}>Visa alla</button>}>
    <div aria-hidden="true" style={{ display: 'flex', height: 14, borderRadius: 999, overflow: 'hidden', background: 'var(--rx-divider)', gap: 2 }}>
      {total > 0 && sums.filter(s => s.amount > 0).map(s => <span key={s.key}
        style={{ width: `${Math.max(0.8, (s.amount / total) * 100)}%`, background: s.color, opacity: bucket === 'all' || bucket === s.key ? 1 : 0.3 }} />)}
    </div>
    <div className="rx-grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', gap: 10 }}>
      {sums.map(s => {
        const on = bucket === s.key;
        return <button key={s.key} type="button" aria-pressed={on} onClick={() => onBucket(on ? 'all' : s.key)}
          aria-label={`${s.label}: ${fmtSek(s.amount)}, ${s.count} fakturor. ${on ? 'Visar urvalet, klicka för att visa alla.' : 'Filtrera listan.'}`}
          style={{
            textAlign: 'left', font: 'inherit', cursor: 'pointer', borderRadius: 10, padding: '10px 12px',
            border: `1px solid ${on ? '#C9C4F2' : 'var(--rx-border)'}`,
            background: on ? 'var(--rx-accent-softer)' : '#fff',
            display: 'flex', flexDirection: 'column', gap: 3,
          }}>
          <span style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12.5, color: 'var(--rx-ink-3)' }}>
            <span style={{ width: 8, height: 8, borderRadius: 999, background: s.color, flex: '0 0 8px' }} />{s.label}
          </span>
          <span style={{ fontSize: 16, fontWeight: 650, color: 'var(--rx-ink)', fontVariantNumeric: 'tabular-nums' }}>{fmtSek(s.amount)}</span>
          <span style={{ fontSize: 12, color: 'var(--rx-ink-3)' }}>{rxPlural(s.count, 'faktura', 'fakturor')} · {rxFinPct(s.amount, total)} %</span>
        </button>;
      })}
    </div>
    {unknown.length > 0 && <span className="rx-muted" style={{ fontSize: 12.5 }}>
      {rxPlural(unknown.length, 'faktura saknar', 'fakturor saknar')} förfallodag ({fmtSek(unknown.reduce((s, i) => s + i.balance, 0))}) och ingår inte i åldersanalysen.
    </span>}
  </RxPanel>;
}

function RxFinInvoiceTable({ mode, invoices }) {
  return <table className="rx-table" style={{ background: '#fff', border: '1px solid var(--rx-border)', borderRadius: 10 }}>
    <thead>
      <tr>
        <th>{mode === 'ar' ? 'Fakturanummer' : 'Löpnummer'}</th>
        <th>Fakturadatum</th>
        <th>Förfallodag</th>
        <th className="rx-num">Dagar sen</th>
        <th className="rx-num">Totalt</th>
        <th className="rx-num">Saldo</th>
      </tr>
    </thead>
    <tbody>
      {invoices.map(i => <tr key={i.id}>
        <td className="rx-mono">{i.id}</td>
        <td>{rxFinDate(i.invoiceDate)}</td>
        <td>{rxFinDate(i.dueDate) || <span className="rx-faint">Saknas</span>}</td>
        <td className="rx-num">
          {i.daysLate != null && i.daysLate > 0
            ? <span style={{ fontWeight: 600, color: i.daysLate > 30 ? 'var(--rx-danger)' : 'var(--rx-warn)' }}>{i.daysLate}</span>
            : <span className="rx-faint">{i.daysLate == null ? '' : 'Ej förfallen'}</span>}
        </td>
        <td className="rx-num rx-muted">{fmtSek(i.total)}{i.currency !== 'SEK' ? ` (${i.currency})` : ''}</td>
        <td className="rx-num" style={{ fontWeight: 600 }}>{fmtSek(i.balance)}{i.currency !== 'SEK' ? ` (${i.currency})` : ''}</td>
      </tr>)}
    </tbody>
  </table>;
}

function RxFinPartyTable({ mode, groups, statsFor, statsLoading, filtered, expanded, onToggle }) {
  const cfg = rxFinModes[mode];
  const [showAll, setShowAll] = React.useState(false);
  const limit = 40;
  const visible = showAll ? groups : groups.slice(0, limit);
  if (!groups.length) return <RxEmpty title="Inga fakturor i urvalet">Ändra filtret eller sökningen.</RxEmpty>;
  return <div style={{ overflowX: 'auto' }}>
    <table className="rx-table">
      <thead>
        <tr>
          <th>{cfg.party}</th>
          <th className="rx-num">{filtered ? 'Öppet i urvalet' : 'Öppet'}</th>
          <th className="rx-num">Fakturor</th>
          <th className="rx-num">Förfallet</th>
          <th>Äldsta förfallodag</th>
          <th className="rx-num">Kredittid</th>
          <th>{cfg.patternLabel}</th>
        </tr>
      </thead>
      <tbody>
        {visible.map(g => {
          const st = statsFor(g);
          const open = expanded === g.key;
          const pattern = st ? rxFinPattern(st.avgLate) : null;
          return <React.Fragment key={g.key}>
            <tr className={`rx-row--click${open ? ' rx-row--selected' : ''}`} onClick={() => onToggle(g.key)}>
              <td>
                <button type="button" aria-expanded={open} onClick={e => { e.stopPropagation(); onToggle(g.key); }}
                  aria-label={`${open ? 'Dölj' : 'Visa'} öppna fakturor för ${g.name}`}
                  style={{ display: 'flex', alignItems: 'center', gap: 8, border: 0, background: 'transparent', padding: 0, font: 'inherit', cursor: 'pointer', textAlign: 'left', color: 'var(--rx-ink)' }}>
                  <span style={{ display: 'inline-flex', color: 'var(--rx-ink-4)', transform: open ? 'rotate(90deg)' : 'none', transition: 'transform .15s' }}>{I.chev}</span>
                  <span style={{ fontWeight: 600 }}>{g.name}</span>
                </button>
              </td>
              <td className="rx-num" style={{ fontWeight: 600 }}>{fmtSek(g.open)}</td>
              <td className="rx-num">{fmtInt(g.count)}</td>
              <td className="rx-num">
                {g.overdueCount > 0
                  ? <span style={{ fontWeight: 600, color: 'var(--rx-warn)' }}>{fmtSek(g.overdue)} <span style={{ fontWeight: 400 }}>({g.overdueCount})</span></span>
                  : <span className="rx-faint">0 kr</span>}
              </td>
              <td>
                {g.oldestDue ? <span>{rxFinDate(g.oldestDue)}
                  {g.maxLate != null && g.maxLate > 0 && <span style={{ marginLeft: 6, fontSize: 12.5, color: g.maxLate > 30 ? 'var(--rx-danger)' : 'var(--rx-warn)' }}>{g.maxLate} d sen</span>}
                </span> : <span className="rx-faint">Saknas</span>}
              </td>
              <td className="rx-num">
                {st && st.avgCredit != null ? `${st.avgCredit} d` : <span className="rx-faint">{statsLoading ? '…' : 'Ingen historik'}</span>}
              </td>
              <td>{pattern ? <RxPill tone={pattern.tone}>{pattern.text}</RxPill> : <span className="rx-faint">{statsLoading ? 'Hämtar…' : 'Ingen historik'}</span>}</td>
            </tr>
            {open && <tr>
              <td colSpan={7} style={{ background: 'var(--rx-surface-alt)', padding: '12px 16px 16px' }}>
                <div style={{ display: 'flex', gap: 12, alignItems: 'baseline', marginBottom: 8, flexWrap: 'wrap' }}>
                  <span style={{ fontWeight: 600 }}>{filtered ? 'Öppna fakturor i urvalet' : 'Öppna fakturor'}</span>
                  {g.partyNo && <span className="rx-muted" style={{ fontSize: 12.5 }}>{mode === 'ar' ? 'Kundnummer' : 'Leverantörsnummer'} {g.partyNo}</span>}
                  {st && st.paidCount > 0 && <span className="rx-muted" style={{ fontSize: 12.5 }}>Kredittid och mönster från {rxPlural(st.paidCount, 'betald faktura', 'betalda fakturor')}, senaste 180 dagarna</span>}
                </div>
                <RxFinInvoiceTable mode={mode} invoices={g.invoices} />
              </td>
            </tr>}
          </React.Fragment>;
        })}
      </tbody>
    </table>
    {groups.length > limit && <div style={{ padding: '12px 18px', borderTop: '1px solid var(--rx-divider)' }}>
      <button type="button" className="rx-link" onClick={() => setShowAll(v => !v)}>
        {showAll ? `Visa de ${limit} största` : `Visa alla ${fmtInt(groups.length)} ${cfg.parties}`}
      </button>
    </div>}
  </div>;
}

function RxFinDoubleCount({ facilities }) {
  const factoring = (facilities || []).filter(f => f.facilityType === 'factoring');
  return <RxPanel title="Risk för dubbelräkning" tone="warn">
    <div style={{ fontSize: 13.5, lineHeight: 1.55, color: 'var(--rx-ink-2)', display: 'flex', flexDirection: 'column', gap: 8 }}>
      <p style={{ margin: 0 }}>
        En kunds totala skuld är Prioritets reskontra plus det som är öppet i Fortnox och inte finns hos Prioritet.
      </p>
      <p style={{ margin: 0 }}>
        Fakturor som sålts till Prioritet via fakturaköp men ännu inte matchats mot utbetalningen ligger öppna i båda. Att lägga ihop reskontrorna räknar dem två gånger.
      </p>
      <p style={{ margin: 0, color: 'var(--rx-ink-3)' }}>
        Prioritets reskontra finns inte i systemet ännu, så överlappet kan inte beräknas här. Öppna kundfordringar ovan kan därför innehålla fakturor som redan sålts. Gör avstämningen i Prioritets portal: bläddra igenom alla sidor och stäm av mot summaraden, eftersom "Visa alla rader" inte laddar alla rader.
      </p>
      {factoring.length > 0 && <div style={{ borderTop: '1px solid var(--rx-divider)', paddingTop: 8, display: 'flex', flexDirection: 'column', gap: 6 }}>
        {factoring.map(f => <div key={f.id} style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8 }}>
            <span style={{ fontWeight: 600, color: 'var(--rx-ink)' }}>{f.name}</span>
            <span style={{ fontVariantNumeric: 'tabular-nums' }}>{fmtSek(f.currentBalanceSek)} av {fmtSek(f.ceilingSek)}</span>
          </div>
          <RxBar pct={f.utilizationPct} tone={f.utilizationPct >= 90 ? 'warn' : undefined} />
          <span className="rx-muted" style={{ fontSize: 12 }}>Fakturaköp, manuellt registrerat utnyttjande{f.updatedAt ? `, uppdaterat ${rxFinDate(String(f.updatedAt).slice(0, 10))}` : ''}.</span>
        </div>)}
      </div>}
    </div>
  </RxPanel>;
}

function RxFinCreditLimits({ util, onNavigate }) {
  const list = util.data?.utilization || [];
  const link = <button type="button" className="rx-link" onClick={() => onNavigate && onNavigate('clients/kredit')}>Sätt kreditgränser</button>;
  return <RxPanel title="Kreditgränser" actions={list.length > 0 ? link : null}>
    {util.loading && <RxLoading />}
    {util.error && <RxError error={util.error} onRetry={util.reload} />}
    {!util.loading && !util.error && list.length === 0 && <div style={{ display: 'flex', flexDirection: 'column', gap: 6, fontSize: 13.5 }}>
      <span style={{ fontWeight: 600 }}>Inga kreditgränser satta</span>
      <span className="rx-muted">Kreditutnyttjande kan inte beräknas förrän kunderna har en kreditgräns.</span>
      <span>{link}</span>
    </div>}
    {!util.loading && !util.error && list.length > 0 && <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
      {list.map(c => <div key={c.clientId} style={{ display: 'flex', flexDirection: 'column', gap: 4, fontSize: 13 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', gap: 8 }}>
          <span style={{ fontWeight: 600 }}>{c.clientName}</span>
          {c.overLimit ? <RxPill tone="danger">Över gränsen</RxPill> : <span className="rx-muted">{c.utilizationPct} %</span>}
        </div>
        <RxBar pct={c.utilizationPct} tone={c.overLimit || c.utilizationPct >= 90 ? 'warn' : c.utilizationPct < 60 ? 'ok' : undefined} />
        <span className="rx-muted" style={{ fontSize: 12, fontVariantNumeric: 'tabular-nums' }}>{fmtSek(c.openBalanceSek)} öppet av {fmtSek(c.creditLimitSek)}</span>
      </div>)}
      <span className="rx-muted" style={{ fontSize: 12 }}>Öppet saldo i Fortnox inkl. moms, kopplat via kundens e-postdomän. Fakturor hos Prioritet ingår inte.</span>
    </div>}
  </RxPanel>;
}

function RxFinExport({ mode, invoices }) {
  const cfg = rxFinModes[mode];
  const overdue = invoices.filter(i => i.daysLate != null && i.daysLate > 0);
  const late30 = overdue.filter(i => i.daysLate > 30);
  const sum = list => list.reduce((s, i) => s + i.balance, 0);
  const canDownload = typeof downloadCsv === 'function';
  return <RxPanel title={mode === 'ar' ? 'Underlag för betalningspåminnelser' : 'Förfallna leverantörsfakturor'}>
    <div style={{ fontSize: 13.5, lineHeight: 1.55, color: 'var(--rx-ink-2)', display: 'flex', flexDirection: 'column', gap: 10 }}>
      {overdue.length === 0
        ? <span>Inga förfallna {cfg.invoiceWord}.</span>
        : <span>
            {rxPlural(overdue.length, 'faktura', 'fakturor')} är förfallna, totalt {fmtSek(sum(overdue))}.
            {late30.length > 0 && ` ${rxPlural(late30.length, 'är', 'är')} mer än 30 dagar sena, ${fmtSek(sum(late30))}.`}
          </span>}
      <div>
        <button type="button" className="btn" disabled={!overdue.length || !canDownload}
          onClick={() => rxFinExportOverdue(mode, invoices)}>
          {I.download}<span>Exportera förfallna som CSV</span>
        </button>
      </div>
      <span className="rx-muted" style={{ fontSize: 12 }}>Filen skapas i webbläsaren. Inget skickas till {mode === 'ar' ? 'kund' : 'leverantör'}.</span>
    </div>
  </RxPanel>;
}

/* ── Sidan ──────────────────────────────────────────────── */

function RxFinancePage({ onNavigate }) {
  const [mode, setMode] = React.useState('ar');
  const [apSeen, setApSeen] = React.useState(false);
  const [bucket, setBucket] = React.useState('all');
  const [query, setQuery] = React.useState('');
  const [sort, setSort] = React.useState('open');
  const [expanded, setExpanded] = React.useState(null);

  const summary = useRxFetch('/v1/admin/fortnox/summary');
  const intel = useRxFetch('/v1/admin/fortnox/intelligence');
  const ar = useRxFetch('/v1/admin/fortnox/ar?status=open');
  const ap = useRxFetch(apSeen ? '/v1/admin/fortnox/ap?status=open' : null);
  const apPaid = useRxFetch(apSeen ? `/v1/admin/fortnox/ap?status=paid&from=${rxFinIsoDaysAgo(180)}` : null);
  const dso = useRxFetch('/v1/admin/finance/customer-dso');
  const util = useRxFetch('/v1/admin/clients/credit-utilization');
  const facilities = useRxFetch('/v1/admin/finance/credit-facilities');

  const changeMode = m => {
    if (m === 'ap') setApSeen(true);
    setMode(m); setBucket('all'); setExpanded(null); setQuery('');
  };

  const cfg = rxFinModes[mode];
  const src = mode === 'ar' ? ar : ap;

  const invoices = React.useMemo(
    () => (src.data?.invoices || []).filter(i => Number(i.balance) > 0).map(i => rxFinNormalize(i, mode)),
    [src.data, mode],
  );

  const dsoMap = React.useMemo(() => {
    const m = {};
    (dso.data?.customers || []).forEach(c => {
      m[c.customerNumber || c.customerName] = { avgCredit: c.avgDso, avgLate: c.avgDaysLate, paidCount: c.paidCount };
    });
    return m;
  }, [dso.data]);
  const supplierMap = React.useMemo(() => rxFinSupplierStats(apPaid.data?.invoices), [apPaid.data]);

  const statsFor = g => {
    const m = mode === 'ar' ? dsoMap : supplierMap;
    const st = m[g.partyNo] || m[g.name];
    if (!st || !st.paidCount) return null;
    return st;
  };
  const statsLoading = mode === 'ar' ? dso.loading : apPaid.loading;

  const totals = React.useMemo(() => {
    const open = invoices.reduce((s, i) => s + i.balance, 0);
    const od = invoices.filter(i => i.daysLate != null && i.daysLate > 0);
    return { open, openCount: invoices.length, overdue: od.reduce((s, i) => s + i.balance, 0), overdueCount: od.length };
  }, [invoices]);

  const foreign = React.useMemo(() => {
    const set = {};
    invoices.forEach(i => { if (i.currency !== 'SEK') set[i.currency] = (set[i.currency] || 0) + 1; });
    return Object.entries(set);
  }, [invoices]);

  const groups = React.useMemo(() => {
    const q = query.trim().toLowerCase();
    const list = invoices.filter(i => (bucket === 'all' || i.bucket === bucket) &&
      (!q || i.party.toLowerCase().includes(q) || String(i.id).toLowerCase().includes(q)));
    const g = rxFinGroup(list);
    const cmp = {
      open: (a, b) => b.open - a.open,
      overdue: (a, b) => b.overdue - a.overdue || b.open - a.open,
      oldest: (a, b) => String(a.oldestDue || '9999').localeCompare(String(b.oldestDue || '9999')),
      name: (a, b) => a.name.localeCompare(b.name, 'sv'),
    }[sort];
    return g.sort(cmp);
  }, [invoices, bucket, query, sort]);

  const filtered = bucket !== 'all' || query.trim() !== '';

  /* Nyckeltal */
  const it = intel.data;
  const cr = it?.collectionRate;
  let creditValue = '…', creditSub = '';
  if (intel.error) { creditValue = 'Ej tillgängligt'; }
  else if (it) {
    if (mode === 'ar') {
      const none = !cr || !cr.totalPaid;
      creditValue = none ? 'Ingen historik' : rxFinDays(it.dso);
      creditSub = 'Kundfordringarnas omloppstid, betalda fakturor senaste 180 dagarna';
    } else {
      const none = apPaid.data && !(apPaid.data.invoices || []).some(i => i.final_pay_date);
      creditValue = none ? 'Ingen historik' : rxFinDays(it.dpo);
      creditSub = 'Fakturadatum till betalning, senaste 180 dagarna';
    }
  }

  let fourth;
  if (mode === 'ar') {
    const pct = cr && cr.totalPaid ? rxFinPct(cr.onTime, cr.totalPaid) : null;
    fourth = <RxKpi label="Betalda i tid"
      value={intel.error ? 'Ej tillgängligt' : !it ? '…' : pct == null ? 'Ingen historik' : `${pct} %`}
      tone={pct != null && pct < 70 ? 'warn' : undefined}
      sub={cr && cr.totalPaid ? `${fmtInt(cr.onTime)} av ${fmtInt(cr.totalPaid)} betalda fakturor, senaste 180 dagarna` : ''} />;
  } else {
    fourth = <RxKpi label="Kassacykel"
      value={intel.error ? 'Ej tillgängligt' : !it ? '…' : rxFinDays(it.ccc)}
      sub="Kredittid kunder minus kredittid leverantörer, lager ingår inte" />;
  }

  const net = summary.data?.netPosition;

  return <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
    <div style={{ display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap' }}>
      <RxFinModeToggle mode={mode} onChange={changeMode} />
      <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap' }}>
        {net != null && <span style={{ fontSize: 13, color: 'var(--rx-ink-3)' }}
          title="Öppna kundfordringar minus öppna leverantörsskulder i Fortnox">
          Nettoposition <strong style={{ color: 'var(--rx-ink)', fontVariantNumeric: 'tabular-nums' }}>{fmtSek(net)}</strong>
        </span>}
        <RxFinSyncLine summary={summary.data} error={summary.error} />
      </div>
    </div>

    {src.error && <RxError error={src.error} onRetry={src.reload} />}
    {src.loading && !src.data && <RxPanel><RxLoading label={`Hämtar ${cfg.ledger.toLowerCase()} från Fortnox…`} /></RxPanel>}

    {src.data && <>
      <div className="rx-grid rx-grid--4">
        <RxKpi label={cfg.openLabel} value={fmtSek(totals.open)} sub={rxPlural(totals.openCount, 'faktura', 'fakturor')} />
        <RxKpi label="Förfallet" value={fmtSek(totals.overdue)} tone={totals.overdue > 0 ? 'warn' : undefined}
          sub={`${rxPlural(totals.overdueCount, 'faktura', 'fakturor')} · ${rxFinPct(totals.overdue, totals.open)} % av öppet`} />
        <RxKpi label={`Genomsnittlig ${cfg.creditLabel.toLowerCase()}`} value={creditValue} sub={creditSub} />
        {fourth}
      </div>

      {foreign.length > 0 && <RxCallout tone="warn" title="Fakturor i annan valuta.">
        {foreign.map(([c, n]) => `${n} i ${c}`).join(', ')}. Saldot summeras som Fortnox lagrar det, utan omräkning till kronor.
      </RxCallout>}

      <div className="rx-split">
        <div className="rx-split__main">
          <RxFinAging invoices={invoices} bucket={bucket} onBucket={b => { setBucket(b); setExpanded(null); }} total={totals.open} />

          <RxPanel flush title={`Per ${cfg.party.toLowerCase()}`}
            sub={`${rxPlural(groups.length, cfg.party.toLowerCase(), cfg.parties)}${bucket !== 'all' ? `, ${rxFinBuckets.find(b => b.key === bucket)?.label.toLowerCase()}` : ''}`}>
            <div style={{ padding: '0 18px 12px' }}>
              <RxFilterRow value={sort} onChange={setSort} filters={[
                { key: 'open', label: 'Störst öppet' },
                { key: 'overdue', label: 'Störst förfallet' },
                { key: 'oldest', label: 'Äldst förfallodag' },
                { key: 'name', label: 'Namn' },
              ]}>
                <div style={{ marginLeft: 'auto' }}>
                  <RxSearch value={query} onChange={setQuery}
                    placeholder={`Sök ${cfg.party.toLowerCase()} eller fakturanummer`}
                    label={`Sök ${cfg.party.toLowerCase()} eller fakturanummer`} />
                </div>
              </RxFilterRow>
            </div>
            {mode === 'ar' && dso.error && <div style={{ padding: '0 18px 12px' }}><RxError error={dso.error} onRetry={dso.reload} /></div>}
            {mode === 'ap' && apPaid.error && <div style={{ padding: '0 18px 12px' }}><RxError error={apPaid.error} onRetry={apPaid.reload} /></div>}
            <RxFinPartyTable key={`${mode}-${bucket}-${sort}`} mode={mode} groups={groups} statsFor={statsFor} statsLoading={statsLoading}
              filtered={filtered} expanded={expanded} onToggle={k => setExpanded(e => e === k ? null : k)} />
            <div style={{ padding: '10px 18px 14px', borderTop: '1px solid var(--rx-divider)', fontSize: 12, color: 'var(--rx-ink-3)' }}>
              {mode === 'ar'
                ? 'Kredittid är snittet från fakturadatum till betalning. Betalningsmönster är snittet av dagar före eller efter förfallodag. Båda från betalda fakturor de senaste 180 dagarna.'
                : 'Kredittid är snittet från fakturadatum till vår betalning. Vår betalning är snittet av dagar före eller efter förfallodag. Båda från betalda leverantörsfakturor de senaste 180 dagarna.'}
            </div>
          </RxPanel>
        </div>

        <div className="rx-split__side">
          {mode === 'ar' && <RxFinDoubleCount facilities={facilities.data?.facilities} />}
          {mode === 'ar' && <RxFinCreditLimits util={util} onNavigate={onNavigate} />}
          <RxFinExport mode={mode} invoices={invoices} />
        </div>
      </div>
    </>}
  </div>;
}
