/* global React, Icon, Badge, Tag, Avatar, Button, Field, Toggle, Tabs, Segmented, PeriodPicker, Card, Metric, Modal, Pager, LineChart, BarChart, AreaChart, DonutCard, Gauge, Sparkline, ProgressBar, toast */

var { useState: useState_hm, useMemo: useMemo_hm } = React;

// ============================================================
// HOTEL MAPPING & PORTFOLIO MANAGEMENT
// Bedbank B2B — autonomous mapping, admin monitors & intervenes
// ============================================================

const HM_VENDORS = [
{ id: "JUN-RHK", name: "Juniper RHK", type: "Bedbank", trust: 94, hotels: 1_240_000, mapped: 980_000, giata: 88, status: "Active", lastImport: "3 min ago", importOk: true },
{ id: "TG-STN", name: "TravelGate STN", type: "Bedbank", trust: 91, hotels: 980_000, mapped: 742_000, giata: 82, status: "Active", lastImport: "11 min ago", importOk: true },
{ id: "TM-01", name: "TourMind", type: "Tech hub", trust: 86, hotels: 642_000, mapped: 428_000, giata: 71, status: "Active", lastImport: "1 hr ago", importOk: true },
{ id: "JUN-ALB", name: "Juniper ALB", type: "Bedbank", trust: 89, hotels: 540_000, mapped: 392_000, giata: 79, status: "Active", lastImport: "22 min ago", importOk: true },
{ id: "TG-EU2", name: "TravelGate EU2", type: "Bedbank", trust: 92, hotels: 488_000, mapped: 366_000, giata: 84, status: "Active", lastImport: "8 min ago", importOk: true },
{ id: "TG-MED", name: "TravelGate MED", type: "Bedbank", trust: 88, hotels: 412_000, mapped: 281_000, giata: 76, status: "Active", lastImport: "34 min ago", importOk: true },
{ id: "JUN-NOR", name: "Juniper Nordics", type: "Bedbank", trust: 90, hotels: 288_000, mapped: 214_000, giata: 80, status: "Active", lastImport: "6 min ago", importOk: true },
{ id: "TG-APAC", name: "TravelGate APAC", type: "Bedbank", trust: 78, hotels: 264_000, mapped: 142_000, giata: 58, status: "Paused", lastImport: "2 days ago", importOk: false },
{ id: "TM-LATAM", name: "TourMind LATAM", type: "Tech hub", trust: 74, hotels: 198_000, mapped: 96_000, giata: 52, status: "Active", lastImport: "3 hr ago", importOk: true },
{ id: "TG-UK", name: "TravelGate UK", type: "Bedbank", trust: 93, hotels: 182_000, mapped: 148_000, giata: 86, status: "Active", lastImport: "15 min ago", importOk: true },
{ id: "JUN-IB", name: "Juniper Iberia", type: "Bedbank", trust: 91, hotels: 164_000, mapped: 132_000, giata: 83, status: "Active", lastImport: "19 min ago", importOk: true },
{ id: "TG-DACH", name: "TravelGate DACH", type: "Bedbank", trust: 48, hotels: 142_000, mapped: 38_000, giata: 31, status: "Error", lastImport: "Failed 5 hr ago", importOk: false }];


const HM_STATUS = {
  validated: { tone: "success", label: "Validated", dot: "#0e7c66" },
  active: { tone: "info", label: "Active", dot: "#2a6fdb" },
  shadow: { tone: "warning", label: "Shadow", dot: "#d4a017" },
  quarantine: { tone: "warning", label: "Quarantine", dot: "#e8833a" },
  suspect: { tone: "danger", label: "Suspect", dot: "#d4366a" },
  unmapped: { tone: "neutral", label: "Unmapped", dot: "#8b95a5" },
  disputed: { tone: "neutral", label: "Disputed", dot: "#3c4257" }
};

const HM_METHODS = {
  giata_consensus: "GIATA consensus",
  exact_match: "Exact match",
  algo_consensus: "Algo consensus",
  shadow_mode: "Shadow mode",
  manual: "Manual",
  legacy: "Legacy"
};

const COUNTRIES = [
{ code: "ES", flag: "🇪🇸", name: "Spain" }, { code: "FR", flag: "🇫🇷", name: "France" },
{ code: "IT", flag: "🇮🇹", name: "Italy" }, { code: "GB", flag: "🇬🇧", name: "United Kingdom" },
{ code: "DE", flag: "🇩🇪", name: "Germany" }, { code: "US", flag: "🇺🇸", name: "United States" },
{ code: "GR", flag: "🇬🇷", name: "Greece" }, { code: "PT", flag: "🇵🇹", name: "Portugal" },
{ code: "AE", flag: "🇦🇪", name: "UAE" }, { code: "TH", flag: "🇹🇭", name: "Thailand" }];


function hmRnd(seed) {let s = seed % 2147483647;return () => (s = s * 48271 % 2147483647) / 2147483647;}
function makeHotels(n) {
  const r = hmRnd(99);
  const names = ["Marriott", "Hilton", "AC Hotels", "NH Collection", "Meliá", "Barceló", "Riu", "Iberostar", "Hyatt", "Sofitel", "Novotel", "Radisson", "Eurostars", "Vincci", "Catalonia"];
  const suffix = ["Barcelona", "Madrid Centro", "Costa del Sol", "Paris Opéra", "Rome Forum", "London Bridge", "Berlin Mitte", "Athens Plaza", "Lisbon Marquês", "Dubai Marina"];
  const statuses = ["validated", "validated", "active", "active", "active", "shadow", "quarantine", "suspect", "unmapped", "disputed"];
  const out = [];
  for (let i = 0; i < n; i++) {
    const c = COUNTRIES[Math.floor(r() * COUNTRIES.length)];
    const st = statuses[Math.floor(r() * statuses.length)];
    const nVendors = st === "unmapped" ? 1 : 2 + Math.floor(r() * 3);
    const conf = st === "validated" ? 96 + Math.floor(r() * 4) : st === "active" ? 81 + Math.floor(r() * 14) : st === "shadow" ? 61 + Math.floor(r() * 19) : st === "quarantine" ? 41 + Math.floor(r() * 19) : st === "suspect" ? 15 + Math.floor(r() * 25) : st === "unmapped" ? 0 : 30 + Math.floor(r() * 20);
    out.push({
      id: "TRK-" + (4000000 + i),
      name: names[Math.floor(r() * names.length)] + " " + suffix[Math.floor(r() * suffix.length)],
      country: c, city: suffix[Math.floor(r() * suffix.length)].split(" ")[0],
      stars: 3 + Math.floor(r() * 3),
      vendors: nVendors, confidence: conf, status: st,
      giata: r() > 0.3 ? "G" + (700000 + Math.floor(r() * 99999)) : null,
      lastValidated: ["2 min ago", "1 hr ago", "Today", "Yesterday", "3 days ago", "1 week ago"][Math.floor(r() * 6)],
      quality: 50 + Math.floor(r() * 50)
    });
  }
  return out;
}
const HM_HOTELS = makeHotels(60);

// ---------- reusable components ----------
function ConfidenceBar({ value }) {
  const color = value === 0 ? "#8b95a5" : value <= 30 ? "#d4366a" : value <= 60 ? "#e8833a" : value <= 80 ? "#d4a017" : value <= 95 ? "#3fa66a" : "#0e7c66";
  return (
    <div style={{ position: "relative", height: 18, background: "var(--bg-subtle)", borderRadius: 4, overflow: "hidden", minWidth: 90 }}>
      <div style={{ width: Math.max(value, value === 0 ? 0 : 8) + "%", height: "100%", background: color, borderRadius: 4, transition: "width 240ms" }} />
      <span style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 10.5, fontWeight: 700, color: value > 55 ? "#fff" : "var(--ink-1)", fontVariantNumeric: "tabular-nums" }}>{value === 0 ? "—" : value + "%"}</span>
    </div>);
}

