/* Stakk Admin, redesign 2026-09: gemensamma byggstenar.
 *
 * Laddas som text/babel före huvudskriptet i index.html. Filen deklarerar
 * bara funktioner och konstanter med prefixet Rx/rx, så att den inte krockar
 * med huvudskriptets globala namn. React-hooks anropas som React.useState
 * osv, eftersom huvudskriptet redan deklarerar useState som global const.
 *
 * Hjälpfunktioner från huvudskriptet (apiFetch, fmtSek, fmtInt, I, Icon)
 * används först vid rendering, när huvudskriptet har körts.
 */

/* ── Format ─────────────────────────────────────────────── */
const RX_WEEKDAYS = ['sön', 'mån', 'tis', 'ons', 'tor', 'fre', 'lör'];

function rxParseDate(v) {
  if (!v) return null;
  // 'YYYY-MM-DD' tolkas som lokal dag, inte UTC-midnatt.
  if (/^\d{4}-\d{2}-\d{2}$/.test(v)) {
    const [y, m, d] = v.split('-').map(Number);
    return new Date(y, m - 1, d);
  }
  // Opter skickar lokal tid utan zon ('2026-09-23T13:37:00').
  const d = new Date(v);
  return isNaN(d) ? null : d;
}

function rxToday() {
  const n = new Date();
  return new Date(n.getFullYear(), n.getMonth(), n.getDate());
}

/** 'ons 23/9', eller 'idag'/'imorgon'/'igår' när relative=true. */
function rxDay(v, { relative = false } = {}) {
  const d = rxParseDate(v);
  if (!d) return '';
  if (relative) {
    const diff = Math.round((new Date(d.getFullYear(), d.getMonth(), d.getDate()) - rxToday()) / 864e5);
    if (diff === 0) return 'idag';
    if (diff === 1) return 'imorgon';
    if (diff === -1) return 'igår';
  }
  return `${RX_WEEKDAYS[d.getDay()]} ${d.getDate()}/${d.getMonth() + 1}`;
}

/** '13:37' */
function rxTime(v) {
  const d = rxParseDate(v);
  if (!d) return '';
  return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
}

/** '23/9 13:37' */
function rxDayTime(v) {
  const d = rxParseDate(v);
  if (!d) return '';
  return `${d.getDate()}/${d.getMonth() + 1} ${rxTime(v)}`;
}

/** '3 h 18 min', '2 dagar 4 h', '45 min' */
function rxDuration(fromV, toV) {
  const a = rxParseDate(fromV), b = rxParseDate(toV);
  if (!a || !b) return '';
  let min = Math.round((b - a) / 60000);
  if (min < 0) return '';
  const days = Math.floor(min / 1440); min -= days * 1440;
  const h = Math.floor(min / 60); min -= h * 60;
  if (days) return `${days} ${days === 1 ? 'dag' : 'dagar'}${h ? ` ${h} h` : ''}`;
  if (h) return `${h} h${min ? ` ${min} min` : ''}`;
  return `${min} min`;
}

/** Hela dagar mellan två datum (b minus a). */
function rxDayDiff(aV, bV) {
  const a = rxParseDate(aV), b = rxParseDate(bV);
  if (!a || !b) return null;
  const da = new Date(a.getFullYear(), a.getMonth(), a.getDate());
  const db = new Date(b.getFullYear(), b.getMonth(), b.getDate());
  return Math.round((db - da) / 864e5);
}

/** '1,92 Mkr' / '664 tkr' / '850 kr' */
function rxShortSek(v) {
  const n = Number(v) || 0;
  const abs = Math.abs(n);
  if (abs >= 1e6) return `${(n / 1e6).toLocaleString('sv-SE', { maximumFractionDigits: 2 })} Mkr`;
  if (abs >= 1e4) return `${Math.round(n / 1e3).toLocaleString('sv-SE')} tkr`;
  return `${Math.round(n).toLocaleString('sv-SE')} kr`;
}

function rxPlural(n, one, many) { return `${fmtInt(n)} ${n === 1 ? one : many}`; }

/* ── Datahämtning ───────────────────────────────────────── */
/**
 * Hämtar en endpoint och håller { data, error, loading, reload }.
 * deps styr när den hämtas om. path = null hoppar över hämtningen.
 */
function useRxFetch(path, deps = []) {
  const [state, setState] = React.useState({ data: null, error: null, loading: !!path });
  const [nonce, setNonce] = React.useState(0);
  React.useEffect(() => {
    if (!path) { setState({ data: null, error: null, loading: false }); return; }
    let cancelled = false;
    setState(s => ({ ...s, loading: true, error: null }));
    apiFetch(path)
      .then(data => { if (!cancelled) setState({ data, error: null, loading: false }); })
      .catch(error => { if (!cancelled) setState({ data: null, error, loading: false }); });
    return () => { cancelled = true; };
  }, [path, nonce, ...deps]);
  return { ...state, reload: () => setNonce(n => n + 1) };
}

