/* page-rewards.jsx — Rewards + Redemptions module (03-ui §2). IIFE-scoped; exports via window.
 * Deps: helpers.jsx — reward.* RBAC keys + _go, _emailLink, _phoneLink, _fmtDate, T_MUTED
 *       mock-data.js — SB4_REWARDS (renamed fields), SB4_REDEMPTIONS, SB4_BRANDS, SB4_BRAND_BY_ID
 *                      CM_CUSTOMER_BY_ID, SB2_COUNTRIES, SB3_AREAS, SB3_OUTLETS
 *       StatusPill kind: "reward" (covers active/draft/archived/pending/approved/rejected)
 *
 * NOTE: page-partnerships.jsx already reads SB4_REWARDS.partner_id. The rename
 * (brand_id→partner_id, points_cost→points_required, hero_image→split, submitted_by→submitted_by_user_id)
 * is applied here in mock-data.js. If a dashboard/loyalty page reads the old field names it will silently
 * return "—" — flag to orchestrator for cross-page consumer audit on: points_cost, brand_id, hero_image,
 * submitted_by, redemption_count (renamed fields).
 *
 * NOTE: Country eligibility section is retained in the read drawer (present in the form + list table
 * Countries column) even though it is absent from the injected Table 2. Likely an injection omission —
 * flagged to orchestrator; keeping it prevents data loss until confirmed dropped by PO.
 */