function StatusChip({ status }) {
  const s = HM_STATUS[status] || HM_STATUS.unmapped;
  return (
    <span style={{ display: "inline-flex", alignItems: "center", gap: 5, fontSize: 11.5, fontWeight: 500, padding: "2px 8px", borderRadius: 999, background: "var(--bg-subtle)", border: "1px solid var(--border)", whiteSpace: "nowrap" }}>
      <span style={{ width: 7, height: 7, borderRadius: 999, background: s.dot }} />{s.label}
    </span>);
}

// ============================================================
function PageHotelMapping() {
  const [tab, setTab] = useState_hm("explorer");
  const [openHotel, setOpenHotel] = useState_hm(null);
  const [openVendor, setOpenVendor] = useState_hm(null);
  // Real overview KPIs from /admin/mapping/*. Until the request comes
  // back we keep the prototype's mock values (5.34M etc.) so the page
  // doesn't render with empty cards.
  const [overview, setOverview] = useState_hm(null);
  const [vendorsCount, setVendorsCount] = useState_hm(null);
  const [anomalyCount, setAnomalyCount] = useState_hm(null);
  const [anomalies, setAnomalies] = useState_hm(null);
  React.useEffect(() => {
    if (!window.TrekkoAPI) return;
    window.TrekkoAPI.getMappingOverview().then(setOverview);
    window.TrekkoAPI.getMappingVendors().then(function (arr) { if (Array.isArray(arr)) setVendorsCount(arr.length); });
    window.TrekkoAPI.getMappingAnomalies().then(function (d) { if (d) { setAnomalyCount(d.count || 0); setAnomalies(d); } });
  }, []);
  function fmtBig(n) {
    if (n == null) return null;
    if (n >= 1e6) return (n / 1e6).toFixed(2) + "M";
    if (n >= 1e3) return (n / 1e3).toFixed(1) + "k";
    return String(n);
  }
  function vendorTypeHint(o) {
    if (!o || !o.vendors_by_type) return "Connected providers";
    return Object.keys(o.vendors_by_type)
      .map(function (k) { return k + " ×" + o.vendors_by_type[k]; })
      .join(" · ");
  }

  return (
    <div>
      {/* sticky portfolio header */}
      <div style={{ position: "sticky", top: 0, zIndex: 20, background: "var(--bg-app)", paddingBottom: 4, marginBottom: 14 }}>
        <div className="page-head" style={{ marginBottom: 12 }} data-comment-anchor="5fe4470018-div-107-9">
          <div>
            <h1 className="page-head__title">Hotel Mapping & Portfolio</h1>
            <div className="page-head__sub row" style={{ gap: 8 }}>
              <span style={{ display: "inline-flex", alignItems: "center", gap: 5 }}><span style={{ width: 7, height: 7, borderRadius: 999, background: "#0e7c66", display: "inline-block" }} /> Live</span>
              · Autonomous mapping engine · Last sync 3 min ago
            </div>
          </div>
          <div className="page-head__actions">
            <Button kind="secondary" icon={<Icon.Refresh />} onClick={() => toast({ title: "Re-sync not yet wired", body: "Forcing a vendor feed re-import from the CRM is not yet implemented (no backend endpoint). Imports run automatically on schedule.", tone: "warning" })}>Re-sync</Button>
            <Button kind="secondary" icon={<Icon.Download />} onClick={() => {
              window.TrekkoAPI.exportMappingCsv(1000)
                .then(function () { toast({ title: "Mapping CSV downloaded", body: "Capped at 1000 rows.", tone: "success" }); })
                .catch(function (e) { toast({ title: "Export failed", body: String(e && e.message || e), tone: "danger" }); });
            }}>Export</Button>
          </div>
        </div>
        <div className="grid grid--4" style={{ gap: 12 }}>
          <Metric accent label="Master hotels" value={fmtBig(overview && overview.master_hotels) || "5.34M"} hint="Unique accommodation IDs" />
          <Metric label="Mapped (multi-vendor)" value={fmtBig(overview && overview.mapped) || "2.64M"} hint="Vendor codes attached" />
          <Metric label="Coverage" value={overview ? (overview.coverage_pct + "%") : "49.5%"} hint="of master hotels" />
          <Metric label="Active vendors" value={overview ? String(overview.active_vendors) : "12"} hint={vendorTypeHint(overview)} />
        </div>
      </div>

      <Tabs active={tab} onChange={setTab} tabs={[
      { id: "explorer", label: "Hotel explorer" },
      { id: "vendors", label: "Vendors", count: vendorsCount != null ? vendorsCount : HM_VENDORS.length },
      { id: "geo", label: "By country" },
      { id: "anomaly", label: "Anomaly inbox", count: anomalyCount != null ? anomalyCount : 14 }]
      } />

      <div style={{ display: "grid", gridTemplateColumns: "1fr 280px", gap: 18, alignItems: "start" }}>
        <div style={{ minWidth: 0 }}>
          {tab === "explorer" && <HotelExplorer onOpen={setOpenHotel} />}
          {tab === "vendors" && <VendorGrid onOpen={setOpenVendor} />}
          {tab === "geo" && <GeoView />}
          {tab === "anomaly" && <AnomalyInbox />}
        </div>
        <HealthSidebar onAnomaly={() => setTab("anomaly")} anomalies={anomalies} />
      </div>

      {openHotel && <HotelDrawer hotel={openHotel} onClose={() => setOpenHotel(null)} />}
      {openVendor && <VendorMappingModal vendor={openVendor} onClose={() => setOpenVendor(null)} />}
    </div>);
}