/* ── Byggstenar ─────────────────────────────────────────── */
function RxPanel({ title, sub, actions, tone, flush, children, style }) {
  return <section className={`rx-panel${flush ? ' rx-panel--flush' : ''}${tone ? ` rx-panel--${tone}` : ''}`} style={style}>
    {(title || actions) && <div className="rx-panel__head">
      {title && <h2 className="rx-panel__title">{title}</h2>}
      {sub && <span className="rx-panel__sub">{sub}</span>}
      {actions && <div className="rx-panel__actions">{actions}</div>}
    </div>}
    {children}
  </section>;
}

function RxKpi({ label, value, sub, tone }) {
  return <div className={`rx-kpi${tone ? ` rx-kpi--${tone}` : ''}`}>
    <span className="rx-kpi__label">{label}</span>
    <span className="rx-kpi__value">{value}</span>
    {sub && <span className="rx-kpi__sub">{sub}</span>}
  </div>;
}

/** tone: ok | warn | danger | info | neutral */
function RxPill({ tone = 'neutral', children, title }) {
  return <span className={`rx-pill${tone !== 'neutral' ? ` rx-pill--${tone}` : ''}`} title={title}>{children}</span>;
}

function RxCallout({ tone, title, children, action }) {
  return <div className={`rx-callout${tone ? ` rx-callout--${tone}` : ''}`} role={tone === 'warn' ? 'alert' : 'note'}>
    <span style={{ flexShrink: 0, marginTop: 1, color: tone === 'warn' ? '#9A5B00' : 'currentColor' }}>{tone === 'warn' ? I.warning : <Icon d="M12 22a10 10 0 100-20 10 10 0 000 20zM12 8v4M12 16h.01" />}</span>
    <div className="rx-callout__body">
      {title && <span className="rx-callout__title">{title} </span>}
      {children}
    </div>
    {action && <div className="rx-callout__action">{action}</div>}
  </div>;
}

function RxEmpty({ title, children, action }) {
  return <div className="rx-empty">
    {title && <div className="rx-empty__title">{title}</div>}
    {children && <div>{children}</div>}
    {action}
  </div>;
}

function RxLoading({ label = 'Hämtar…' }) {
  return <div className="rx-empty"><span className="spinner" /> {label}</div>;
}

function RxError({ error, onRetry }) {
  return <RxCallout tone="warn" title="Kunde inte hämta data."
    action={onRetry && <button className="rx-link" onClick={onRetry}>Försök igen</button>}>
    {String(error && error.message || error || '')}
  </RxCallout>;
}

function RxBar({ pct, tone }) {
  const w = Math.max(0, Math.min(100, Number(pct) || 0));
  return <div className={`rx-bar${tone ? ` rx-bar--${tone}` : ''}`}><span style={{ width: `${w}%` }} /></div>;
}

/** filters: [{ key, label, count?, tone? }] */
function RxFilterRow({ filters, value, onChange, children }) {
  return <div className="rx-filters">
    {filters.map(f => <button key={f.key} type="button" aria-pressed={value === f.key}
      className={`rx-filter${value === f.key ? ' rx-filter--on' : ''}`} onClick={() => onChange(f.key)}>
      {f.label}{f.count != null && <span className={`rx-count${f.tone ? ` rx-count--${f.tone}` : ''}`}>{fmtInt(f.count)}</span>}
    </button>)}
    {children}
  </div>;
}

function RxSearch({ value, onChange, placeholder, label }) {
  return <label className="rx-search">
    {I.search}
    <span style={{ position: 'absolute', width: 1, height: 1, overflow: 'hidden', clip: 'rect(0 0 0 0)' }}>{label || placeholder}</span>
    <input type="search" value={value} placeholder={placeholder} onChange={e => onChange(e.target.value)} />
  </label>;
}

/** steps: [{ label, meta, state: 'done' | 'now' | 'todo' }] */
function RxTimeline({ steps }) {
  return <ol className="rx-timeline">
    {steps.map((s, i) => <li key={i}>
      <div className="rx-timeline__rail">
        <span className={`rx-timeline__dot${s.state === 'done' ? ' rx-timeline__dot--done' : s.state === 'now' ? ' rx-timeline__dot--now' : ''}`} />
        <span className="rx-timeline__line" />
      </div>
      <div className="rx-timeline__text">
        <span style={{ fontWeight: s.state === 'todo' ? 400 : 600, color: s.state === 'todo' ? 'var(--rx-ink-3)' : 'var(--rx-ink)' }}>{s.label}</span>
        {s.meta && <span className="rx-timeline__meta">{s.meta}</span>}
      </div>
    </li>)}
  </ol>;
}

