// Shared UI primitives

const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ---- Icons ----
const I = {
  inbox:    p => <svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M2 9l1.5-5.5a1 1 0 0 1 1-.7h7a1 1 0 0 1 1 .7L14 9"/><path d="M2 9h3.5l1 2h3l1-2H14v4.5a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5V9z"/></svg>,
  check:    p => <svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="2"   strokeLinecap="round" strokeLinejoin="round" {...p}><polyline points="3 8.5 6.5 12 13 4.5"/></svg>,
  clock:    p => <svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="8" cy="8" r="6"/><path d="M8 4.5V8l2.5 1.5"/></svg>,
  gavel:    p => <svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M9.5 2.5l4 4M7 5l4 4M2 13.5l6-6M10 10l3-3"/></svg>,
  folder:   p => <svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M2 4.5a1 1 0 0 1 1-1h3l1.5 1.5H13a1 1 0 0 1 1 1V12a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4.5z"/></svg>,
  settings: p => <svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="8" cy="8" r="2"/><path d="M8 1.5v2M8 12.5v2M14.5 8h-2M3.5 8h-2M12.6 3.4l-1.4 1.4M4.8 11.2l-1.4 1.4M12.6 12.6l-1.4-1.4M4.8 4.8L3.4 3.4"/></svg>,
  chev:     p => <svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" {...p}><polyline points="4 6 8 10 12 6"/></svg>,
  ext:      p => <svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M6.5 3H3v10h10V9.5"/><path d="M9 3h4v4M13 3L7 9"/></svg>,
  clip:     p => <svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M11.5 7L7.2 11.3a2.3 2.3 0 0 1-3.2-3.2l5.3-5.3a1.5 1.5 0 0 1 2.1 2.1L6.6 9.7"/></svg>,
  refresh:  p => <svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M14 4v3.5H10.5"/><path d="M13.2 7.5A5.5 5.5 0 1 1 12 3"/></svg>,
  plus:     p => <svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" {...p}><path d="M8 3v10M3 8h10"/></svg>,
  search:   p => <svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" {...p}><circle cx="7" cy="7" r="4.5"/><path d="M10.5 10.5L14 14"/></svg>,
  close:    p => <svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" {...p}><path d="M4 4l8 8M12 4l-8 8"/></svg>,
  trash:    p => <svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M3 5h10M6 5V3.5h4V5M4.5 5l.5 8.5a1 1 0 0 0 1 .9h4a1 1 0 0 0 1-.9L11.5 5"/></svg>,
  copy:     p => <svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" {...p}><rect x="5" y="5" width="8" height="8" rx="1"/><path d="M3 11V4a1 1 0 0 1 1-1h7"/></svg>,
  send:     p => <svg viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M14 2L2 7l5 2 2 5 5-12z"/></svg>,
  sparkle:  p => <svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor" {...p}><path d="M8 1l1.3 3.7L13 6l-3.7 1.3L8 11 6.7 7.3 3 6l3.7-1.3z"/><path d="M12.5 11l.6 1.4 1.4.6-1.4.6-.6 1.4-.6-1.4-1.4-.6 1.4-.6z"/></svg>,
  ai:       p => <svg viewBox="0 0 16 16" width="12" height="12" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" {...p}><path d="M8 2l1.2 3.3L12.5 6.5l-3.3 1.2L8 11l-1.2-3.3L3.5 6.5l3.3-1.2z"/></svg>,
  drag:     p => <svg viewBox="0 0 16 16" width="12" height="12" fill="currentColor" {...p}><circle cx="6" cy="4" r="1"/><circle cx="10" cy="4" r="1"/><circle cx="6" cy="8" r="1"/><circle cx="10" cy="8" r="1"/><circle cx="6" cy="12" r="1"/><circle cx="10" cy="12" r="1"/></svg>,
  ms365:    p => <svg viewBox="0 0 20 20" width="20" height="20" fill="currentColor" {...p}><rect x="1" y="1" width="8" height="8" fill="#f25022"/><rect x="11" y="1" width="8" height="8" fill="#7fba00"/><rect x="1" y="11" width="8" height="8" fill="#00a4ef"/><rect x="11" y="11" width="8" height="8" fill="#ffb900"/></svg>,
  spin:     p => <svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" {...p} style={{animation:'spin 0.9s linear infinite', ...(p.style||{})}}><path d="M8 2a6 6 0 0 1 6 6"/></svg>,
};