// ---------- Hotel explorer ----------
function HotelExplorer({ onOpen }) {
  const [q, setQ] = useState_hm("");
  const [statusF, setStatusF] = useState_hm("");
  const [vendorF, setVendorF] = useState_hm("");
  const [giataF, setGiataF] = useState_hm("");
  const [sel, setSel] = useState_hm([]);
  const [page, setPage] = useState_hm(1);
  const PAGE = 12;
  // Real backend list. Falls back to HM_HOTELS mock if endpoint errors.
  const [backendRows, setBackendRows] = useState_hm(null);
  const [backendTotal, setBackendTotal] = useState_hm(null);
  const [loading, setLoading] = useState_hm(false);
  const [vendorOptions, setVendorOptions] = useState_hm([]);
  React.useEffect(() => {
    if (!window.TrekkoAPI) return;
    window.TrekkoAPI.getMappingVendors().then(function (arr) { if (Array.isArray(arr)) setVendorOptions(arr); });
  }, []);
  React.useEffect(() => {
    if (!window.TrekkoAPI) return;
    setLoading(true);
    var t = setTimeout(function () {
      window.TrekkoAPI.getMappingHotels({
        page: page,
        pageSize: PAGE,
        search: q || undefined,
        statusFilter: statusF || undefined,
        vendor: vendorF || undefined,
        giata: giataF || undefined,
      }).then(function (d) {
        if (d && d.data) {
          // Map backend shape → prototype shape consumed by the table.
          var mapped = d.data.map(function (h) {
            return {
              id: h.trekko_id,
              name: h.name,
              country: { flag: "", name: h.city || "-" },
              stars: h.stars || 0,
              vendors: h.vendor_count,
              confidence: h.confidence,
              status: h.status,
              giata: h.giata_code,
              lastValidated: "live",
              _real: h,
            };
          });
          setBackendRows(mapped);
          setBackendTotal(d.total_items);
        } else {
          setBackendRows([]);
          setBackendTotal(0);
        }
      }).finally(function () { setLoading(false); });
    }, q ? 300 : 0);
    return function () { clearTimeout(t); };
  }, [page, q, statusF, vendorF, giataF]);

  // Local filter only used when backend never returned.
  const fallbackRows = useMemo_hm(() => {
    let r = HM_HOTELS;
    if (q) r = r.filter((h) => (h.name + h.country.name + h.id).toLowerCase().includes(q.toLowerCase()));
    if (statusF) r = r.filter((h) => h.status === statusF);
    return r;
  }, [q, statusF]);
  const slice = backendRows != null ? backendRows : fallbackRows.slice((page - 1) * PAGE, page * PAGE);
  const totalShown = backendTotal != null ? backendTotal : fallbackRows.length;
  const allSel = slice.length > 0 && slice.every((h) => sel.includes(h.id));

  return (
    <div>
      <div className="filter-bar" style={{ borderRadius: "8px 8px 0 0" }}>
        <input className="input input--search" placeholder="Search hotel, country or Trekko ID…" style={{ width: 300 }} value={q} onChange={(e) => setQ(e.target.value)} />
        <select className="select" value={statusF} onChange={(e) => setStatusF(e.target.value)}>
          <option value="">All statuses</option>
          {Object.keys(HM_STATUS).map((k) => <option key={k} value={k}>{HM_STATUS[k].label}</option>)}
        </select>
        <select className="select" value={vendorF} onChange={(e) => {setVendorF(e.target.value); setPage(1);}}>
          <option value="">All vendors</option>
          {(vendorOptions.length ? vendorOptions : HM_VENDORS).map((v) => <option key={v.id} value={v.id}>{v.name}</option>)}
        </select>
        <select className="select" value={giataF} onChange={(e) => {setGiataF(e.target.value); setPage(1);}}>
          <option value="">GIATA: any</option>
          <option value="with">Has GIATA</option>
          <option value="without">No GIATA</option>
        </select>
        <span className="spacer" />
        <span className="tiny muted">{loading ? "Loading…" : Number(totalShown || 0).toLocaleString() + " shown"}</span>
      </div>

      {sel.length > 0 &&
      <div className="bulk-bar">
        <span className="bulk-bar__count">{sel.length} selected</span>
        <button className="btn btn--ghost btn--sm" onClick={() => setSel([])}>Clear</button>
        <div className="bulk-bar__actions">
          <Button kind="secondary" size="sm" icon={<Icon.Refresh />} onClick={() => {
            window.TrekkoAPI.reevaluateHotels(sel)
              .then(function (r) { toast({ title: "Re-evaluation queued", body: (r && r.queued || 0) + " hotels", tone: "success" }); })
              .catch(function (e) { toast({ title: "Re-evaluate failed", body: String(e && e.message || e), tone: "danger" }); });
          }}>Re-evaluate</Button>
          <Button kind="danger" size="sm" icon={<Icon.X />} onClick={() => {
            if (!window.confirm("Exclude " + sel.length + " hotel(s) from auto-mapping?")) return;
            var reason = window.prompt("Reason for exclusion?") || "";
            window.TrekkoAPI.excludeHotels(sel, reason)
              .then(function (r) { toast({ title: "Excluded " + (r && r.excluded || 0) + " hotel(s)", tone: "success" }); })
              .catch(function (e) { toast({ title: "Exclude failed", body: String(e && e.message || e), tone: "danger" }); });
          }}>Exclude</Button>
          <Button kind="secondary" size="sm" icon={<Icon.Download />} onClick={() => {
            window.TrekkoAPI.exportMappingCsv(1000)
              .then(function () { toast({ title: "Mapping CSV downloaded", body: "Capped at 1000 rows.", tone: "success" }); })
              .catch(function (e) { toast({ title: "Export failed", body: String(e && e.message || e), tone: "danger" }); });
          }}>Export CSV</Button>
        </div>
      </div>}

      <div className="tbl-wrap tbl-wrap--linked">
        <table className="tbl">
          <thead>
            <tr>
              <th className="col-check"><input className="checkbox" type="checkbox" checked={allSel} onChange={() => setSel(allSel ? sel.filter((id) => !slice.some((h) => h.id === id)) : Array.from(new Set([...sel, ...slice.map((h) => h.id)])))} /></th>
              <th>Hotel</th><th>Country</th><th className="center">★</th><th className="num">Vendors</th><th>Confidence</th><th>Status</th><th>GIATA</th><th>Validated</th><th className="col-actions"></th>
            </tr>
          </thead>
          <tbody>
            {slice.map((h) =>
            <tr key={h.id} onClick={() => onOpen(h)} style={{ cursor: "pointer" }}>
                <td className="col-check" onClick={(e) => e.stopPropagation()}><input className="checkbox" type="checkbox" checked={sel.includes(h.id)} onChange={() => setSel(sel.includes(h.id) ? sel.filter((x) => x !== h.id) : [...sel, h.id])} /></td>
                <td><div className="bold">{h.name}</div><div className="tiny muted mono">{h.id}</div></td>
                <td>{h.country.flag} {h.country.name}</td>
                <td className="center" style={{ color: "#d4a017", whiteSpace: "nowrap" }}>{"★".repeat(h.stars)}</td>
                <td className="num"><Badge tone={h.vendors >= 3 ? "success" : h.vendors === 2 ? "info" : "neutral"}>{h.vendors}</Badge></td>
                <td><ConfidenceBar value={h.confidence} /></td>
                <td><StatusChip status={h.status} /></td>
                <td>{h.giata ? <span className="mono tiny">{h.giata}</span> : <span className="tiny muted">—</span>}</td>
                <td className="tiny muted">{h.lastValidated}</td>
                <td className="col-actions"><Button kind="ghost" size="sm" iconOnly icon={<Icon.More />} onClick={(e) => {e.stopPropagation();onOpen(h);}} /></td>
              </tr>
            )}
          </tbody>
        </table>
      </div>
      <Pager page={page} pageSize={PAGE} total={totalShown} onPage={setPage} />
    </div>);
}