/* ── Leveransstatus från Opter ──────────────────────────── */
// Samma nycklar som src/lib/deliveryView.ts returnerar i `status`.
const RX_DELIVERY_STATUS = {
  awaiting_status: { label: 'Inväntar status', tone: 'neutral', dot: '#9C9AA3' },
  booked:          { label: 'Bokad',           tone: 'neutral', dot: '#6E6C77' },
  picked_up:       { label: 'Hämtad',          tone: 'info',    dot: '#8D86EA' },
  in_transit:      { label: 'På väg',          tone: 'info',    dot: '#4338CA' },
  arrived:         { label: 'Framme',          tone: 'info',    dot: '#2F25A8' },
  partially_delivered: { label: 'Delvis levererad', tone: 'warn', dot: '#C2760A' },
  delivered:       { label: 'Levererad',       tone: 'ok',      dot: '#1F8A55' },
  deviation:       { label: 'Avvikelse',       tone: 'danger',  dot: '#A12C1E' },
  // Historik: skickad i Shopify före Opter-starten (src/lib/opterScope.ts).
  shopify_shipped: { label: 'Skickad enligt Shopify', tone: 'ok', dot: '#6FA88A' },
};

// `cancelled` döljer inte statusen utan ställer sig bredvid den. En makulerad
// order som är Levererad är själva poängen: godset har rört sig på en order vi
// sagt nej till, och det syns bara om båda beskeden står kvar.
function RxDeliveryPill({ status, label, cancelled, isReturn, isRma }) {
  const s = RX_DELIVERY_STATUS[status] || { label: label || status || 'Okänd', tone: 'neutral' };
  const pill = <RxPill tone={s.tone}>{label || s.label}</RxPill>;
  if (!cancelled && !isReturn && !isRma) return pill;
  return <span style={{ display: 'inline-flex', gap: 6, alignItems: 'center', flexWrap: 'wrap' }}>
    {pill}
    {/* Riktningen, inte statusen. En retur som är Framme är framme hos OSS. */}
    {isRma && <RxPill title="Retur eller reklamation som lagts upp som en order, inte en leverans">RMA</RxPill>}
    {isReturn && <RxPill tone="warn" title="Sändningen går tillbaka till lagret, inte ut till kund">Retur</RxPill>}
    {cancelled && <RxPill tone="danger" title="Ordern är annullerad i Shopify, men Opter har rapporterat om den">Makulerad</RxPill>}
  </span>;
}

/* ── Arbetsyta: rubrik och flikar ───────────────────────── */
function RxWorkspaceHeader({ title, description, actions }) {
  return <div className="rx-ws__head">
    <div className="rx-ws__titles">
      <h1 className="rx-ws__title">{title}</h1>
      {description && <p className="rx-ws__desc">{description}</p>}
    </div>
    {actions && <div className="rx-ws__actions">{actions}</div>}
  </div>;
}

function RxTabs({ tabs, active, onNavigate }) {
  if (!tabs || tabs.length < 2) return null;
  return <nav className="rx-tabs" aria-label="Flikar">
    {tabs.map(t => <button key={t.route} type="button" aria-current={t.route === active ? 'page' : undefined}
      className={`rx-tab${t.route === active ? ' rx-tab--active' : ''}`} onClick={() => onNavigate(t.route)}>
      {t.label}
    </button>)}
  </nav>;
}

/* ── Sidomeny ───────────────────────────────────────────── */
function RxRail({ workspaces, activeKey, onNavigate, onClose }) {
  const groups = [];
  workspaces.forEach(ws => {
    let g = groups.find(x => x.name === ws.group);
    if (!g) { g = { name: ws.group, items: [] }; groups.push(g); }
    g.items.push(ws);
  });
  const main = groups.filter(g => g.name !== 'System');
  const sys = groups.find(g => g.name === 'System');
  const item = ws => <button key={ws.key} type="button" aria-current={ws.key === activeKey ? 'page' : undefined}
    className={`rx-rail__item${ws.key === activeKey ? ' rx-rail__item--active' : ''}`}
    onClick={() => onNavigate(ws.tabs[0].route)}>
    <span className="rx-rail__icon">{ws.icon}</span><span className="rx-rail__text">{ws.label}</span>
  </button>;
  return <>
    <div className="admin-rail-backdrop" onClick={onClose} />
    <aside className="rx-rail" aria-label="Huvudmeny">
      <div className="rx-rail__brand">
        <span className="rx-rail__mark">S</span><span>Stakk</span><span className="rx-rail__env">Admin</span>
      </div>
      {main.map(g => <div key={g.name} className="rx-rail__group">
        {g.name && <div className="rx-rail__label">{g.name}</div>}
        {g.items.map(item)}
      </div>)}
      <div className="rx-rail__foot">
        {sys && sys.items.map(item)}
        <div className="rx-rail__user">
          <span className="rx-rail__avatar">S</span>
          <span style={{ fontWeight: 600 }}>Stakk Admin</span>
          <button type="button" className="rx-rail__logout" aria-label="Logga ut" title="Logga ut" onClick={async () => {
            await fetch('/v1/admin/auth/session', { method: 'DELETE', credentials: 'include' }).catch(() => {});
            location.reload();
          }}><Icon d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4M16 17l5-5-5-5M21 12H9" /></button>
        </div>
      </div>
    </aside>
  </>;
}

