// =================================================================
// pw-screens-op-payment-methods.jsx — Operator · Payment methods by country (the switchboard, 0071).
//
// Which countries may use a payment method. A method is available only when the master switch, the
// payer's country and the publisher's registered country all allow it. Each scope has a default row
// and optional per-country rows; rows are dated and append-only (like campaign minimums): a change is a
// new row from now or a later date, and only a row that has not started yet can be deleted.
//
// SETTINGS ONLY in this build: stored and resolvable, not yet used by any payment flow. Every method is
// seeded off everywhere. Superadmin writes; managers read (the API refuses their writes with a 403).
// =================================================================

const PM_METHODS = [
  { value: "stablecoin_usdc", label: "USDC stablecoin", hint: "Stablecoin top-ups and payments in USDC." },
  { value: "x402_usdc", label: "x402 (USDC)", hint: "Per-request agent payments over the x402 protocol, in USDC." },
];
const PM_SCOPES = [
  { value: "global", label: "Master switch" },
  { value: "payer", label: "Payer country" },
  { value: "publisher", label: "Publisher country" },
];
const PM_REASON_TEXT = {
  allowed: "Allowed: the master switch, the payer's country and the publisher's country all allow it.",
  master_switch_off: "Not available: the master switch is off.",
  payer_country_blocked: "Not available: the payer's country is blocked.",
  publisher_country_blocked: "Not available: the publisher's country is blocked.",
  no_rule: "Not available: a scope has no rule in force.",
};
const pmNotify = (msg, tone) => (window.__toast ? window.__toast(msg, tone) : null);
const pmMethodLabel = (m) => (PM_METHODS.find((x) => x.value === m) || { label: m }).label;
const pmScopeLabel = (s) => (PM_SCOPES.find((x) => x.value === s) || { label: s }).label;
const pmCountryName = (() => {
  let names = null;
  try { names = new Intl.DisplayNames(["en"], { type: "region" }); } catch (e) { names = null; }
  return (c) => (c ? `${(names && names.of(c)) || c} (${c})` : "Default");
})();
// Seed rows start at the epoch; say so rather than print 1970 in the viewer's time zone.
const pmWhen = (iso) => (iso && Date.parse(iso) <= 0 ? "from the start" : fmt.date(iso));
const pmKey = (r) => `${r.method}|${r.scope}|${r.country || "**"}`;

// Demo: statuses and the matrix derived from the dated rows, the way the database derives them.
const pmDerive = (history, now = Date.now()) => {
  const groups = {};
  for (const r of history) (groups[pmKey(r)] = groups[pmKey(r)] || []).push(r);
  const rows = [];
  const matrix = [];
  for (const list of Object.values(groups)) {
    const sorted = [...list].sort((a, b) => Date.parse(a.effective_from) - Date.parse(b.effective_from));
    const started = sorted.filter((r) => Date.parse(r.effective_from) <= now);
    const inForce = started[started.length - 1] || null;
    const next = sorted.find((r) => Date.parse(r.effective_from) > now) || null;
    for (const r of sorted) rows.push({ ...r, status: Date.parse(r.effective_from) > now ? "scheduled" : r === inForce ? "in_force" : "superseded" });
    const s = sorted[0];
    matrix.push({ method: s.method, scope: s.scope, country: s.country, allowed: inForce ? inForce.allowed : null,
      rule_id: inForce ? inForce.rule_id : null, effective_from: inForce ? inForce.effective_from : null,
      next: next ? { rule_id: next.rule_id, allowed: next.allowed, effective_from: next.effective_from } : null });
  }
  rows.sort((a, b) => Date.parse(b.effective_from) - Date.parse(a.effective_from) || String(b.created_at).localeCompare(String(a.created_at)));
  return { matrix, history: rows, settings_only: true };
};