// ---------- Vendor grid ----------
function VendorGrid({ onOpen }) {
  // Real backend vendor list. Falls back to HM_VENDORS mock if endpoint fails.
  const [vendors, setVendors] = useState_hm(null);
  React.useEffect(() => {
    if (!window.TrekkoAPI) return;
    window.TrekkoAPI.getMappingVendors().then(function (arr) {
      if (Array.isArray(arr) && arr.length) {
        // Normalise shape so prototype consumers (mapped/hotels/coverage)
        // keep working with backend rows.
        setVendors(arr.map(function (v) {
          return {
            id: v.id,
            name: v.name,
            type: v.type,
            trust: v.trust,
            hotels: v.hotels,
            mapped: v.mapped,
            giata: v.giata,
            status: v.status,
            lastImport: v.last_import ? new Date(v.last_import).toLocaleString() : "-",
            importOk: v.import_ok,
          };
        }));
      } else {
        setVendors([]);
      }
    });
  }, []);
  const VENDORS = vendors != null && vendors.length ? vendors : HM_VENDORS;
  return (
    <div>
      <div className="filter-bar" style={{ borderRadius: 8, border: "1px solid var(--border)", marginBottom: 14 }}>
        <input className="input input--search" placeholder="Search vendor…" style={{ width: 240 }} />
        <select className="select"><option>All statuses</option><option>Active</option><option>Paused</option><option>Error</option></select>
        <select className="select"><option>All types</option><option>Bedbank</option><option>Tech hub</option></select>
        <span className="spacer" />
      </div>
      <div className="grid" style={{ gridTemplateColumns: "repeat(auto-fill, minmax(340px, 1fr))", gap: 14 }}>
        {VENDORS.map((v) => {
          const cov = Math.round(v.mapped / v.hotels * 100);
          return (
            <div key={v.id} className="card" style={{ padding: 16 }}>
              <div className="row" style={{ justifyContent: "space-between", marginBottom: 14 }}>
                <div>
                  <div className="bold" style={{ fontSize: 14 }}>{v.name}</div>
                  <div className="tiny muted">{v.type} · {v.id}</div>
                </div>
                {v.status === "Active" ? <Badge tone="success">Active</Badge> : v.status === "Paused" ? <Badge tone="warning">Paused</Badge> : <Badge tone="danger">Error</Badge>}
              </div>
              <div className="row" style={{ gap: 16, alignItems: "center", marginBottom: 14 }}>
                <div style={{ flexShrink: 0 }}><Gauge value={v.trust} size={104} label="Trust score" color={v.trust >= 85 ? "#0e7c66" : v.trust >= 70 ? "#d4a017" : "#d4366a"} /></div>
                <div style={{ flex: 1, display: "grid", gap: 10 }}>
                  <div>
                    <div className="row" style={{ justifyContent: "space-between", fontSize: 12.5, marginBottom: 3 }}><span className="muted">Hotels</span><span className="bold mono">{(v.hotels / 1000).toFixed(0)}k</span></div>
                    <div className="row" style={{ justifyContent: "space-between", fontSize: 12.5, marginBottom: 3 }}><span className="muted">Mapped</span><span className="bold mono">{cov}%</span></div>
                    <ProgressBar value={cov} color="#2a6fdb" />
                  </div>
                  <div>
                    <div className="row" style={{ justifyContent: "space-between", fontSize: 12.5, marginBottom: 3 }}><span className="muted">GIATA coverage</span><span className="bold mono">{v.giata}%</span></div>
                    <ProgressBar value={v.giata} color="#635bff" />
                  </div>
                </div>
              </div>
              <div className="tiny muted" style={{ display: "flex", alignItems: "center", gap: 5, paddingTop: 12, borderTop: "1px solid var(--border-hairline)" }}>
                {v.importOk ? <Icon.Check size={12} stroke="var(--success)" /> : <Icon.Warning size={12} stroke="var(--danger)" />}
                Last import {v.lastImport}
              </div>
              <div className="row" style={{ gap: 6, marginTop: 12 }}>
                <Button kind="secondary" size="sm" onClick={() => onOpen(v)}>View hotels</Button>
                <Button kind="ghost" size="sm" icon={<Icon.Refresh />} onClick={() => {
                  if (!window.confirm("Queue a re-import for " + v.name + "? (Full re-pull, can take hours.)")) return;
                  window.TrekkoAPI.reimportVendor(v.id, true)
                    .then(function () { toast({ title: "Reimport queued", body: v.name, tone: "success" }); })
                    .catch(function (e) { toast({ title: "Reimport failed", body: String(e && e.message || e), tone: "danger" }); });
                }}>Reimport</Button>
                <Button kind="ghost" size="sm" iconOnly icon={v.status === "Paused" ? <Icon.Refresh /> : <Icon.X />} onClick={() => {
                  var nextActive = v.status === "Paused";
                  if (!window.confirm((nextActive ? "Resume" : "Pause") + " vendor " + v.name + "?")) return;
                  if (!window.TrekkoAPI || !window.TrekkoAPI.setVendorActive) { toast({ title: "Endpoint unavailable", tone: "danger" }); return; }
                  window.TrekkoAPI.setVendorActive(v.id, nextActive)
                    .then(function (r) { toast({ title: nextActive ? "Vendor resumed" : "Vendor paused", body: r && r.name || v.name, tone: "success" }); })
                    .catch(function (e) { toast({ title: "Vendor toggle failed", body: String(e && e.message || e), tone: "danger" }); });
                }} />
              </div>
            </div>);
        })}
      </div>
    </div>);
}

// ---------- Geo view ----------
function GeoView() {
  const [drill, setDrill] = useState_hm(null);
  const data = [
  { c: "Spain", flag: "🇪🇸", total: 642000, mapped: 78, top: "Juniper Iberia", unmapped: 141000 },
  { c: "Italy", flag: "🇮🇹", total: 584000, mapped: 71, top: "TravelGate MED", unmapped: 169000 },
  { c: "France", flag: "🇫🇷", total: 512000, mapped: 74, top: "TravelGate EU2", unmapped: 133000 },
  { c: "United Kingdom", flag: "🇬🇧", total: 388000, mapped: 81, top: "TravelGate UK", unmapped: 73000 },
  { c: "Germany", flag: "🇩🇪", total: 342000, mapped: 64, top: "TravelGate DACH", unmapped: 123000 },
  { c: "Greece", flag: "🇬🇷", total: 286000, mapped: 69, top: "TravelGate MED", unmapped: 88000 },
  { c: "United States", flag: "🇺🇸", total: 824000, mapped: 42, top: "TourMind", unmapped: 478000 },
  { c: "Thailand", flag: "🇹🇭", total: 198000, mapped: 38, top: "TourMind LATAM", unmapped: 122000 }];

  if (drill) {
    const cHotels = HM_HOTELS.filter((h) => h.country.name === drill.c || h.country.name === drill.c.replace("United States", "United States")).concat(HM_HOTELS).slice(0, 14);
    return (
      <div className="col" style={{ gap: 14 }} data-comment-anchor="geo-drill">
        <div className="row" style={{ gap: 8 }}>
          <Button kind="ghost" size="sm" icon={<Icon.ChevronLeft />} onClick={() => setDrill(null)}>All countries</Button>
          <span className="muted">/</span>
          <span className="bold">{drill.flag} {drill.c}</span>
          <Badge tone="info">{drill.total.toLocaleString()} hotels</Badge>
        </div>
        <div className="filter-bar" style={{ borderRadius: "8px 8px 0 0" }}>
          <input className="input input--search" placeholder={`Search hotels in ${drill.c}…`} style={{ width: 280 }} />
          <select className="select"><option>All statuses</option>{Object.keys(HM_STATUS).map((k) => <option key={k}>{HM_STATUS[k].label}</option>)}</select>
          <select className="select"><option>All vendors</option>{HM_VENDORS.map((v) => <option key={v.id}>{v.name}</option>)}</select>
          <select className="select"><option>GIATA: any</option><option>Has GIATA</option><option>No GIATA</option></select>
          <span className="spacer" />
          <span className="tiny muted">{drill.unmapped.toLocaleString()} unmapped</span>
        </div>
        <div className="tbl-wrap tbl-wrap--linked">
          <table className="tbl">
            <thead><tr><th>Hotel</th><th>City</th><th className="center">★</th><th className="num">Vendors</th><th>Confidence</th><th>Status</th><th>GIATA</th></tr></thead>
            <tbody>
              {cHotels.map((h, i) =>
              <tr key={h.id + i}>
                <td><div className="bold">{h.name}</div><div className="tiny muted mono">{h.id}</div></td>
                <td>{h.city}</td>
                <td className="center" style={{ color: "#d4a017", whiteSpace: "nowrap" }}>{"★".repeat(h.stars)}</td>
                <td className="num"><Badge tone={h.vendors >= 3 ? "success" : h.vendors === 2 ? "info" : "neutral"}>{h.vendors}</Badge></td>
                <td><ConfidenceBar value={h.confidence} /></td>
                <td><StatusChip status={h.status} /></td>
                <td>{h.giata ? <span className="mono tiny">{h.giata}</span> : <span className="tiny muted">—</span>}</td>
              </tr>
              )}
            </tbody>
          </table>
        </div>
      </div>);
  }

  return (
    <div className="col" style={{ gap: 14 }}>
      <Card title="Mapping density by country" sub="% mapped · click a country to drill into its hotels">
        <div className="grid" style={{ gridTemplateColumns: "repeat(auto-fill, minmax(120px, 1fr))", gap: 8 }}>
          {data.map((d) =>
          <div key={d.c} onClick={() => setDrill(d)} style={{ padding: 12, borderRadius: 8, background: `color-mix(in srgb, #2a6fdb ${d.mapped}%, var(--bg-subtle))`, color: d.mapped > 55 ? "#fff" : "var(--ink-1)", cursor: "pointer" }} title={`${d.c}: ${d.mapped}% mapped — click to drill`}>
              <div style={{ fontSize: 18 }}>{d.flag}</div>
              <div className="bold" style={{ fontSize: 12.5, marginTop: 4 }}>{d.c}</div>
              <div className="mono" style={{ fontSize: 11, opacity: 0.85 }}>{d.mapped}% · {(d.total / 1000).toFixed(0)}k</div>
            </div>
          )}
        </div>
      </Card>
      <Card title="Country breakdown" flush>
        <table className="tbl">
          <thead><tr><th>Country</th><th className="num">Total hotels</th><th className="num">Mapped</th><th>Coverage</th><th>Top vendor</th><th className="num">Unmapped</th><th className="col-actions"></th></tr></thead>
          <tbody>
            {data.map((d) =>
            <tr key={d.c} onClick={() => setDrill(d)} style={{ cursor: "pointer" }}>
                <td className="bold">{d.flag} {d.c}</td>
                <td className="num mono">{d.total.toLocaleString()}</td>
                <td className="num mono">{d.mapped}%</td>
                <td style={{ width: 120 }}><ProgressBar value={d.mapped} color="#2a6fdb" /></td>
                <td className="muted">{d.top}</td>
                <td className="num mono" style={{ color: "var(--warning)" }}>{d.unmapped.toLocaleString()}</td>
                <td className="col-actions"><Button kind="ghost" size="sm" iconOnly icon={<Icon.ChevronRight />} onClick={(e) => { e.stopPropagation(); setDrill(d); }} /></td>
              </tr>
            )}
          </tbody>
        </table>
      </Card>
    </div>);
}

