/* The Foundry — Interactive Price List & Site Map */
/* global React, ReactDOM, FOUNDRY_DATA */
const { useState, useEffect, useMemo, useRef, useCallback } = React;

// ---------- Utilities ------------------------------------------------------
// Pricing/status/photos/etc. now live in Supabase (see supabase-data.js).
// data.js still provides the seed (polygons + initial values).

const fmtMoney = (n, opts = {}) => {
  if (n == null) return "—";
  return "$" + Math.round(n).toLocaleString("en-AU");
};
const fmtArea = (n) => n == null ? "—" : Number(n).toLocaleString("en-AU") + " m²";

// Hardstand tenancies are leased on their yard area, so the headline figure
// (hover chip, etc.) should be the hardstand m² — not the small internal slab.
const isHardstandTenancy = (t) => /^Hardstand/i.test(t.id);
const headlineArea = (t) =>
  isHardstandTenancy(t) ? (t.hardstand || t.internal) : (t.internal || t.hardstand);

// Auto-rolling "MONTH YEAR" stamp (e.g. for the rates eyebrow). Always reflects
// the calendar at view time so the brochure feels current without admin work.
function currentMonthYear() {
  return new Date()
    .toLocaleDateString("en-AU", { month: "long", year: "numeric" })
    .toUpperCase();
}
function currentMonthYearTitle() {
  // Title-case version ("May 2026") for inline use in the table meta.
  return new Date().toLocaleDateString("en-AU", { month: "long", year: "numeric" });
}
// Long-form date for the print stamp ("Printed 27 May 2026").
function currentLongDate() {
  return new Date().toLocaleDateString("en-AU", {
    day: "numeric", month: "long", year: "numeric"
  });
}

// ---------- Roles -----------------------------------------------------------
// Default behaviour: anyone who signs in gets FULL admin (can edit rates,
// status, tenancy details, photos). The list below carves out a restricted
// "sub-admin" role — currently used for MartinBros, who upload photos and
// edit tenancy descriptions but should not touch the rates table.
//
// To make a user sub-admin, EITHER:
//   1) add their email to SUB_ADMIN_EMAILS below, OR
//   2) set user_metadata.role = "subadmin" on their Supabase user record.
const SUB_ADMIN_EMAILS = [
  "marketing@martinbrothers.com.au"
];
function roleForUser(user) {
  if (!user) return null;
  const email = (user.email || "").toLowerCase();
  if (SUB_ADMIN_EMAILS.includes(email)) return "subadmin";
  const meta = (user.user_metadata && user.user_metadata.role) || "";
  if (meta === "subadmin") return "subadmin";
  return "admin";
}

// Natural sort for tenancy IDs: T1..T19 (with a/b), WH1..WH2, Hardstand 1..3
function tenancyOrder(id) {
  if (/^T\d/i.test(id)) {
    const m = id.match(/^T(\d+)([a-z]?)$/i);
    if (m) return [0, parseInt(m[1], 10), (m[2] || "").toLowerCase()];
  }
  if (/^WH/i.test(id)) {
    const n = parseInt(id.replace(/[^0-9]/g, ""), 10) || 0;
    return [1, n, ""];
  }
  if (/^Hardstand/i.test(id)) {
    const n = parseInt(id.replace(/[^0-9]/g, ""), 10) || 0;
    return [2, n, ""];
  }
  return [9, 0, id];
}
function byTenancyOrder(a, b) {
  const A = tenancyOrder(a.id),B = tenancyOrder(b.id);
  return A[0] - B[0] || A[1] - B[1] || A[2].localeCompare(B[2]);
}

// Map JS camelCase tenancy fields → Supabase snake_case columns and back.
const FIELD_TO_COL = {
  status: "status",
  net: "net",
  outgoings: "outgoings",
  totalAnnual: "total_annual",
  totalMonthly: "total_monthly",
  internal: "internal",
  hardstand: "hardstand",
  description: "description",
  bullets: "bullets",
  note: "note",
  photos: "photos",
  pano: "pano"
};
const COL_TO_FIELD = Object.fromEntries(Object.entries(FIELD_TO_COL).map(([k, v]) => [v, k]));

// Local-only preview panos (Claude Designs). Any pano stored in Supabase
// overrides these. Remove this map once the real pano is uploaded live.
const DEMO_PANOS = {}; // 360 tour feature disabled (per Angus, 2026-07)

function applyEditRowToTenancy(seed, row) {
  if (!row) return seed;
  const out = { ...seed };
  for (const [col, field] of Object.entries(COL_TO_FIELD)) {
    if (row[col] !== undefined && row[col] !== null) out[field] = row[col];
  }
  return out;
}

// ---------- Site Map -------------------------------------------------------
function SiteMap({ tenancies, selectedId, hoverId, setHoverId, onSelect, accent, density, multiSelect, theme }) {
  const { ANCILLARY, LABELS, VIEW, SURVEY } = FOUNDRY_DATA;
  const showLabels = density !== "minimal";
  const showAncillary = density !== "minimal";

  const tenancyFill = theme === "dark" ? "#e7dfd0" : "#ffffff";
  const tenancyStroke = theme === "dark" ? "#a89c84" : "#5a5240";
  const labelInkColor = theme === "dark" ? "#15120e" : "#1a1612";
  const leasedTextColor = "#1a1612"; // black on the cream hatch — readable in either theme
  const leasedStroke = theme === "dark" ? "#c64545" : "#b13838";

  return (
    <svg
      className="map"
      viewBox={`0 0 ${VIEW.w} ${VIEW.h}`}
      preserveAspectRatio="xMidYMid meet"
      role="img"
      aria-label="Site map of 63 Currumbin Creek Road">

      <defs>
        <pattern id="leased-hatch" patternUnits="userSpaceOnUse" width="14" height="14" patternTransform="rotate(45)">
          <rect width="14" height="14" fill={theme === "dark" ? "#3a1414" : "#f3dada"} />
          <line x1="0" y1="0" x2="0" y2="14" stroke={leasedStroke} strokeWidth="3" />
        </pattern>
        <pattern id="yard-hatch" patternUnits="userSpaceOnUse" width="10" height="10" patternTransform="rotate(45)">
          <rect width="10" height="10" fill="transparent" />
          <line x1="0" y1="0" x2="0" y2="10" stroke={theme === "dark" ? "#a89c84" : "#a89c84"} strokeWidth="1" opacity="0.5" />
        </pattern>
      </defs>

      {/* Architectural drawing — referenced as image (fast load, sharp at display res) */}
      <image
        className="survey-bg"
        href={SURVEY.src}
        x="0" y="0"
        width={VIEW.w} height={VIEW.h}
        preserveAspectRatio="xMidYMid meet"
      />

      {/* Zone labels (yards) */}
      {LABELS.map((l, i) => {
        const transform = l.rotate ? `rotate(${l.rotate} ${l.x} ${l.y})` : undefined;
        return (
          <g key={i}>
            <text
              x={l.x}
              y={l.y}
              transform={transform}
              className={l.road ? "road-label" : "zone-label"}
              fontSize={l.font || 18}
              textAnchor="middle">
              {l.text}
            </text>
            {l.sub && !l.road &&
            <text
              x={l.x}
              y={l.y + (l.font || 18) * 1.2}
              className="zone-sub"
              fontSize={(l.font || 18) * 0.65}
              textAnchor="middle">
                {l.sub}
              </text>
            }
          </g>);
      })}

      {/* Ancillary buildings (small non-leasable utility blocks) */}
      {showAncillary && ANCILLARY.map((a) =>
      <g key={a.id} className="ancillary" pointerEvents="none">
          <path d={a.d} fill={theme === "dark" ? "#3a342a" : "#5a5240"} stroke={theme === "dark" ? "#5a5240" : "#8e7048"} strokeWidth="1" opacity="0.7" />
          {density === "detailed" && <text x={a.cx} y={a.cy} className="ancillary-label" textAnchor="middle" dominantBaseline="middle" fontSize="12" fill={theme === "dark" ? "#c9c1b3" : "#f3ece0"}>{a.id}</text>}
        </g>
      )}

      {/* Tenancies — main polygon + extras (offices, yards) */}
      {tenancies.map((t) => {
        const inCompare = multiSelect && multiSelect.includes(t.id);
        const isActive = selectedId === t.id;
        const isSel = isActive || inCompare;
        const isHover = hoverId === t.id;
        const leased = t.status === "leased";

        const fillOpacity = isActive || isHover ? 0.85 : (inCompare ? 0.18 : 0);
        const dashStroke = inCompare && !isActive && !isHover;

        const baseFill = leased
          ? "url(#leased-hatch)"
          : (isActive || isHover || inCompare ? accent : "transparent");
        const baseStroke = isActive
          ? accent
          : (leased ? leasedStroke : (isHover || inCompare ? accent : "transparent"));
        const sw = isActive ? 4 : (inCompare ? 2 : 1.5);

        return (
          <g
            key={t.id}
            className={`tenancy ${leased ? "is-leased" : "is-available"} ${isSel ? "is-selected" : ""} ${isHover ? "is-hover" : ""}`}
            onClick={() => onSelect(t.id)}
            onMouseEnter={() => setHoverId(t.id)}
            onMouseLeave={() => setHoverId(null)}>

            {/* Yard extras drawn first so they appear behind the main building */}
            {t.extras && t.extras.filter((e) => e.kind === "yard").map((e, i) =>
              <path key={"y" + i}
                d={e.d}
                fill={leased ? "url(#leased-hatch)" : (isActive || isHover ? accent : "transparent")}
                stroke={baseStroke}
                strokeWidth={sw}
                strokeDasharray={dashStroke ? "8 4" : (isHover || isActive ? "6 4" : "0")}
                fillOpacity={leased ? 1 : fillOpacity}
                opacity={isActive || isHover ? 0.5 : 1} />
            )}

            {/* Main polygon */}
            <path
              d={t.d}
              fill={baseFill}
              stroke={baseStroke}
              strokeWidth={sw}
              strokeDasharray={dashStroke ? "8 4" : "0"}
              fillOpacity={leased ? 1 : fillOpacity}
              opacity={leased ? 0.95 : 1} />

            {/* Office extras drawn on top of main */}
            {t.extras && t.extras.filter((e) => e.kind !== "yard").map((e, i) =>
              <path key={"o" + i}
                d={e.d}
                fill={baseFill}
                stroke={baseStroke}
                strokeWidth={sw}
                strokeDasharray={dashStroke ? "8 4" : "0"}
                fillOpacity={leased ? 1 : fillOpacity}
                opacity={leased ? 0.95 : 1} />
            )}

            {/* Yard labels — e.g. "WH2 Hardstand" so the yard reads as
                belonging to its tenancy directly on the map (no click needed). */}
            {showLabels && t.extras && t.extras.filter((e) => e.kind === "yard" && e.label && e.lx != null).map((e, i) =>
              <text key={"yl" + i}
                x={e.lx}
                y={e.ly}
                className="yard-label"
                textAnchor="middle"
                dominantBaseline="middle"
                fontSize="30"
                pointerEvents="none"
                fill={leased ? leasedTextColor : labelInkColor}>
                {e.label}
              </text>
            )}

            {showLabels &&
            <g pointerEvents="none">
                <text
                x={t.cx}
                y={t.cy}
                className="tenancy-id"
                textAnchor="middle"
                dominantBaseline="middle"
                fontSize={tenancyFontSize(t)}
                fill={leased ? leasedTextColor : labelInkColor}>
                  {t.id.replace("Hardstand ", "H")}
                </text>
              </g>
            }
          </g>);
      })}
      {/* Yards disclaimer — bottom-left of the drawing */}
      <text x="40" y={VIEW.h - 30} className="yards-disclaimer" fontSize="13">
        Yards (shaded on hover) are shared common areas; boundaries indicative only.
      </text>
    </svg>);
}