// Demo: resolve_payment_method over the derived matrix.
const pmResolveDemo = (matrix, method, payer, publisher) => {
  const cell = (scope, country) => matrix.find((c) => c.method === method && c.scope === scope && (c.country || null) === (country || null) && c.rule_id);
  const g = cell("global", null);
  const p = (payer && cell("payer", payer)) || cell("payer", null);
  const q = (publisher && cell("publisher", publisher)) || cell("publisher", null);
  const reason = !g || !p || !q ? "no_rule" : !g.allowed ? "master_switch_off" : !p.allowed ? "payer_country_blocked" : !q.allowed ? "publisher_country_blocked" : "allowed";
  return { method, payer_country: payer || null, publisher_country: publisher || null, allowed: reason === "allowed", reason,
    global_rule_id: g ? g.rule_id : null, payer_rule_id: p ? p.rule_id : null, publisher_rule_id: q ? q.rule_id : null };
};

// One cell: what is in force, and what is scheduled next.
const PmCell = ({ cell, fallback }) => {
  const now = !cell || cell.allowed === null || cell.allowed === undefined
    ? <span style={{ fontSize: 12.5, color: "var(--muted)" }}>{cell ? "not set yet" : "inherits default"}{fallback !== undefined ? ` (${fallback ? "allowed" : "off"})` : ""}</span>
    : <Pill tone={cell.allowed ? "good" : "warn"}>{cell.allowed ? "allowed" : "blocked"}</Pill>;
  return (
    <div style={{ display: "grid", gap: 3 }}>
      <div>{now}</div>
      {cell && cell.next && (
        <div style={{ fontSize: 11.5, color: "var(--accent-strong)" }}>
          scheduled: {cell.next.allowed ? "allow" : "block"} from {fmt.date(cell.next.effective_from)}
        </div>
      )}
    </div>
  );
};

const PaymentMethodRuleModal = ({ initial, onClose, onSaved, demoCheck }) => {
  const [f, setF] = React.useState({ method: "stablecoin_usdc", scope: "payer", country: "", allowed: true, when: "now", at: "", note: "", ...(initial || {}) });
  const [busy, setBusy] = React.useState(false);
  const set = (p) => setF((x) => ({ ...x, ...p }));
  const global = f.scope === "global";
  const at = f.when === "date" && f.at ? new Date(f.at) : null;
  const dateOk = f.when === "now" || Boolean(at && at.getTime() >= Date.now() - 60_000);
  const canSave = dateOk && f.note.trim() && !busy;
  const save = async () => {
    setBusy(true);
    const body = { method: f.method, scope: f.scope, country: global || !f.country ? null : f.country, allowed: f.allowed,
      ...(at ? { effective_from: at.toISOString() } : {}), note: f.note.trim() };
    try {
      const problem = demoCheck && demoCheck(body);
      if (problem) { pmNotify(`Couldn’t add that rule — ${problem}`, "warn"); return; }
      await onSaved(body);
      onClose();
    } catch (e) { /* write() has already said why, with the API's message */ } finally { setBusy(false); }
  };
  return (
    <Modal open onClose={onClose} title="Add a switchboard rule" width={540}
      footer={<><Btn kind="secondary" onClick={onClose}>Cancel</Btn><Btn kind="primary" disabled={!canSave} onClick={save}>{busy ? "Saving…" : "Add rule"}</Btn></>}>
      <div style={{ fontSize: 13, color: "var(--ink-2)", marginBottom: 12, lineHeight: 1.5 }}>
        A rule never edits another: it takes over from its date. Settings only — no payment flow reads this yet.
      </div>
      <FormField label="Method" hint={(PM_METHODS.find((m) => m.value === f.method) || {}).hint}>
        <Select value={f.method} onChange={(v) => set({ method: v })} options={PM_METHODS.map((m) => ({ label: m.label, value: m.value }))}/>
      </FormField>
      <FormField label="Scope" hint="Master switch: the method everywhere. Payer: where the payer is. Publisher: the publisher's registered country.">
        <SegmentedControl value={f.scope} onChange={(v) => set({ scope: v, country: v === "global" ? "" : f.country })} options={PM_SCOPES}/>
      </FormField>
      {!global && (
        <FormField label="Country" hint="Default applies to every country without its own rule.">
          <Select value={f.country} onChange={(v) => set({ country: v })} options={[{ label: "Default (every other country)", value: "" }, ...window.pwCountryOptions()]}/>
        </FormField>
      )}
      <FormField label="Setting">
        <SegmentedControl value={f.allowed ? "allow" : "block"} onChange={(v) => set({ allowed: v === "allow" })}
          options={[{ label: global ? "Switch on" : "Allow", value: "allow" }, { label: global ? "Switch off" : "Block", value: "block" }]}/>
      </FormField>
      <FormField label="Effective" hint="Now, or a later date. The past is refused.">
        <SegmentedControl value={f.when} onChange={(v) => set({ when: v })} options={[{ label: "From now", value: "now" }, { label: "From a date", value: "date" }]}/>
      </FormField>
      {f.when === "date" && (
        <FormField label="From"><Input type="datetime-local" value={f.at} onChange={(e) => set({ at: e.target.value })}/></FormField>
      )}
      <FormField label="Note" hint="Required. Why — recorded with the rule and in the audit trail.">
        <Input value={f.note} maxLength={500} onChange={(e) => set({ note: e.target.value })} placeholder="Legal sign-off ref…"/>
      </FormField>
      {!dateOk && <div style={{ fontSize: 12.5, color: "var(--bad)" }}>Choose a date that is not in the past.</div>}
      {f.allowed && (
        <div style={{ fontSize: 12.5, color: "var(--warn)" }}>Every method is off until legal sign-off. An allow row here changes nothing today, but it will be what a future payment flow reads.</div>
      )}
    </Modal>
  );
};