// ---------- Anomaly inbox ----------
function AnomalyInbox() {
  const [backendAnomalies, setBackendAnomalies] = useState_hm(null);
  React.useEffect(() => {
    if (!window.TrekkoAPI) return;
    window.TrekkoAPI.getMappingAnomalies().then(function (d) {
      if (d && Array.isArray(d.anomalies)) setBackendAnomalies(d.anomalies);
    });
  }, []);
  return _AnomalyInboxBody(backendAnomalies);
}

function _AnomalyInboxBody(backendAnomalies) {
  // When backend returns real anomalies, render those; otherwise fall back
  // to the prototype's illustrative mock items (so the page still shows
  // the design when the endpoint is down).
  var KIND_META = {
    duplicate_giata: { type: "Possible duplicate", icon: "Copy", tone: "warning", rec: "Merge accommodations under the canonical GIATA" },
    no_codes: { type: "No vendor codes", icon: "X", tone: "danger", rec: "Cleanup, re-import or attach a vendor code" },
    multi_vendor_no_giata: { type: "Multi-vendor, no GIATA", icon: "Warning", tone: "warning", rec: "Attach a GIATA code as cross-vendor anchor" },
  };
  const items = (Array.isArray(backendAnomalies) && backendAnomalies.length) ? backendAnomalies.map(function (a, i) {
    var meta = KIND_META[a.kind] || { type: a.title, icon: "Warning", tone: a.severity === "danger" ? "danger" : "warning", rec: "Review" };
    return {
      id: i + 1,
      type: meta.type,
      icon: meta.icon,
      tone: a.severity === "danger" ? "danger" : "warning",
      hotels: [a.title.replace(/^\d+\s+/, "")],
      reason: a.detail,
      rec: meta.rec,
    };
  }) : [
  { id: 1, type: "Possible duplicate", icon: "Copy", tone: "warning", hotels: ["Meliá Barcelona Sky", "Melia BCN Sky Hotel"], reason: "Name + geo match at 89% confidence, but GIATA IDs differ", rec: "Merge under TRK-4001288" },
  { id: 2, type: "GIATA conflict", icon: "Warning", tone: "danger", hotels: ["NH Collection Madrid"], reason: "Juniper RHK reports G744102, TravelGate STN reports G744890", rec: "Trust GIATA consensus (2 of 3 agree on G744102)" },
  { id: 3, type: "Booking failures", icon: "X", tone: "danger", hotels: ["Riu Costa del Sol"], reason: "3 booking failures on TG-MED mapping in last 48h", rec: "Quarantine TG-MED vendor_code, keep JUN-RHK" },
  { id: 4, type: "Vendor trust drop", icon: "Trend", tone: "warning", hotels: ["TravelGate DACH (vendor)"], reason: "Trust weight fell from 0.81 to 0.48 after import errors", rec: "Pause vendor, await clean import" },
  { id: 5, type: "Possible duplicate", icon: "Copy", tone: "warning", hotels: ["Hyatt Paris Opéra", "Hyatt Regency Paris Opera"], reason: "Address identical, name variance 92%", rec: "Merge under TRK-4002910" }];

  return (
    <div className="col" style={{ gap: 12 }}>
      <div className="filter-bar" style={{ borderRadius: 8, border: "1px solid var(--border)" }} data-comment-anchor="e1e2915983-div-329-7">
        <input className="input input--search" placeholder="Search hotel, vendor or reason…" style={{ width: 280 }} />
        <select className="select"><option>All types</option><option>Possible duplicate</option><option>GIATA conflict</option><option>Booking failures</option><option>Vendor trust drop</option></select>
        <select className="select"><option>All severities</option><option>Critical</option><option>Warning</option></select>
        <span className="spacer" />
        <span className="muted tiny">14 items · ~5 min/day review</span>
      </div>
      {items.map((it) => {
        const Ic = Icon[it.icon] || Icon.Warning;
        return (
          <div key={it.id} className="card" style={{ padding: 16 }}>
            <div className="row" style={{ gap: 12, alignItems: "flex-start" }}>
              <span style={{ width: 32, height: 32, borderRadius: 8, flexShrink: 0, display: "grid", placeItems: "center", background: `var(--${it.tone}-bg)`, color: `var(--${it.tone})` }}><Ic size={16} /></span>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div className="row" style={{ gap: 8 }}><span className="bold">{it.type}</span><Badge tone={it.tone}>Needs decision</Badge></div>
                <div className="row" style={{ gap: 6, margin: "6px 0", flexWrap: "wrap" }}>
                  {it.hotels.map((h) => <span key={h} className="tag" style={{ fontSize: 11.5 }}>{h}</span>)}
                </div>
                <div className="tiny muted">{it.reason}</div>
                <div style={{ marginTop: 6, fontSize: 12.5, padding: "6px 10px", background: "var(--bg-subtle)", borderRadius: 6, display: "inline-flex", gap: 6, alignItems: "center" }}>
                  <Icon.Info size={12} stroke="var(--indigo)" /> <span className="muted">Recommendation:</span> <span className="bold">{it.rec}</span>
                </div>
              </div>
              <div className="col" style={{ gap: 6 }}>
                <Button kind="success" size="sm" icon={<Icon.Check />} onClick={() => {
                  var aid = it.accommodation_id || it.id;
                  if (!aid || !window.TrekkoAPI || !window.TrekkoAPI.resolveAnomaly) { toast({ title: "No accommodation_id available", tone: "danger" }); return; }
                  window.TrekkoAPI.resolveAnomaly(aid, "approve", it.rec || "")
                    .then(function () { toast({ title: "Approved", body: "Logged to audit", tone: "success" }); })
                    .catch(function (e) { toast({ title: "Approve failed", body: String(e && e.message || e), tone: "danger" }); });
                }}>Approve</Button>
                <Button kind="danger" size="sm" icon={<Icon.X />} onClick={() => {
                  var aid = it.accommodation_id || it.id;
                  if (!aid || !window.TrekkoAPI || !window.TrekkoAPI.resolveAnomaly) { toast({ title: "No accommodation_id available", tone: "danger" }); return; }
                  window.TrekkoAPI.resolveAnomaly(aid, "reject", it.rec || "")
                    .then(function () { toast({ title: "Rejected", body: "Logged to audit", tone: "success" }); })
                    .catch(function (e) { toast({ title: "Reject failed", body: String(e && e.message || e), tone: "danger" }); });
                }}>Reject</Button>
                <Button kind="ghost" size="sm" icon={<Icon.Eye />} onClick={() => {
                  var aid = it.accommodation_id || it.id;
                  if (!aid) { toast({ title: "No accommodation_id", tone: "danger" }); return; }
                  window.TrekkoAPI.investigateHotel(aid)
                    .then(function (r) { toast({ title: r.name || "Hotel " + aid, body: "GIATA: " + (r.giata_code || "—") + " · vendor codes: " + (r.vendor_codes_count || 0) + " · stars: " + (r.stars || "—"), tone: "info" }); })
                    .catch(function (e) { toast({ title: "Investigate failed", body: String(e && e.message || e), tone: "danger" }); });
                }}>Investigate</Button>
              </div>
            </div>
          </div>);
      })}
    </div>);
}