function tenancyFontSize(t) {
  const area = (t.internal || 0) + (t.hardstand || 0);
  if (area > 1500) return 44;
  if (area > 500) return 36;
  if (area > 250) return 28;
  if (area > 150) return 22;
  return 18;
}

// ---------- Print helper -----------------------------------------------
function printWithOrientation(orientation) {
  let style = document.getElementById("print-orientation");
  if (!style) {
    style = document.createElement("style");
    style.id = "print-orientation";
    document.head.appendChild(style);
  }
  style.textContent = `@page { size: A4 ${orientation}; margin: ${orientation === "landscape" ? "10mm 14mm" : "16mm 14mm"}; }`;
  document.documentElement.setAttribute("data-print-mode", orientation);
  setTimeout(() => window.print(), 60);
}

// ---------- Header ---------------------------------------------------------
function Header({ adminUnlocked, onAdminClick, onLock, updatedAt, view, setView }) {
  const [printOpen, setPrintOpen] = useState(false);
  const printRef = useRef(null);

  useEffect(() => {
    if (!printOpen) return;
    function onDocClick(e) {
      if (printRef.current && !printRef.current.contains(e.target)) setPrintOpen(false);
    }
    document.addEventListener("mousedown", onDocClick);
    return () => document.removeEventListener("mousedown", onDocClick);
  }, [printOpen]);

  return (
    <header className="topbar">
      <a href="#" className="brand" onClick={(e) => e.preventDefault()}>
        <img src="foundry-logo.svg" alt="The Foundry" className="brand-logo"/>
      </a>

      <nav className="crumbs">
        <span>Price List</span>
        <span className="sep">/</span>
        <span className="now">63 Currumbin Creek Rd</span>
      </nav>

      <div className="topright">
        <div className="view-toggle" role="tablist">
          <button role="tab" className={view === "map" ? "active" : ""} onClick={() => setView("map")}>Map</button>
          <button role="tab" className={view === "table" ? "active" : ""} onClick={() => setView("table")}>Table</button>
        </div>
        <div className="print-menu" ref={printRef}>
          <button className="ghost" onClick={() => setPrintOpen(!printOpen)} title="Print / Save as PDF" aria-expanded={printOpen}>
            <Icon name="print" /> Print
            <svg viewBox="0 0 10 6" width="9" height="6" style={{marginLeft: 2, opacity: 0.6}}><path d="M1 1 L5 5 L9 1" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/></svg>
          </button>
          {printOpen && (
            <div className="print-menu-pop">
              <button onClick={() => { setPrintOpen(false); printWithOrientation("portrait"); }}>
                <span className="pm-icon" aria-hidden="true">▯</span>
                <span>Portrait</span>
              </button>
              <button onClick={() => { setPrintOpen(false); printWithOrientation("landscape"); }}>
                <span className="pm-icon pm-icon-l" aria-hidden="true">▭</span>
                <span>Landscape</span>
              </button>
            </div>
          )}
        </div>
        {adminUnlocked ?
        <button className="ghost admin-on" onClick={onLock} title="Lock admin">
            <Icon name="lock-open" /> Admin
          </button> :

        <button className="ghost" onClick={onAdminClick} title="Unlock admin">
            <Icon name="lock" /> Admin
          </button>
        }
      </div>
    </header>);

}

function Icon({ name }) {
  const paths = {
    "lock": "M6 10V7a4 4 0 1 1 8 0v3M5 10h10v8H5z",
    "lock-open": "M6 10V7a4 4 0 0 1 7-2.83M5 10h10v8H5z",
    "print": "M6 7V3h8v4M6 14H4v-5h12v5h-2M6 12h8v6H6z",
    "search": "M9 16a7 7 0 1 1 0-14 7 7 0 0 1 0 14zM14 14l5 5",
    "x": "M5 5l10 10M15 5L5 15",
    "compare": "M3 6h7M3 14h7M14 3v14M11 6l3-3 3 3M17 14l-3 3-3-3",
    "mail": "M3 5h14v10H3zM3 5l7 6 7-6",
    "check": "M4 10l4 4 8-8",
    "edit": "M3 17l3-1 9-9-2-2-9 9zM13 5l2 2",
    "filter": "M3 4h14l-5 7v5l-4 1v-6z",
    "plus": "M10 4v12M4 10h12",
    "download": "M10 3v10M6 9l4 4 4-4M4 17h12"
  };
  return (
    <svg viewBox="0 0 20 20" width="14" height="14" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d={paths[name] || ""} />
    </svg>);

}

// ---------- Stats Strip ----------------------------------------------------
function StatsStrip({ tenancies, updatedAt }) {
  const stats = useMemo(() => {
    const total = tenancies.length;
    const leased = tenancies.filter((t) => t.status === "leased").length;
    const available = total - leased;
    const totalInternal = tenancies.reduce((s, t) => s + (t.internal || 0), 0);
    const totalHardstand = tenancies.reduce((s, t) => s + (t.hardstand || 0), 0);
    const availableArea = tenancies.filter((t) => t.status === "available").reduce((s, t) => s + (t.internal || 0) + (t.hardstand || 0), 0);
    return { total, leased, available, totalArea: totalInternal + totalHardstand, availableArea };
  }, [tenancies]);

  return (
    <div className="stats">
      <Stat label="Available" value={stats.available} of={stats.total} accent />
      <Stat label="Leased" value={stats.leased} of={stats.total} />
      <Stat label="Total leasable area" value={fmtArea(stats.totalArea)} />
      <Stat label="Available area" value={fmtArea(stats.availableArea)} accent />
      <Stat label="Updated" value={updatedAt ? new Date(updatedAt).toLocaleDateString("en-AU", { day: "numeric", month: "short", year: "numeric" }) : currentMonthYearTitle()} />
    </div>);

}
function Stat({ label, value, of, accent }) {
  return (
    <div className={`stat ${accent ? "is-accent" : ""}`}>
      <div className="stat-label">{label}</div>
      <div className="stat-value">
        {value}
        {of != null && <span className="stat-of">/{of}</span>}
      </div>
    </div>);

}

