// Nexa — shared dashboard pieces (helpers, hooks, badges, common rows)

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

// ── Formatters ─────────────────────────────────────────
const fmtN = (n, d = 2) =>
  Number(n).toLocaleString("en-GB", { minimumFractionDigits: d, maximumFractionDigits: d });

const CURRENCY_SYMBOLS = { GBP: "£", EUR: "€", USD: "$", CAD: "C$", AUD: "A$", JPY: "¥", CHF: "CHF " };
const symbolFor = (cur) => CURRENCY_SYMBOLS[cur] || "";

const fmtMoney = (n, cur, d = 2) => `${symbolFor(cur)}${fmtN(Number(n) || 0, d)}`;

const fmtCompact = (n, cur) => {
  const sym = symbolFor(cur);
  const v = Number(n) || 0;
  if (Math.abs(v) >= 1_000_000) return `${sym}${(v/1_000_000).toFixed(2)}M`;
  if (Math.abs(v) >= 1_000)     return `${sym}${(v/1_000).toFixed(1)}k`;
  return `${sym}${fmtN(v)}`;
};

const fmtTime = (iso) => {
  if (!iso) return "—";
  const d = new Date(iso);
  if (isNaN(d)) return "—";
  return d.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
};
const fmtDate = (iso) => {
  if (!iso) return "—";
  const d = new Date(iso);
  if (isNaN(d)) return "—";
  return d.toLocaleDateString("en-GB", { day: "2-digit", month: "short" });
};
const fmtDateLong = (iso) => {
  if (!iso) return "—";
  const d = new Date(iso);
  if (isNaN(d)) return "—";
  return d.toLocaleDateString("en-GB", { day: "2-digit", month: "short", year: "numeric" });
};
const fmtRelative = (iso) => {
  if (!iso) return "—";
  const d = new Date(iso);
  if (isNaN(d)) return "—";
  const ms = Date.now() - d.getTime();
  const m = Math.round(ms / 60000);
  if (m < 1)  return "just now";
  if (m < 60) return `${m}m ago`;
  const h = Math.round(m / 60);
  if (h < 24) return `${h}h ago`;
  const days = Math.round(h / 24);
  return `${days}d ago`;
};
const fmtUntil = (iso) => {
  if (!iso) return "—";
  const d = new Date(iso);
  if (isNaN(d)) return "—";
  const ms = d.getTime() - Date.now();
  if (ms <= 0) return "expired";
  const h = Math.round(ms / 3600000);
  if (h < 1) return "<1h";
  return `${h}h`;
};

const operationLabel = {
  off_ramp: "off-ramp",
  on_ramp: "on-ramp",
  "fx.spot": "fx · spot",
  "card.issue": "card · issue",
  "card.cap.set": "card · cap",
  "beneficiary.add": "beneficiary",
};
const labelOp = (op) => operationLabel[op] || (op ? String(op).replace(/_/g, " ") : "—");

// ── Normalizers (API → UI) ─────────────────────────────
// Transactions: API returns {instruction_id, timestamp, operation, agent_id,
// agent_name, amount, currency, counterparty_name, destination_country,
// status, payment_reason, reason_category}. Sign of amount = direction.
function normTxn(t) {
  if (!t) return t;
  const amount = Number(t.amount) || 0;
  return {
    id: t.instruction_id || t.id,
    timestamp: t.timestamp,
    operation: t.operation,
    agentId: t.agent_id,
    agentName: t.agent_name,
    amount: Math.abs(amount),
    signedAmount: amount,
    currency: t.currency,
    counterparty: t.counterparty_name,
    country: t.destination_country,
    status: t.status,
    direction: amount >= 0 ? "in" : "out",
    paymentReason: t.payment_reason,
    reasonCategory: t.reason_category,
  };
}