const PmResolver = ({ live, matrix }) => {
  const [f, setF] = React.useState({ method: "stablecoin_usdc", payer_country: "", publisher_country: "" });
  const [out, setOut] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const test = async () => {
    setBusy(true);
    try {
      if (live) {
        const query = { method: f.method, ...(f.payer_country ? { payer_country: f.payer_country } : {}), ...(f.publisher_country ? { publisher_country: f.publisher_country } : {}) };
        setOut(await window.__api.call("adminResolvePaymentMethod", { query }));
      } else {
        setOut(pmResolveDemo(matrix, f.method, f.payer_country, f.publisher_country));
      }
    } catch (e) {
      pmNotify(`Couldn’t resolve that — ${e.message}`, "warn");
    } finally { setBusy(false); }
  };
  const countries = [{ label: "Not given (uses the default row)", value: "" }, ...window.pwCountryOptions()];
  return (
    <Card title="Test a combination" subtitle="What resolve_payment_method answers right now — the call a future payment flow must make.">
      <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", gap: 8, alignItems: "end" }}>
        <FormField label="Method"><Select value={f.method} onChange={(v) => setF({ ...f, method: v })} options={PM_METHODS.map((m) => ({ label: m.label, value: m.value }))}/></FormField>
        <FormField label="Payer country"><Select value={f.payer_country} onChange={(v) => setF({ ...f, payer_country: v })} options={countries}/></FormField>
        <FormField label="Publisher country"><Select value={f.publisher_country} onChange={(v) => setF({ ...f, publisher_country: v })} options={countries}/></FormField>
        <div style={{ marginBottom: 14 }}><Btn kind="primary" disabled={busy} onClick={test}>{busy ? "Testing…" : "Test"}</Btn></div>
      </div>
      {out && (
        <div style={{ display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap", fontSize: 13 }}>
          <Pill tone={out.allowed ? "good" : "warn"}>{out.allowed ? "available" : "not available"}</Pill>
          <span>{PM_REASON_TEXT[out.reason] || out.reason}</span>
          <span style={{ fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--muted)" }}>reason: {out.reason}</span>
        </div>
      )}
    </Card>
  );
};

const OpPaymentMethodsScreen = ({ data }) => {
  const { session } = useSession();
  const manager = rcIsManager(session);
  const r = useApRead("adminListPaymentMethodRules", {}, () => pmDerive(((data.paymentMethodRules || {}).history) || []));
  React.useEffect(() => { if (r.error) pmNotify("Could not load the switchboard.", "bad"); }, [r.error]);
  const [method, setMethod] = React.useState("stablecoin_usdc");
  const [modal, setModal] = React.useState(null);
  const view = r.value || { matrix: [], history: [], settings_only: true };
  const cells = view.matrix.filter((c) => c.method === method);
  const cell = (scope, country) => cells.find((c) => c.scope === scope && (c.country || null) === (country || null)) || null;
  const payerDef = cell("payer", null), pubDef = cell("publisher", null), master = cell("global", null);
  // Countries with a row in either scope: from the matrix, and from history for a row the matrix omits.
  const countries = Array.from(new Set([
    ...cells.filter((c) => c.country).map((c) => c.country),
    ...view.history.filter((h) => h.method === method && h.country).map((h) => h.country),
  ])).sort();
  const historyCell = (scope, country) => {
    const c = cell(scope, country);
    if (c) return c;
    const next = view.history.filter((h) => h.method === method && h.scope === scope && h.country === country && h.status === "scheduled")
      .sort((a, b) => Date.parse(a.effective_from) - Date.parse(b.effective_from))[0];
    return next ? { allowed: null, next: { rule_id: next.rule_id, allowed: next.allowed, effective_from: next.effective_from } } : null;
  };

  // Demo: the database's refusals, so the modal behaves the same without a backend.
  const demoCheck = r.live ? null : (b) => {
    const eff = b.effective_from || null;
    if (b.scope === "global" && b.country) return "The master switch has no country.";
    if (eff && (data.paymentMethodRules.history || []).some((h) => pmKey(h) === pmKey(b) && h.effective_from === eff)) return "A rule for that cell already starts then (duplicate_rule).";
    return null;
  };
  const create = async (body) => {
    const now = new Date().toISOString();
    const row = { rule_id: (crypto.randomUUID ? crypto.randomUUID() : `pm-${Date.now()}`), ...body, effective_from: body.effective_from || now,
      status: "scheduled", created_by: "you", created_at: now };
    await window.__api.write("adminCreatePaymentMethodRule",
      (d) => ({ ...d, paymentMethodRules: { ...d.paymentMethodRules, history: [row, ...((d.paymentMethodRules || {}).history || [])] } }),
      { body, ok: body.effective_from ? "Rule scheduled" : "Rule in force", err: "Couldn’t add that rule" });
    if (r.live) r.reload();
  };
  const remove = async (row) => {
    if (!window.confirm(`Delete the scheduled ${pmMethodLabel(row.method)} rule for ${pmScopeLabel(row.scope).toLowerCase()} ${pmCountryName(row.country)} from ${fmt.date(row.effective_from)}?`)) return;
    try {
      await window.__api.write("adminDeletePaymentMethodRule",
        (d) => ({ ...d, paymentMethodRules: { ...d.paymentMethodRules, history: (d.paymentMethodRules.history || []).filter((h) => h.rule_id !== row.rule_id) } }),
        { params: { rule_id: row.rule_id }, ok: "Scheduled rule deleted", err: "Couldn’t delete that rule" });
      if (r.live) r.reload();
    } catch (e) { /* write() has already said why */ }
  };

  const fallbackOf = (c) => (c && c.allowed !== null && c.allowed !== undefined ? c.allowed : undefined);
  return (
    <Screen>
      <PageHead icon={IconShield} title="Payment methods by country"
        subtitle="Where each payment method may be offered: a master switch, then the payer's country and the publisher's registered country. All three must allow it."
        actions={manager ? null : <Btn kind="primary" size="sm" onClick={() => setModal({})}>Add a rule</Btn>}/>
      <Banner tone="warn" icon={<IconInfo size={15}/>}>
        <strong>Settings only — stored and resolvable, not yet used by any payment flow. Every method is off until legal sign-off.</strong>
      </Banner>
      {manager && <Banner tone="info" icon={<IconInfo size={15}/>}>Read only. Superadmin only: managers can view the switchboard but cannot change it.</Banner>}
      <div style={{ maxWidth: 420 }}>
        <SegmentedControl value={method} onChange={setMethod} options={PM_METHODS.map((m) => ({ label: m.label, value: m.value }))}/>
      </div>

      <KpiGrid cols={3}>
        {[["Master switch", master, "global"], ["Payer default", payerDef, "payer"], ["Publisher default", pubDef, "publisher"]].map(([label, c, scope]) => (
          <Card key={label} title={label} subtitle={scope === "global" ? "Off means off everywhere, whatever the countries say." : "Every country without its own rule."}>
            <PmCell cell={c}/>
            {c && c.effective_from && <div style={{ fontSize: 11.5, color: "var(--muted)", marginTop: 6 }}>{Date.parse(c.effective_from) <= 0 ? "seeded, in force from the start" : `since ${fmt.date(c.effective_from)}`}</div>}
            {!manager && <div style={{ marginTop: 10 }}><Btn kind="secondary" size="sm" onClick={() => setModal({ method, scope, country: "", allowed: !(c && c.allowed) })}>Change</Btn></div>}
          </Card>
        ))}
      </KpiGrid>

      <Card title="Countries" subtitle="Per-country overrides. A country without a row inherits that scope's default." padded={false}>
        <DataTable rows={countries.map((c) => ({ country: c }))} keyField="country" rowsPerPage={0} empty="No country has its own rule for this method."
          columns={[
            { key: "country", label: "Country", render: (x) => <strong style={{ color: "var(--ink)" }}>{pmCountryName(x.country)}</strong> },
            { key: "payer", label: "Payers in this country", sortable: false, render: (x) => <PmCell cell={historyCell("payer", x.country)} fallback={fallbackOf(payerDef)}/> },
            { key: "publisher", label: "Publishers registered here", sortable: false, render: (x) => <PmCell cell={historyCell("publisher", x.country)} fallback={fallbackOf(pubDef)}/> },
            { key: "actions", label: "", sortable: false, render: (x) => manager ? null
              : <Btn kind="secondary" size="sm" onClick={() => setModal({ method, scope: "payer", country: x.country })}>Add rule</Btn> },
          ]}/>
      </Card>

      <Card title="History" subtitle="Every dated row, newest first. Only a row that has not started can be deleted." padded={false}>
        <DataTable rows={view.history.filter((h) => h.method === method)} keyField="rule_id" searchKeys={["country", "note", "scope"]} searchPlaceholder="Search country, scope or note"
          defaultSort={{ key: "effective_from", dir: "desc" }} empty={r.loading ? "Loading…" : "No rows."}
          columns={[
            { key: "effective_from", label: "From", render: (h) => <span style={{ fontFamily: "var(--mono)", fontSize: 12.5 }}>{pmWhen(h.effective_from)}</span> },
            { key: "scope", label: "Scope", render: (h) => pmScopeLabel(h.scope) },
            { key: "country", label: "Country", accessor: (h) => h.country || "", render: (h) => h.scope === "global" ? "—" : pmCountryName(h.country) },
            { key: "allowed", label: "Setting", render: (h) => <Pill tone={h.allowed ? "good" : "warn"}>{h.allowed ? "allow" : "block"}</Pill> },
            { key: "status", label: "State", render: (h) => <Pill tone={RC_STATE_TONE[h.status] || "neutral"}>{String(h.status || "").replace("_", " ")}</Pill> },
            { key: "note", label: "Note", render: (h) => <span style={{ fontSize: 12.5, color: "var(--muted)" }}>{h.note}{h.created_by ? ` · ${h.created_by}` : ""}</span> },
            { key: "actions", label: "", sortable: false, render: (h) => (h.status === "scheduled" && !manager)
              ? <Btn kind="danger" size="sm" icon={<IconTrash size={13}/>} onClick={(e) => { e.stopPropagation(); remove(h); }}>Delete</Btn> : null },
          ]}/>
      </Card>

      <PmResolver live={r.live} matrix={view.matrix}/>

      {modal && <PaymentMethodRuleModal initial={{ method, ...modal }} onClose={() => setModal(null)} onSaved={create} demoCheck={demoCheck}/>}
    </Screen>
  );
};

Object.assign(window, { OpPaymentMethodsScreen });