// ---------- Command Panel (right side) -------------------------------------
function CommandPanel({
  tenancies, selected, setSelected, query, setQuery,
  filters, setFilters, compareList, addCompare, removeCompare,
  adminUnlocked, onEdit, photos, onPhotosChange, panos, onPanoChange, onEditField
}) {
  const filtered = useMemo(() => {
    return tenancies.slice().sort(byTenancyOrder).filter((t) => {
      if (filters.status !== "all" && t.status !== filters.status) return false;
      if (query) {
        const q = query.toLowerCase();
        if (!t.id.toLowerCase().includes(q)) return false;
      }
      const area = areaOf(t);
      if (area < filters.minArea || (filters.maxArea != null && area > filters.maxArea)) return false;
      const monthly = t.totalMonthly || 0;
      if (t.status === "available" && (monthly < filters.minPrice || (filters.maxPrice != null && monthly > filters.maxPrice))) return false;
      return true;
    });
  }, [tenancies, query, filters]);
  const areaCap = useMemo(() => sliderCap(tenancies.map(areaOf), 2500, 50), [tenancies]);
  const priceCap = useMemo(() => sliderCap(tenancies.map((t) => t.totalMonthly), 70000, 500), [tenancies]);

  if (selected) {
    return (
      <DetailsPanel
        tenancy={selected}
        onClose={() => setSelected(null)}
        onCompare={() => addCompare(selected.id)}
        canCompare={!compareList.includes(selected.id) && compareList.length < 3}
        adminUnlocked={adminUnlocked}
        onEdit={onEdit}
        onEditField={onEditField}
        photos={photos[selected.id] || []}
        onPhotosChange={(list) => onPhotosChange(selected.id, list)}
        pano={panos[selected.id] || null}
        onPanoChange={(url) => onPanoChange(selected.id, url)} />);


  }

  return (
    <div className="panel">
      <div className="panel-head panel-head-row">
        <div>
          <div className="panel-title">Tenancy directory</div>
          <div className="panel-sub">Click a unit on the map, or pick below.</div>
        </div>
        {compareList.length > 0 && (
          <button
            className="compare-chip"
            onClick={() => {
              const el = document.getElementById("compare-section");
              if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
            }}
            title="Jump to comparison">
            <Icon name="compare" />
            <span>Compare</span>
            <span className="compare-chip-count">{compareList.length}</span>
          </button>
        )}
      </div>

      <div className="search-row">
        <div className="search-wrap">
          <Icon name="search" />
          <input
            type="search"
            placeholder="Search T1, WH2, Hardstand…"
            value={query}
            onChange={(e) => setQuery(e.target.value)} />
          
        </div>
      </div>

      <Filters filters={filters} setFilters={setFilters} count={filtered.length} total={tenancies.length} areaCap={areaCap} priceCap={priceCap} />

      <div className="list-wrap">
        <div className="list-head">
          <div>Unit</div>
          <div>Area</div>
          <div className="right">Gross / month</div>
          <div></div>
        </div>
        <div className="list">
          {filtered.length === 0 && <div className="list-empty">No matches — adjust filters.</div>}
          {filtered.map((t) => {
            const inCompare = compareList.includes(t.id);
            return (
              <div key={t.id} className={`list-row ${t.status}`} onClick={() => setSelected(t.id)}>
                <div className="row-id">
                  <span className={`dot ${t.status}`} />
                  {t.id}
                </div>
                <div className="row-area mono">
                  {fmtArea(t.internal)}
                  {t.hardstand && <span className="hs"> + {t.hardstand}m² HS</span>}
                </div>
                <div className="row-price mono right">
                  {t.status === "leased"
                    ? <span className="leased-tag">LEASED</span>
                    : fmtMoney(t.totalMonthly)}
                </div>
                <div className="row-actions">
                  <button
                    className={`compare-pill ${inCompare ? "in" : ""}`}
                    onClick={(e) => {e.stopPropagation();inCompare ? removeCompare(t.id) : addCompare(t.id);}}
                    disabled={!inCompare && compareList.length >= 3}
                    title={inCompare ? "Remove from compare" : "Add to compare"}>
                    
                    {inCompare ? "✓" : "+"}
                  </button>
                </div>
              </div>);

          })}
        </div>
      </div>
    </div>);

}

// ---------- Filters --------------------------------------------------------
// Slider top ends follow the largest unit / rent so no tenancy is hidden by default.
// A null max in filters means "no upper limit".
const areaOf = (t) => t.internal != null ? t.internal : (t.hardstand || 0);
function sliderCap(values, floor, step) {
  const m = Math.max(0, ...values.filter((v) => v != null));
  return Math.max(floor, Math.ceil(m / step) * step);
}

function Filters({ filters, setFilters, count, total, areaCap, priceCap }) {
  return (
    <div className="filters">
      <div className="filter-row">
        <div className="seg">
          <button className={filters.status === "all" ? "on" : ""} onClick={() => setFilters({ ...filters, status: "all" })}>All <span className="seg-n">{total}</span></button>
          <button className={filters.status === "available" ? "on" : ""} onClick={() => setFilters({ ...filters, status: "available" })}>Available</button>
          <button className={filters.status === "leased" ? "on" : ""} onClick={() => setFilters({ ...filters, status: "leased" })}>Leased</button>
        </div>
        <div className="count">{count} shown</div>
      </div>

      <div className="filter-row">
        <RangeRow
          label="Floor area"
          unit="m²"
          min={0} max={areaCap} step={50}
          lo={filters.minArea} hi={filters.maxArea ?? areaCap}
          onChange={(lo, hi) => setFilters({ ...filters, minArea: lo, maxArea: hi >= areaCap ? null : hi })} />
        
      </div>
      <div className="filter-row">
        <RangeRow
          label="Monthly rent"
          unit=""
          min={0} max={priceCap} step={500}
          lo={filters.minPrice} hi={filters.maxPrice ?? priceCap}
          onChange={(lo, hi) => setFilters({ ...filters, minPrice: lo, maxPrice: hi >= priceCap ? null : hi })}
          money />
        
      </div>
    </div>);

}

function RangeRow({ label, unit, min, max, step, lo, hi, onChange, money }) {
  const display = (v) => money ? "$" + Math.round(v).toLocaleString("en-AU") : v.toLocaleString("en-AU") + (unit ? " " + unit : "");
  const loPct = ((lo - min) / (max - min)) * 100;
  const hiPct = ((hi - min) / (max - min)) * 100;
  return (
    <div className="range">
      <div className="range-head">
        <span>{label}</span>
        <span className="mono">{display(lo)} — {display(hi)}</span>
      </div>
      <div className="dual-range">
        <div className="dr-track">
          <div className="dr-fill" style={{ left: loPct + "%", right: (100 - hiPct) + "%" }} />
        </div>
        <input
          type="range" min={min} max={max} step={step} value={lo}
          className="dr-input dr-input-lo"
          onChange={(e) => onChange(Math.min(Number(e.target.value), hi - step), hi)}
        />
        <input
          type="range" min={min} max={max} step={step} value={hi}
          className="dr-input dr-input-hi"
          onChange={(e) => onChange(lo, Math.max(Number(e.target.value), lo + step))}
        />
      </div>
    </div>
  );
}