// ---- Formatters ----
const formatTime = iso => {
  const d = new Date(iso);
  return `${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`;
};

const formatDate = iso => {
  const d = new Date(iso);
  const now = new Date();
  const diffDays = Math.floor((now - d) / 86400000);
  if (diffDays === 0) return `Today · ${formatTime(iso)}`;
  if (diffDays === 1) return `Yesterday · ${formatTime(iso)}`;
  if (diffDays < 7)  return d.toLocaleDateString('en-AU', { weekday:'short', day:'numeric', month:'short' }) + ' · ' + formatTime(iso);
  return d.toLocaleDateString('en-AU', { day:'numeric', month:'short', year:'numeric' });
};

// ---- Primitives ----
function Check({ checked, onClick }) {
  return (
    <button
      className={`check ${checked ? 'checked' : ''}`}
      role="checkbox" aria-checked={checked}
      onClick={e => { e.stopPropagation(); onClick?.(); }}
    >
      {checked && <I.check />}
    </button>
  );
}

function Badge({ kind, children }) {
  return <span className={`badge ${kind || ''}`}>{children}</span>;
}

function Toast({ msg, folder, onDone }) {
  useEffect(() => {
    const t = setTimeout(() => onDone?.(), 3500);
    return () => clearTimeout(t);
  }, []);
  return (
    <div className="toast">
      <span className="dot" />
      <span>{msg}</span>
      {folder && <span className="folder">{folder}</span>}
    </div>
  );
}

function ToastHost({ toasts, remove }) {
  return (
    <div className="toast-wrap">
      {toasts.map(t => <Toast key={t.id} msg={t.msg} folder={t.folder} onDone={() => remove(t.id)} />)}
    </div>
  );
}

function Modal({ children, onClose, wide }) {
  useEffect(() => {
    const fn = e => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', fn);
    return () => window.removeEventListener('keydown', fn);
  }, [onClose]);
  return (
    <div className="modal-backdrop" onClick={onClose}>
      <div className={`modal ${wide ? 'wide' : ''}`} onClick={e => e.stopPropagation()}>
        {children}
      </div>
    </div>
  );
}

function ConfirmDialog({ title, body, confirmLabel, danger, onCancel, onConfirm }) {
  return (
    <Modal onClose={onCancel}>
      <div className="confirm">
        <div className="body">
          <h3>{title}</h3>
          <p>{body}</p>
        </div>
        <div className="modal-foot">
          <button className="btn ghost" onClick={onCancel}>Cancel</button>
          <button className={`btn ${danger ? 'danger' : 'primary'}`} onClick={onConfirm}>
            {confirmLabel || 'Confirm'}
          </button>
        </div>
      </div>
    </Modal>
  );
}

// API helper
const api = {
  async get(path) {
    const r = await fetch(path);
    if (!r.ok) throw new Error(await r.text());
    return r.json();
  },
  async post(path, body) {
    const r = await fetch(path, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    });
    if (!r.ok) {
      const txt = await r.text().catch(() => '');
      let msg = txt;
      try { msg = JSON.parse(txt)?.error || txt; } catch {}
      throw new Error(msg || `HTTP ${r.status}`);
    }
    // Handle 204 No Content
    if (r.status === 204) return { ok: true };
    const text = await r.text();
    if (!text) return { ok: true };
    try { return JSON.parse(text); } catch { return { ok: true }; }
  },
};

Object.assign(window, {
  I, Check, Badge, Toast, ToastHost, Modal, ConfirmDialog,
  formatTime, formatDate, api,
  useState, useEffect, useRef, useMemo, useCallback,
});