// Approvals: instruction_summary holds the human-readable bits.
function normApproval(a) {
  if (!a) return a;
  const s = a.instruction_summary || {};
  return {
    id: a.approval_id || a.id,
    agentId: a.agent_id,
    status: a.status,
    reason: a.reason,
    createdAt: a.created_at,
    expiresAt: a.expires_at,
    operation: s.operation,
    amount: Number(s.amount) || 0,
    currency: s.currency,
    counterparty: s.counterparty,
    destination: s.destination,
  };
}

function normBeneficiary(b) {
  if (!b) return b;
  return {
    id: b.beneficiary_id || b.id,
    name: b.name,
    country: b.country,
    currency: b.currency,
    iban: b.iban,
    status: b.status,
    addedAt: b.created_at,
    totalPaid: Number(b.total_paid) || 0,
    paymentCount: b.payment_count || 0,
    lastPaidAt: b.last_paid_at,
  };
}

function normAccount(a) {
  if (!a) return a;
  const balance = Number(a.balance) || 0;
  const held = Number(a.held) || 0;
  const available = a.available != null ? Number(a.available) : balance - held;
  return {
    id: a.account_id || a.id,
    currency: a.currency,
    label: a.label,
    iban: a.iban,
    balance, held, available,
  };
}

function normAgent(a) {
  if (!a) return a;
  return {
    id: a.agent_id || a.id,
    name: a.name,
    status: a.status,
    apiKey: a.api_key || "",
    apiKeyMasked: a.api_key_masked || "",
    createdAt: a.created_at,
    lastUsedAt: a.last_used_at || null,
    allowedOperations: a.allowed_operations || [],
    allowedWalletIds: a.allowed_wallet_ids || [],
    walletScope: a.wallet_scope || "all",
    suspensionReason: a.suspension_reason,
    handle: a.handle || a.agent_id || "",
    description: a.description || "",
  };
}

function normPolicyRule(r) {
  if (!r) return r;
  return {
    id: r.rule_id || r.id,
    type: r.policy_type,
    amount: r.amount,
    currency: r.currency,
    values: r.values || [],
    periodHours: r.period_hours,
    maxCount: r.max_count,
    startHour: r.start_hour,
    endHour: r.end_hour,
    mode: r.is_hard_block ? "block" : "approval",
    enabled: r.enabled !== false,
  };
}

const RULE_TYPE_LABELS = {
  transaction_limit: "Per-Transaction Limit",
  daily_limit: "Daily Aggregate Limit",
  weekly_limit: "Weekly Aggregate Limit",
  monthly_limit: "Monthly Aggregate Limit",
  approval_threshold: "Approval Threshold",
  min_balance: "Minimum Balance Floor",
  counterparty_whitelist: "Counterparty Whitelist",
  counterparty_blacklist: "Counterparty Blacklist",
  currency_whitelist: "Currency Whitelist",
  corridor_blacklist: "Corridor Blacklist",
  velocity_limit: "Velocity Limit",
  time_window: "Time Window",
};

function ruleLabel(r) { return RULE_TYPE_LABELS[r.type] || r.type; }
function ruleDetail(r) {
  switch (r.type) {
    case "transaction_limit":
    case "daily_limit":
    case "weekly_limit":
    case "monthly_limit":
    case "approval_threshold":
    case "min_balance":
      return r.amount != null ? `${r.currency || ""} ${Number(r.amount).toLocaleString("en-GB")}`.trim() : "—";
    case "counterparty_whitelist":
    case "counterparty_blacklist":
    case "currency_whitelist":
    case "corridor_blacklist":
      return (r.values || []).join(", ") || "—";
    case "velocity_limit":
      return `${r.maxCount ?? "—"} txns / ${r.periodHours ?? "—"}h`;
    case "time_window":
      return `${String(r.startHour ?? "—").padStart(2,"0")}:00–${String(r.endHour ?? "—").padStart(2,"0")}:00 UTC`;
    default:
      return "";
  }
}