// ---------- Photo Gallery --------------------------------------------------
function PhotoGallery({ tenancyId, photos, onPhotosChange, adminUnlocked }) {
  const fileRef = useRef(null);
  const [lightbox, setLightbox] = useState(null);
  const [uploading, setUploading] = useState(false);
  const [dragOver, setDragOver] = useState(false);
  const list = photos || [];

  // Gallery keyboard nav (← → to move, Esc to close)
  useEffect(() => {
    if (lightbox == null) return;
    function onKey(e) {
      if (e.key === "Escape") setLightbox(null);
      else if (e.key === "ArrowRight") setLightbox((i) => (i + 1) % list.length);
      else if (e.key === "ArrowLeft") setLightbox((i) => (i - 1 + list.length) % list.length);
    }
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [lightbox, list.length]);

  function pickFile() { fileRef.current?.click(); }

  async function uploadFiles(files) {
    if (!files || files.length === 0) return;
    const remaining = Math.max(0, 4 - list.length);
    if (remaining === 0) { alert("This tenancy already has 4 photos. Remove one first."); return; }
    const accepted = Array.from(files)
      .filter((f) => f.type.startsWith("image/"))
      .slice(0, remaining);
    if (accepted.length === 0) { alert("Please drop image files."); return; }
    setUploading(true);
    try {
      const next = [...list];
      for (const file of accepted) {
        if (file.size > 10 * 1024 * 1024 && !confirm(`"${file.name}" is over 10 MB. Continue?`)) continue;
        const url = await window.FOUNDRY_BACKEND.uploadPhoto(tenancyId, file);
        next.push(url);
      }
      onPhotosChange(next.slice(0, 4));
    } catch (err) {
      alert("Upload failed: " + (err.message || err));
    } finally {
      setUploading(false);
    }
  }

  async function onFile(e) {
    await uploadFiles(e.target.files);
    e.target.value = "";
  }

  function onDragOver(e) {
    if (!adminUnlocked || uploading || list.length >= 4) return;
    e.preventDefault();
    e.dataTransfer.dropEffect = "copy";
    setDragOver(true);
  }
  function onDragLeave(e) {
    // Only clear when the drag actually leaves the gallery, not when it
    // crosses over a child element.
    if (e.currentTarget.contains(e.relatedTarget)) return;
    setDragOver(false);
  }
  async function onDrop(e) {
    if (!adminUnlocked || uploading) return;
    e.preventDefault();
    setDragOver(false);
    await uploadFiles(e.dataTransfer.files);
  }

  async function removePhoto(idx) {
    const removed = list[idx];
    const next = list.filter((_, i) => i !== idx);
    onPhotosChange(next);
    // Best-effort delete from Storage; ignore failures (URL still works on cache)
    try { await window.FOUNDRY_BACKEND.deletePhoto(removed); } catch (e) {}
  }

  if (list.length === 0 && !adminUnlocked) return null;

  const canAdd = adminUnlocked && list.length < 4 && !uploading;
  const dropHandlers = adminUnlocked
    ? { onDragOver, onDragLeave, onDrop }
    : {};

  return (
    <div className={`photo-gallery ${dragOver ? "is-drag-over" : ""}`} {...dropHandlers}>
      <div className="pg-head">
        <span className="pg-title">Photos {list.length > 1 && <span className="pg-count">{list.length}</span>}</span>
        {adminUnlocked && (
          <button className="ghost-sm" onClick={pickFile} disabled={!canAdd}>
            {uploading ? "Uploading…" : "+ Add"}
          </button>
        )}
      </div>
      <input ref={fileRef} type="file" accept="image/*" multiple style={{ display: "none" }} onChange={onFile} />
      {list.length > 0 ? (
        <div className={`pg-grid pg-grid-${Math.min(list.length, 4)}`}>
          {list.map((src, i) => (
            <div key={i} className="pg-cell" onClick={() => setLightbox(i)}>
              <img src={src} alt={`${tenancyId} photo ${i + 1}`} />
              {adminUnlocked && (
                <button className="pg-del" onClick={(e) => { e.stopPropagation(); removePhoto(i); }} title="Remove photo">×</button>
              )}
            </div>
          ))}
        </div>
      ) : (
        canAdd && (
          <button className="pg-empty" onClick={pickFile}>
            <Icon name="plus" /> {dragOver ? "Release to upload" : "Drop in up to 4 photos"}
          </button>
        )
      )}
      {lightbox != null && list[lightbox] && (
        <div className="pg-lightbox" onClick={() => setLightbox(null)}>
          <button className="pg-lightbox-close" onClick={(e) => { e.stopPropagation(); setLightbox(null); }}><Icon name="x"/></button>
          {list.length > 1 && (
            <button className="pg-nav prev" onClick={(e) => { e.stopPropagation(); setLightbox((i) => (i - 1 + list.length) % list.length); }} title="Previous (←)">‹</button>
          )}
          <div className="pg-lightbox-stage" onClick={(e) => e.stopPropagation()}>
            <img src={list[lightbox]} alt={`${tenancyId} photo ${lightbox + 1}`} />
          </div>
          {list.length > 1 && (
            <button className="pg-nav next" onClick={(e) => { e.stopPropagation(); setLightbox((i) => (i + 1) % list.length); }} title="Next (→)">›</button>
          )}
          {list.length > 1 && (
            <div className="pg-counter" onClick={(e) => e.stopPropagation()}>
              <span>{lightbox + 1} / {list.length}</span>
              <span className="pg-dots">
                {list.map((_, i) => (
                  <button key={i} className={`pg-dot ${i === lightbox ? "active" : ""}`} onClick={() => setLightbox(i)} title={`Photo ${i + 1}`} />
                ))}
              </span>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ---------- Tenancy Description + bullets --------------------------------
function TenancyDescription({ tenancy: t, adminUnlocked, onEditField }) {
  const bullets = Array.isArray(t.bullets) ? t.bullets : [];
  const [draftBullet, setDraftBullet] = useState("");

  function setDescription(v) { onEditField(t.id, "description", v); }
  function setBullets(next) { onEditField(t.id, "bullets", next); }
  function addBullet() {
    const v = draftBullet.trim();
    if (!v) return;
    setBullets([...bullets, v]);
    setDraftBullet("");
  }
  function editBullet(i, v) {
    const next = bullets.slice();
    next[i] = v;
    setBullets(next);
  }
  function removeBullet(i) {
    setBullets(bullets.filter((_, idx) => idx !== i));
  }

  const hasContent = t.description || bullets.length > 0;
  if (!hasContent && !adminUnlocked) return null;

  return (
    <div className="tenancy-desc">
      <div className="td-head">
        <span className="td-title">About this {t.id.startsWith("WH") ? "warehouse" : t.id.startsWith("Hardstand") ? "hardstand" : "tenancy"}</span>
      </div>

      {adminUnlocked ? (
        <textarea
          className="td-textarea"
          value={t.description || ""}
          placeholder="Short description — what's the space like, fitout, access, condition…"
          onChange={(e) => setDescription(e.target.value)}
          rows={3}
        />
      ) : (
        t.description && <p className="td-body">{t.description}</p>
      )}

      {(bullets.length > 0 || adminUnlocked) && (
        <ul className="td-bullets">
          {bullets.map((b, i) => (
            <li key={i}>
              {adminUnlocked ? (
                <>
                  <input
                    type="text"
                    className="td-bullet-input"
                    value={b}
                    onChange={(e) => editBullet(i, e.target.value)}
                  />
                  <button className="td-bullet-del" onClick={() => removeBullet(i)} title="Remove">×</button>
                </>
              ) : (
                <span>{b}</span>
              )}
            </li>
          ))}
          {adminUnlocked && (
            <li className="td-bullet-add">
              <input
                type="text"
                className="td-bullet-input"
                placeholder="e.g. 2 dedicated parking spots"
                value={draftBullet}
                onChange={(e) => setDraftBullet(e.target.value)}
                onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); addBullet(); } }}
              />
              <button className="td-bullet-add-btn" onClick={addBullet} disabled={!draftBullet.trim()}>+ Add</button>
            </li>
          )}
        </ul>
      )}
    </div>
  );
}

// ---------- 360 Panorama Tour ----------------------------------------------
// Pannellum (equirectangular WebGL viewer) loaded on demand from CDN so the
// page stays light until someone actually opens a tour.
let __pnlmPromise = null;
function ensurePannellum() {
  if (window.pannellum) return Promise.resolve();
  if (__pnlmPromise) return __pnlmPromise;
  __pnlmPromise = new Promise((resolve, reject) => {
    const css = document.createElement("link");
    css.rel = "stylesheet";
    css.href = "https://cdn.jsdelivr.net/npm/pannellum@2.5.6/build/pannellum.css";
    document.head.appendChild(css);
    const s = document.createElement("script");
    s.src = "https://cdn.jsdelivr.net/npm/pannellum@2.5.6/build/pannellum.js";
    s.onload = () => resolve();
    s.onerror = () => reject(new Error("Failed to load the 360 viewer"));
    document.head.appendChild(s);
  });
  return __pnlmPromise;
}

// Downscale + recompress a raw 360 export (often 10000×5000+, 40MB+) to a
// web-friendly equirectangular JPEG. 8192px keeps us within the common WebGL
// max-texture limit and lands the file around a few MB.
async function downscalePano(file, maxW = 8192, quality = 0.82) {
  if (!file.type.startsWith("image/")) throw new Error("Not an image file");
  const url = URL.createObjectURL(file);
  try {
    const img = await new Promise((res, rej) => {
      const im = new Image();
      im.onload = () => res(im);
      im.onerror = () => rej(new Error("Could not read image"));
      im.src = url;
    });
    const w = img.naturalWidth, h = img.naturalHeight;
    const scale = w > maxW ? maxW / w : 1;
    if (scale === 1 && file.size < 6 * 1024 * 1024) return file; // already fine
    const cw = Math.round(w * scale), ch = Math.round(h * scale);
    const canvas = document.createElement("canvas");
    canvas.width = cw; canvas.height = ch;
    canvas.getContext("2d").drawImage(img, 0, 0, cw, ch);
    const blob = await new Promise((r) => canvas.toBlob(r, "image/jpeg", quality));
    if (!blob) return file;
    const name = file.name.replace(/\.[^.]+$/, "") + "-360.jpg";
    return new File([blob], name, { type: "image/jpeg" });
  } finally {
    URL.revokeObjectURL(url);
  }
}

function PanoTour({ tenancyId, pano, onPanoChange, adminUnlocked }) {
  return null; // 360 tour feature disabled (per Angus, 2026-07)
  // eslint-disable-next-line no-unreachable
  const fileRef = useRef(null);
  const [uploading, setUploading] = useState(false);
  const [open, setOpen] = useState(false);
  const [dragOver, setDragOver] = useState(false);
  const [viewerErr, setViewerErr] = useState(null);
  const viewerRef = useRef(null);
  const instanceRef = useRef(null);

  useEffect(() => {
    if (!open || !pano) return;
    let cancelled = false;
    setViewerErr(null);
    ensurePannellum().then(() => {
      if (cancelled || !viewerRef.current) return;
      instanceRef.current = window.pannellum.viewer(viewerRef.current, {
        type: "equirectangular",
        panorama: pano,
        autoLoad: true,
        autoRotate: -2,
        showZoomCtrl: true,
        showFullscreenCtrl: true,
        compass: false,
        hfov: 100,
        friction: 0.15
      });
    }).catch((e) => { if (!cancelled) setViewerErr(e.message || "Could not load 360 viewer"); });
    return () => {
      cancelled = true;
      if (instanceRef.current) { try { instanceRef.current.destroy(); } catch (e) {} instanceRef.current = null; }
    };
  }, [open, pano]);

  useEffect(() => {
    if (!open) return;
    const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [open]);

  function pick() { fileRef.current?.click(); }

  async function handleFiles(files) {
    const file = Array.from(files || []).find((f) => f.type.startsWith("image/"));
    if (!file) { alert("Please choose an equirectangular (360°) image."); return; }
    setUploading(true);
    try {
      const resized = await downscalePano(file);
      const url = await window.FOUNDRY_BACKEND.uploadPhoto(tenancyId + "-360", resized);
      if (pano) { try { await window.FOUNDRY_BACKEND.deletePhoto(pano); } catch (e) {} }
      onPanoChange(url);
    } catch (err) {
      alert("360 upload failed: " + (err.message || err));
    } finally {
      setUploading(false);
    }
  }

  async function onFile(e) { await handleFiles(e.target.files); e.target.value = ""; }

  async function removePano() {
    if (!confirm("Remove the 360 tour for " + tenancyId + "?")) return;
    const old = pano;
    onPanoChange(null);
    try { await window.FOUNDRY_BACKEND.deletePhoto(old); } catch (e) {}
  }

  const dropHandlers = adminUnlocked ? {
    onDragOver: (e) => { if (uploading) return; e.preventDefault(); e.dataTransfer.dropEffect = "copy"; setDragOver(true); },
    onDragLeave: (e) => { if (e.currentTarget.contains(e.relatedTarget)) return; setDragOver(false); },
    onDrop: async (e) => { e.preventDefault(); setDragOver(false); await handleFiles(e.dataTransfer.files); }
  } : {};

  if (!pano && !adminUnlocked) return null;

  return (
    <div className={`pano-tour ${dragOver ? "is-drag-over" : ""}`} {...dropHandlers}>
      <div className="pt-head">
        <span className="pt-title">360° Virtual Tour</span>
        {adminUnlocked && pano && (
          <div className="pt-admin">
            <button className="ghost-sm" onClick={pick} disabled={uploading}>{uploading ? "Uploading…" : "Replace"}</button>
            <button className="ghost-sm pt-remove" onClick={removePano} disabled={uploading}>Remove</button>
          </div>
        )}
      </div>
      <input ref={fileRef} type="file" accept="image/*" style={{ display: "none" }} onChange={onFile} />
      {pano ? (
        <button className="pt-btn" onClick={() => setOpen(true)}>
          <span className="pt-launch-ico">▸</span> Launch 360 tour
        </button>
      ) : (
        <button className="pt-empty" onClick={pick} disabled={uploading}>
          {uploading ? "Processing & uploading…" : (dragOver ? "Release to upload 360°" : "Drop in a 360° panorama — auto-resized for the web")}
        </button>
      )}
      {open && pano && (
        <div className="pt-modal" onClick={() => setOpen(false)}>
          <button className="pg-lightbox-close" onClick={(e) => { e.stopPropagation(); setOpen(false); }}><Icon name="x" /></button>
          <div className="pt-viewer-wrap" onClick={(e) => e.stopPropagation()}>
            <div ref={viewerRef} className="pt-viewer" />
            {viewerErr && <div className="pt-err">{viewerErr}</div>}
          </div>
          <div className="pt-hint mono">{tenancyId} · Drag to look around · scroll to zoom</div>
        </div>
      )}
    </div>
  );
}

// ---------- Details Panel --------------------------------------------------
function DetailsPanel({ tenancy: t, onClose, onCompare, canCompare, adminUnlocked, onEdit, photos, onPhotosChange, pano, onPanoChange, onEditField }) {
  const enquireBody = encodeURIComponent(
    `Hi,\n\nI'm interested in tenancy ${t.id} at 63 Currumbin Creek Road, Currumbin Waters.\n\n` +
    `Internal floor area: ${fmtArea(t.internal)}\n` +
    (t.hardstand ? `Hardstand: ${t.hardstand} m²\n` : "") +
    `\nPlease send me the latest pricing and availability.\n\nRegards,`
  );
  const mailto = `mailto:joe@pcrew.com.au?subject=${encodeURIComponent("Enquiry — " + t.id + " — 63 Currumbin Creek Rd")}&body=${enquireBody}`;

  return (
    <div className="panel details">
      <div className="details-head">
        <button className="back" onClick={onClose}>← Back</button>
        <button className="iconbtn" onClick={onClose} title="Close"><Icon name="x" /></button>
      </div>

      <div className="details-id">
        <div className="details-id-mark">{t.id}</div>
        <div className={`status-pill ${t.status}`}>
          <span className="dot" /> {t.status === "leased" ? "Leased" : "Available now"}
        </div>
        {t.note && <div className="details-note">{t.note}</div>}
      </div>

      <PdfLink className="btn ghost brochure-btn" href={FOUNDRY_DATA.BROCHURE_LINKS && FOUNDRY_DATA.BROCHURE_LINKS[t.id]}><Icon name="download" /> Download brochure</PdfLink>

      <div className="details-areas">
        <div className="area-card">
          <div className="card-label">Internal floor area</div>
          <div className="card-value mono">{fmtArea(t.internal)}</div>
        </div>
        {t.hardstand &&
        <div className="area-card">
            <div className="card-label">Hardstand</div>
            <div className="card-value mono">{fmtArea(t.hardstand)}</div>
          </div>
        }
      </div>

      <PanoTour tenancyId={t.id} pano={pano} onPanoChange={onPanoChange} adminUnlocked={adminUnlocked} />

      <PhotoGallery tenancyId={t.id} photos={photos} onPhotosChange={onPhotosChange} adminUnlocked={adminUnlocked} />

      <TenancyDescription tenancy={t} adminUnlocked={adminUnlocked} onEditField={onEditField} />

      {t.status === "available" ?
      <>
          <div className="hero-price">
            <div className="hp-label">Total gross / month</div>
            <div className="hp-value mono">{fmtMoney(t.totalMonthly)}</div>
            <div className="hp-sub mono">{fmtMoney(t.totalAnnual)} / annum · ex GST</div>
          </div>

          <div className="breakdown">
            <Line label="Net rental" value={t.net} />
            <Line label="Outgoings" value={t.outgoings} />
            <Line label="Total gross / annum" value={t.totalAnnual} strong />
          </div>

          <div className="cta-row">
            <a className="btn primary" href={mailto}><Icon name="mail" /> Enquire about {t.id}</a>
            <button className="btn ghost" onClick={onCompare} disabled={!canCompare}><Icon name="compare" /> Compare</button>
          </div>
        </> :

      <div className="leased-state">
          <div className="leased-big">LEASED</div>
          <div className="leased-sub">This {t.id.startsWith("WH") ? "warehouse" : t.id.startsWith("Hardstand") ? "hardstand" : "unit"} is currently let. Register your interest to be notified when the lease expires.</div>
          <a className="btn ghost" href={`mailto:joe@pcrew.com.au?subject=${encodeURIComponent("Register interest — " + t.id)}`}><Icon name="mail" /> Register interest</a>
        </div>
      }

      {adminUnlocked &&
      <div className="admin-actions">
          <button className="btn admin" onClick={() => onEdit(t.id)}><Icon name="edit" /> Edit pricing & status</button>
        </div>
      }
    </div>);

}
function Line({ label, value, strong }) {
  return (
    <div className={`bk-line ${strong ? "strong" : ""}`}>
      <div className="bk-label">{label}</div>
      <div className="bk-value mono">{fmtMoney(value)}</div>
    </div>);

}

// ---------- Compare Drawer -------------------------------------------------
// ---------- Compare Section (inline below workspace) -----------------------
function CompareDrawer({ tenancies, ids, removeCompare, clear, onOpen, adminUnlocked }) {
  const items = ids.map((id) => tenancies.find((t) => t.id === id)).filter(Boolean);
  if (items.length === 0) return null;
  return (
    <section id="compare-section" className="compare-section">
      <div className="compare-section-head">
        <div>
          <h2 className="section-h">Comparing {items.length} {items.length === 1 ? "tenancy" : "tenancies"}</h2>
          <div className="table-meta">Selected from the map above · side-by-side overview</div>
        </div>
        <button className="ghost-sm" onClick={clear}>Clear all</button>
      </div>
      <div className="compare-grid">
        {items.map((t) => {
          const bullets = Array.isArray(t.bullets) ? t.bullets : [];
          return (
            <div key={t.id} className="cmp-card">
              <div className="cmp-head">
                <div className="cmp-id">{t.id}</div>
                <button className="iconbtn" onClick={() => removeCompare(t.id)} title="Remove from compare"><Icon name="x" /></button>
              </div>
              <div className={`cmp-status ${t.status}`}>{t.status}</div>
              <CmpRow label="Internal" value={fmtArea(t.internal)} />
              <CmpRow label="Hardstand" value={fmtArea(t.hardstand)} />

              {/* Pricing — always visible (agent-focused). Used to be admin-gated;
                  now the comparison view is treated as a fact sheet so agents
                  and prospects see the same numbers. */}
              {t.status === "available" && (
                <div className="cmp-pricing">
                  <CmpRow label="Net p/a" value={fmtMoney(t.net)} />
                  <CmpRow label="Outgoings p/a" value={fmtMoney(t.outgoings)} />
                  <CmpRow label="Gross p/a" value={fmtMoney(t.totalAnnual)} strong />
                  <CmpRow label="Gross p/m" value={fmtMoney(t.totalMonthly)} strong accent />
                </div>
              )}

              {/* Description + features — always shown so the comparison
                  is enough on its own to choose between tenancies. */}
              {(t.description || bullets.length > 0) && (
                <div className="cmp-extra">
                  {t.description && <p className="cmp-desc">{t.description}</p>}
                  {bullets.length > 0 && (
                    <ul className="cmp-bullets">
                      {bullets.map((b, i) => <li key={i}>{b}</li>)}
                    </ul>
                  )}
                </div>
              )}

              <button className="btn ghost" onClick={() => onOpen(t.id)}>View details</button>
            </div>
          );
        })}
      </div>
    </section>);

}
function CmpRow({ label, value, strong, accent }) {
  return (
    <div className={`cmp-row ${strong ? "strong" : ""} ${accent ? "accent" : ""}`}>
      <span>{label}</span>
      <span className="mono">{value}</span>
    </div>);

}

// ---------- Pricing Table --------------------------------------------------
function PricingTable({ tenancies, onSelect, adminUnlocked, role, onEdit, onToggleLeased, onEditField }) {
  // Sub-admins (e.g. MartinBros) can view the rates table but cannot
  // edit it. Only full admins get inline editors + status toggles.
  const canEditRates = adminUnlocked && role === "admin";
  const sorted = useMemo(() => tenancies.slice().sort(byTenancyOrder), [tenancies]);
  return (
    <div className="table-wrap">
      <div className="table-head-row">
        <h2 className="section-h">Schedule of rental rates</h2>
        <div className="table-meta">
          All figures are ex GST · {currentMonthYearTitle()}
          {canEditRates && " · CLICK ANY VALUE TO EDIT"}
        </div>
      </div>
      <table className="rental-table">
        <thead>
          <tr>
            <th>Tenancy</th>
            <th className="num">Internal m²</th>
            <th className="num">Hardstand m²</th>
            <th className="num">Net $ p/a</th>
            <th className="num">Outgoings $ p/a</th>
            <th className="num">Total gross $ p/a</th>
            <th className="num">Total gross $ p/m</th>
            {canEditRates && <th>Status</th>}
          </tr>
        </thead>
        <tbody>
          {sorted.map((t) =>
          <tr key={t.id} className={t.status} onClick={() => !canEditRates && onSelect(t.id)}>
              <td className="t-id">
                <span className={`dot ${t.status}`} />{t.id}
              </td>
              <td className="num mono">
                {canEditRates ?
              <InlineNumber value={t.internal} onCommit={(v) => onEditField(t.id, "internal", v)} /> :
              t.internal || ""}
              </td>
              <td className="num mono">
                {canEditRates ?
              <InlineNumber value={t.hardstand} onCommit={(v) => onEditField(t.id, "hardstand", v)} /> :
              t.hardstand || ""}
              </td>
              {t.status === "leased" && !canEditRates ?
            <td className="num leased-cell" colSpan={4}>LEASED</td> :

            <>
                  <td className="num mono">
                    {canEditRates ?
                <InlineNumber value={t.net} onCommit={(v) => onEditField(t.id, "net", v)} /> :
                fmtMoney(t.net)}
                  </td>
                  <td className="num mono">
                    {canEditRates ?
                <InlineNumber value={t.outgoings} onCommit={(v) => onEditField(t.id, "outgoings", v)} /> :
                fmtMoney(t.outgoings)}
                  </td>
                  <td className="num mono">
                    {canEditRates ?
                <InlineNumber value={t.totalAnnual} onCommit={(v) => onEditField(t.id, "totalAnnual", v)} /> :
                fmtMoney(t.totalAnnual)}
                  </td>
                  <td className="num mono strong">
                    {canEditRates ?
                <InlineNumber value={t.totalMonthly} onCommit={(v) => onEditField(t.id, "totalMonthly", v)} /> :
                fmtMoney(t.totalMonthly)}
                  </td>
                </>
            }
              {canEditRates &&
            <td className="admin-col">
                  <button
                className={`status-toggle ${t.status}`}
                onClick={(e) => {e.stopPropagation();onToggleLeased(t.id);}}
                title={`Mark ${t.status === "leased" ? "available" : "leased"}`}>
                
                    {t.status === "leased" ? "● LEASED" : "○ AVAILABLE"}
                  </button>
                </td>
            }
            </tr>
          )}
        </tbody>
      </table>
    </div>);

}

function InlineNumber({ value, onCommit }) {
  const [v, setV] = useState(value == null ? "" : String(value));
  useEffect(() => {setV(value == null ? "" : String(value));}, [value]);
  return (
    <input
      type="text"
      className="inline-number mono"
      value={v}
      onChange={(e) => setV(e.target.value.replace(/[^0-9.]/g, ""))}
      onBlur={() => onCommit(v === "" ? null : Number(v))}
      onClick={(e) => e.stopPropagation()}
      onKeyDown={(e) => {if (e.key === "Enter") e.target.blur();}} />);


}

// ---------- Admin Sign-in Gate ---------------------------------------------
function PinGate({ open, onClose, onSuccess }) {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [stage, setStage] = useState("idle"); // idle | sending | error
  const [errMsg, setErrMsg] = useState("");
  const emailRef = useRef(null);
  useEffect(() => {
    if (open) {
      setEmail(""); setPassword(""); setStage("idle"); setErrMsg("");
      setTimeout(() => emailRef.current?.focus(), 50);
    }
  }, [open]);

  if (!open) return null;

  async function submit(e) {
    e.preventDefault();
    const trimmed = email.trim();
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
      setErrMsg("Enter a valid email address."); setStage("error"); return;
    }
    if (!password) {
      setErrMsg("Enter your password."); setStage("error"); return;
    }
    setStage("sending"); setErrMsg("");
    try {
      await window.FOUNDRY_BACKEND.signInWithPassword(trimmed, password);
      // Auth listener in App will unlock admin mode + close this modal
    } catch (err) {
      setErrMsg(err.message || "Sign-in failed. Check your email & password."); setStage("error");
    }
  }

  return (
    <div className="modal-backdrop" onClick={onClose}>
      <form className="modal pin" onClick={(e) => e.stopPropagation()} onSubmit={submit}>
        <div className="modal-head">
          <div className="modal-title">Admin sign-in</div>
          <button type="button" className="iconbtn" onClick={onClose}><Icon name="x" /></button>
        </div>
        <p className="modal-sub">Sign in to edit pricing, status &amp; details.</p>
        <input
          ref={emailRef}
          type="email"
          autoComplete="email"
          className="pin-input mono"
          style={{ fontSize: 14, letterSpacing: 0, textAlign: "left", padding: "12px 14px", marginBottom: 8 }}
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          placeholder="Email"
          disabled={stage === "sending"} />
        <input
          type="password"
          autoComplete="current-password"
          className="pin-input mono"
          style={{ fontSize: 14, letterSpacing: 0, textAlign: "left", padding: "12px 14px" }}
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          placeholder="Password"
          disabled={stage === "sending"} />
        {stage === "error" && <div className="pin-hint mono" style={{ color: "var(--leased)" }}>{errMsg}</div>}
        <button type="submit" className="btn primary block" disabled={stage === "sending"}>
          {stage === "sending" ? "Signing in…" : "Sign in"}
        </button>
      </form>
    </div>);

}

// ---------- PDF-not-available toast ----------------------------------------
let pdfToastTimer = null;
function showPdfToast(msg = "PDF not available") {
  let el = document.getElementById("pdf-toast");
  if (!el) {
    el = document.createElement("div");
    el.id = "pdf-toast";
    el.className = "pdf-toast";
    document.body.appendChild(el);
  }
  el.textContent = msg;
  el.classList.add("show");
  clearTimeout(pdfToastTimer);
  pdfToastTimer = setTimeout(() => el.classList.remove("show"), 2200);
}
function PdfLink({ className, href, children }) {
  const available = !!href;
  return (
    <a
      className={className}
      href={available ? href : undefined}
      target={available ? "_blank" : undefined}
      rel={available ? "noopener" : undefined}
      aria-disabled={!available}
      onClick={(e) => {
        if (!available) { e.preventDefault(); showPdfToast(); }
      }}
      onMouseEnter={() => { if (!available) showPdfToast(); }}
    >
      {children}
    </a>
  );
}

// ---------- App ------------------------------------------------------------
function App() {
  const [tenancies, setTenancies] = useState(FOUNDRY_DATA.TENANCIES);
  const [updatedAt, setUpdatedAt] = useState(null);
  const [photos, setPhotos] = useState({}); // { id: [url, ...] }
  const [panos, setPanos] = useState({}); // { id: equirect-url }

  const [hoverId, setHoverId] = useState(null);
  const [selectedId, setSelectedId] = useState(null);
  const [query, setQuery] = useState("");
  const [filters, setFilters] = useState({
    status: "all",
    minArea: 0, maxArea: null,
    minPrice: 0, maxPrice: null
  });
  const [compareList, setCompareList] = useState([]);
  const [view, setView] = useState("map"); // map | table
  const [adminUnlocked, setAdminUnlocked] = useState(false);
  const [pinOpen, setPinOpen] = useState(false);
  const [adminUser, setAdminUser] = useState(null);
  const userRole = roleForUser(adminUser); // "admin" | "subadmin" | null

  // Tweakable defaults (host can rewrite this JSON block)
  const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
    "theme": "light",
    "accent": "#b89055",
    "density": "labels",
    "showHoverChip": true
  } /*EDITMODE-END*/;
  const [tweaks, setTweak] = window.useTweaks(TWEAK_DEFAULTS);

  // Apply theme to root
  useEffect(() => {
    document.documentElement.setAttribute("data-theme", tweaks.theme || "light");
  }, [tweaks.theme]);

  // Track current view on root so print CSS can target correct view
  useEffect(() => {
    document.documentElement.setAttribute("data-view", view);
  }, [view]);

  // ---- Supabase: initial load + realtime + auth state -------------------
  async function refreshFromBackend() {
    const backend = window.FOUNDRY_BACKEND;
    if (!backend) return;
    const edits = await backend.loadEdits();
    const merged = FOUNDRY_DATA.TENANCIES.map((t) => applyEditRowToTenancy(t, edits[t.id]));
    setTenancies(merged);
    const ph = {};
    const pn = {};
    let latest = null;
    for (const [id, row] of Object.entries(edits)) {
      if (row.photos && row.photos.length) ph[id] = row.photos;
      if (row.pano) pn[id] = row.pano;
      if (row.updated_at && (!latest || row.updated_at > latest)) latest = row.updated_at;
    }
    setPhotos(ph);
    setPanos({ ...DEMO_PANOS, ...pn });
    setUpdatedAt(latest);
  }

  useEffect(() => {
    const backend = window.FOUNDRY_BACKEND;
    if (!backend) { console.warn("Supabase backend missing — running in seed-only mode."); return; }

    refreshFromBackend();

    // Realtime: re-pull on any change so all viewers stay in sync
    const unsubData = backend.subscribeEdits(() => refreshFromBackend());

    // Auth: track sign-in state; unlock admin UI when authenticated
    backend.getUser().then((u) => { if (u) { setAdminUser(u); setAdminUnlocked(true); } });
    const unsubAuth = backend.onAuthChange((session) => {
      if (session && session.user) { setAdminUser(session.user); setAdminUnlocked(true); setPinOpen(false); }
      else { setAdminUser(null); setAdminUnlocked(false); }
    });

    return () => { unsubData && unsubData(); unsubAuth && unsubAuth(); };
  }, []);

  // ---- Optimistic-update + Supabase write helpers -----------------------
  function applyLocalEdit(id, patch) {
    setTenancies((prev) => prev.map((t) => t.id === id ? { ...t, ...patch } : t));
    setUpdatedAt(new Date().toISOString());
  }

  async function pushToBackend(id, patch) {
    const backend = window.FOUNDRY_BACKEND;
    if (!backend) return;
    // Translate camelCase field names to snake_case columns
    const dbPatch = {};
    for (const [k, v] of Object.entries(patch)) {
      const col = FIELD_TO_COL[k];
      if (col) dbPatch[col] = v;
    }
    if (Object.keys(dbPatch).length === 0) return;
    try {
      await backend.upsertEdit(id, dbPatch);
    } catch (err) {
      alert("Save failed: " + (err.message || err));
    }
  }

  function setTenancyPhotos(id, list) {
    setPhotos((prev) => ({ ...prev, [id]: list }));
    setUpdatedAt(new Date().toISOString());
    pushToBackend(id, { photos: list });
  }

  function setTenancyPano(id, url) {
    setPanos((prev) => {
      const next = { ...prev };
      if (url) next[id] = url; else delete next[id];
      return next;
    });
    setUpdatedAt(new Date().toISOString());
    pushToBackend(id, { pano: url || null });
  }

  function editField(id, key, value) {
    // Compute the optimistic local patch, including auto-derived totals
    const seed = tenancies.find((t) => t.id === id);
    if (!seed) return;
    const patch = { [key]: value };
    if (key === "net" || key === "outgoings") {
      const n = key === "net" ? value : seed.net;
      const o = key === "outgoings" ? value : seed.outgoings;
      if (n != null && o != null) {
        patch.totalAnnual = (n || 0) + (o || 0);
        patch.totalMonthly = Math.round(patch.totalAnnual / 12);
      }
    } else if (key === "totalAnnual" && value != null) {
      patch.totalMonthly = Math.round(value / 12);
    } else if (key === "totalMonthly" && value != null) {
      patch.totalAnnual = value * 12;
    }
    applyLocalEdit(id, patch);
    pushToBackend(id, patch);
  }

  function toggleLeased(id) {
    const t = tenancies.find((x) => x.id === id);
    if (!t) return;
    const newStatus = t.status === "leased" ? "available" : "leased";
    applyLocalEdit(id, { status: newStatus });
    pushToBackend(id, { status: newStatus });
  }

  async function resetData() {
    if (!confirm("Reset all pricing, status, descriptions, and photos to the original brochure values? This affects ALL visitors.")) return;
    const backend = window.FOUNDRY_BACKEND;
    if (!backend) return;
    try {
      // Delete every row in tenancy_edits — reverts to data.js seed values everywhere
      const { error } = await backend.client.from("tenancy_edits").delete().neq("id", "__never__");
      if (error) throw error;
      setTenancies(FOUNDRY_DATA.TENANCIES);
      setPhotos({});
      setUpdatedAt(null);
    } catch (err) {
      alert("Reset failed: " + (err.message || err));
    }
  }

  function exportEdits() {
    // Bundle current admin edits into a JSON file the user can hand back
    // to bake permanently into data.js (so all visitors see them).
    const payload = {
      exportedAt: new Date().toISOString(),
      updatedAt,
      tenancies: tenancies.map((t) => ({
        id: t.id,
        status: t.status,
        net: t.net, outgoings: t.outgoings,
        totalAnnual: t.totalAnnual, totalMonthly: t.totalMonthly,
        internal: t.internal, hardstand: t.hardstand,
        description: t.description,
        bullets: t.bullets,
        note: t.note
      })),
      photos: photos
    };
    const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = `foundry-edits-${new Date().toISOString().slice(0, 10)}.json`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
  }

  // When admin locks, sign out from Supabase (auth listener flips state)
  async function lockAdmin() {
    const backend = window.FOUNDRY_BACKEND;
    if (backend) {
      try { await backend.signOut(); } catch (e) {}
    }
    setAdminUnlocked(false);
    setAdminUser(null);
    if (view === "table") setView("map");
  }
  function addCompare(id) {
    setCompareList((cl) => cl.includes(id) ? cl : cl.length >= 3 ? cl : [...cl, id]);
  }
  function removeCompare(id) {setCompareList((cl) => cl.filter((x) => x !== id));}

  const selected = selectedId ? tenancies.find((t) => t.id === selectedId) : null;

  return (
    <div className="app" style={{ "--accent": tweaks.accent }}>
      <Header
        adminUnlocked={adminUnlocked}
        onAdminClick={() => setPinOpen(true)}
        onLock={lockAdmin}
        updatedAt={updatedAt}
        view={view}
        setView={setView} />
      

      <main className="main">
        <div className="print-header" aria-hidden="true">
          <img src="foundry-logo.svg" alt="The Foundry" className="print-logo"/>
          <div className="print-date mono">Printed {currentLongDate()}</div>
        </div>
        <section className="hero">
          <div className="hero-eyebrow mono">LEASING RENTAL RATES · {currentMonthYear()}</div>
          <h1 className="hero-h1">63 Currumbin Creek Rd<span className="comma">,</span><br/>Currumbin Waters</h1>
          <div className="hero-lede">Interactive site map of <strong>26 tenancies</strong> across warehouses, showroom units &amp; hardstand. Hover any unit for the headline figure; click to open full pricing, enquire, or add to compare.</div>
        </section>

        <StatsStrip tenancies={tenancies} updatedAt={updatedAt} />

        {view === "map" ?
        <section className="workspace">
            <div className="map-wrap">
              <div className="map-toolbar">
                <div className="legend">
                  <span className="lg-item"><span className="sw available"></span>Available</span>
                  <span className="lg-item"><span className="sw leased"></span>Leased</span>
                </div>
                {hoverId && tweaks.showHoverChip &&
              <HoverChip tenancy={tenancies.find((t) => t.id === hoverId)} adminUnlocked={adminUnlocked} />
              }
              </div>
              <SiteMap
              tenancies={tenancies}
              selectedId={selectedId}
              hoverId={hoverId}
              setHoverId={setHoverId}
              onSelect={(id) => setSelectedId(id)}
              accent={tweaks.accent}
              density={tweaks.density}
              multiSelect={compareList}
              theme={tweaks.theme} />

              {/* PRINT-ONLY: description callout pinned beneath the map.
                  Mirrors the selected tenancy's description/features as a
                  pop-up style card so page 1 carries the map + the story,
                  and the right column stays purely facts + pricing. */}
              {selected && (selected.description || (Array.isArray(selected.bullets) && selected.bullets.length > 0) || selected.note) &&
              <div className="map-desc-print" aria-hidden="true">
                  <div className="mdp-head">
                    <span className="mdp-eyebrow">About {selected.id}</span>
                    <span className={`mdp-status ${selected.status}`}>{selected.status === "leased" ? "Leased" : "Available now"}</span>
                  </div>
                  {selected.description && <p className="mdp-body">{selected.description}</p>}
                  {Array.isArray(selected.bullets) && selected.bullets.length > 0 &&
                <ul className="mdp-bullets">
                      {selected.bullets.map((b, i) => <li key={i}>{b}</li>)}
                    </ul>
                }
                  {selected.note && <div className="mdp-note">{selected.note}</div>}
                </div>
              }

              <PdfLink className="im-download-btn" href={FOUNDRY_DATA.IM_LINK}>
                <Icon name="download" /> Download Foundry Complete IM
              </PdfLink>

            </div>
            <CommandPanel
            tenancies={tenancies}
            selected={selected}
            setSelected={setSelectedId}
            query={query}
            setQuery={setQuery}
            filters={filters}
            setFilters={setFilters}
            compareList={compareList}
            addCompare={addCompare}
            removeCompare={removeCompare}
            adminUnlocked={adminUnlocked}
            onEdit={() => setView("table")}
            photos={photos}
            onPhotosChange={setTenancyPhotos}
            panos={panos}
            onPanoChange={setTenancyPano}
            onEditField={editField} />
          
          </section> :

        <PricingTable
          tenancies={tenancies}
          onSelect={(id) => {setSelectedId(id);setView("map");}}
          adminUnlocked={adminUnlocked}
          role={userRole}
          onEdit={() => {}}
          onToggleLeased={toggleLeased}
          onEditField={editField} />

        }

        {/* PRINT-ONLY: the selected tenancy's photos on their own page, large.
            Keeps page 1 clean (map + facts) and gives the imagery room. */}
        {view === "map" && selected && (photos[selected.id] || []).length > 0 &&
        <section className={`print-photos-page pp-count-${Math.min((photos[selected.id] || []).length, 4)}`} aria-hidden="true">
            <div className="pp-head">
              <h2 className="pp-title">{selected.id} — Photos</h2>
              <div className="pp-sub mono">{selected.status === "leased" ? "Leased" : "Available now"} · Internal {fmtArea(selected.internal)}</div>
            </div>
            <div className="pp-grid">
              {(photos[selected.id] || []).slice(0, 4).map((src, i) =>
              <div className="pp-cell" key={i}><img src={src} alt={`${selected.id} photo ${i + 1}`} /></div>
              )}
            </div>
          </section>
        }

        {/* Print-only table removed — print now shows whatever is visible onscreen */}

        {/* Hide the comparison block in Table view — the rates schedule is
            the canonical reference there; comparison stays a Map-mode tool. */}
        {view === "map" &&
        <CompareDrawer
          tenancies={tenancies}
          ids={compareList}
          removeCompare={removeCompare}
          clear={() => setCompareList([])}
          onOpen={(id) => {setSelectedId(id);setView("map");}}
          adminUnlocked={adminUnlocked} />
        }

        {adminUnlocked &&
        <div className="admin-bar">
            <div className="ab-left mono">
              ADMIN MODE · {adminUser ? adminUser.email : "signed in"} · {userRole === "admin" ? "full access (pricing + details)" : "tenancy editor (photos + descriptions only)"} · changes sync to all visitors instantly
            </div>
            <div className="ab-right">
              {userRole === "admin" && <button className="ghost-sm" onClick={resetData}>Reset to defaults</button>}
              <button className="ghost-sm" onClick={lockAdmin} title="All your edits save instantly — this just signs you out">Save &amp; sign out</button>
            </div>
          </div>
        }
      </main>

      <footer className="footer">
        <div>The Foundry · Est 1941</div>
        <div className="mono">63 Currumbin Creek Rd, Currumbin Waters QLD 4223</div>
        <div>All figures ex GST. Subject to change.</div>
      </footer>

      <PinGate
        open={pinOpen}
        onClose={() => setPinOpen(false)}
        onSuccess={() => setPinOpen(false)} />
      

      <window.TweaksPanel title="Tweaks">
        <window.TweakSection title="Theme">
          <window.TweakRadio
            label="Theme"
            value={tweaks.theme}
            onChange={(v) => setTweak("theme", v)}
            options={[
            { value: "light", label: "Light" },
            { value: "dark", label: "Dark" }]
            } />
          
          <window.TweakColor
            label="Accent"
            value={tweaks.accent}
            onChange={(v) => setTweak("accent", v)}
            options={["#b89055", "#c9a36b", "#7d9d65", "#b85c5c", "#5b7a8d"]} />
          
        </window.TweakSection>
        <window.TweakSection title="Map">
          <window.TweakRadio
            label="Map detail"
            value={tweaks.density}
            onChange={(v) => setTweak("density", v)}
            options={[
            { value: "minimal", label: "Minimal" },
            { value: "labels", label: "Labels" },
            { value: "detailed", label: "Detailed" }]
            } />
          
          <window.TweakToggle
            label="Show hover chip"
            value={tweaks.showHoverChip}
            onChange={(v) => setTweak("showHoverChip", v)} />
          
        </window.TweakSection>
      </window.TweaksPanel>
    </div>);

}

function HoverChip({ tenancy: t, adminUnlocked }) {
  if (!t) return null;
  return (
    <div className="hover-chip">
      <div className="hc-id">{t.id}</div>
      <div className="hc-area mono">{fmtArea(headlineArea(t))}</div>
      <div className="hc-price mono">
        {t.status === "leased" ? "LEASED" : fmtMoney(t.totalMonthly) + "/mo"}
      </div>
    </div>);

}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);