/* ── Topprad ────────────────────────────────────────────── */
const RX_JOB_LABELS = { shopify_orders: 'Shopify', fortnox_ar_ap: 'Fortnox' };

function RxSyncChips() {
  const [health, setHealth] = React.useState(null);
  React.useEffect(() => {
    let cancelled = false;
    const load = () => apiFetch('/v1/admin/sync-health').then(d => { if (!cancelled) setHealth(d); }).catch(() => {});
    load();
    const iv = setInterval(load, 5 * 60 * 1000);
    return () => { cancelled = true; clearInterval(iv); };
  }, []);
  if (!health || !health.jobs) return null;
  return <>
    {Object.entries(health.jobs).map(([key, j]) => {
      const bad = j.status === 'error';
      const stale = j.isStale;
      const label = RX_JOB_LABELS[key] || key;
      const when = j.finishedAt ? rxTime(String(j.finishedAt).replace(' ', 'T')) : '';
      return <span key={key} className={`rx-chip${bad || stale ? ' rx-chip--warn' : ''}`} title={j.errorMessage || ''}>
        <span className={`rx-dot${bad ? ' rx-dot--danger' : stale ? ' rx-dot--warn' : ''}`} />
        {label} · {bad ? 'uppdatering misslyckades' : stale ? `försenad, senast ${when}` : `uppdaterad ${when}`}
      </span>;
    })}
  </>;
}

function RxTopBar({ crumbs, onSync, onMenuToggle }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    const fn = e => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', fn);
    return () => document.removeEventListener('mousedown', fn);
  }, []);
  return <header className="admin-topbar">
    <button className="admin-topbar__menu-btn" onClick={onMenuToggle} aria-label="Öppna meny">
      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round"><path d="M3 12h18M3 6h18M3 18h18" /></svg>
    </button>
    <div className="rx-crumbs">
      {crumbs.map((c, i) => <React.Fragment key={i}>
        {i > 0 && <span aria-hidden="true" style={{ color: '#9C9AA3' }}>›</span>}
        <span className={i === crumbs.length - 1 ? 'rx-crumbs__current' : ''}>{c}</span>
      </React.Fragment>)}
    </div>
    <div className="admin-topbar__spacer" />
    <RxSyncChips />
    <div ref={ref} style={{ position: 'relative' }}>
      <button className="btn" onClick={() => setOpen(o => !o)} aria-expanded={open}>
        {I.sync}<span>Uppdatera</span><span style={{ opacity: .5, fontSize: 10 }}>▾</span>
      </button>
      {open && <div style={{ position: 'absolute', top: 'calc(100% + 6px)', right: 0, background: '#fff', border: '1px solid var(--rx-border)', borderRadius: 10, boxShadow: 'var(--shadow-md)', minWidth: 260, zIndex: 50, padding: 6 }}>
        {[['orders', 'Uppdatera Shopify-ordrar', 'Hämtar nya och ändrade ordrar'],
          ['inventory', 'Uppdatera lagersaldo', 'Hämtar saldo från Ongoing'],
          ['order', 'Uppdatera en order mot Fortnox…', 'För en enskild order']].map(([id, label, sub]) =>
          <button key={id} onClick={() => { setOpen(false); onSync(id); }}
            style={{ display: 'block', width: '100%', textAlign: 'left', border: 0, background: 'transparent', padding: '8px 10px', borderRadius: 6, cursor: 'pointer', font: 'inherit' }}
            onMouseEnter={e => e.currentTarget.style.background = 'var(--admin-row-hover)'}
            onMouseLeave={e => e.currentTarget.style.background = 'transparent'}>
            <div style={{ fontSize: 13, fontWeight: 500 }}>{label}</div>
            <div style={{ fontSize: 12, color: 'var(--rx-ink-3)' }}>{sub}</div>
          </button>)}
      </div>}
    </div>
  </header>;
}
