/* global React, Icon, PageSkeleton, DemoChip, Chips, PersonAvatar,
   useAuditLog, AUDIT_ACTIONS, auditSentence, auditName */
// Activity — what changed, who changed it, newest first.
//
// Not purely staff any more: since set_membership_tier landed on the
// golfer-app side, a member joining or cancelling SBX+ writes its own row
// here too. Those read in the first person ("joined SBX+") so they are not
// mistaken for staff acting on a member's account.
//
// Read as a feed rather than a table. A table invites you to compare rows
// column by column, which is not the question anyone brings here; the
// question is "what happened, and who did it", and that is a sentence.
//
// The raw before/after stays one click away instead of on the surface. It is
// the thing you want in the rare case where the sentence is not enough, and
// putting jsonb in front of somebody by default would bury the readable part.

// "4m ago" / "3h ago" / a date once it stops being recent. An audit trail is
// usually read about something that just happened, so relative time is the
// useful default — but the exact stamp is always in the title attribute.
function actAgo(iso) {
  const ms = Date.now() - new Date(iso).getTime();
  const min = Math.round(ms / 60000);
  if (min < 1) return 'just now';
  if (min < 60) return `${min}m ago`;
  const hr = Math.round(min / 60);
  if (hr < 24) return `${hr}h ago`;
  const d = Math.round(hr / 24);
  if (d < 7) return `${d}d ago`;
  return new Date(iso).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}
const actStamp = (iso) => new Date(iso).toLocaleString('en-US', {
  weekday: 'short', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit',
});

// Shape carries severity, the way match results and payout states already do:
// something lost is outlined in the loss tone, everything else is quiet.
function ActionPill({ action }) {
  const meta = AUDIT_ACTIONS[action] || { label: action, destructive: false };
  const s = meta.destructive
    ? { background: 'rgba(155,58,46,0.18)', color: 'var(--loss-soft, #E7B8A7)', border: '1px solid rgba(155,58,46,0.5)' }
    : { background: 'transparent', color: 'var(--ink-muted)', border: '1px solid var(--line-strong)' };
  return <span className="pill-mono" style={{ ...s, fontSize: 8.5, padding: '2px 7px', whiteSpace: 'nowrap' }}>{meta.label}</span>;
}

// The before/after, for when the sentence isn't enough.
function JsonPeek({ label, value }) {
  if (!value || !Object.keys(value).length) return null;
  return (
    <div style={{ minWidth: 180, flex: '1 1 200px' }}>
      <div className="eyebrow" style={{ marginBottom: 4 }}>{label}</div>
      <pre style={{
        margin: 0, padding: '8px 10px', borderRadius: 'var(--r-xs)',
        background: 'rgba(0,0,0,0.18)', border: '1px solid var(--line)',
        fontFamily: 'var(--font-mono)', fontSize: 11, lineHeight: 1.5,
        color: 'var(--ink-soft)', whiteSpace: 'pre-wrap', wordBreak: 'break-word',
      }}>{JSON.stringify(value, null, 1)}</pre>
    </div>
  );
}