// Apply button for "④ Status" card — wired to POST /admin/mapping/update_hotel_status.
function HotelStatusApplyButton({ hotel }) {
  var aid = hotel.id || (hotel._real && (hotel._real.trekko_id || hotel._real.id));
  // Find the closest <select> within this card to read user choice.
  var ref = React.useRef(null);
  return (
    <Button kind="secondary" block ref={ref} onClick={(e) => {
      var card = e.currentTarget && e.currentTarget.closest && e.currentTarget.closest(".card");
      var sel = card && card.querySelector("select");
      var st = sel && sel.value;
      if (!aid) { toast({ title: "No accommodation_id", tone: "danger" }); return; }
      if (!st) { toast({ title: "No status selected", tone: "danger" }); return; }
      window.TrekkoAPI.updateHotelStatus(aid, st)
        .then(function (r) { toast({ title: "Status set to " + r.status, tone: "success" }); })
        .catch(function (er) { toast({ title: "Status update failed", body: String(er && er.message || er), tone: "danger" }); });
    }}>Apply</Button>
  );
}

// Editor for "Force GIATA" card — wired to POST /admin/mapping/force_giata.
function ForceGiataEditor({ hotel }) {
  var aid = hotel.id || (hotel._real && hotel._real.trekko_id) || (hotel._real && hotel._real.id);
  var inputRef = React.useRef(null);
  return (
    <>
      <Field label="GIATA override" hint="Overrides shadow inference">
        <input ref={inputRef} className="input" defaultValue={hotel.giata || ""} placeholder="G000000" />
      </Field>
      <div style={{ height: 8 }} />
      <Button kind="secondary" block icon={<Icon.Lock />} onClick={() => {
        if (!aid) { toast({ title: "No accommodation_id resolved", tone: "danger" }); return; }
        var val = inputRef.current && inputRef.current.value;
        if (!val) { toast({ title: "Enter a GIATA code first", tone: "warning" }); return; }
        if (!window.TrekkoAPI || !window.TrekkoAPI.forceHotelGiata) { toast({ title: "Endpoint unavailable", tone: "danger" }); return; }
        window.TrekkoAPI.forceHotelGiata(aid, val)
          .then(function (r) { toast({ title: "GIATA forced", body: r && r.giata_code, tone: "success" }); })
          .catch(function (e) { toast({ title: "Force GIATA failed", body: String(e && e.message || e), tone: "danger" }); });
      }}>Force GIATA</Button>
    </>
  );
}

// Editor for "Fix master data" card — wired to POST /admin/mapping/save_master.
function MasterDataEditor({ hotel }) {
  var aid = hotel.id || (hotel._real && hotel._real.trekko_id) || (hotel._real && hotel._real.id);
  var initialLat = (hotel._real && hotel._real.latitude) || hotel.latitude || "";
  var initialLon = (hotel._real && hotel._real.longitude) || hotel.longitude || "";
  var initialStars = hotel.stars || 0;
  var initialName = hotel.name || "";
  var nameRef = React.useRef(null);
  var starsRef = React.useRef(null);
  var latRef = React.useRef(null);
  var lonRef = React.useRef(null);
  return (
    <>
      <div className="grid grid--2" style={{ gap: 10 }}>
        <Field label="Name"><input ref={nameRef} className="input" defaultValue={initialName} /></Field>
        <Field label="Stars"><input ref={starsRef} className="input" type="number" defaultValue={initialStars} /></Field>
        <Field label="Latitude"><input ref={latRef} className="input" defaultValue={initialLat} /></Field>
        <Field label="Longitude"><input ref={lonRef} className="input" defaultValue={initialLon} /></Field>
      </div>
      <div style={{ height: 8 }} />
      <Button kind="primary" icon={<Icon.Check />} onClick={() => {
        if (!aid) { toast({ title: "No accommodation_id resolved", tone: "danger" }); return; }
        if (!window.TrekkoAPI || !window.TrekkoAPI.saveMasterHotel) { toast({ title: "Endpoint unavailable", tone: "danger" }); return; }
        var payload = {
          name: nameRef.current && nameRef.current.value,
          stars: starsRef.current && starsRef.current.value !== "" ? Number(starsRef.current.value) : null,
          latitude: latRef.current && latRef.current.value !== "" ? Number(latRef.current.value) : null,
          longitude: lonRef.current && lonRef.current.value !== "" ? Number(lonRef.current.value) : null,
        };
        window.TrekkoAPI.saveMasterHotel(aid, payload)
          .then(function (r) { toast({ title: "Master data saved", body: "Changed: " + Object.keys((r && r.changed) || {}).join(", "), tone: "success" }); })
          .catch(function (e) { toast({ title: "Save failed", body: String(e && e.message || e), tone: "danger" }); });
      }}>Save corrections</Button>
    </>
  );
}