(function(){
const { useState, useMemo } = React;

// ── Country options (shared across both components) ──────────────────────────
const COUNTRY_OPTS = [
  { value: "PH", label: "🇵🇭 Philippines" },
  { value: "SG", label: "🇸🇬 Singapore" },
  { value: "ID", label: "🇮🇩 Indonesia" },
  { value: "TH", label: "🇹🇭 Thailand" },
];

// ── RBAC helpers ─────────────────────────────────────────────────────────────
const _canCreate  = (role) => _hasRbac(role, "reward.create");   // GA only
const _canPropose = (role) => _hasRbac(role, "reward.propose");  // GA, CM, AM

// ── Helper: brand/partner name from id ───────────────────────────────────────
const _brandName = (id) => {
  if (!id) return "—";
  const map = (typeof SB4_BRAND_BY_ID !== "undefined") ? SB4_BRAND_BY_ID : (window.SB4_BRAND_BY_ID || {});
  return map[id] || id;
};

// Resolve a user_id to a display name; falls back to the raw id.
const _rUserName = (id) => {
  if (!id) return "—";
  if (id === "current_user") return "You";
  const users = (typeof SB3_USERS !== "undefined") ? SB3_USERS : (window.SB3_USERS || []);
  const u = users.find(x => x.id === id);
  return u ? u.full_name : id;
};

// Window tz frame for a reward: single eligible country → that country; multi-country
// (or none) → the GA/HQ default (_gaTz). Drives the Window wall-clock display.
const _rewardTz = (r) => {
  const cs = Object.keys(r.country_eligible || {}).filter(c => (r.country_eligible || {})[c]);
  return cs.length === 1 ? cs[0] : _gaTz();
};

// ── Customer PII helper ───────────────────────────────────────────────────────
const _custById = (id) => {
  if (!id) return { name: "—", email: null, phone: null };
  const map = (typeof CM_CUSTOMER_BY_ID !== "undefined") ? CM_CUSTOMER_BY_ID : (window.CM_CUSTOMER_BY_ID || {});
  const c = map[id];
  if (!c) return { name: id, email: null, phone: null };
  return { name: c.name, email: c.email || null, phone: c.phone || null };
};

// ── GeoScope data builder (canonical — mirrors page-loyalty.jsx) ─────────────
const _geoData = () => ({
  countries: (typeof SB2_COUNTRIES !== "undefined" ? SB2_COUNTRIES : (window.SB2_COUNTRIES || []))
    .map(c => ({ value: c.country_id, label: `${c.flag} ${c.country_name}` })),
  areas: (typeof SB3_AREAS !== "undefined" ? SB3_AREAS : (window.SB3_AREAS || []))
    .map(a => ({ value: a.id, label: a.name, country_id: a.country_id })),
  outlets: (typeof SB3_OUTLETS !== "undefined" ? SB3_OUTLETS : (window.SB3_OUTLETS || []))
    .map(o => ({ value: o.id, label: o.location, area_id: o.area_id })),
});

// ── Approval / Status options ─────────────────────────────────────────────────
const REWARD_APPROVAL_OPTS = [
  { value: "draft",    label: "Draft"    },
  { value: "pending",  label: "Pending"  },
  { value: "approved", label: "Approved" },
  { value: "rejected", label: "Rejected" },
  { value: "inactive", label: "Inactive" },
];
const REWARD_STATUS_OPTS = [
  { value: "draft",    label: "Draft"    },
  { value: "active",   label: "Active"   },
  { value: "inactive", label: "Inactive" },
];

// ── Empty draft for create ────────────────────────────────────────────────────
const _emptyReward = (role, scopeCountry) => ({
  id: null,
  name: "",
  description: "",
  partner_id: null,
  thumbnail_image_url: "",
  hero_image_url: "",
  points_required: "",
  country_eligible: { SG: false, PH: false, ID: false, TH: false },
  start_date: "",
  end_date: "",
  status: "draft",
  approval_status: "draft",
  submitted_by_user_id: null,
  approved_by: null,
  approved_at: null,
});

// ─────────────────────────────────────────────────────────────────────────────
/**
 * @spec GA-080 · CM-070 · AM-070 · OM-070
 */
function RewardList({ role = "GA", scope = {} }) {
  if (!["GA", "CM", "AM", "OM"].includes(role)) return _notAuthorised;

  const isGA = role === "GA";
  const isCM = role === "CM";
  const isAM = role === "AM";
  const isOM = role === "OM";

  const scopeCountry = scope && scope.countryId ? scope.countryId : "PH";

  // Mutable store — drawer create/edit apply here
  const [rewards, setRewards] = useState(() => {
    const raw = (typeof SB4_REWARDS !== "undefined") ? SB4_REWARDS : (window.SB4_REWARDS || []);
    return raw.map(r => ({ ...r }));
  });

  // ── filter + pagination state ──
  const [searchQuery, setSearchQuery]   = useState("");
  const [filters, setFilters]           = useState({});
  const [advFilters, setAdvFilters]     = useState({});
  const [page, setPage]                 = useState(1);
  const [pageSize, setPageSize]         = useState(20);

  // ── drawer state ──
  const [drawerFor, setDrawerFor]       = useState(null);
  const [mode, setMode]                 = useState("read");
  const [draft, setDraft]               = useState(null);

  // ── modal state ──

  // ── toast ──
  const [toast, setToast] = useState(null);
  const fireToast = (msg) => { setToast(msg); setTimeout(() => setToast(null), 2400); };

  // ── row scope ──
  const allRewards = useMemo(() => {
    if (isGA) return rewards;
    if (isCM || isAM) return rewards.filter(r => {
      const elig = r.country_eligible || {};
      return elig[scopeCountry] || Object.keys(elig).length === 0;
    });
    /* OM — read-only, active approved only */
    return rewards.filter(r => r.status === "active" && r.approval_status === "approved");
  }, [rewards, role, scopeCountry]);

  // ── filtered rows ──
  const rows = useMemo(() => {
    let r = allRewards;
    if (searchQuery) {
      const q = searchQuery.toLowerCase();
      r = r.filter(rw => rw.name.toLowerCase().includes(q));
    }
    if (filters.approval_status) r = r.filter(rw => rw.approval_status === filters.approval_status);
    if (filters.status)          r = r.filter(rw => rw.status === filters.status);
    if (filters.partner_id)      r = r.filter(rw => rw.partner_id === filters.partner_id);
    if (advFilters.geo) {
      const { countries: gc } = advFilters.geo || {};
      if (gc && gc.length > 0) {
        r = r.filter(rw => gc.some(cid => (rw.country_eligible || {})[cid]));
      }
    }
    return r;
  }, [allRewards, searchQuery, filters, advFilters]);

  const totalPages  = Math.max(1, Math.ceil(rows.length / pageSize));
  const visibleRows = rows.slice((page - 1) * pageSize, page * pageSize);

  // ── drawer helpers ──
  const drawerRow  = drawerFor ? rewards.find(r => r.id === drawerFor) || null : null;
  const isCreate   = mode === "create";
  const isEdit     = mode === "edit";
  const isForm     = isEdit || isCreate;

  const openRead   = (row) => { setDrawerFor(row.id); setMode("read"); setDraft(null); };
  const closeDrawer = () => { setDrawerFor(null); setMode("read"); setDraft(null); };
  const startEdit  = () => { setDraft({ ...drawerRow }); setMode("edit"); };
  const startCreate = () => {
    setDrawerFor(null);
    setDraft(_emptyReward(role, scopeCountry));
    setMode("create");
  };
  const setD = (k, v) => setDraft(d => ({ ...d, [k]: v }));

  // ── save ──
  // GA edit: persist approval_status + status verbatim from draft (no override).
  // GA create: auto-approved/active (no dropdowns shown on create).
  // CM/AM save on a draft: auto-sets pending (Submit path).
  const isCMorAMDraft = (isCM || isAM) && draft && draft.approval_status === "draft";
  const saveForm = () => {
    if (!draft || !draft.name.trim()) return;
    if (isAM && !draft.partner_id) return;
    const rec = { ...draft };
    if (isGA && isCreate) {
      // GA create: auto-approve
      rec.approval_status = "approved";
      rec.status = "active";
    } else if (isGA && isEdit) {
      // GA edit: persist draft values verbatim; stamp approved_at if approving now
      if (rec.approval_status === "approved" && !rec.approved_at) {
        rec.approved_at = new Date().toISOString();
      }
      if (rec.approval_status === "approved" && !rec.approved_by) {
        rec.approved_by = "u_ga_01";
      }
    } else if (isCMorAMDraft) {
      // CM/AM Save on a draft = Submit
      rec.approval_status = "pending";
      rec.submitted_by_user_id = "current_user";
    }

    if (isCreate) {
      const id = `lr_NEW_${Date.now()}`;
      setRewards(ps => [{ ...rec, id }, ...ps]);
      setDrawerFor(id);
      fireToast(isGA ? "Reward created" : "Reward submitted for approval");
    } else {
      setRewards(ps => ps.map(r => r.id === rec.id ? { ...r, ...rec } : r));
      fireToast("Reward saved");
    }
    setMode("read");
    setDraft(null);
  };

  // ── approval actions (mirrors promotions semantics) ──
  const approveReward = () => {
    if (!drawerRow) return;
    setRewards(ps => ps.map(r => r.id === drawerRow.id
      ? { ...r, approval_status: "approved", approved_by: "u_ga_01", approved_at: new Date().toISOString() }
      : r
    ));
    fireToast("Reward approved");
  };
  const rejectReward = (reason) => {
    if (!drawerRow) return;
    setRewards(ps => ps.map(r => r.id === drawerRow.id
      ? { ...r, approval_status: "rejected", rejection_reason: reason }
      : r
    ));
    fireToast("Reward rejected");
  };
  const withdrawReward = () => {
    if (!drawerRow) return;
    setRewards(ps => ps.map(r => r.id === drawerRow.id
      ? { ...r, approval_status: "draft" }
      : r
    ));
    fireToast("Reward withdrawn");
  };


  // ── per-role titles ──
  // AM title reverted "Partner Rewards" → "Rewards" (PO/visual review 2026-06-06; partner-tie behaviour retained). OQ logged.
  const pageTitle = "Rewards";
  const titleDesc = isGA  ? "Brand partner and first-party rewards for redemption."
                  : isCM  ? "Rewards available in your country."
                  : isAM  ? "Rewards you propose."
                  : /* OM */ "Active rewards at your outlet.";

  // Remarks sub-block — keeps _useRemarks (a hook) at a stable top level, not inside the drawer JSX.
  // RemarksSection renders its own "Remarks" header — render direct, never inside a titled FormSection.
  function RewardRemarks({ rewardId, seed }) {
    const [remarks, addRemark, editRemark, deleteRemark] = _useRemarks("reward", rewardId, seed || []);
    return <RemarksSection entityType="reward" entityId={rewardId} remarks={remarks} onAdd={addRemark} currentUser="you" onEdit={editRemark} onDelete={deleteRemark} role={role}/>;
  }

  // ── Partner options for filter ──
  const brands = (typeof SB4_BRANDS !== "undefined") ? SB4_BRANDS : (window.SB4_BRANDS || []);
  const PARTNER_OPTS = [
    { value: "", label: "All partners" },
    ...brands.filter(b => b.id !== "br_sh").map(b => ({ value: b.id, label: b.name })),
    { value: "br_sh", label: "Sixhands (first-party)" },
  ];

  const APPROVAL_STATUS_FILTER_OPTS = [
    { value: "",         label: "All approvals"  },
    { value: "draft",    label: "Draft"           },
    { value: "pending",  label: "Pending"         },
    { value: "approved", label: "Approved"        },
    { value: "rejected", label: "Rejected"        },
  ];
  const STATUS_FILTER_OPTS = [
    { value: "",         label: "All statuses" },
    { value: "active",   label: "Active"       },
    { value: "inactive", label: "Inactive"     },
  ];

  // ── filter config ──
  const geo = _geoData();
  const primaryFilter = isOM
    ? [{ key: "status", label: "Status", options: STATUS_FILTER_OPTS.filter(o => o.value !== "inactive") }]
    : [
        { key: "approval_status", label: "Approval", options: APPROVAL_STATUS_FILTER_OPTS },
        { key: "status",          label: "Status",   options: STATUS_FILTER_OPTS           },
      ];

  const advancedFilters = [
    { key: "partner_id", kind: "select", label: "Partner", options: PARTNER_OPTS },
    ...(isOM ? [] : [{
      key: "geo", kind: "geoscope", label: "Network scope", role,
      countries: geo.countries, areas: geo.areas, outlets: geo.outlets,
    }]),
  ];

  // ── columns ──
  const colChevron = { label: "", width: 36, render: () => <span style={{ color: "var(--forest)" }}>{Ic.chevR(16)}</span> };

  const colsBase = isOM
    ? [
        { label: "Reward",         sortable: true, sortKey: "name",       render: r => <span style={{ ...T.primary() }}>{r.name}</span> },
        { label: "Points",         width: 110, sortable: true, sortKey: "points_required",  render: r => <span style={T_MUTED}>{(r.points_required || 0).toLocaleString()}</span> },
        { label: "Status",         width: 120, sortable: true, sortKey: "status",       render: r => <StatusPill status={r.status} kind="reward"/> },
        { label: "Valid until",    width: 150, sortable: true, sortKey: "end_date",      render: r => <span style={T_MUTED}>{_fmtDate(r.end_date) || "—"}</span> },
        colChevron,
      ]
    : [
        { label: "Reward",         sortable: true, sortKey: "name",       render: r => <span style={{ ...T.primary() }}>{r.name}</span> },
        { label: "Partner",        width: 200, sortable: true, sortKey: "partner_id",    render: r => <span style={T_MUTED}>{r.partner_id ? _brandName(r.partner_id) : <em style={T_MUTED}>First-party</em>}</span> },
        { label: "Points",         width: 110, sortable: true, sortKey: "points_required", render: r => <span style={T_MUTED}>{(r.points_required || 0).toLocaleString()}</span> },
        { label: "Countries",      width: 200, sortable: false, render: r => {
            const elig = r.country_eligible || {};
            const active = Object.entries(elig).filter(([,v]) => v).map(([k]) => k);
            if (active.length === 0) return <span style={T_MUTED}>—</span>;
            const countryMap = { PH: "🇵🇭", SG: "🇸🇬", ID: "🇮🇩", TH: "🇹🇭" };
            return (
              <span style={{ display: "inline-flex", flexWrap: "wrap", gap: 4 }}>
                {active.map(c => <CountryChip key={c} flag={countryMap[c] || "🏳️"} code={c}/>)}
              </span>
            );
          }
        },
        { label: "Approval",       width: 130, sortable: true, sortKey: "approval_status", render: r => <StatusPill status={r.approval_status} kind="reward"/> },
        { label: "Status",         width: 120, sortable: true, sortKey: "status",           render: r => <StatusPill status={r.status} kind="reward"/> },
        colChevron,
      ];

  // ── drawer form (create + edit): sections ────────────────────────────────
  const saveLabel = isCreate
    ? (isGA ? "Create reward" : "Submit reward")
    : (isCMorAMDraft ? "Submit for approval" : "Save");

  const formContent = draft && (
    <>
      {/* 1 · General (Status lives here — convention 14) */}
      <FormSection title="General">
        <Field label="Name" required>
          <Input value={draft.name} onChange={v => setD("name", v)} placeholder="e.g. Free bowl reward"/>
        </Field>
        <Field label="Description">
          <Textarea value={draft.description || ""} onChange={v => setD("description", v)} rows={2}
            placeholder="Brief reward description shown to customers."/>
        </Field>
        <Field label="Points required" required>
          <Input type="number" value={draft.points_required || ""} onChange={v => setD("points_required", parseInt(v, 10) || "")}
            placeholder="e.g. 800"/>
        </Field>
        <Field label="Partner" required={isAM} hint={isAM ? "Required — select the partner brand for this reward." : "Leave blank for first-party Sixhands rewards."}>
          <Select
            value={draft.partner_id || ""}
            onChange={v => setD("partner_id", v || null)}
            options={[...(isAM ? [] : [{ value: "", label: "First-party (Sixhands)" }]), ...brands.filter(b => b.id !== "br_sh").map(b => ({ value: b.id, label: b.name }))]}
          />
        </Field>
        <Field label="Status">
          {isGA && isEdit
            ? <Select value={draft.status} onChange={v => setD("status", v)} options={REWARD_STATUS_OPTS}/>
            : <StatusPill status={draft.status} kind="reward"/>}
        </Field>
      </FormSection>

      {/* 2 · Approval (most-frequent action — convention 14) */}
      <FormSection title="Approval">
        {isGA && isEdit ? (
          <Field label="Approval status">
            <Select value={draft.approval_status} onChange={v => setD("approval_status", v)} options={REWARD_APPROVAL_OPTS}/>
          </Field>
        ) : (
          <>
            <Field label="Approval status">
              <StatusPill status={draft.approval_status} kind="reward"/>
            </Field>
            {(isCM || isAM) && (
              <InlineAlert kind="info">Saving submits this reward for Global Admin approval.</InlineAlert>
            )}
          </>
        )}
        {isEdit && (
          <KeyValueGrid items={[
            { label: "Submitted by", value: _rUserName(draft.submitted_by_user_id) },
            { label: "Approved by",  value: _rUserName(draft.approved_by) },
            { label: "Approved at",  value: draft.approved_at ? _fmtDateTime(draft.approved_at, _scopeTz(scope)) : "—" },
          ]}/>
        )}
      </FormSection>

      <FormSection title="Media">
        <Field label="Thumbnail URL">
          <Input value={draft.thumbnail_image_url || ""} onChange={v => setD("thumbnail_image_url", v)}
            placeholder="https://cdn.sixhands.co/rewards/…"/>
        </Field>
        <Field label="Hero image URL">
          <Input value={draft.hero_image_url || ""} onChange={v => setD("hero_image_url", v)}
            placeholder="https://cdn.sixhands.co/rewards/…"/>
        </Field>
      </FormSection>

      <FormSection title="Window">
        <Field label="Start">
          <Input type="datetime-local" value={draft.start_date || ""} onChange={v => setD("start_date", v)}/>
        </Field>
        <Field label="End">
          <Input type="datetime-local" value={draft.end_date || ""} onChange={v => setD("end_date", v)}/>
        </Field>
      </FormSection>

      <FormSection title="Country eligibility">
        {Object.entries({ PH: "🇵🇭 Philippines", SG: "🇸🇬 Singapore", ID: "🇮🇩 Indonesia", TH: "🇹🇭 Thailand" }).map(([code, label]) => (
          <CheckboxRow
            key={code}
            label={label}
            checked={!!(draft.country_eligible || {})[code]}
            onChange={next => setD("country_eligible", { ...(draft.country_eligible || {}), [code]: next })}
            disabled={!isGA && code !== scopeCountry}
          />
        ))}
      </FormSection>

      {/* Remarks — shown in edit (existing record), hidden in create (no record yet); convention 6 */}
      {isEdit && <RewardRemarks rewardId={draft.id} seed={draft.reward_remarks}/>}
    </>
  );

  // ── read mode drawer ──────────────────────────────────────────────────────
  const readContent = !isForm && drawerRow && (() => {
    const r = drawerRow;
    const elig = r.country_eligible || {};
    const activeCodes = Object.entries(elig).filter(([,v]) => v).map(([k]) => k);
    const countryMap = { PH: "🇵🇭", SG: "🇸🇬", ID: "🇮🇩", TH: "🇹🇭" };
    // Approval card message
    const approvalMsg = r.approval_status === "pending"
      ? "Awaiting Global Admin approval."
      : r.approval_status === "approved"
        ? r.approved_by
          ? `Approved by ${_rUserName(r.approved_by)}${r.approved_at ? " on " + _fmtDate(r.approved_at) : ""}.`
          : "Approved."
        : r.approval_status === "rejected"
          ? `Rejected${r.rejection_reason ? ": " + r.rejection_reason : "."}`
          : "Draft — not yet submitted.";

    const canApproveCard = isGA && r.approval_status === "pending";
    // Proposer (submitted_by_user_id) or CM/AM role can withdraw a pending reward they submitted
    const canWithdrawCard = (isCM || isAM) && r.approval_status === "pending";

    return (
      <>
        <ApprovalCard
          status={r.approval_status}
          kind="reward"
          message={approvalMsg}
          canApprove={canApproveCard}
          canWithdraw={canWithdrawCard}
          onApprove={approveReward}
          onReject={rejectReward}
          onWithdraw={withdrawReward}
        />

        {/* 1 · General (Status here — convention 14) */}
        <FormSection title="General">
          <KeyValueGrid items={[
            { label: "Name",            value: r.name },
            { label: "Description",     value: r.description || "—" },
            { label: "Points required", value: (r.points_required || 0).toLocaleString() },
            { label: "Partner",         value: r.partner_id ? _brandName(r.partner_id) : "First-party (Sixhands)" },
            { label: "Status",          value: <StatusPill status={r.status} kind="reward"/> },
          ]}/>
        </FormSection>

        {/* 2 · Approval (submitted_by · approved_by · approved_at — convention 14) */}
        <FormSection title="Approval">
          <KeyValueGrid items={[
            { label: "Approval status", value: <StatusPill status={r.approval_status} kind="reward"/> },
            { label: "Submitted by",    value: _rUserName(r.submitted_by_user_id) },
            { label: "Approved by",     value: _rUserName(r.approved_by) },
            { label: "Approved at",     value: r.approved_at ? _fmtDateTime(r.approved_at, _scopeTz(scope)) : "—" },
          ]}/>
        </FormSection>

        {/* Media */}
        <FormSection title="Media">
          <KeyValueGrid items={[
            { label: "Thumbnail", value: r.thumbnail_image_url
              ? <img src={r.thumbnail_image_url} alt="Thumbnail" style={{ height: 48, borderRadius: "var(--r)", objectFit: "cover" }}/>
              : "—"
            },
            { label: "Hero",      value: r.hero_image_url
              ? <img src={r.hero_image_url} alt="Hero" style={{ height: 48, borderRadius: "var(--r)", objectFit: "cover" }}/>
              : "—"
            },
          ]}/>
        </FormSection>

        {/* Window — start + end with date+time (03-ui Table 2) */}
        <FormSection title="Window">
          <KeyValueGrid items={[
            { label: "Start", value: _fmtLocalDateTime(r.start_date, _rewardTz(r)) },
            { label: "End",   value: _fmtLocalDateTime(r.end_date,   _rewardTz(r)) },
          ]}/>
        </FormSection>

        {/* Country eligibility — retained (not in injected Table 2 but used in list; flagged to orchestrator) */}
        <FormSection title="Country eligibility">
          <KeyValueGrid items={[{
            label: "Countries",
            value: activeCodes.length
              ? <span style={{ display: "inline-flex", flexWrap: "wrap", gap: 4 }}>
                  {activeCodes.map(c => <CountryChip key={c} flag={countryMap[c] || "🏳️"} code={c}/>)}
                </span>
              : "—",
          }]}/>
        </FormSection>

        {/* Activity — redemption_count (read-only derived; 03-ui Table 2) */}
        <FormSection title="Activity">
          <KeyValueGrid items={[
            { label: "Redemptions", value: r.redemption_count != null ? r.redemption_count.toLocaleString() : "—" },
          ]}/>
        </FormSection>

        {/* Remarks (Ops-module convention 6; own-author add/edit/delete) */}
        <RewardRemarks rewardId={r.id} seed={r.reward_remarks}/>

        {!isOM && (
          <DeleteSection
            entityLabel="reward"
            soft={r.status !== "draft"}
            note={r.status === "draft"
              ? "Permanently delete this draft reward."
              : "Decommission (soft-delete) this reward. Historical redemptions retained. GA-only."}
            gate={_canDelete(role, { status: r.status, isReferenced: (r.redemption_count || 0) > 0, rbacOk: !isOM })}
            confirmBody={<div>{r.status === "draft" ? "Permanently delete" : "Decommission"} <strong>{r.name}</strong>? Writes to <strong>audit_logs</strong>.</div>}
            onDelete={() => { setRewards(ps => ps.filter(x => x.id !== r.id)); closeDrawer(); }}
          />
        )}
      </>
    );
  })();

  // ── drawer actions ──
  // Read: [Delete danger — GA + draft only] · [Close secondary] · [Edit primary]
  // Form: [Cancel secondary] · [Save/Submit primary]
  const canEditDrawer = !isOM && drawerRow && (
    isGA ||
    ((isCM || isAM) && (drawerRow.approval_status === "draft" || drawerRow.approval_status === "rejected"))
  );

  const drawerActions = isForm ? (
    <>
      <Btn variant="secondary" onClick={() => { if (isCreate) closeDrawer(); else { setMode("read"); setDraft(null); } }}>Cancel</Btn>
      <Btn variant="primary" onClick={saveForm} disabled={!draft || !draft.name.trim() || (isAM && !draft.partner_id)}>
        {saveLabel}
      </Btn>
    </>
  ) : (
    <>
      <Btn variant="secondary" onClick={closeDrawer}>Close</Btn>
      {canEditDrawer && <Btn variant="primary" onClick={startEdit}>Edit</Btn>}
    </>
  );

  const drawerTitle = isCreate ? "New reward" : (drawerRow ? drawerRow.name : "");

  // ── PageHead actions: all create-capable roles use "New reward" ──
  const headActions = (
    <>
      <ExportButton role={role} variant="secondary" onClick={() => fireToast("Exporting rewards…")}/>
      {(isGA || ((isCM || isAM) && _canPropose(role))) && (
        <Btn variant="primary" onClick={startCreate}>New reward</Btn>
      )}
    </>
  );

  return (
    <div className="page-inner">
      <PageHead
        title={pageTitle}
        description={titleDesc}
        actions={headActions}
      />

      <FilterBar
        primaryFilter={primaryFilter}
        primaryValue={filters}
        onPrimaryChange={next => { setFilters(f => ({ ...f, ...next })); setPage(1); }}
        advancedFilters={advancedFilters}
        advancedValues={advFilters}
        onAdvancedChange={(k, v) => { setAdvFilters(f => ({ ...f, [k]: v })); setPage(1); }}
        onAdvancedReset={() => { setAdvFilters({}); setPage(1); }}
        onReset={() => { setFilters({}); setAdvFilters({}); setSearchQuery(""); setPage(1); }}
        search={searchQuery}
        onSearch={q => { setSearchQuery(q); setPage(1); }}
        searchPlaceholder="Search rewards…"
      />

      <Table
        columns={colsBase}
        rows={visibleRows}
        onRow={openRead}
        emptyText="No rewards match these filters."
      />

      <TableFooter
        page={page}
        totalPages={totalPages}
        onPage={setPage}
        count={pageSize}
        onCountChange={c => { setPageSize(c); setPage(1); }}
      />

      {/* ── Detail Drawer (read | edit | create) ── */}
      <DetailDrawer
        open={isCreate || !!drawerFor}
        onClose={closeDrawer}
        title={drawerTitle}
        actions={drawerActions}
        width={700}
      >
        {isForm && formContent}
        {!isForm && drawerFor && readContent}
      </DetailDrawer>


      {toast && <Toast message={toast}/>}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
/**
 * @spec GA-081 · CM-071 · AM-071 · OM-071
 */
function RedemptionList({ role = "GA", scope = {} }) {
  if (!["GA", "CM", "AM", "OM"].includes(role)) return _notAuthorised;

  const isGA = role === "GA";
  const isCM = role === "CM";
  const isAM = role === "AM";
  const isOM = role === "OM";

  const scopeCountry = scope && scope.countryId ? scope.countryId : "PH";

  // ── filter + pagination state ──
  const [searchQuery, setSearchQuery]   = useState("");
  const [filters, setFilters]           = useState({});
  const [advFilters, setAdvFilters]     = useState({});
  const [page, setPage]                 = useState(1);
  const [pageSize, setPageSize]         = useState(20);

  // ── drawer state (read-only) ──
  const [drawerFor, setDrawerFor]       = useState(null);

  // ── toast ──
  const [toast, setToast] = useState(null);
  const fireToast = (msg) => { setToast(msg); setTimeout(() => setToast(null), 2400); };

  // ── row scope ──
  // AM narrows to its area's outlets; OM to its own outlet (canonical idiom — page-orders.jsx).
  const scopeOutlets = useMemo(() => {
    if (isAM) return new Set(SB3_OUTLETS.filter(o => o.area_id === AM_AREA_ID).map(o => o.id));
    if (isOM) return new Set([OM_OUTLET_ID]);
    return null;
  }, [role]);

  const allRedemptions = useMemo(() => {
    const raw = (typeof SB4_REDEMPTIONS !== "undefined") ? SB4_REDEMPTIONS : (window.SB4_REDEMPTIONS || []);
    if (isGA) return raw;
    if (isCM) return raw.filter(r => r.country_id === scopeCountry);
    /* AM / OM */ return raw.filter(r => scopeOutlets && scopeOutlets.has(r.outlet_id));
  }, [role, scopeCountry, scopeOutlets]);

  // Remarks sub-block — keeps _useRemarks (a hook) at a stable top level, not inside the drawer JSX.
  // RemarksSection renders its own "Remarks" header — render direct, never inside a titled FormSection.
  function RedemptionRemarks({ redemptionId, seed }) {
    const [remarks, addRemark, editRemark, deleteRemark] = _useRemarks("redemption", redemptionId, seed || []);
    return <RemarksSection entityType="redemption" entityId={redemptionId} remarks={remarks} onAdd={addRemark} currentUser="you" onEdit={editRemark} onDelete={deleteRemark} role={role}/>;
  }

  // ── reward name lookup ──
  const _rewardName = (id) => {
    const rewardsArr = (typeof SB4_REWARDS !== "undefined") ? SB4_REWARDS : (window.SB4_REWARDS || []);
    const r = rewardsArr.find(x => x.id === id);
    return r ? r.name : (id || "—");
  };

  // ── reward options for filter ──
  const rewardOpts = useMemo(() => {
    const rewardsArr = (typeof SB4_REWARDS !== "undefined") ? SB4_REWARDS : (window.SB4_REWARDS || []);
    return [
      { value: "", label: "All rewards" },
      ...rewardsArr.map(r => ({ value: r.id, label: r.name })),
    ];
  }, []);

  // ── filtered rows ──
  const rows = useMemo(() => {
    let r = allRedemptions;
    if (searchQuery) {
      const q = searchQuery.toLowerCase();
      r = r.filter(x => _rewardName(x.reward_id).toLowerCase().includes(q));
    }
    if (filters.reward_id) r = r.filter(x => x.reward_id === filters.reward_id);
    if (advFilters.geo) {
      const { countries: gc } = advFilters.geo || {};
      if (gc && gc.length > 0) {
        r = r.filter(x => gc.includes(x.country_id));
      }
    }
    if (advFilters.redeemed_at) {
      r = r.filter(x => _dtfMatch(advFilters.redeemed_at, x.redeemed_at, x.country_id || "SG"));
    }
    return r;
  }, [allRedemptions, searchQuery, filters, advFilters]);

  const totalPages  = Math.max(1, Math.ceil(rows.length / pageSize));
  const visibleRows = rows.slice((page - 1) * pageSize, page * pageSize);

  // ── drawer helpers ──
  const redemptions = (typeof SB4_REDEMPTIONS !== "undefined") ? SB4_REDEMPTIONS : (window.SB4_REDEMPTIONS || []);
  const drawerRow  = drawerFor ? redemptions.find(r => r.id === drawerFor) || null : null;
  const openRead   = (row) => { setDrawerFor(row.id); };
  const closeDrawer = () => setDrawerFor(null);

  // ── GeoScope data ──
  const geo = _geoData();

  // ── filter config ──
  // Multi-country ledger (WS4): pass raw scope so the GA scope chip narrows
  // presets + filter tz (§38). todOptions from scope; filter baseline tz = _scopeTz.
  const todOptions = useMemo(
    () => _scopePresets(scope, (typeof CUSTOM_OPTIONS !== "undefined" ? CUSTOM_OPTIONS : (window.CUSTOM_OPTIONS || []))),
    [scope]
  );
  const filterTz = _scopeTz(scope);

  const advancedFilters = [
    ...(isOM ? [] : [{
      key: "geo", kind: "geoscope", label: "Network scope", role,
      countries: geo.countries, areas: geo.areas, outlets: geo.outlets,
    }]),
    { key: "redeemed_at", kind: "datetime", label: "Redeemed on", country: filterTz, todOptions },
  ];

  // ── columns ──
  const colChevron = { label: "", width: 36, render: () => <span style={{ color: "var(--forest)" }}>{Ic.chevR(16)}</span> };

  const colsBase = [
    { label: "Reward",      sortable: true, sortKey: "reward_id",
      render: r => (
        <span style={{ ...T.primary() }}>
          <span
            style={{ color: "var(--forest)", cursor: "pointer", textDecoration: "underline" }}
            onClick={e => { e.stopPropagation(); _go(`${role.toLowerCase()}-rewards`, { reward_id: r.reward_id }); }}>
            {_rewardName(r.reward_id)}
          </span>
        </span>
      )
    },
    { label: "Customer",    sortable: true, sortKey: "customer_id",
      render: r => {
        const c = _custById(r.customer_id);
        return (
          <span style={{ display: "inline-flex", flexDirection: "column", gap: 2 }}>
            <span style={T_MUTED}>{c.name}</span>
            {c.email && _emailLink(c.email, { muted: true })}
          </span>
        );
      }
    },
    { label: "Points",      width: 100, sortable: true, sortKey: "points_spent",  render: r => <span style={T_MUTED}>{(r.points_spent || 0).toLocaleString()}</span> },
    ...(!isOM ? [
      { label: "Country",   width: 120, sortable: true, sortKey: "country_id",
        render: r => {
          const countryMap = { PH: "🇵🇭", SG: "🇸🇬", ID: "🇮🇩", TH: "🇹🇭" };
          return r.country_id
            ? <CountryChip flag={countryMap[r.country_id] || "🏳️"} code={r.country_id}/>
            : <span style={T_MUTED}>—</span>;
        }
      },
    ] : []),
    { label: "Redeemed",    width: 180, sortable: true, sortKey: "redeemed_at",  render: r => <span style={T_MUTED}>{_fmtDateTime(r.redeemed_at, r.country_id)}</span> },
    colChevron,
  ];

  // ── drawer read-only content ──────────────────────────────────────────────
  const readContent = drawerRow && (() => {
    const r = drawerRow;
    const cust = _custById(r.customer_id);
    const countryMap = { PH: "🇵🇭", SG: "🇸🇬", ID: "🇮🇩", TH: "🇹🇭" };
    return (
      <>
        <FormSection title="General">
          <KeyValueGrid items={[
            { label: "Reward",         value: (
              <span
                style={{ color: "var(--forest)", cursor: "pointer", textDecoration: "underline" }}
                onClick={() => _go(`${role.toLowerCase()}-rewards`, { reward_id: r.reward_id })}>
                {_rewardName(r.reward_id)}
              </span>
            ) },
            { label: "Points spent",   value: (r.points_spent || 0).toLocaleString() },
            { label: "Code",           value: r.code_id || "—" },
            { label: "Redeemed at",    value: _fmtDateTime(r.redeemed_at, r.country_id) },
          ]}/>
        </FormSection>

        <FormSection title="Customer">
          <KeyValueGrid items={[
            { label: "Name",           value: cust.name },
            { label: "Email",          value: cust.email ? _emailLink(cust.email) : "—" },
            ...(!isOM && cust.phone ? [{ label: "Phone", value: _phoneLink(cust.phone) }] : []),
          ]}/>
        </FormSection>

        <FormSection title="Context">
          <KeyValueGrid items={[
            { label: "Country",        value: r.country_id
              ? <CountryChip flag={countryMap[r.country_id] || "🏳️"} code={r.country_id}/>
              : "—"
            },
            { label: "Outlet",         value: r.outlet_id || "—" },
            { label: "Order",          value: (() => {
              if (!r.order_id) return "—";
              const orderById = (typeof SB4_ORDER_BY_ID !== "undefined" ? SB4_ORDER_BY_ID : (window.SB4_ORDER_BY_ID || {}));
              if (!orderById[r.order_id]) return <span style={T_MUTED}>{r.order_id}</span>;  // phantom id → no dead link
              // All roles deep-link to the order detail drawer (OrdersList auto-opens on incoming id).
              // OM uses its live-orders route (om-orders-live, also OrdersList); other tiers use ${role}-orders.
              const ordersRoute = isOM ? "om-orders-live" : `${role.toLowerCase()}-orders`;
              return (
                <span
                  style={{ color: "var(--forest)", cursor: "pointer", textDecoration: "underline" }}
                  onClick={() => _go(ordersRoute, { id: r.order_id })}>
                  {r.order_id}
                </span>
              );
            })() },
          ]}/>
        </FormSection>

        {/* Remarks (Ops-module convention 6; own-author add/edit/delete) */}
        <RedemptionRemarks redemptionId={r.id} seed={r.redemption_remarks}/>
      </>
    );
  })();

  return (
    <div className="page-inner">
      <PageHead
        title="Redemptions"
        description={{
          GA: "Reward redemption records.",
          CM: "Your country's redemptions.",
          AM: "Your area's redemptions.",
          OM: "Your outlet's redemptions.",
        }[role] || "Reward redemption records."}
        actions={<ExportButton role={role} variant="primary" onClick={() => fireToast("Exporting redemptions…")}/>}
      />

      <FilterBar
        primaryFilter={[{ key: "reward_id", label: "Reward", options: rewardOpts }]}
        primaryValue={filters}
        onPrimaryChange={next => { setFilters(f => ({ ...f, ...next })); setPage(1); }}
        advancedFilters={advancedFilters}
        advancedValues={advFilters}
        onAdvancedChange={(k, v) => { setAdvFilters(f => ({ ...f, [k]: v })); setPage(1); }}
        onAdvancedReset={() => { setAdvFilters({}); setPage(1); }}
        onReset={() => { setFilters({}); setAdvFilters({}); setSearchQuery(""); setPage(1); }}
        search={searchQuery}
        onSearch={q => { setSearchQuery(q); setPage(1); }}
        searchPlaceholder="Search redemptions…"
      />

      <Table
        columns={colsBase}
        rows={visibleRows}
        onRow={openRead}
        emptyText="No redemptions match these filters."
      />

      <TableFooter
        page={page}
        totalPages={totalPages}
        onPage={setPage}
        count={pageSize}
        onCountChange={c => { setPageSize(c); setPage(1); }}
      />

      {/* ── Read-only Detail Drawer ── */}
      <DetailDrawer
        open={!!drawerFor}
        onClose={closeDrawer}
        title={drawerRow ? _rewardName(drawerRow.reward_id) : ""}
        actions={<Btn variant="primary" onClick={closeDrawer}>Close</Btn>}
        width={700}
      >
        {drawerFor && readContent}
      </DetailDrawer>

      {toast && <Toast message={toast}/>}
    </div>
  );
}

Object.assign(window, { RewardList, RedemptionList });
})();
