/* page-wallet.jsx — Wallet stored-value ledger. Ops module, SG-only, all roles (GA/CM/AM/OM). @spec US-105 */
(function () {
  const { useState, useMemo } = React;

  // ── txn_type options ──
  const TXN_TYPE_OPTS = [
    { value: "",             label: "All types" },
    { value: "top_up",       label: "Top-up" },
    { value: "spend_order",  label: "Spend (order)" },
    { value: "spend_gift",   label: "Spend (gift)" },
    { value: "refund",       label: "Refund" },
  ];

  const STATUS_OPTS = [
    { value: "",            label: "All statuses" },
    { value: "completed",   label: "Completed" },
    { value: "cancelled",   label: "Cancelled" },
  ];

  // ── Outlet label helper ──
  const _outletLabel = (id) => {
    const o = typeof SB3_OUTLET_BY_ID !== "undefined" ? SB3_OUTLET_BY_ID[id] : null;
    return o ? o.location : (id || "—");
  };

  // ── Date/time-of-day FILTERING — Wallet is SG-only (UTC+8). ──
  // Row-match uses the shared _dtfMatch(value, iso, "SG") predicate (helpers.jsx)
  // — the single source of truth every ledger page filters by, evaluated in the
  // SGT record-local frame. Timestamp DISPLAY uses _fmtDateTime(iso, "SG").

  // ── Normalise wallet_remarks (shape {text,timestamp,author}) → RemarksSection shape ──
  const _normaliseRemarks = (walletRemarks) =>
    (walletRemarks || []).map((r, i) => ({
      id: r.id || ("wr_" + i),
      content: r.text || r.content || "",
      created_by_name: r.author || r.created_by_name || "System",
      created_at: r.timestamp || r.created_at || "",
    }));

  // ── Remarks sub-block — hook at stable top level (not inside drawer JSX) ──
  function WalletTxnRemarks({ txnId, seed }) {
    const [remarks, addRemark, editRemark, deleteRemark] = _useRemarks("wallet_txn", txnId, _normaliseRemarks(seed));
    return (
      <RemarksSection
        entityType="wallet_txn"
        entityId={txnId}
        remarks={remarks}
        onAdd={addRemark}
        currentUser="you"
        onEdit={editRemark}
        onDelete={deleteRemark}
      />
    );
  }

  // ── Stat computation — B2 recipe ──
  // Top-ups: Σ amount where [SG ∩ date+tod-filtered ∩ completed ∩ txn_type==='top_up']
  // Spend:   Σ |amount| where [SG ∩ date+tod-filtered ∩ completed ∩ amount<0]
  // Stats react to date+tod ONLY (the shared _dtfMatch predicate) — never txn_type/status (B2).
  const _computeStats = (allTxns, dtf) => {
    const r = allTxns.filter(
      (t) => t.country_id === "SG" && t.status === "completed" && _dtfMatch(dtf, t.created_at, "SG")
    );
    const topUps = r
      .filter((t) => t.txn_type === "top_up")
      .reduce((sum, t) => sum + t.amount, 0);
    const spend = r
      .filter((t) => t.amount < 0)
      .reduce((sum, t) => sum + Math.abs(t.amount), 0);
    return { topUps, spend };
  };

  // ── Stat sub-caption — reflects active date/range + time window ──
  const _statSub = (dtf) => {
    const mode = dtf && dtf.mode === "range" ? "range" : "date";
    let datePart;
    if (mode === "range") {
      const r = (dtf && dtf.range) ? dtf.range : { from: "", to: "" };
      if (!r.from && !r.to) datePart = "All dates";
      else datePart = (r.from || "start") + " – " + (r.to || "now");
    } else {
      const d = (dtf && dtf.date) ? dtf.date : null;
      datePart = d || "Today";
    }
    const time = (dtf && dtf.time && !dtf.time.full) ? dtf.time : null;
    let timePart;
    if (!time) timePart = "Full day";
    else if (time.start && time.end) timePart = time.start + "–" + time.end;
    else if (time.start) timePart = "from " + time.start;
    else if (time.end)   timePart = "until " + time.end;
    else timePart = "Full day";
    return "Completed · " + datePart + " · " + timePart;
  };

  /** @spec US-105 */
  function WalletLedger({ role, scope }) {
    // Role gate — wallet is available to GA, CM, AM, OM (all roles, SG-scoped)
    if (!["GA", "CM", "AM", "OM"].includes(role)) return _notAuthorised;

    const [search,       setSearch]       = useState("");
    const [txnFilter,    setTxnFilter]    = useState("");
    const [statusFilter, setStatusFilter] = useState("");
    // Inline DateTimeFilter state — null shape = default (Today · Full day).
    // Single source of truth for BOTH the date/range slice and the time-window slice.
    const [dtf,          setDtf]          = useState({ mode: "date", date: null, range: { from: "", to: "" }, time: { full: true } });
    // GeoScope (GA/CM/AM only; SG-scoped) — {countries,areas,outlets}
    const [geo,          setGeo]          = useState({ countries: [], areas: [], outlets: [] });
    const [page,         setPage]         = useState(1);
    const [pageSize,     setPageSize]     = useState(25);
    const [selected,     setSelected]     = useState(null);
    const [drawerOpen,   setDrawerOpen]   = useState(false);

    const allTxns = typeof SB_WALLET_TXNS !== "undefined" ? SB_WALLET_TXNS : [];

    // ── walletCountries — SG only (B1) ──
    const walletCountries = useMemo(() => {
      const flags     = typeof SB2_FLAG_VALUES !== "undefined" ? SB2_FLAG_VALUES : {};
      const countries = typeof SB2_COUNTRIES   !== "undefined" ? SB2_COUNTRIES   : [];
      return countries
        .filter((c) => flags[c.country_id] && flags[c.country_id].wallet_enabled)
        .map((c) => c.country_id);
    }, []);

    // ── SG-scoped area/outlet lists for GeoScope ──
    const sgAreas = useMemo(() => {
      const areas = typeof SB3_AREAS !== "undefined" ? SB3_AREAS : [];
      return areas.filter((a) => walletCountries.includes(a.country_id));
    }, [walletCountries]);

    const sgOutlets = useMemo(() => {
      const outlets = typeof SB3_OUTLETS !== "undefined" ? SB3_OUTLETS : [];
      return outlets.filter((o) => walletCountries.includes(o.country_id));
    }, [walletCountries]);

    // ── Filter scope — Wallet is SG-only, so it always resolves presets + the
    // baseline FILTER tz for SG, regardless of the GA header scope chip. This
    // flows through the shared scope resolvers (proves the wiring; WS4 ledgers
    // pass the raw `scope`). Raw scope here would regress CM/AM/OM presets. ──
    const _walletScope = { ...scope, countryId: "SG" };
    // ── todOptions — SG time_of_day_range presets via shared resolver ──
    const todOptions = useMemo(
      () => _scopePresets(_walletScope, typeof CUSTOM_OPTIONS !== "undefined" ? CUSTOM_OPTIONS : []),
      []
    );
    // ── filterTz — baseline FILTER tz via shared resolver (SG). Row DISPLAY tz
    // stays record-local ("SG") in the column renders below — unchanged. ──
    const filterTz = _scopeTz(_walletScope);

    // ── Memoised filter → scoped rows ──
    const rows = useMemo(() => {
      // Base: SG-only (B1)
      let r = allTxns.filter((t) => walletCountries.includes(t.country_id));

      // Role scope
      if (role === "CM") {
        const home = scope && scope.countryId ? scope.countryId : "SG";
        r = r.filter((t) => t.country_id === home);
      } else if (role === "AM") {
        const areaId = scope && scope.areaId ? scope.areaId : "ar_01";
        const areaOutlets = sgOutlets.filter((o) => o.area_id === areaId).map((o) => o.id);
        r = r.filter((t) => areaOutlets.includes(t.outlet_id));
      } else if (role === "OM") {
        const outletId = scope && scope.outletId ? scope.outletId : "SH-SG-001";
        r = r.filter((t) => t.outlet_id === outletId);
      }
      // GA: no role scope — all SG rows

      // GeoScope (GA/CM/AM only)
      if (role !== "OM") {
        if (geo.countries && geo.countries.length)
          r = r.filter((t) => geo.countries.includes(t.country_id));
        if (geo.areas && geo.areas.length) {
          const scopedOutlets = sgOutlets
            .filter((o) => geo.areas.includes(o.area_id))
            .map((o) => o.id);
          if (scopedOutlets.length) r = r.filter((t) => scopedOutlets.includes(t.outlet_id));
        }
        if (geo.outlets && geo.outlets.length)
          r = r.filter((t) => geo.outlets.includes(t.outlet_id));
      }

      // txn_type primaryFilter
      if (txnFilter) r = r.filter((t) => t.txn_type === txnFilter);

      // status advancedFilter
      if (statusFilter) r = r.filter((t) => t.status === statusFilter);

      // Date + time-of-day filter — shared SGT-aware predicate (single-date OR range, full-day OR time window)
      r = r.filter((t) => _dtfMatch(dtf, t.created_at, "SG"));

      // search
      if (search) {
        const q = search.toLowerCase();
        r = r.filter(
          (t) =>
            t.id.toLowerCase().includes(q) ||
            (t.customer && t.customer.id && t.customer.id.toLowerCase().includes(q)) ||
            (t.customer && t.customer.name && t.customer.name.toLowerCase().includes(q)) ||
            (t.order_id && t.order_id.toLowerCase().includes(q))
        );
      }

      return r;
    }, [allTxns, role, scope, walletCountries, sgOutlets, txnFilter, statusFilter, dtf, search, geo]);

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

    // ── Stats (B2 recipe — DateTimeFilter ONLY; not txn_type/status) ──
    const stats   = useMemo(() => _computeStats(allTxns, dtf), [allTxns, dtf]);
    const statSub = _statSub(dtf);

    // ── Columns — GA/CM/AM full; OM drops payment_method + order_id ──
    const columns = role === "OM"
      ? [
          { key: "id",         label: "Txn Ref",  sortable: true,
            render: (r) => <span style={{ ...T.primary() }}>{r.id}</span> },
          { key: "customer",   label: "Customer",  sortable: true, sortKey: "customer.name",
            render: (r) => (
              <span>
                <span style={{ ...T.primary() }}>{r.customer ? r.customer.name : "—"}</span>
                {r.customer && r.customer.email && (
                  <span style={{ marginLeft: 6, ...T.muted }}>{_emailLink(r.customer.email, { muted: true })}</span>
                )}
              </span>
            ) },
          { key: "txn_type",   label: "Type",      sortable: true, sortKey: "txn_type",
            render: (r) => <StatusPill status={r.txn_type} kind="wallet"/> },
          { key: "amount",     label: "Amount",    sortable: true, sortKey: "amount",
            render: (r) => {
              const { text, color } = _walletAmount(r);
              return <span style={{ ..._MONO, color, fontWeight: 600 }}>{text}</span>;
            }},
          { key: "status",     label: "Status",    sortable: true, sortKey: "status",
            render: (r) => <StatusPill status={r.status} kind="wallet"/> },
          { key: "created_at", label: "Date",      sortable: true,
            render: (r) => <span style={{ ...T.muted, whiteSpace: "nowrap" }}>{_fmtDateTime(r.created_at, "SG")}</span> },
        ]
      : [
          { key: "id",             label: "Txn Ref",        sortable: true,
            render: (r) => <span style={{ ...T.primary() }}>{r.id}</span> },
          { key: "customer",       label: "Customer",        sortable: true, sortKey: "customer.name",
            render: (r) => (
              <span>
                <span style={{ ...T.primary() }}>{r.customer ? r.customer.name : "—"}</span>
                {r.customer && r.customer.email && (
                  <span style={{ marginLeft: 6, ...T.muted }}>{_emailLink(r.customer.email, { muted: true })}</span>
                )}
              </span>
            ) },
          { key: "txn_type",       label: "Type",            sortable: true, sortKey: "txn_type",
            render: (r) => <StatusPill status={r.txn_type} kind="wallet"/> },
          { key: "amount",         label: "Amount",          sortable: true, sortKey: "amount",
            render: (r) => {
              const { text, color } = _walletAmount(r);
              return <span style={{ ..._MONO, color, fontWeight: 600 }}>{text}</span>;
            }},
          { key: "payment_method", label: "Payment Method",  sortable: true, sortKey: "payment_method",
            render: (r) => r.payment_method
              ? <span style={{ ...T.muted }}>{r.payment_method}</span>
              : <span style={T.muted}>—</span> },
          { key: "status",         label: "Status",          sortable: true, sortKey: "status",
            render: (r) => <StatusPill status={r.status} kind="wallet"/> },
          { key: "order_id",       label: "Order",           sortable: true, sortKey: "order_id",
            render: (r) => r.order_id
              ? <a className="ghost-link" onClick={(e) => { e.stopPropagation(); _go("ga-order-detail", { id: r.order_id }); }} style={{ cursor: "pointer" }}>{r.order_id}</a>
              : <span style={T.muted}>—</span> },
          { key: "created_at",     label: "Date",            sortable: true,
            render: (r) => <span style={{ ...T.muted, whiteSpace: "nowrap" }}>{_fmtDateTime(r.created_at, "SG")}</span> },
        ];

    // ── Per-role description copy (no spec IDs in operator copy) ──
    const descriptions = {
      GA: "Stored-value transactions (SG).",
      CM: "Your country's wallet txns.",
      AM: "Your area's wallet txns.",
      OM: "Your outlet's wallet txns.",
    };

    // ── FilterBar config (v2.2 API) ──
    const primaryFilter = {
      key: "txn_type",
      label: "Type",
      options: TXN_TYPE_OPTS,
    };

    const advancedFilters = [
      { key: "status", kind: "select", label: "Status", options: STATUS_OPTS },
      ...(role !== "OM" ? [{
        key: "geo", kind: "geoscope", label: "Network scope", role,
        countries: (typeof SB2_COUNTRIES !== "undefined" ? SB2_COUNTRIES : [])
          .filter((c) => walletCountries.includes(c.country_id))
          .map((c) => ({ value: c.country_id, label: c.flag + " " + c.country_name })),
        areas:    sgAreas.map((a) => ({ value: a.id, label: a.name, country_id: a.country_id })),
        outlets:  sgOutlets.map((o) => ({ value: o.id, label: o.location, area_id: o.area_id })),
      }] : []),
      // Date + time-of-day is ONE DateTimeFilter (single source of truth, the `dtf` state).
      // It is surfaced BOTH inline (dateTimeFilter prop) AND in the advanced drawer (this entry);
      // both edit the same `dtf` state, so a change in either place updates the other.
      { key: "dtf", kind: "datetime", label: "Date & time", country: filterTz, todOptions },
    ];

    const advancedValues = {
      status: statusFilter,
      dtf,
      ...(role !== "OM" ? { geo } : {}),
    };

    const onAdvancedChange = (k, v) => {
      if (k === "geo")         { setGeo(v);           setPage(1); }
      else if (k === "status") { setStatusFilter(v);  setPage(1); }
      else if (k === "dtf")    { setDtf(v);           setPage(1); }
    };

    const _dtfDefault = { mode: "date", date: null, range: { from: "", to: "" }, time: { full: true } };

    const onAdvancedReset = () => {
      setStatusFilter("");
      setGeo({ countries: [], areas: [], outlets: [] });
      setDtf(_dtfDefault);
      setPage(1);
    };

    const onReset = () => {
      setSearch("");
      setTxnFilter("");
      setStatusFilter("");
      setDtf(_dtfDefault);
      setGeo({ countries: [], areas: [], outlets: [] });
      setPage(1);
    };

    const txn = selected;

    return (
      <div className="page-inner">
        <PageHead
          title="Wallet"
          description={descriptions[role] || "Stored-value transactions."}
          actions={<ExportButton role={role}/>}
        />

        {/* B1: GA-only InlineAlert — SG only flag */}
        {role === "GA" && (
          <InlineAlert kind="info">
            Wallet is available in Singapore only.
          </InlineAlert>
        )}

        {/* FilterBar — drives both stats AND table (date+tod shared predicate) */}
        <FilterBar
          search={search}
          onSearch={(q) => { setSearch(q); setPage(1); }}
          searchPlaceholder="Search wallet transactions…"
          primaryFilter={primaryFilter}
          primaryValue={txnFilter}
          onPrimaryChange={(v) => { setTxnFilter(v); setPage(1); }}
          dateTimeFilter={{
            value: dtf,
            onChange: (v) => { setDtf(v); setPage(1); },
            country: filterTz,
            todOptions: todOptions,
          }}
          advancedFilters={advancedFilters}
          advancedValues={advancedValues}
          onAdvancedChange={onAdvancedChange}
          onAdvancedReset={onAdvancedReset}
          onReset={onReset}
        />

        {/* Snapshot StatPanel (no state/onChange per B2) — BELOW FilterBar */}
        <StatPanel cols={2}>
          <StatCard
            label="Top-ups"
            value={`S$ ${stats.topUps.toLocaleString("en-SG", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`}
            sub={statSub}
          />
          <StatCard
            label="Spend"
            value={`S$ ${stats.spend.toLocaleString("en-SG", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`}
            sub={statSub}
          />
        </StatPanel>

        <Table
          dense
          columns={columns}
          rows={visibleRows}
          emptyText="No wallet transactions match your filters."
          onRow={(r) => {
            setSelected(r);
            setDrawerOpen(true);
          }}
        />

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

        {txn && (
          <DetailDrawer
            open={drawerOpen}
            onClose={() => setDrawerOpen(false)}
            title={txn.id}
            subtitle={txn.txn_type ? txn.txn_type.replace(/_/g, " ") : ""}
            width={700}
            actions={[
              <Btn key="close" variant="secondary" onClick={() => setDrawerOpen(false)}>Close</Btn>,
            ]}
          >
            {/* Transaction (Money) */}
            <FormSection title="General">
              <KeyValueGrid items={[
                { label: "ID",     value: txn.id },
                { label: "Type",   value: <StatusPill status={txn.txn_type} kind="wallet"/> },
                { label: "Amount", value: (() => {
                    const { text, color } = _walletAmount(txn);
                    return <span style={{ ..._MONO, color, fontWeight: 700 }}>{text}</span>;
                  })() },
                { label: "Status", value: <StatusPill status={txn.status} kind="wallet"/> },
                { label: "Date",   value: _fmtDateTime(txn.created_at, "SG") },
              ]}/>
            </FormSection>

            {/* Customer (Identity) — PII per role */}
            <FormSection title="Customer">
              <KeyValueGrid items={[
                { label: "Customer ID", value: txn.customer ? txn.customer.id : "—" },
                { label: "Name",        value: txn.customer ? txn.customer.name : "—" },
                ...(role !== "OM" ? [
                  { label: "Email", value: txn.customer && txn.customer.email
                      ? _emailLink(txn.customer.email)
                      : "—" },
                  { label: "Phone", value: txn.customer && txn.customer.phone
                      ? _phoneLink(txn.customer.phone)
                      : "—" },
                ] : []),
              ]}/>
            </FormSection>

            {/* Context (Order & payment) */}
            <FormSection title="Context">
              <KeyValueGrid items={[
                { label: "Order", value: txn.order_id
                    ? <a className="ghost-link" onClick={() => _go("ga-order-detail", { id: txn.order_id })} style={{ cursor: "pointer" }}>{txn.order_id}</a>
                    : <span style={T.muted}>—</span> },
                ...(role !== "OM" ? [
                  { label: "Payment method", value: txn.payment_method || <span style={T.muted}>—</span> },
                ] : []),
                { label: "Country", value: txn.country_id || "—" },
                { label: "Outlet",  value: _outletLabel(txn.outlet_id) },
              ]}/>
            </FormSection>

            {/* Remarks (Operator notes) — append-only */}
            <WalletTxnRemarks txnId={txn.id} seed={txn.wallet_remarks}/>
          </DetailDrawer>
        )}
      </div>
    );
  }

  Object.assign(window, { WalletLedger });
})();