function ActivityRow({ row, nameFor, first }) {
  const [open, setOpen] = React.useState(false);
  // "Unknown admin" was right when only staff wrote here; members write
  // their own membership changes now.
  const who = auditName(row.actor) || 'Unknown account';
  const hasDetail = !!((row.before && Object.keys(row.before).length) || (row.after && Object.keys(row.after).length));

  return (
    <div style={{ borderTop: first ? 'none' : '1px solid var(--line-soft)' }}>
      <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, padding: '13px 18px' }}>
        <PersonAvatar src={row.actor && row.actor.avatar_url} name={who} size={30}/>

        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 13.5, color: 'var(--ink)', lineHeight: 1.5 }}>
            <strong style={{ color: 'var(--paper)' }}>{who}</strong>{' '}
            {auditSentence(row, nameFor)}
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 4, flexWrap: 'wrap' }}>
            <span title={actStamp(row.created_at)}
              style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--ink-faint)' }}>
              {actAgo(row.created_at)}
            </span>
            <ActionPill action={row.action}/>
            {hasDetail && (
              <button onClick={() => setOpen(o => !o)}
                aria-expanded={open}
                style={{
                  background: 'none', border: 'none', padding: 0, font: 'inherit', cursor: 'pointer',
                  fontSize: 11, color: 'var(--ink-muted)', textDecoration: 'underline',
                }}>
                {open ? 'Hide detail' : 'Detail'}
              </button>
            )}
          </div>

          {open && (
            <div style={{ display: 'flex', gap: 12, marginTop: 10, flexWrap: 'wrap' }}>
              <JsonPeek label="Before" value={row.before}/>
              <JsonPeek label="After" value={row.after}/>
              <div style={{ minWidth: 150, flex: '0 1 auto' }}>
                <div className="eyebrow" style={{ marginBottom: 4 }}>Record</div>
                <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--ink-soft)', wordBreak: 'break-all' }}>
                  {row.entity}<br/>{row.entity_id || '—'}
                </div>
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

// ─── ActivityLog ──────────────────────────────────────────────────────
function ActivityLog() {
  const [pages, setPages] = React.useState(1);
  const [log, reload] = useAuditLog(pages);
  const [who, setWho] = React.useState('all');
  const [kind, setKind] = React.useState('all');

  if (!log) return <PageSkeleton/>;

  // The table or its read policy is missing — say which, and what fixes it.
  if (log.blocked) {
    return (
      <div style={{ maxWidth: 720, margin: '0 auto' }}>
        <div className="card" style={{ padding: 26 }}>
          <div className="eyebrow">Activity</div>
          <div style={{ fontSize: 14, color: 'var(--paper)', fontWeight: 700, marginTop: 10 }}>
            The activity log can&rsquo;t be read.
          </div>
          <div style={{ fontSize: 13, color: 'var(--ink-soft)', marginTop: 8, lineHeight: 1.55 }}>
            The <code style={{ fontFamily: 'var(--font-mono)' }}>admin_audit</code> table is missing, or its
            admin read policy is. Run <code style={{ fontFamily: 'var(--font-mono)' }}>sql/admin-overhaul.sql</code> in
            Supabase — it creates both.
          </div>
          <div className="form-error" role="alert" style={{ marginTop: 14, marginBottom: 0 }}>{log.message}</div>
        </div>
      </div>
    );
  }

  const nameFor = (id) => {
    const row = log.rows.find(r => r.entity_id === id && r.targetProfile);
    return row ? auditName(row.targetProfile) : null;
  };

  // Filters are built from what is actually in the log, not from every action
  // the portal can theoretically record — a filter for a thing that has never
  // happened is a dead end.
  const actorCounts = {};
  const kindCounts = {};
  log.rows.forEach(r => {
    const k = r.actor_id || 'unknown';
    actorCounts[k] = (actorCounts[k] || 0) + 1;
    kindCounts[r.action] = (kindCounts[r.action] || 0) + 1;
  });
  const actors = Object.keys(actorCounts).map(id => {
    const row = log.rows.find(r => (r.actor_id || 'unknown') === id);
    return { key: id, label: auditName(row && row.actor) || 'Unknown', count: actorCounts[id] };
  });

  const shown = log.rows.filter(r =>
    (who === 'all' || (r.actor_id || 'unknown') === who)
    && (kind === 'all'
      || (kind === 'destructive' ? (AUDIT_ACTIONS[r.action] || {}).destructive : r.action === kind)));

  const since = new Date(); since.setHours(0, 0, 0, 0);
  const today = log.rows.filter(r => new Date(r.created_at) >= since).length;
  const destructive = log.rows.filter(r => (AUDIT_ACTIONS[r.action] || {}).destructive).length;

  return (
    <div style={{ maxWidth: 1000, margin: '0 auto' }}>
      {log.demo && <div style={{ marginBottom: 12 }}><DemoChip corner={false}/></div>}

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(168px, 1fr))', gap: 12, marginBottom: 16 }}>
        {[
          { label: 'Actions Logged', value: log.rows.length.toLocaleString(), sub: log.hasMore ? 'Most recent — more below' : 'Everything on record' },
          { label: 'Today', value: String(today), sub: today ? 'Since midnight' : 'Nothing yet today' },
          { label: 'Destructive', value: String(destructive), sub: 'Deletions and revocations' },
          { label: 'Admins Active', value: String(actors.length), sub: 'In the current view' },
        ].map(k => (
          <div key={k.label} className="card" style={{ padding: '15px 16px' }}>
            <div className="eyebrow">{k.label}</div>
            <div style={{ fontFamily: 'var(--font-display)', fontSize: 25, color: 'var(--paper)', marginTop: 3, fontVariantNumeric: 'tabular-nums' }}>{k.value}</div>
            <div style={{ fontSize: 11.5, color: 'var(--ink-muted)', marginTop: 2 }}>{k.sub}</div>
          </div>
        ))}
      </div>

      <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', marginBottom: 12 }}>
        <Chips value={who} onChange={setWho}
          options={[{ key: 'all', label: 'Everyone', count: log.rows.length }, ...actors]}/>
        <Chips value={kind} onChange={setKind} options={[
          { key: 'all', label: 'Any action' },
          { key: 'destructive', label: 'Destructive only', count: destructive },
          ...Object.keys(kindCounts).map(a => ({
            key: a, label: (AUDIT_ACTIONS[a] || {}).label || a, count: kindCounts[a],
          })),
        ]}/>
        <div style={{ flex: 1 }}/>
        <button className="btn btn-quiet" onClick={reload} title="Refresh"><Icon name="refresh" size={14}/></button>
      </div>

      <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
        {shown.length ? shown.map((r, i) => (
          <ActivityRow key={r.id} row={r} nameFor={nameFor} first={i === 0}/>
        )) : (
          <div style={{ padding: 34, textAlign: 'center' }}>
            <div style={{ fontSize: 14, color: 'var(--paper)', fontWeight: 700 }}>
              {log.rows.length ? 'Nothing matches these filters.' : 'No admin actions recorded yet.'}
            </div>
            {!log.rows.length && (
              <div style={{ fontSize: 12.5, color: 'var(--ink-muted)', marginTop: 8, lineHeight: 1.55, maxWidth: 460, margin: '8px auto 0' }}>
                The log records from the moment it was switched on — deleting an account, changing a
                tier, moving a payout, editing a match. Anything done before then isn&rsquo;t here and
                can&rsquo;t be recovered.
              </div>
            )}
          </div>
        )}
      </div>

      {log.hasMore && (
        <div style={{ display: 'flex', justifyContent: 'center', marginTop: 14 }}>
          <button className="btn btn-ghost" onClick={() => setPages(p => p + 1)}>Load older</button>
        </div>
      )}

      <div style={{ fontSize: 11.5, color: 'var(--ink-faint)', marginTop: 14, lineHeight: 1.55, textAlign: 'center' }}>
        Staff actions are recorded by this portal as they happen; membership changes a golfer
        makes themselves are recorded by the golfer app. Routine edits — tee slots, yardages,
        pin placements — are deliberately not logged.
      </div>
    </div>
  );
}

Object.assign(window, { ActivityLog });