// ── Hook: useFetch ─────────────────────────────────────
function useFetch(fn, deps = [], { polling = 0 } = {}) {
  const [state, setState] = useState({ loading: true, data: null, error: null });
  const [tick, setTick] = useState(0);
  const fnRef = useRef(fn);
  fnRef.current = fn;

  useEffect(() => {
    let alive = true;
    setState(s => ({ data: s.data, loading: true, error: null }));
    fnRef.current()
      .then(d => alive && setState({ loading: false, data: d, error: null }))
      .catch(e => alive && setState({ loading: false, data: null, error: e.message || String(e) }));
    return () => { alive = false; };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [...deps, tick]);

  useEffect(() => {
    if (!polling) return;
    const id = setInterval(() => setTick(t => t + 1), polling);
    return () => clearInterval(id);
  }, [polling]);

  const refetch = useCallback(() => setTick(t => t + 1), []);
  return { ...state, refetch };
}

// ── Status / direction badges ─────────────────────────
const StatusBadge = ({ status }) => {
  const map = {
    settled:           { cls: "is-settled",    label: "settled" },
    completed:         { cls: "is-settled",    label: "completed" },
    approved:          { cls: "is-settled",    label: "approved" },
    pending_approval:  { cls: "is-pending",    label: "pending approval" },
    pending_review:    { cls: "is-pending",    label: "pending review" },
    pending:           { cls: "is-pending",    label: "pending" },
    rejected:          { cls: "is-rejected",   label: "rejected" },
    blocked:           { cls: "is-rejected",   label: "blocked" },
    processing:        { cls: "is-processing", label: "processing" },
    submitted:         { cls: "is-processing", label: "submitted" },
  };
  const m = map[status] || { cls: "", label: status || "—" };
  return (
    <span className={`tc-badge ${m.cls}`}>
      <span className="tc-badge-dot" />
      {m.label}
    </span>
  );
};

const AgentBadge = ({ status }) => {
  const map = {
    active:    { cls: "is-active",    label: "active" },
    suspended: { cls: "is-suspended", label: "suspended" },
    test:      { cls: "is-test",      label: "test mode" },
    revoked:   { cls: "is-revoked",   label: "revoked" },
    created:   { cls: "is-test",      label: "created" },
  };
  const m = map[status] || { cls: "", label: status || "—" };
  return (
    <span className={`tc-badge ${m.cls}`}>
      <span className="tc-badge-dot" />
      {m.label}
    </span>
  );
};

const DirectionGlyph = ({ dir }) => (
  <span className={`tc-direction ${dir === "in" ? "is-in" : "is-out"}`} aria-label={dir === "in" ? "inflow" : "outflow"}>
    <svg width="11" height="11" viewBox="0 0 12 12" fill="none">
      {dir === "in"
        ? <path d="M6 10V2M3 5l3-3 3 3" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/>
        : <path d="M6 2v8M3 7l3 3 3-3" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"/>}
    </svg>
  </span>
);

const Flag = ({ code }) => {
  if (!code || code === "—") return <span className="tc-flag" aria-hidden="true">—</span>;
  return <span className="tc-flag">{String(code).slice(0, 3)}</span>;
};

const AgentTag = ({ agentId, agentName, agents }) => {
  if (!agentId) return (
    <span className="tc-agent-tag is-human">
      <span className="tc-agent-tag-dot" />
      <span>human</span>
    </span>
  );
  const a = (agents || []).find(x => x.id === agentId);
  return (
    <span className="tc-agent-tag">
      <span className="tc-agent-tag-dot" />
      <span>{a ? a.name : (agentName || agentId)}</span>
    </span>
  );
};

const Toggle = ({ on, onChange, disabled }) => (
  <button
    type="button"
    role="switch"
    aria-checked={on}
    disabled={disabled}
    className={`tc-toggle ${on ? "is-on" : ""}`}
    onClick={() => !disabled && onChange(!on)}
  />
);

// ── Loading / error chrome ─────────────────────────────
const Spinner = ({ size = 16 }) => (
  <span className="tc-spinner" style={{ width: size, height: size }} aria-label="loading" />
);

const LoadingBlock = ({ label = "Loading…" }) => (
  <div className="tc-loading">
    <Spinner />
    <span>{label}</span>
  </div>
);

const ErrorBlock = ({ error, onRetry, label = "Couldn't load" }) => (
  <div className="tc-error">
    <div className="tc-error-h">{label}</div>
    <div className="tc-error-b">{error}</div>
    {onRetry && <button className="tc-btn tc-btn-ghost tc-btn-sm" onClick={onRetry}>Retry</button>}
  </div>
);

// Side nav items
const NAV = [
  { id: "overview",      label: "Overview" },
  { id: "transactions",  label: "Transactions" },
  { id: "beneficiaries", label: "Beneficiaries" },
  { id: "documents",     label: "Documents" },
  { id: "policies",      label: "Policies" },
  { id: "agents",        label: "Agents" },
];

const SideIcon = ({ name }) => {
  const props = { width: 16, height: 16, viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.4, strokeLinecap: "round", strokeLinejoin: "round" };
  switch (name) {
    case "overview":      return (<svg {...props}><rect x="2" y="2" width="5.5" height="5.5" rx="1"/><rect x="8.5" y="2" width="5.5" height="5.5" rx="1"/><rect x="2" y="8.5" width="5.5" height="5.5" rx="1"/><rect x="8.5" y="8.5" width="5.5" height="5.5" rx="1"/></svg>);
    case "transactions":  return (<svg {...props}><path d="M2 5h10l-2-2"/><path d="M14 11H4l2 2"/></svg>);
    case "beneficiaries": return (<svg {...props}><circle cx="8" cy="6" r="2.5"/><path d="M3 14c0-2.5 2.2-4.5 5-4.5s5 2 5 4.5"/></svg>);
    case "policies":      return (<svg {...props}><path d="M3 2h7l3 3v9H3z"/><path d="M5.5 7h5M5.5 9.5h5M5.5 12h3"/></svg>);
    case "agents":        return (<svg {...props}><circle cx="8" cy="8" r="2"/><path d="M8 3v1.5M8 11.5V13M3 8h1.5M11.5 8H13M4.5 4.5l1 1M10.5 10.5l1 1M4.5 11.5l1-1M10.5 5.5l1-1"/></svg>);
    case "documents":     return (<svg {...props}><path d="M4 2h6l3 3v9H4z"/><path d="M10 2v3h3"/><path d="M6.5 7h4M6.5 9.5h4M6.5 12h2.5"/></svg>);
    default: return null;
  }
};

const PlusIcon    = () => (<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"><path d="M6 2v8M2 6h8"/></svg>);
const ChevronIcon = () => (<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"><path d="M3.5 5.5L7 9l3.5-3.5"/></svg>);
const SearchIcon  = () => (<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round"><circle cx="6" cy="6" r="4"/><path d="M9 9l3 3"/></svg>);
const CheckIcon   = () => (<svg width="11" height="11" viewBox="0 0 11 11" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M2.5 6l2 2 4-4"/></svg>);
const XIcon       = () => (<svg width="11" height="11" viewBox="0 0 11 11" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"><path d="M3 3l5 5M8 3l-5 5"/></svg>);
const RefreshIcon = () => (<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round"><path d="M10 3v3H7"/><path d="M2 9V6h3"/><path d="M9.5 6a3.7 3.7 0 0 0-6.9-1M2.5 6a3.7 3.7 0 0 0 6.9 1"/></svg>);

window.TC_HELPERS = {
  fmtN, fmtMoney, fmtCompact, fmtTime, fmtDate, fmtDateLong, fmtRelative, fmtUntil,
  operationLabel, labelOp, symbolFor,
  normTxn, normApproval, normBeneficiary, normAccount, normAgent, normPolicyRule,
  ruleLabel, ruleDetail, RULE_TYPE_LABELS,
  useFetch, NAV,
};
window.TC_UI = {
  StatusBadge, AgentBadge, DirectionGlyph, Flag, AgentTag, Toggle,
  Spinner, LoadingBlock, ErrorBlock,
  SideIcon, PlusIcon, ChevronIcon, SearchIcon, CheckIcon, XIcon, RefreshIcon,
};