// ---------- Health sidebar ----------
function HealthSidebar({ onAnomaly, anomalies }) {
  // Real anomaly counts when the /admin/mapping/anomalies payload is in.
  const byKind = (anomalies && anomalies.by_kind) || {};
  const total = (anomalies && anomalies.count) != null ? anomalies.count : null;
  const giata = byKind.no_giata != null ? byKind.no_giata : null;
  const dispDistribution = byKind.suspect != null ? byKind.suspect : null;
  return (
    <div className="col" style={{ gap: 14, position: "sticky", top: 150 }}>
      <Card title="Mapping activity" sub="Live counters" flush>
        <table className="tbl" style={{ fontSize: 12.5 }}>
          <tbody>
            <tr><td>Anomalies total</td><td className="num mono bold">{total != null ? total.toLocaleString() : "—"}</td></tr>
            <tr><td>Quarantine</td><td className="num mono bold">{byKind.quarantine != null ? byKind.quarantine.toLocaleString() : "—"}</td></tr>
            <tr><td>Shadow</td><td className="num mono bold">{byKind.shadow != null ? byKind.shadow.toLocaleString() : "—"}</td></tr>
            <tr><td>No GIATA</td><td className="num mono bold">{giata != null ? giata.toLocaleString() : "—"}</td></tr>
          </tbody>
        </table>
      </Card>

      <div className="card" style={{ padding: 14, borderColor: "var(--warning-border)" }}>
        <div className="row" style={{ gap: 6, marginBottom: 10 }}><Icon.Warning size={14} stroke="var(--warning)" /><span className="bold" style={{ fontSize: 13 }}>Alerts</span></div>
        <ul style={{ margin: 0, padding: 0, listStyle: "none", fontSize: 12.5, display: "grid", gap: 8 }}>
          <li className="row" style={{ gap: 6 }}><span style={{ color: "var(--danger)" }}>•</span> {byKind.quarantine != null ? byKind.quarantine.toLocaleString() : "—"} hotels in quarantine</li>
          <li className="row" style={{ gap: 6 }}><span style={{ color: "var(--danger)" }}>•</span> {dispDistribution != null ? dispDistribution.toLocaleString() : "—"} suspect rows</li>
          <li className="row" style={{ gap: 6 }}><span style={{ color: "var(--warning)" }}>•</span> {giata != null ? giata.toLocaleString() : "—"} without GIATA</li>
        </ul>
        <Button kind="secondary" size="sm" block style={{ marginTop: 12 }} icon={<Icon.ChevronRight />} onClick={onAnomaly}>Open Anomaly Inbox</Button>
      </div>

      <Card title="Accuracy" sub="Sample audit" flush>
        <table className="tbl" style={{ fontSize: 12.5 }}>
          <tbody>
            <tr><td>False positive</td><td className="num mono bold muted">—</td></tr>
            <tr><td>Booking success</td><td className="num mono bold muted">—</td></tr>
            <tr><td>Avg consensus</td><td className="num mono bold muted">—</td></tr>
          </tbody>
        </table>
      </Card>
    </div>);
}

// ---------- Hotel detail drawer ----------
function HotelDrawer({ hotel: h, onClose }) {
  const [tab, setTab] = useState_hm("mappings");
  const vendorMaps = [
  { v: "Juniper RHK", code: "RHK_4321", conf: 97, method: "giata_consensus", success: "99.2%" },
  { v: "TravelGate STN", code: "TG_98765", conf: 94, method: "exact_match", success: "98.1%" },
  { v: "TourMind", code: "TM_5544", conf: 88, method: "algo_consensus", success: "96.4%" },
  { v: "Juniper ALB", code: "ALB_9871", conf: 72, method: "shadow_mode", success: "—" }].
  slice(0, h.vendors);

  return (
    <>
      <div className="drawer-veil" onClick={onClose} />
      <div className="drawer" style={{ width: "min(60%, 820px)" }}>
        <div className="modal__head" style={{ flexDirection: "column", alignItems: "stretch", gap: 10 }}>
          <div className="row">
            <div>
              <div className="row" style={{ gap: 8 }}>
                <h3 className="modal__title">{h.name}</h3>
                <StatusChip status={h.status} />
              </div>
              <div className="tiny muted" style={{ marginTop: 2 }}>{h.country.flag} {h.city}, {h.country.name} · <span style={{ color: "#d4a017" }}>{"★".repeat(h.stars)}</span></div>
            </div>
            <button className="modal__close" onClick={onClose} style={{ marginLeft: "auto" }}><Icon.X /></button>
          </div>
          <div className="row" style={{ gap: 16, fontSize: 12 }}>
            <span><span className="muted">Trekko ID</span> <span className="mono bold">{h.id}</span></span>
            <span><span className="muted">GIATA</span> <span className="mono bold">{h.giata || "—"}</span></span>
            <span><span className="muted">Confidence</span> </span><div style={{ width: 90 }}><ConfidenceBar value={h.confidence} /></div>
          </div>
        </div>
        <div className="modal__body">
          <Tabs active={tab} onChange={setTab} tabs={[
          { id: "mappings", label: "Vendor Mappings", count: vendorMaps.length },
          { id: "bookings", label: "Booking History" },
          { id: "audit", label: "Audit Log" },
          { id: "fix", label: "⚠ Manual Fix" }]
          } />

          {tab === "mappings" &&
          <div className="tbl-wrap" style={{ border: "1px solid var(--border)", borderRadius: 8 }}>
            <table className="tbl">
              <thead><tr><th>Vendor</th><th>Code</th><th>Confidence</th><th>Method</th><th className="num">Success</th><th className="col-actions"></th></tr></thead>
              <tbody>
                {vendorMaps.map((m) =>
                <tr key={m.code}>
                    <td className="bold">{m.v}</td>
                    <td className="mono tiny">{m.code}</td>
                    <td style={{ width: 110 }}><ConfidenceBar value={m.conf} /></td>
                    <td><Tag kind={m.method === "manual" ? "hotel" : "neutral"}>{HM_METHODS[m.method]}</Tag></td>
                    <td className="num mono">{m.success}</td>
                    <td className="col-actions">
                      <div className="row" style={{ gap: 4, justifyContent: "flex-end" }}>
                        <Button kind="ghost" size="sm" onClick={() => {
                          var aid = hotel.id || (hotel._real && (hotel._real.trekko_id || hotel._real.id));
                          var vidRaw = window.prompt("Vendor id to re-validate against this hotel? (the panel shows vendor name only)");
                          if (!vidRaw) return;
                          window.TrekkoAPI.revalidateHotelVendor(aid, Number(vidRaw))
                            .then(function () { toast({ title: "Re-validate queued", body: m.v, tone: "success" }); })
                            .catch(function (e) { toast({ title: "Re-validate failed", body: String(e && e.message || e), tone: "danger" }); });
                        }}>Re-validate</Button>
                        <Button kind="ghost" size="sm" iconOnly icon={<Icon.Trash />} onClick={() => toast({ title: "Unlinked", body: m.v, tone: "danger" })} />
                      </div>
                    </td>
                  </tr>
                )}
              </tbody>
            </table>
          </div>}

          {tab === "bookings" &&
          <ul style={{ margin: 0, padding: 0, listStyle: "none" }}>
            {[
            { ok: true, txt: "Booking BK-90412 confirmed via Juniper RHK", when: "2 hr ago" },
            { ok: true, txt: "Booking BK-90388 confirmed via TravelGate STN", when: "Yesterday" },
            { ok: false, txt: "Booking BK-90301 failed — rate unavailable (TG-MED)", when: "2 days ago" },
            { ok: true, txt: "Booking BK-90244 confirmed via Juniper RHK", when: "3 days ago" }].
            map((b, i) =>
            <li key={i} className="row" style={{ gap: 10, padding: "10px 4px", borderBottom: "1px solid var(--border-hairline)" }}>
              {b.ok ? <Icon.Check size={14} stroke="var(--success)" /> : <Icon.X size={14} stroke="var(--danger)" />}
              <span style={{ flex: 1, fontSize: 13 }}>{b.txt}</span><span className="tiny muted">{b.when}</span>
            </li>)}
          </ul>}

          {tab === "audit" &&
          <ul style={{ margin: 0, padding: 0, listStyle: "none" }}>
            {[
            { who: "Engine", act: "Shadow → live promotion (confidence 96%)", when: "2 hr ago", rev: false },
            { who: "Engine", act: "Merged vendor TM_5544 (algo consensus)", when: "1 day ago", rev: true },
            { who: "N. Demo", act: "Manual GIATA override → G744102", when: "3 days ago", rev: true, manual: true },
            { who: "Engine", act: "Quarantine resolved after 2 successful bookings", when: "5 days ago", rev: false }].
            map((a, i) =>
            <li key={i} className="row" style={{ gap: 10, padding: "10px 4px", borderBottom: "1px solid var(--border-hairline)", alignItems: "flex-start" }}>
              <Avatar name={a.who} size="sm" />
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 13 }}>{a.act} {a.manual && <Badge tone="indigo">manual_override</Badge>}</div>
                <div className="tiny muted">{a.who} · {a.when}</div>
              </div>
              {a.rev && <Button kind="ghost" size="sm" icon={<Icon.Refresh />} onClick={() => toast({ title: "Rolled back", tone: "warning" })}>Rollback</Button>}
            </li>)}
          </ul>}

          {tab === "fix" && <ManualFix hotel={h} />}
        </div>
      </div>
    </>);
}

