/* page-outlets.jsx — extracted verbatim from pages.jsx. IIFE-scoped; exports via window. */
(function(){
const { useState, useMemo, useEffect, useRef } = React;

// ── Module-local helpers (shared by both detail Hours tabs + Settings/Integration) ──

const _OUTLET_DAY_LABELS = [
  ["mon", "Mon"], ["tue", "Tue"], ["wed", "Wed"], ["thu", "Thu"],
  ["fri", "Fri"], ["sat", "Sat"], ["sun", "Sun"],
];
const _OUTLET_DEFAULT_HOURS = {
  mon: { shifts: [{ open: "08:00", close: "22:00" }], closed: false },
  tue: { shifts: [{ open: "08:00", close: "22:00" }], closed: false },
  wed: { shifts: [{ open: "08:00", close: "22:00" }], closed: false },
  thu: { shifts: [{ open: "08:00", close: "22:00" }], closed: false },
  fri: { shifts: [{ open: "08:00", close: "23:00" }], closed: false },
  sat: { shifts: [{ open: "09:00", close: "23:00" }], closed: false },
  sun: { shifts: [{ open: "09:00", close: "21:00" }], closed: false },
};
const _fmtTargetAmount = (amount, ccy) =>
  `${ccy} ${Number(amount || 0).toLocaleString()}`;
const _fmtYearMonth = (ym) => {
  const [y, m] = String(ym).split("-");
  const MON = ["", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
  return `${MON[parseInt(m, 10)] || m} ${y}`;
};

/**
 * OutletHoursEditor — per-day shift editor + special-hours editor.
 * editable=false renders a read-only KeyValueGrid summary (detail tabs, read scope).
 * editable=true renders the full per-day shift + special-hours editor (GA / CM / AM).
 * Module-local (not a shared component) — consumed by both detail Hours tabs.
 */
function OutletHoursEditor({ editable, hours, onHoursChange, specialHours = [], onSpecialChange }) {
  const h = hours || _OUTLET_DEFAULT_HOURS;

  if (!editable) {
    const summary = _OUTLET_DAY_LABELS.map(([key, label]) => {
      const day = h[key] || {};
      const value = day.closed
        ? "Closed"
        : (day.shifts || []).map(s => `${s.open} – ${s.close}`).join(", ");
      return { label, value };
    });
    return (
      <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
        <KeyValueGrid columns={2} items={summary}/>
        {specialHours.length > 0 && (
          <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
            <div style={{ ...T.label }}>Special hours</div>
            <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
              {specialHours.map(s => (
                <div key={s.date} className="row ai-c" style={{ gap: 12 }}>
                  <span style={{ ...T.mono, fontSize: 12, color: "var(--forest)", width: 100 }}>{s.date}</span>
                  <span style={{ ...T.primary(1), flex: 1 }}>{s.label}</span>
                  <span style={{ ...T_MUTED }}>{s.closed_all_day ? "Closed all day" : `${s.start_time}–${s.end_time}`}</span>
                </div>
              ))}
            </div>
          </div>
        )}
      </div>
    );
  }

  const setDay = (key, next) => onHoursChange && onHoursChange({ ...h, [key]: next });
  const addShift = (key) => {
    const day = h[key] || { shifts: [], closed: false };
    setDay(key, { ...day, shifts: [...(day.shifts || []), { open: "12:00", close: "17:00" }] });
  };
  const removeShift = (key, idx) => {
    const day = h[key];
    setDay(key, { ...day, shifts: day.shifts.filter((_, i) => i !== idx) });
  };
  const setShiftField = (key, idx, field, value) => {
    const day = h[key];
    setDay(key, { ...day, shifts: day.shifts.map((s, i) => i === idx ? { ...s, [field]: value } : s) });
  };
  const setDayClosed = (key, closed) => setDay(key, { ...(h[key] || { shifts: [{ open: "08:00", close: "22:00" }] }), closed });

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
      <div style={{ display: "flex", flexDirection: "column", gap: 10, border: "1.5px solid var(--line)", borderRadius: "var(--r)", padding: 12 }}>
        {_OUTLET_DAY_LABELS.map(([key, label]) => {
          const day = h[key] || { shifts: [{ open: "08:00", close: "22:00" }], closed: false };
          const shifts = day.shifts || [{ open: "08:00", close: "22:00" }];
          return (
            <div key={key} style={{ display: "flex", flexDirection: "column", gap: 6 }}>
              {day.closed ? (
                <div className="row ai-c" style={{ gap: 8 }}>
                  <span style={{ width: 44, ...T.primary(1) }}>{label}</span>
                  <Toggle on={day.closed} onToggle={() => setDayClosed(key, false)} label="Closed"/>
                </div>
              ) : shifts.map((s, idx) => (
                <div key={idx} className="row ai-c" style={{ gap: 8 }}>
                  <span style={{ width: 44, ...T.primary(1) }}>{idx === 0 ? label : ""}</span>
                  <div style={{ width: 110 }}><Input type="time" value={s.open} onChange={v => setShiftField(key, idx, "open", v)}/></div>
                  <span style={{ color: "var(--ink-2)" }}>–</span>
                  <div style={{ width: 110 }}><Input type="time" value={s.close} onChange={v => setShiftField(key, idx, "close", v)}/></div>
                  {idx === 0 && <Toggle on={false} onToggle={() => setDayClosed(key, true)} label="Closed"/>}
                  {shifts.length > 1 && (
                    <button onClick={() => removeShift(key, idx)} aria-label="Remove shift" style={{ color: "var(--ink-2)", background: "none", border: "none", cursor: "pointer", padding: "2px 4px", display: "inline-flex" }}><LIcon name="X" size={16}/></button>
                  )}
                </div>
              ))}
              {!day.closed && shifts.length < 3 && (
                <div style={{ paddingLeft: 52 }}>
                  <button onClick={() => addShift(key)} className="row ai-c" style={{ gap: 4, font: "600 12px/1 var(--font-ui)", color: "var(--forest)", background: "none", border: "none", cursor: "pointer", padding: "2px 0" }}>
                    <LIcon name="Plus" size={14}/> Add time range
                  </button>
                </div>
              )}
            </div>
          );
        })}
      </div>

      <Field label="Special hours" hint="Public holidays, seasonal closures, or one-off exceptions.">
        <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
          {specialHours.map(s => (
            <div key={s.id} style={{ display: "flex", flexDirection: "column", gap: 6, padding: 10, border: "1.5px solid var(--line)", borderRadius: "var(--r)", background: "var(--surface-2)" }}>
              <div className="row ai-c" style={{ gap: 8 }}>
                <div style={{ width: 150 }}><DateField value={s.date} onChange={v => onSpecialChange("update", s.id, "date", v)}/></div>
                <div className="grow"><Input value={s.label} onChange={v => onSpecialChange("update", s.id, "label", v)} placeholder="e.g. National Day"/></div>
                <Toggle on={s.allDay} onToggle={() => onSpecialChange("update", s.id, "allDay", !s.allDay)} label="Closed all day"/>
                <button onClick={() => onSpecialChange("remove", s.id)} aria-label="Remove" style={{ color: "var(--ink-2)", background: "none", border: "none", cursor: "pointer", display: "inline-flex", padding: "2px 4px" }}><LIcon name="X" size={16}/></button>
              </div>
              {!s.allDay && (
                <div className="row ai-c" style={{ gap: 8, paddingLeft: 158 }}>
                  <div style={{ width: 120 }}><Input type="time" value={s.open} onChange={v => onSpecialChange("update", s.id, "open", v)}/></div>
                  <span style={{ color: "var(--ink-2)" }}>–</span>
                  <div style={{ width: 120 }}><Input type="time" value={s.close} onChange={v => onSpecialChange("update", s.id, "close", v)}/></div>
                </div>
              )}
            </div>
          ))}
          <div><Btn variant="ghost" size="sm" onClick={() => onSpecialChange("add")}>Add exception</Btn></div>
        </div>
      </Field>

      <InlineAlert kind="info">
        Hours sync to the consumer app within 60s of being saved. Out-of-hours orders are blocked server-side.
      </InlineAlert>
    </div>
  );
}

/**
 * OutletTargetsSection — per-month sales targets + monthly bonus target.
 * canEditSales: GA + AM edit outlet_sales_targets (Target column) + can "Add month"; CM + OM view-only.
 * canEditBonus: GA sets monthly_bonus_target; all others view-only.
 * showActual: renders the "Actual" + "vs Target" columns (detail + edit form).
 * actualEditable: Actual input editable — EDIT form only (GA+AM). On the detail it is READ-ONLY
 *   for every role incl OM — OM is fully read-only on the outlet (03-ui §2 Table 2, corrected 2026-06-02).
 *   Actual is NOT shown in Create (a new outlet has no actuals). (Authority per 05-logic BR4.)
 */
function OutletTargetsSection({ outlet, canEditSales, canEditBonus, showActual = false, actualEditable = false }) {
  const [targets, setTargets] = useState(
    (outlet.outlet_sales_targets || []).map(t => ({ ...t }))
  );
  const [bonus, setBonus] = useState(outlet.monthly_bonus_target || 0);
  const ccy = (targets[0] && targets[0].currency_code) || "SGD";
  const setTarget = (ym, val) => setTargets(ts => ts.map(t => t.year_month === ym ? { ...t, target_amount: val === "" ? 0 : Number(val) } : t));
  const setActual = (ym, val) => setTargets(ts => ts.map(t => t.year_month === ym ? { ...t, actual_amount: val === "" ? null : Number(val) } : t));
  // Append a new editable month row (default = month after the last, or current). canEditSales-gated.
  const addMonth = () => setTargets(ts => {
    const last = ts.length ? ts[ts.length - 1].year_month : null;
    let nextYm;
    if (last) {
      let [y, m] = String(last).split("-").map(Number);
      m += 1; if (m > 12) { m = 1; y += 1; }
      nextYm = `${y}-${String(m).padStart(2, "0")}`;
    } else {
      const d = new Date();
      nextYm = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
    }
    return [...ts, { year_month: nextYm, target_amount: 0, actual_amount: null, currency_code: ccy }];
  });
  const setMonth = (oldYm, val) => setTargets(ts => ts.map(t => t.year_month === oldYm ? { ...t, year_month: val } : t));

  return (
    <FormSection title="Targets" description="Monthly revenue goals used for incentive tracking.">
      <Field label="Monthly bonus target" hint={canEditBonus ? "Set by Global Admin. Visible to all roles." : "Set by Global Admin."}>
        {canEditBonus
          ? <div style={{ width: 220 }}><Input type="number" value={String(bonus)} onChange={v => setBonus(v === "" ? 0 : Number(v))}/></div>
          : <div style={{ ...T.amount }}>{_fmtTargetAmount(bonus, ccy)}</div>}
      </Field>
      <Field label="Sales targets" hint={canEditSales ? "Editable per month — use Add month to extend the plan." : showActual ? (actualEditable ? "Record this outlet's Actual per month." : "Read-only — Target & Actual are set in the Edit form.") : "Set by Global Admin / Area Manager."}>
        <div className="table">
          <div className="table-scroll">
            <table>
              <thead>
                <tr>
                  <th style={{ textAlign: "left" }}>Month</th>
                  <th style={{ textAlign: "left" }}>Target</th>
                  {showActual && <th style={{ textAlign: "left" }}>Actual</th>}
                  {showActual && <th style={{ textAlign: "left" }}>vs Target</th>}
                </tr>
              </thead>
              <tbody>
                {targets.map(t => {
                  const actual = t.actual_amount;
                  const variance = (actual != null && t.target_amount) ? actual - t.target_amount : null;
                  return (
                  <tr key={t.year_month}>
                    <td>
                      {canEditSales
                        ? <div style={{ width: 130 }}><Input type="month" value={t.year_month} onChange={v => setMonth(t.year_month, v)}/></div>
                        : <span style={{ ...T.primary(1) }}>{_fmtYearMonth(t.year_month)}</span>}
                    </td>
                    <td>
                      {canEditSales
                        ? <div style={{ width: 200 }}><Input type="number" value={String(t.target_amount)} onChange={v => setTarget(t.year_month, v)}/></div>
                        : <span style={{ ...T.amount }}>{_fmtTargetAmount(t.target_amount, t.currency_code)}</span>}
                    </td>
                    {showActual && (
                      <td>
                        {actualEditable
                          ? <div style={{ width: 200 }}><Input type="number" value={actual != null ? String(actual) : ""} onChange={v => setActual(t.year_month, v)} placeholder="Record actual…"/></div>
                          : (actual != null ? <span style={{ ...T.amount }}>{_fmtTargetAmount(actual, t.currency_code)}</span> : <span style={{ ...T_MUTED }}>—</span>)}
                      </td>
                    )}
                    {showActual && (
                      <td>
                        {variance == null
                          ? <span style={{ ...T_MUTED }}>—</span>
                          : <span style={{ ...T.amount, color: variance >= 0 ? "var(--forest)" : "var(--berry)" }}>{variance >= 0 ? "+" : "−"}{_fmtTargetAmount(Math.abs(variance), t.currency_code)}</span>}
                      </td>
                    )}
                  </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        </div>
        {canEditSales && (
          <div style={{ marginTop: 10 }}>
            <Btn variant="ghost" size="sm" onClick={addMonth}><LIcon name="Plus" size={14}/> Add month</Btn>
          </div>
        )}
      </Field>
    </FormSection>
  );
}

// Filter SB3_OUTLETS rows by a GeoScope value (most-specific non-empty level wins).
function _outletMatchesGeo(o, geo) {
  if (!geo) return true;
  const { countries = [], areas = [], outlets = [] } = geo;
  if (outlets.length)  return outlets.includes(o.id);
  if (areas.length)    return areas.includes(o.area_id);
  if (countries.length) return countries.includes(o.country_id);
  return true;
}
/**
 * @spec GA-040 — Outlets list (global)
 * US-020/021/022. Cascading filters (country → franchisee → area → status).
 */
function OutletsGlobalList({ role = "GA" }) {
  // Deep-link contract (target side): incoming filter.franchise_id seeds the franchisee filter + removable chip.
  const incomingFilter = typeof window !== "undefined" && window.__sixhands_route_payload
    ? window.__sixhands_route_payload.filter : null;
  const ctxFr = incomingFilter && incomingFilter.franchise_id ? incomingFilter.franchise_id : "";
  const [status, setStatus] = useState("");
  const [geo, setGeo] = useState({ countries: [], areas: [], outlets: [] });
  const [franchisee, setFranchisee] = useState(ctxFr);
  const [ctxChip, setCtxChip] = useState(ctxFr ? { label: incomingFilter.label } : null);
  const [searchQuery, setSearchQuery] = useState("");
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(20);
  if (role !== "GA") return _notAuthorised;
  const clearCtx = () => { setCtxChip(null); setFranchisee(""); };

  // GeoScope option arrays — mapped to the {value,label,country_id,area_id} shape the picker reads.
  const geoCountries = SB2_COUNTRIES.map(c => ({ value: c.country_id, label: `${c.flag} ${c.country_name}` }));
  const geoAreas     = SB3_AREAS.map(a => ({ value: a.id, label: a.name, country_id: a.country_id }));
  const geoOutlets   = SB3_OUTLETS.map(o => ({ value: o.id, label: o.name, area_id: o.area_id, country_id: o.country_id }));

  const q = searchQuery.trim().toLowerCase();
  const rows = SB3_OUTLETS
    .filter(o => _outletMatchesGeo(o, geo))
    .filter(o => !franchisee || o.franchise_id === franchisee)
    .filter(o => !status     || o.status === status)
    .filter(o => !q          || o.name.toLowerCase().includes(q) || (o.address || "").toLowerCase().includes(q));
  const totalPages = Math.max(1, Math.ceil(rows.length / pageSize));
  const visibleRows = rows.slice((page - 1) * pageSize, page * pageSize);

  const resetAll = () => { setStatus(""); setGeo({ countries: [], areas: [], outlets: [] }); setFranchisee(""); setSearchQuery(""); setCtxChip(null); };

  return (
    <div className="page-inner">
      <PageHead title="Outlets" description="All outlets in the network."
        actions={
          <div className="row" style={{ gap: 8 }}>
            <Btn variant="primary" onClick={() => _go("ga-outlet-edit")}>New outlet</Btn>
          </div>
        }/>

      <FilterBar
        primaryFilter={{ key: "status", label: "Status", options: [
          { value: "", label: "All statuses" },
          ...["draft","active","inactive"].map(s => ({ value: s, label: s })),
        ]}}
        primaryValue={status}
        onPrimaryChange={setStatus}
        advancedFilters={[
          { key: "geo", kind: "geoscope", label: "Network scope", role: "GA", countries: geoCountries, areas: geoAreas, outlets: geoOutlets },
          { key: "franchisee", kind: "select", label: "Franchisee", options: [{ value: "", label: "All franchisees" }, ...SB3_FRANCHISEES.map(f => ({ value: f.id, label: f.display_name }))] },
        ]}
        advancedValues={{ geo, franchisee }}
        onAdvancedChange={(k, v) => { if (k === "geo") setGeo(v); else if (k === "franchisee") { setFranchisee(v); if (!v) setCtxChip(null); } }}
        onAdvancedReset={() => { setGeo({ countries: [], areas: [], outlets: [] }); setFranchisee(""); setCtxChip(null); }}
        onReset={resetAll}
        search={searchQuery}
        onSearch={setSearchQuery}
        searchPlaceholder="Search outlets…"
      />
      {ctxChip && (
        <div style={{ marginBottom: 12 }}>
          <span className="chip on">
            Franchisee: {ctxChip.label}
            <button onClick={clearCtx} aria-label="Clear context filter"
              style={{ display: "inline-flex", alignItems: "center", background: "none", border: "none", padding: 0, marginLeft: 2, cursor: "pointer", color: "inherit" }}>
              <LIcon name="X" size={14}/>
            </button>
          </span>
        </div>
      )}

      <Table
        emptyText="No outlets match these filters."
        columns={[
          { label: "Country",    width: 130, sortable: true, sortKey: "country_id", render: r => <span style={{ ...T.primary(1) }}>{_countryLabel(r.country_id)}</span> },
          { label: "Franchisee", width: 150, sortable: true, sortKey: "franchise_id", render: r => <span style={{ ...T_MUTED }}>{_franchiseeLabel(r.franchise_id)}</span> },
          { label: "Area",       width: 130, sortable: true, sortKey: "area_id", render: r => <span style={{ ...T_MUTED }}>{_areaLabel(r.area_id)}</span> },
          { label: "Outlet", sortable: true, sortKey: "name", render: r => (
            <div style={{ display: "flex", flexDirection: "column", gap: 2, minWidth: 0 }}>
              <span style={{ ...T.primary() }}>{r.name}</span>
              <span style={{ ...T_MUTED, fontSize: 12 }}>{r.address}</span>
            </div>
          ) },
          { label: "Status",   width: 120, sortable: true, sortKey: "status", render: r => <StatusPill status={r.status} kind="outlet"/> },
          { label: "POS sync", width: 110, sortable: true, sortKey: "pos_sync", render: r => <StatusPill status={r.pos_sync} kind="sync"/> },
          { label: "Last active", width: 150, sortable: true, sortKey: "last_activity", render: r => <span style={{ ...T_MUTED }}>{r.last_activity}</span> },
        ]}
        rows={visibleRows}
        onRow={r => _go("ga-outlet-detail", { id: r.id })}
      />

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

/**
 * @spec GA-041 / CM-021 / AM-022 — Outlet create / edit (role-parameterised)
 * @rbac outlet.create + outlet.edit → ["GA","CM","AM"] (02-roles-permissions.md §C lines 63–64)
 * US-020/023. Sections mirror the detail tabs (03-ui §2 Outlets Table 3): Network · Manager ·
 * Identity · Location · Integration (read-only reuse) · Hours · Targets · Status.
 * Cancel + Save at top AND bottom (convention 9).
 */
function OutletEdit({ role = "GA" }) {
  if (!_hasRbac(role, "outlet.edit")) return _notAuthorised;
  const existing = typeof window !== "undefined" && window.__sixhands_route_payload
    ? SB3_OUTLET_BY_ID[window.__sixhands_route_payload.id] || null
    : null;
  const isNew = !existing;
  const canEditSalesTargets = ["GA", "AM"].includes(role);   // 05-logic BR4
  const canEditBonusTarget  = role === "GA";

  const [form, setForm] = useState(existing ? {
    ...existing,
    concept: existing.concept || "",
    location: existing.location || "",
    address_street: existing.street_address || "",
    address_unit: existing.unit_building || "",
    address_postal: existing.postal_code || "",
    outlet_size_id: existing.outlet_size_id || "",
    is_flagship: !!existing.is_flagship,
    latitude: existing.geolocation_lat != null ? existing.geolocation_lat : null,
    longitude: existing.geolocation_lng != null ? existing.geolocation_lng : null,
  } : {
    id: "", concept: "", location: "",
    address_street: "", address_unit: "", address_postal: "",
    outlet_size_id: "", is_flagship: false,
    latitude: null, longitude: null,
    country_id: "", franchise_id: "", area_id: "",
    pos_store_id: "", status: "draft",
  });
  // Hours / special-hours editable at create/edit (supersedes B-20). OM never reaches this form.
  const [hours, setHours] = useState(_OUTLET_DEFAULT_HOURS);
  const [specialHoursState, setSpecialHoursState] = useState(
    ((existing && existing.special_hours) || []).map((h, i) => ({ id: `sh_${i}`, date: h.date, label: h.label, allDay: !!h.closed_all_day, open: h.start_time || "00:00", close: h.end_time || "23:59" }))
  );
  const onSpecial = (op, id, field, value) => {
    if (op === "add") setSpecialHoursState(sh => [...sh, { id: `sh_${Date.now()}`, date: "", label: "", allDay: true, open: "00:00", close: "23:59" }]);
    else if (op === "remove") setSpecialHoursState(sh => sh.filter(h => h.id !== id));
    else setSpecialHoursState(sh => sh.map(h => h.id === id ? { ...h, [field]: value } : h));
  };
  const [errors, setErrors] = useState({});
  const [savedToast, setSavedToast] = useState(false);
  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));

  const franchiseeOptions = SB3_FRANCHISEES.filter(f => f.status !== "inactive" && (!form.country_id || f.country_id === form.country_id));
  const areaOptions = SB3_AREAS.filter(a => (!form.country_id || a.country_id === form.country_id) && (!form.franchise_id || a.franchise_id === form.franchise_id));

  // Integration — inherited from country (read-only via IntegrationBlock). Editable: pos_store_id only.
  const cid = form.country_id;
  const intStatus = cid ? _getIntegrationStatus(cid) : {};
  const ctry = cid ? (SB2_COUNTRY_BY_ID[cid] || {}) : {};
  const posContext = { currency_code: ctry.currency_code, tax_type: ctry.tax_type, tax_rate: ctry.tax_rate, service_charge_enabled: ctry.service_charge_enabled, service_charge_rate: ctry.service_charge_rate };

  // Eligible OM users for edit-mode manager select (matching role_type, in country scope).
  const eligibleOMs = SB3_USERS.filter(u => u.role_type === "outlet_manager" && (!cid || u.country_id === cid)).map(u => ({ id: u.id, full_name: u.full_name, email: u.email, phone: u.phone }));
  const [savedToastMgr, setSavedToastMgr] = useState(null);

  const onChangeField = (k, v) => setForm(f => {
    const next = { ...f, [k]: v };
    if (k === "country_id")    { next.franchise_id = ""; next.area_id = ""; }
    if (k === "franchise_id")  { next.area_id = ""; }
    return next;
  });

  const validate = () => {
    const e = {};
    if (!form.concept) e.concept = "Concept is required.";
    if (!form.location) e.location = "Location is required.";
    if (!form.address_street) e.address_street = "Street address is required.";
    if (!form.address_postal) e.address_postal = "Postal code is required.";
    if (isNew && !form.country_id) e.country_id = "Country is required.";
    if (isNew && !form.franchise_id) e.franchise_id = "Franchisee is required.";
    if (!form.pos_store_id && !isNew) e.pos_store_id = "POS store ID is required once the outlet is active.";
    setErrors(e); return Object.keys(e).length === 0;
  };

  const detailRoute = role === "CM" ? "cm-outlet-detail" : role === "AM" ? "am-outlet-detail" : "ga-outlet-detail";
  // Save → redirect to the outlet detail (create AND edit), mirroring the Country wizard → CountryDetail
  // redirect. Edit keeps the existing id; create uses the form id (detail falls back gracefully).
  const save = () => {
    if (!validate()) return;
    setSavedToast(true);
    const detailId = existing ? existing.id : (form.id || "");
    setTimeout(() => _go(detailRoute, { id: detailId }), 1200);
  };
  const cancel = () => _go(role === "CM" ? "cm-outlets" : role === "AM" ? "am-outlets" : "ga-outlets");
  const backDetail = existing
    ? { label: "Outlet", onClick: () => _go(role === "CM" ? "cm-outlet-detail" : role === "AM" ? "am-outlet-detail" : "ga-outlet-detail", { id: existing.id }) }
    : undefined;

  const actionRow = (bottom) => (
    <div className={bottom ? "row jc-e" : "row"} style={{ gap: 8 }}>
      <Btn variant="secondary" onClick={cancel}>Cancel</Btn>
      <Btn variant="primary" icon={<span style={{ color: "var(--lime)" }}>{Ic.check(16)}</span>} onClick={save}>
        {isNew ? "Create outlet" : "Save changes"}
      </Btn>
    </div>
  );

  return (
    <div className="page-inner">
      <PageHead title={isNew ? "New outlet" : `${[form.concept, form.location].filter(Boolean).join(" · ")} · Outlet`}
        description={isNew ? "Register a new outlet in the network." : undefined}
        back={backDetail}
        actions={actionRow(false)}/>

      {/* Network (renamed from Location) — Create: editable cascade; Edit: read-only context. */}
      {_hasRbac(role, "outlet.edit.assignment") && isNew ? (
        <FormSection title="Network" description="Country > Franchisee > Area placement. Each step narrows the next.">
          <div className="row" style={{ gap: 16 }}>
            <div className="grow">
              <Field label="Country" required error={errors.country_id}>
                <Select value={form.country_id} onChange={v => onChangeField("country_id", v)} placeholder="Choose country">
                  {SB2_COUNTRIES.filter(c => c.status === "active" || c.status === "draft").map(c => <option key={c.country_id} value={c.country_id}>{c.flag} {c.country_name}</option>)}
                </Select>
              </Field>
            </div>
            <div className="grow">
              <Field label="Franchisee" required error={errors.franchise_id} hint={form.country_id ? undefined : "Choose country first."}>
                <Select value={form.franchise_id} onChange={v => onChangeField("franchise_id", v)} placeholder={form.country_id ? "Choose franchisee" : "(choose country first)"}>
                  {franchiseeOptions.map(f => <option key={f.id} value={f.id}>{f.display_name}</option>)}
                </Select>
              </Field>
            </div>
            <div className="grow">
              <Field label="Area" hint="Optional — leave empty for countries with no Area Manager layer.">
                <Select value={form.area_id} onChange={v => set("area_id", v)} placeholder={form.franchise_id ? "Choose area (optional)" : "(choose franchisee first)"}>
                  <option value="">— None —</option>
                  {areaOptions.map(a => <option key={a.id} value={a.id}>{a.name}</option>)}
                </Select>
              </Field>
            </div>
          </div>
        </FormSection>
      ) : (
        <FormSection title="Network" description="Network placement is fixed once the outlet is created.">
          <KeyValueGrid columns={3} items={[
            { label: "Country",    value: form.country_id ? _countryLabel(form.country_id) : "—" },
            { label: "Franchisee", value: form.franchise_id ? _franchiseeLabel(form.franchise_id) : "—" },
            { label: "Area",       value: form.area_id ? _areaLabel(form.area_id) : "—" },
          ]}/>
        </FormSection>
      )}

      {/* Manager — create-inline (name+email, skippable) vs edit-select-existing (convention 11). */}
      <FormSection title="Outlet Manager" description={isNew ? "Choose an existing user or quick-add one — or assign later." : "Reassign to an existing eligible user."}>
        <ManagerSection
          mode={isNew ? "create" : "edit"}
          roleType="Outlet Manager"
          eligibleUsers={eligibleOMs}
          onCreateInline={({ name, email }) => { setSavedToastMgr(`Manager added · ${name || email}`); setTimeout(() => setSavedToastMgr(null), 2400); }}
          onAssignExisting={(id) => { const u = SB3_USERS.find(x => x.id === id); setSavedToastMgr(`Assigned · ${u ? u.full_name : id}`); setTimeout(() => setSavedToastMgr(null), 2400); }}
        />
      </FormSection>

      <FormSection title="Identity" description="Name shown to consumers + the size band.">
        <div className="row" style={{ gap: 16 }}>
          <div className="grow">
            <Field label="Concept" required error={errors.concept} hint="Brand concept (e.g. Sixhands).">
              <Input value={form.concept} onChange={v => set("concept", v)} placeholder="e.g. Sixhands"/>
            </Field>
          </div>
          <div className="grow">
            <Field label="Location / outlet name" required error={errors.location} hint="Mall or area (e.g. BGC High Street).">
              <Input value={form.location} onChange={v => set("location", v)} placeholder="e.g. BGC High Street"/>
            </Field>
          </div>
        </div>
        <Field label="Outlet size" hint="Floor area band — used for ops planning.">
          <Select value={form.outlet_size_id || ""} onChange={v => set("outlet_size_id", v)} placeholder="Choose size band">
            {SB3_OUTLET_SIZE_OPTIONS.map(s => <option key={s.value} value={s.value}>{s.label}</option>)}
          </Select>
        </Field>
        {/* Flagship toggle directly beneath outlet size (fixes the misaligned placement). GA-only. */}
        {_hasRbac(role, "outlet.edit.assignment") && (
          <Toggle on={!!form.is_flagship} onToggle={() => set("is_flagship", !form.is_flagship)} label="Flagship outlet"/>
        )}
      </FormSection>

      <FormSection title="Location" description="Address + map coordinates for directions and dispatch.">
        <Field label="Street" required error={errors.address_street}>
          <Input value={form.address_street} onChange={v => set("address_street", v)} placeholder="e.g. 290 Orchard Road"/>
        </Field>
        <div className="row" style={{ gap: 16 }}>
          <div className="grow">
            <Field label="Unit / Building" hint="Optional (e.g. #B1-01, Paragon Shopping Centre)">
              <Input value={form.address_unit} onChange={v => set("address_unit", v)} placeholder="e.g. #B1-01"/>
            </Field>
          </div>
          <div style={{ width: 160 }}>
            <Field label="Postal code" required error={errors.address_postal}>
              <Input value={form.address_postal} onChange={v => set("address_postal", v)} placeholder="e.g. 238859"/>
            </Field>
          </div>
        </div>
        <div className="row" style={{ gap: 16 }}>
          <div className="grow">
            <Field label="Latitude" hint="Decimal degrees. Auto from postal — editable.">
              <Input type="number" value={form.latitude != null ? String(form.latitude) : ""} onChange={v => set("latitude", v === "" ? null : parseFloat(v))} placeholder="e.g. 14.55340"/>
            </Field>
          </div>
          <div className="grow">
            <Field label="Longitude">
              <Input type="number" value={form.longitude != null ? String(form.longitude) : ""} onChange={v => set("longitude", v === "" ? null : parseFloat(v))} placeholder="e.g. 121.04550"/>
            </Field>
          </div>
        </div>
      </FormSection>

      {/* Integration — read-only (inherited from country) reusing IntegrationBlock; editable POS store id. */}
      <FormSection title="Integration" description="POS + payment gateway are inherited from the country. Only this outlet's POS store id is editable.">
        <IntegrationBlock
          intStatus={intStatus}
          posContext={posContext}
          posStoreIdField={
            <Field label="POS store id" required={!isNew && _hasRbac(role, "outlet.edit.pos")} error={_hasRbac(role, "outlet.edit.pos") ? errors.pos_store_id : undefined} hint={_hasRbac(role, "outlet.edit.pos") ? "The identifier the POS uses for this outlet. Issued by DX." : "Issued by DX. Contact Global Admin to update."}>
              <Input value={form.pos_store_id || ""} onChange={v => set("pos_store_id", v)} placeholder="e.g. AP-PH-005" disabled={!_hasRbac(role, "outlet.edit.pos")}/>
            </Field>
          }
        />
      </FormSection>

      {/* Hours — editable at create/edit (supersedes B-20). OM never reaches this form. */}
      <FormSection title="Operating hours" description="Weekly hours and one-off exceptions, set at creation.">
        <OutletHoursEditor editable hours={hours} onHoursChange={setHours} specialHours={specialHoursState} onSpecialChange={onSpecial}/>
      </FormSection>

      {/* Targets — editable (supersedes B-20). Sales: GA+AM write; CM read. Bonus: GA set.
          Actual: editable in EDIT mode only (not create — a new outlet has no actuals). */}
      <OutletTargetsSection outlet={existing || { outlet_sales_targets: [], monthly_bonus_target: 0 }} canEditSales={canEditSalesTargets} canEditBonus={canEditBonusTarget} showActual={!isNew} actualEditable={!isNew && canEditSalesTargets}/>

      <FormSection title="Status" description="Draft outlets are being set up. Active outlets are live + orderable; inactive outlets are paused. An outlet can go active only when its country is active; once live it toggles active ⇄ inactive and cannot return to draft.">
        <Field label="Status">
          {(() => {
            const _cActive = (SB2_COUNTRY_BY_ID[form.country_id] || {}).status === "active";
            const st = form.status || "draft";
            return (
              <Select value={st} onChange={v => set("status", v)}>
                {st === "draft"
                  ? <>
                      <option value="draft">draft</option>
                      <option value="active" disabled={!_cActive}>active</option>
                    </>
                  : <>
                      <option value="active" disabled={!_cActive && st !== "active"}>active</option>
                      <option value="inactive">inactive</option>
                    </>}
              </Select>
            );
          })()}
        </Field>
        {(SB2_COUNTRY_BY_ID[form.country_id] || {}).status !== "active" && (form.status || "draft") !== "active" && (
          <InlineAlert kind="info">This outlet's country isn't active yet — the outlet can't go active until its country is live.</InlineAlert>
        )}
      </FormSection>

      {actionRow(true)}

      <Toast message="Saved · audit_logs updated" show={savedToast}/>
      <Toast message={savedToastMgr || ""} show={!!savedToastMgr}/>
    </div>
  );
}

/**
 * @spec GA-042 / CM-021 / AM-022 / OM-040 — Outlet detail (one role-branched component per entity)
 * 03-ui §2 Outlets Table 2. Full-page tabbed detail; per-role tab matrix:
 *   GA/CM = Overview(edit) · Performance · Hours(edit) · Integration(read) · Settings
 *   AM    = Overview(edit) · Performance · Hours(edit) · Integration(read)            (no Settings)
 *   OM    = Overview(read) ·              Hours(request) · Integration(read)           (no Performance, no Settings)
 * OM consolidates the former OMHours stub here (read-only "My Outlet"). Status → PageHead.
 */
function OutletDetail({ role = "GA", scope = {} }) {
  // OM has no list → resolve own outlet from scope; GA/CM/AM resolve from the row payload.
  const payloadId = typeof window !== "undefined" && window.__sixhands_route_payload ? window.__sixhands_route_payload.id : null;
  const omOutletId = scope.outletId || (SB3_USERS.find(u => u.role_type === "outlet_manager") || {}).outlet_ids?.[0];
  const resolvedId = role === "OM" ? (omOutletId || payloadId) : payloadId;
  const o = SB3_OUTLET_BY_ID[resolvedId] || SB3_OUTLET_BY_ID["SH-PH-001"];

  const [tab, setTab] = useState("overview");
  const [toast, setToast] = useState(null);
  // Editable hours / special-hours (GA/CM/AM edit directly; OM is request-only — read summary).
  const [hours, setHours] = useState(_OUTLET_DEFAULT_HOURS);
  const [specialHoursState, setSpecialHoursState] = useState(
    (o.special_hours || []).map((h, i) => ({ id: `sh_${i}`, date: h.date, label: h.label, allDay: !!h.closed_all_day, open: h.start_time || "00:00", close: h.end_time || "23:59" }))
  );
  const [hoursToast, setHoursToast] = useState(false);
  const [requestOpen, setRequestOpen] = useState(false);   // OM hours-change request flow
  const [requestReason, setRequestReason] = useState("");
  const [requestNote, setRequestNote] = useState("");
  // Gate AFTER all hooks (stable hook count across roles).
  if (!["GA", "CM", "AM", "OM"].includes(role)) return _notAuthorised;

  const onSpecial = (op, id, field, value) => {
    if (op === "add") setSpecialHoursState(sh => [...sh, { id: `sh_${Date.now()}`, date: "", label: "", allDay: true, open: "00:00", close: "23:59" }]);
    else if (op === "remove") setSpecialHoursState(sh => sh.filter(h => h.id !== id));
    else setSpecialHoursState(sh => sh.map(h => h.id === id ? { ...h, [field]: value } : h));
  };

  // ── Per-role wiring ──
  const isOM = role === "OM";
  const canEditHours = ["GA", "CM", "AM"].includes(role);
  const hasPerformance = ["GA", "CM", "AM"].includes(role);
  const hasSettings = ["GA", "CM"].includes(role);
  const rolePrefix = { GA: "ga", CM: "cm", AM: "am", OM: "om" }[role];
  const backRoute = { GA: "ga-outlets", CM: "cm-outlets", AM: "am-outlets" }[role];   // OM has no list
  const editRoute = { GA: "ga-outlet-edit", CM: "cm-outlet-edit", AM: "am-outlet-edit" }[role];
  const pageTitle = isOM ? "My Outlet" : `${o.name} · Outlet`;
  // OM pagehead description = the bare outlet name (e.g. "Greenbelt 5" — o.location, not the
  // concept-prefixed o.name). GA/CM/AM keep the operational description.
  const pageDesc  = isOM ? (o.location || o.name) : "Outlet configuration and operations.";

  const isLive = o.status === "active";
  const outletUserCount = SB3_USERS.filter(u => (u.outlet_ids || []).includes(o.id)).length;
  const omUser = SB3_USERS.find(u => u.role_type === "outlet_manager" && (u.outlet_ids || []).includes(o.id)) || null;

  // ── OVERVIEW: full read-only config (Identity split · Location+Lat/Long · Manager · Targets · Related). ──
  const overview = (
    <>
      <FormSection title="Identity">
        <KeyValueGrid columns={2} items={[
          { label: "Concept",            value: o.concept || "—" },
          { label: "Location / outlet name", value: o.location || "—" },
          { label: "Outlet id",          value: o.id, mono: true },
          { label: "Outlet size",        value: SB3_OUTLET_SIZE_LABEL[o.outlet_size_id] || "—" },
          { label: "Flagship",           value: o.is_flagship ? "Yes" : "No" },
          { label: "Country",            value: _countryLabel(o.country_id) },
          { label: "Franchisee",         value: _franchiseeLabel(o.franchise_id) },
          { label: "Area",               value: _areaLabel(o.area_id) },
        ]}/>
      </FormSection>

      <FormSection title="Location">
        <KeyValueGrid columns={2} items={[
          { label: "Address",     value: o.address },
          { label: "Postal code", value: o.postal_code || "—" },
          { label: "Latitude",    value: o.geolocation_lat != null ? String(o.geolocation_lat) : "—" },
          { label: "Longitude",   value: o.geolocation_lng != null ? String(o.geolocation_lng) : "—" },
        ]}/>
      </FormSection>

      {/* Outlet Manager — reverse lookup over SB3_USERS (outlet_ids). Read on detail; edit behind PageHead Edit. */}
      <FormSection title="Outlet Manager">
        <ManagerSection
          manager={omUser}
          mode="read"
          roleType="Outlet Manager"
          onAddCta={!isOM && editRoute ? () => _go(editRoute, { id: o.id }) : undefined}
        />
      </FormSection>

      {/* Targets — read-only Target inline (relocated from Settings; editing is behind PageHead Edit).
          Actual column is editable by ALL roles incl OM (field-level carve-out — the one place OM edits). */}
      <OutletTargetsSection outlet={o} canEditSales={false} canEditBonus={false} showActual/>

      {/* Related — shortcut deep-links (convention 3). Each pre-applies an outlet_id filter on the target list.
          OM orders route is om-orders-live (repointed to OrdersList — OMOrdersLive is folded in). OM Users card opens their OWN profile ("Me") — om-users → UserDetail. */}
      <FormSection title="Related">
        <StatPanel>
          <StatCard label="Orders" value={isLive ? String(o.orders_today) : "—"} sub="today · open this outlet's orders"
            onClick={() => _go(isOM ? "om-orders-live" : `${rolePrefix}-orders`, { filter: { outlet_id: o.id, label: o.name } })}/>
          <StatCard label="Users" value={isOM ? "1" : String(outletUserCount)} sub={isOM ? "your staff account · open profile" : "staff assigned to this outlet"}
            onClick={() => _go(isOM ? "om-users" : `${rolePrefix}-users`, isOM ? {} : { filter: { outlet_id: o.id, label: o.name } })}/>
        </StatPanel>
      </FormSection>
    </>
  );

  // ── PERFORMANCE: reuse the outlet-scoped dashboard (NOT a recreated StatPanel). page-dashboards consumes scope.outlet_id. ──
  const performance = <PerformanceDashboardContent scope={{ outlet_id: o.id }} role={role}/>;

  // ── HOURS: read-only on the detail for ALL roles (edit via the Edit form); OM = request flow. ──
  const hoursTab = canEditHours ? (
    <FormSection title="Operating hours" description="Read-only — use Edit to change weekly hours and one-off exceptions.">
      <OutletHoursEditor editable={false} hours={hours} specialHours={specialHoursState}/>
      {role === "AM" && (
        <InlineAlert kind="info">Pending Outlet-Manager hour-change requests appear in <button type="button" onClick={() => _go("am-settings")} style={{ background: "none", border: 0, color: "var(--forest)", textDecoration: "underline", cursor: "pointer", padding: 0, font: "inherit" }}>Area settings</button>.</InlineAlert>
      )}
    </FormSection>
  ) : (
    <>
      <FormSection title="Operating hours" description="Read-only. Submit a request below to change these.">
        <OutletHoursEditor editable={false} hours={hours} specialHours={specialHoursState}/>
      </FormSection>
      {!requestOpen ? (
        <Btn variant="secondary" onClick={() => setRequestOpen(true)}>Request update on operating hours</Btn>
      ) : (
        <FormSection title="Request a change" description="Your Area Manager will review and approve or reject this request.">
          <Field label="What would you like to change?" required>
            <Select value={requestReason} onChange={setRequestReason} placeholder="Select type">
              <option value="hours">Operating hours</option>
              <option value="special">Special / holiday hours</option>
            </Select>
          </Field>
          <Field label="Details" required>
            <Textarea value={requestNote} onChange={setRequestNote} placeholder="Describe the change needed and the reason (e.g. extended hours for promo weekend)." rows={3}/>
          </Field>
          <div className="row jc-e" style={{ gap: 10 }}>
            <Btn variant="secondary" onClick={() => { setRequestOpen(false); setRequestReason(""); setRequestNote(""); }}>Cancel</Btn>
            <Btn variant="primary" disabled={!requestReason || !requestNote} onClick={() => {
              setRequestOpen(false); setRequestReason(""); setRequestNote("");
              setToast("Request submitted · your AM has been notified");
              setTimeout(() => setToast(null), 3000);
            }}>Submit request</Btn>
          </div>
        </FormSection>
      )}
    </>
  );

  // ── INTEGRATION: read for ALL roles. Inherited from country via IntegrationBlock (convention 10). ──
  const ctry = SB2_COUNTRY_BY_ID[o.country_id] || {};
  const intStatus = _getIntegrationStatus(o.country_id);
  const posContext = { currency_code: ctry.currency_code, tax_type: ctry.tax_type, tax_rate: ctry.tax_rate, service_charge_enabled: ctry.service_charge_enabled, service_charge_rate: ctry.service_charge_rate };
  const integrationTab = (
    <FormSection title="Integration" description="POS + payment gateway, inherited from the country. Read-only here.">
      <IntegrationBlock
        intStatus={intStatus}
        posContext={posContext}
        posStoreIdField={
          <KeyValueGrid columns={1} items={[{ label: "POS store id", value: o.pos_store_id || "— (not issued) —", mono: true }]}/>
        }
      />
    </FormSection>
  );

  // ── SETTINGS: status via Edit; Delete here. GA/CM. ──
  const settingsTab = (
    <>
      <InlineAlert kind="info">
        Set this outlet's status (<strong>draft, active ⇄ inactive</strong>) from <strong>Edit</strong>. Pausing = set inactive; going live requires its country to be active.
      </InlineAlert>
      <DeleteSection
        entityLabel="outlet"
        soft={o.status !== "draft"}
        note={o.status === "draft"
          ? "Permanently delete this draft outlet. Nothing is live yet."
          : "Close + decommission this outlet (soft-delete). Removed from the consumer app; historical orders retained. GA-only."}
        gate={_canDelete(role, { status: o.status, isReferenced: false })}
        confirmBody={o.status === "draft"
          ? <div>Permanently delete the draft outlet <strong>{o.name}</strong>? Writes to <strong>audit_logs</strong>.</div>
          : <div>Closing <strong>{o.name}</strong> stops all orders and removes it from the consumer app. Historical orders are retained. Writes to <strong>audit_logs</strong>.</div>}
        onDelete={() => {
          setToast(`Logged to audit_logs · outlet ${o.id} ${o.status === "draft" ? "deleted" : "closed"}`);
          setTimeout(() => { setToast(null); backRoute && _go(backRoute); }, 1400);
        }}
      />
    </>
  );

  const tabs = [
    { id: "overview",    label: "Overview" },
    ...(hasPerformance ? [{ id: "performance", label: "Performance" }] : []),
    { id: "hours",       label: "Hours" },
    { id: "integration", label: "Integration" },
    ...(hasSettings ? [{ id: "settings", label: "Settings" }] : []),
  ];

  return (
    <div className="page-inner">
      <PageHead
        title={pageTitle}
        description={pageDesc}
        back={backRoute ? { label: "Outlets", onClick: () => _go(backRoute) } : undefined}
        status={<StatusPill status={o.status} kind="outlet"/>}
        actions={!isOM && editRoute ? <Btn variant="primary" onClick={() => _go(editRoute, { id: o.id })}>Edit</Btn> : undefined}
      />
      <Tabs tabs={tabs} active={tab} onChange={setTab}/>
      {tab === "overview"    && overview}
      {tab === "performance" && hasPerformance && performance}
      {tab === "hours"       && hoursTab}
      {tab === "integration" && integrationTab}
      {tab === "settings"    && hasSettings && settingsTab}

      <Toast message={toast || ""} show={!!toast}/>
      <Toast message="Hours saved · audit_logs updated" show={hoursToast}/>
    </div>
  );
}

// ── Outlet Manager (T3) ───────────────────────────────────────────────

/** @spec CM-020 / AM-020 */
function OutletsList({ role }) {
  if (!["CM","AM"].includes(role)) return _notAuthorised;
  // Deep-link contract (target side): incoming filter.franchise_id seeds the franchisee filter + removable chip.
  const incomingFilter = typeof window !== "undefined" && window.__sixhands_route_payload
    ? window.__sixhands_route_payload.filter : null;
  const ctxFr = incomingFilter && incomingFilter.franchise_id ? incomingFilter.franchise_id : "";
  const [status, setStatus] = useState("");
  const [geo, setGeo] = useState({ countries: [], areas: [], outlets: [] });
  const [franchisee, setFranchisee] = useState(ctxFr);
  const [ctxChip, setCtxChip] = useState(ctxFr ? { label: incomingFilter.label } : null);
  const [searchQuery, setSearchQuery] = useState("");
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(20);
  const clearCtx = () => { setCtxChip(null); setFranchisee(""); };
  // Scope to the role's own slice of the network.
  const scopedOutlets = role === "CM"
    ? SB3_OUTLETS.filter(o => o.country_id === "PH")
    : SB3_OUTLETS.filter(o => o.area_id === AM_AREA_ID);
  // GeoScope option arrays — scoped to the role (CM = country areas/outlets; AM = own-area outlets).
  const scopedAreas   = role === "CM" ? SB3_AREAS.filter(a => a.country_id === "PH") : SB3_AREAS.filter(a => a.id === AM_AREA_ID);
  const geoAreas      = scopedAreas.map(a => ({ value: a.id, label: a.name, country_id: a.country_id }));
  const geoOutlets    = scopedOutlets.map(o => ({ value: o.id, label: o.name, area_id: o.area_id, country_id: o.country_id }));
  const geoCountries  = role === "CM" ? [{ value: "PH", label: "🇵🇭 Philippines" }] : [];

  const q = searchQuery.trim().toLowerCase();
  const rows = scopedOutlets
    .filter(o => _outletMatchesGeo(o, geo))
    .filter(o => !franchisee || o.franchise_id === franchisee)
    .filter(o => !status     || o.status === status)
    .filter(o => !q          || o.name.toLowerCase().includes(q) || (o.address || "").toLowerCase().includes(q));
  const totalPages = Math.max(1, Math.ceil(rows.length / pageSize));
  const visibleRows = rows.slice((page - 1) * pageSize, page * pageSize);
  const detailRoute = role === "CM" ? "cm-outlet-detail" : "am-outlet-detail";
  const pageDescription = { CM: "Your country's outlets.", AM: "Your area's outlets." }[role];
  // Franchisee picker — CM only (an area has a single franchisee, so it adds nothing for AM).
  const cmFranchisees = role === "CM" ? SB3_FRANCHISEES.filter(f => f.country_id === "PH") : [];
  const advancedFilters = [
    { key: "geo", kind: "geoscope", label: "Network scope", role, countries: geoCountries, areas: geoAreas, outlets: geoOutlets },
    ...(role === "CM" ? [{ key: "franchisee", kind: "select", label: "Franchisee", options: [{ value: "", label: "All franchisees" }, ...cmFranchisees.map(f => ({ value: f.id, label: f.display_name }))] }] : []),
  ];
  return (
    <div className="page-inner">
      <PageHead title="Outlets" description={pageDescription}
        actions={role === "AM"
          ? <Btn variant="primary" onClick={() => _go("am-outlet-edit")}>New outlet</Btn>
          : undefined}/>
      <FilterBar
        primaryFilter={{ key: "status", label: "Status", options: [
          { value: "", label: "All statuses" },
          ...["draft","active","inactive"].map(s => ({ value: s, label: s })),
        ]}}
        primaryValue={status}
        onPrimaryChange={setStatus}
        advancedFilters={advancedFilters}
        advancedValues={{ geo, franchisee }}
        onAdvancedChange={(k, v) => { if (k === "geo") setGeo(v); else if (k === "franchisee") { setFranchisee(v); if (!v) setCtxChip(null); } }}
        onAdvancedReset={() => { setGeo({ countries: [], areas: [], outlets: [] }); setFranchisee(""); setCtxChip(null); }}
        onReset={() => { setStatus(""); setGeo({ countries: [], areas: [], outlets: [] }); setFranchisee(""); setSearchQuery(""); setCtxChip(null); }}
        search={searchQuery}
        onSearch={setSearchQuery}
        searchPlaceholder="Search outlets…"/>
      {ctxChip && (
        <div style={{ marginBottom: 12 }}>
          <span className="chip on">
            Franchisee: {ctxChip.label}
            <button onClick={clearCtx} aria-label="Clear context filter"
              style={{ display: "inline-flex", alignItems: "center", background: "none", border: "none", padding: 0, marginLeft: 2, cursor: "pointer", color: "inherit" }}>
              <LIcon name="X" size={14}/>
            </button>
          </span>
        </div>
      )}
      <Table
        columns={[
          ...(role === "CM" ? [{ label: "Area", width: 160, sortable: true, sortKey: "area_id", render: r => <span style={{ ...T_MUTED }}>{_areaLabel(r.area_id)}</span> }] : []),
          { label: "Outlet",      sortable: true, sortKey: "name", render: r => <span style={{ ...T.primary() }}>{r.name}</span> },
          { label: "Status",      width: 120, sortable: true, sortKey: "status", render: r => <StatusPill status={r.status} kind="outlet"/> },
          { label: "POS sync",    width: 120, sortable: true, sortKey: "pos_sync", render: r => <StatusPill status={r.pos_sync} kind="sync"/> },
          { label: "Last active", width: 160, sortable: true, sortKey: "last_activity", render: r => <span style={{ ...T_MUTED }}>{r.last_activity}</span> },
          { label: "",            width: 36,  render: () => <span style={{ color: "var(--forest)" }}>{Ic.chevR(16)}</span> },
        ]}
        rows={visibleRows}
        onRow={r => _go(detailRoute, { id: r.id })}
        emptyText="No outlets match this filter."/>
      <TableFooter page={page} totalPages={totalPages} onPage={setPage} count={pageSize} onCountChange={c => { setPageSize(c); setPage(1); }}/>
    </div>
  );
}

Object.assign(window, { OutletsGlobalList, OutletEdit, OutletDetail, OutletsList });
})();