function ManualFix({ hotel }) {
  return (
    <div className="col" style={{ gap: 14 }}>
      <div className="row" style={{ gap: 10, background: "var(--warning-bg)", color: "var(--warning)", padding: "10px 12px", borderRadius: 8 }}>
        <Icon.Warning stroke="currentColor" /><div style={{ fontSize: 13 }}>All manual changes are logged with a <b>manual_override</b> flag and are reversible from the Audit Log.</div>
      </div>

      <Card title="① Merge duplicate" sub="This hotel is the same as another">
        <div className="row" style={{ gap: 8 }}>
          <input className="input input--search" placeholder="Search hotel to merge with…" style={{ flex: 1 }} />
          <Button kind="primary" icon={<Icon.Copy />} onClick={() => {
            var aid = hotel.id || (hotel._real && (hotel._real.trekko_id || hotel._real.id));
            if (!aid) { toast({ title: "No source hotel id", tone: "danger" }); return; }
            var targetRaw = window.prompt("Target hotel ID to merge INTO?");
            if (!targetRaw) return;
            var target = Number(targetRaw);
            if (!target) { toast({ title: "Invalid target id", tone: "danger" }); return; }
            if (!window.confirm("Merge hotel #" + aid + " INTO hotel #" + target + "? This queues the merge.")) return;
            window.TrekkoAPI.mergeHotel(aid, target)
              .then(function () { toast({ title: "Merge queued", body: "src #" + aid + " -> tgt #" + target, tone: "success" }); })
              .catch(function (e) { toast({ title: "Merge failed", body: String(e && e.message || e), tone: "danger" }); });
          }}>Merge…</Button>
        </div>
      </Card>

      <Card title="② Unlink a mis-mapped vendor">
        <div className="col" style={{ gap: 8 }}>
          {["Juniper RHK · RHK_4321", "TravelGate STN · TG_98765", "TourMind · TM_5544"].slice(0, hotel.vendors).map((v) =>
          <label key={v} className="row" style={{ gap: 8 }}><input type="checkbox" className="checkbox" /> <span className="mono tiny">{v}</span></label>
          )}
        </div>
        <div style={{ height: 10 }} />
        <Field label="Reason (required)"><select className="select"><option>Wrong hotel matched</option><option>Different property</option><option>Vendor data error</option><option>Other</option></select></Field>
        <div style={{ height: 8 }} />
        <Button kind="danger" icon={<Icon.Trash />} onClick={() => {
          var aid = hotel.id || (hotel._real && (hotel._real.trekko_id || hotel._real.id));
          if (!aid) { toast({ title: "No accommodation_id", tone: "danger" }); return; }
          // Vendor unlink in this prototype was scoped per vendor row; we POST one at a time.
          // The checkbox list above is decorative; in production this becomes a multi-select.
          var raw = window.prompt("Vendor ID to unlink from this hotel?");
          if (raw == null || raw === "") return;
          var vid = Number(raw);
          if (!vid) { toast({ title: "Invalid vendor id", tone: "danger" }); return; }
          window.TrekkoAPI.unlinkHotelVendor(aid, vid)
            .then(function (r) { toast({ title: "Removed " + (r && r.removed || 0) + " row(s)", tone: "success" }); })
            .catch(function (e) { toast({ title: "Unlink failed", body: String(e && e.message || e), tone: "danger" }); });
        }}>Unlink selected</Button>
      </Card>

      <Card title="③ Fix master data">
        <MasterDataEditor hotel={hotel} />
      </Card>

      <div className="grid grid--2" style={{ gap: 14 }}>
        <Card title="④ Status">
          <Field label="Mark hotel as"><select className="select" defaultValue={hotel.status}><option value="active">Active</option><option value="closed">Closed</option><option value="under_review">Under review</option></select></Field>
          <div style={{ height: 8 }} />
          <HotelStatusApplyButton hotel={hotel} />
        </Card>
        <Card title="⑤ Force GIATA ID">
          <ForceGiataEditor hotel={hotel} />
        </Card>
      </div>
    </div>);
}

function VendorMappingModal({ vendor, onClose }) {
  return (
    <Modal open title={`${vendor.name} · hotel portfolio`} size="xl" onClose={onClose}
    footer={<Button kind="secondary" onClick={onClose}>Close</Button>}>
      <div className="grid grid--4" style={{ gap: 10, marginBottom: 14 }}>
        <Metric label="Hotels" value={(vendor.hotels / 1000).toFixed(0) + "k"} />
        <Metric label="Mapped" value={Math.round(vendor.mapped / vendor.hotels * 100) + "%"} />
        <Metric label="GIATA" value={vendor.giata + "%"} />
        <Metric label="Trust" value={vendor.trust + "%"} />
      </div>
      <div className="tbl-wrap" style={{ border: "1px solid var(--border)", borderRadius: 8 }}>
        <table className="tbl">
          <thead><tr><th>Trekko ID</th><th>Vendor code</th><th>Hotel</th><th>Confidence</th><th>Status</th></tr></thead>
          <tbody>
            {HM_HOTELS.slice(0, 8).map((h) =>
            <tr key={h.id}>
                <td className="mono tiny">{h.id}</td>
                <td className="mono tiny">{vendor.id.split("-")[0]}_{4000 + h.id.charCodeAt(8) % 9000}</td>
                <td className="bold">{h.name}</td>
                <td style={{ width: 110 }}><ConfidenceBar value={h.confidence} /></td>
                <td><StatusChip status={h.status} /></td>
              </tr>
            )}
          </tbody>
        </table>
      </div>
    </Modal>);
}

window.PageHotelMapping = PageHotelMapping;