/* Organisations — card grid (handoff prototype port).
   Platform-admin only when read across all tenants; non-platform users
   fall back to their own org membership from the login response.
   Live data: aquaos_admin pulls /api/auth/platform/organisations which
   returns site_count / user_count / species_count per org, plus the
   raw org row fields (id, name, slug, status, city, country, created_at).
   Non-platform users see only the orgs they belong to (from the login
   `me.organisations` payload). */

const ORG_STATUS_TONE = {
  active:     'success',
  onboarding: 'warn',
  suspended:  'danger',
  archived:   'neutral',
};

function ORG_humanStatus(s) {
  if (!s) return 'Active';
  return s.charAt(0).toUpperCase() + s.slice(1);
}

function ORG_initials(name) {
  if (!name) return '?';
  const parts = name.trim().split(/\s+/);
  if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
  return (parts[0][0] + parts[1][0]).toUpperCase();
}
function ORG_hue(name) {
  let h = 0;
  for (let i = 0; i < (name || '').length; i++) h = ((h << 5) - h + name.charCodeAt(i)) | 0;
  return Math.abs(h) % 360;
}
function ORG_locationLine(o) {
  const bits = [o.city, o.country].filter(Boolean);
  return bits.join(' · ') || (o.slug ? o.slug : '');
}
function ORG_joinedLabel(iso) {
  if (!iso) return '—';
  return new Date(iso).toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric' });
}
function ORG_fmt(n) {
  return (n == null ? 0 : Number(n)).toLocaleString();
}

function ORG_Avatar({ org, size = 32 }) {
  // Apple pass: was a per-org chroma gradient; under the monochrome theme
  // it fought the palette and carried no real signal. Quiet translucent disc
  // with muted-white initials instead.
  return (
    <span className="aq-org-avatar" style={{
      width: size, height: size,
      fontSize: size <= 28 ? 11 : 13,
      background: 'rgba(255, 255, 255, 0.07)',
      color: 'rgba(255, 255, 255, 0.72)',
      border: '1px solid rgba(255, 255, 255, 0.08)',
    }}>{ORG_initials(org.name)}</span>
  );
}

/* Create-Organisation modal — direct port of the original CMS
   showCreateOrgModal() flow (cms-original L10903-10937, L31374-31424).
   Two sections: org name (required) + optional initial Org Admin
   (name + email + password). On success the parent shows a
   credentials panel with the new admin's auto-generated password so
   the platform admin can share it. */
function CreateOrgModal({ open, onClose, onCreated }) {
  const [name, setName]         = useState('');
  const [adminName, setAdmin]   = useState('');
  const [adminEmail, setEmail]  = useState('');
  const [adminPw, setPw]        = useState('');
  const [busy, setBusy]         = useState(false);
  const [err, setErr]           = useState(null);

  useEffect(() => {
    if (!open) {
      setName(''); setAdmin(''); setEmail(''); setPw('');
      setBusy(false); setErr(null);
    }
  }, [open]);

  async function submit() {
    if (!name.trim()) { setErr('Organisation name is required'); return; }
    setBusy(true); setErr(null);
    try {
      /* 1. Create the org. */
      const orgData = await apiFetch('/api/auth/platform/organisations', {
        method: 'POST',
        body: JSON.stringify({ name: name.trim() }),
      });

      /* 2. If admin details supplied, create the initial admin user.
            Backend returns { default_password } when no password is
            provided so we can show it in the credentials modal. */
      let userData = null;
      if (adminName.trim() && adminEmail.trim()) {
        const body = {
          name: adminName.trim(),
          email: adminEmail.trim(),
          role: 'org_admin',
        };
        if (adminPw.trim()) body.password = adminPw.trim();
        userData = await apiFetch(`/api/auth/platform/organisations/${orgData.id}/users`, {
          method: 'POST',
          body: JSON.stringify(body),
        });
      }
      onCreated({ org: orgData, user: userData });
    } catch (e) {
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  }

  if (!open) return null;
  return (
    <div
      onClick={(e) => { if (e.target === e.currentTarget && !busy) onClose(); }}
      style={{
        position: 'fixed', inset: 0, zIndex: 200,
        background: 'rgba(6, 7, 10, 0.78)', backdropFilter: 'blur(6px)',
        display: 'grid', placeItems: 'center', padding: 24,
      }}
    >
      <div style={{
        width: 'min(520px, 100%)',
        background: 'var(--aq-surface)',
        border: '1px solid var(--aq-line)',
        borderRadius: 14,
        boxShadow: 'var(--aq-shadow-2)',
        display: 'flex', flexDirection: 'column', overflow: 'hidden',
      }}>
        <header style={{
          padding: '14px 18px', borderBottom: '1px solid var(--aq-line)',
          display: 'flex', alignItems: 'center', gap: 12,
        }}>
          <div style={{
            width: 28, height: 28, borderRadius: 8,
            background: 'var(--aq-accent-soft)', color: 'var(--aq-accent)',
            display: 'grid', placeItems: 'center',
          }}><Icon name="building" size={14} /></div>
          <div style={{ flex: 1 }}>
            <div style={{ fontFamily: 'var(--aq-ff-display)', fontSize: 14, color: 'var(--aq-text)' }}>
              Create organisation
            </div>
            <div style={{ fontSize: 11.5, color: 'var(--aq-text-faint)' }}>
              Tenant + optional initial Org Admin in one step
            </div>
          </div>
          <button
            className="aq-icon-btn"
            onClick={onClose}
            disabled={busy}
            aria-label="Close"
          ><Icon name="close" size={13} /></button>
        </header>

        <div style={{ padding: 18, display: 'flex', flexDirection: 'column', gap: 14 }}>
          {err && (
            <div style={{
              padding: '8px 12px', borderRadius: 6,
              background: 'color-mix(in srgb, var(--aq-danger) 12%, transparent)',
              color: 'var(--aq-danger)', fontSize: 12,
            }}>{err}</div>
          )}

          <div className="x-form-field">
            <div className="x-form-label">
              Organisation name <span style={{ color: 'var(--aq-danger)' }}>*</span>
            </div>
            <input
              className="x-input"
              value={name}
              onChange={(e) => setName(e.target.value)}
              placeholder="e.g. Melbourne Aquarium"
              autoFocus
            />
          </div>

          <hr style={{ border: 0, borderTop: '1px solid var(--aq-line)', margin: 0 }} />

          <div>
            <div style={{
              fontFamily: 'var(--aq-ff-display)', fontSize: 13,
              color: 'var(--aq-text)', marginBottom: 4,
            }}>
              Initial Org Admin <span style={{
                color: 'var(--aq-text-faint)', fontSize: 11.5, fontWeight: 400,
                fontFamily: 'var(--aq-ff-sans)',
              }}>(optional)</span>
            </div>
            <div style={{ fontSize: 11.5, color: 'var(--aq-text-faint)', marginBottom: 12 }}>
              Add the first admin user who will manage this organisation. They'll
              receive a password you can share with them.
            </div>
          </div>

          <div className="x-form-field">
            <div className="x-form-label">Full name</div>
            <input
              className="x-input"
              value={adminName}
              onChange={(e) => setAdmin(e.target.value)}
              placeholder="e.g. Jane Smith"
            />
          </div>
          <div className="x-form-field">
            <div className="x-form-label">Email address</div>
            <input
              className="x-input"
              type="email"
              value={adminEmail}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="e.g. jane@aquarium.com"
            />
          </div>
          <div className="x-form-field">
            <div className="x-form-label">
              Password <span style={{
                color: 'var(--aq-text-faint)', fontSize: 11.5, fontWeight: 400,
              }}>(leave blank for auto-generated)</span>
            </div>
            <input
              className="x-input"
              type="text"
              value={adminPw}
              onChange={(e) => setPw(e.target.value)}
              placeholder="Auto-generated if empty"
            />
          </div>
        </div>

        <footer style={{
          padding: '12px 18px', borderTop: '1px solid var(--aq-line)',
          background: 'var(--aq-surface-2)',
          display: 'flex', justifyContent: 'flex-end', gap: 8,
        }}>
          <button
            onClick={onClose}
            disabled={busy}
            style={{
              padding: '7px 12px', fontSize: 12,
              background: 'transparent', border: '1px solid var(--aq-line)',
              color: 'var(--aq-text-dim)', borderRadius: 7, cursor: 'pointer',
            }}
          >Cancel</button>
          <button
            onClick={submit}
            disabled={busy || !name.trim()}
            className="aq-btn-primary"
            style={{ opacity: (busy || !name.trim()) ? 0.5 : 1 }}
          >
            <Icon name="plus" size={13} />
            {busy ? 'Creating…' : 'Create organisation'}
          </button>
        </footer>
      </div>
    </div>
  );
}

/* Credentials modal — surfaced after CreateOrgModal succeeds. Mirrors
   the original showCredentialsModal() (cms-original L31428-31441):
   shows the new admin's name/email/role/password with a copy button so
   the platform admin can share it. */
function CredentialsModal({ data, onClose }) {
  if (!data || !data.user) return null;
  const { org, user } = data;
  const pw = user.default_password || user.password || '—';
  const text = `Organisation: ${org.name}\nName: ${user.name}\nEmail: ${user.email}\nRole: ${user.role}\nPassword: ${pw}`;

  return (
    <div
      onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
      style={{
        position: 'fixed', inset: 0, zIndex: 210,
        background: 'rgba(6, 7, 10, 0.82)', backdropFilter: 'blur(6px)',
        display: 'grid', placeItems: 'center', padding: 24,
      }}
    >
      <div style={{
        width: 'min(480px, 100%)',
        background: 'var(--aq-surface)',
        border: '1px solid var(--aq-line)',
        borderRadius: 14,
        boxShadow: 'var(--aq-shadow-2)',
        overflow: 'hidden',
      }}>
        <header style={{
          padding: '14px 18px', borderBottom: '1px solid var(--aq-line)',
          display: 'flex', alignItems: 'center', gap: 12,
        }}>
          <div style={{
            width: 28, height: 28, borderRadius: 8,
            background: 'color-mix(in srgb, var(--aq-success) 18%, transparent)',
            color: 'var(--aq-success)',
            display: 'grid', placeItems: 'center',
          }}><Icon name="check" size={14} /></div>
          <div style={{ flex: 1 }}>
            <div style={{ fontFamily: 'var(--aq-ff-display)', fontSize: 14, color: 'var(--aq-text)' }}>
              Organisation created
            </div>
            <div style={{ fontSize: 11.5, color: 'var(--aq-text-faint)' }}>
              Share these credentials with the new admin
            </div>
          </div>
          <button
            className="aq-icon-btn"
            onClick={onClose}
            aria-label="Close"
          ><Icon name="close" size={13} /></button>
        </header>

        <div style={{ padding: 18, display: 'flex', flexDirection: 'column', gap: 8, fontSize: 13 }}>
          <Row label="Organisation" value={org.name} />
          <Row label="Name" value={user.name} />
          <Row label="Email" value={user.email} />
          <Row label="Role" value={user.role} />
          <Row label="Password" value={pw} highlight />
        </div>

        <footer style={{
          padding: '12px 18px', borderTop: '1px solid var(--aq-line)',
          background: 'var(--aq-surface-2)',
          display: 'flex', justifyContent: 'space-between', gap: 8,
        }}>
          <button
            onClick={() => {
              try {
                navigator.clipboard.writeText(text);
              } catch (_) { /* noop */ }
            }}
            style={{
              padding: '7px 12px', fontSize: 12,
              background: 'transparent', border: '1px solid var(--aq-line)',
              color: 'var(--aq-text-dim)', borderRadius: 7, cursor: 'pointer',
            }}
          >Copy credentials</button>
          <button onClick={onClose} className="aq-btn-primary">Done</button>
        </footer>
      </div>
    </div>
  );
}
function Row({ label, value, highlight }) {
  return (
    <div style={{ display: 'flex', gap: 12, alignItems: 'baseline' }}>
      <span style={{ width: 110, color: 'var(--aq-text-faint)', fontSize: 12 }}>{label}</span>
      <span style={{
        color: highlight ? 'var(--aq-success)' : 'var(--aq-text)',
        fontFamily: highlight ? 'var(--aq-ff-mono)' : 'var(--aq-ff-sans)',
        fontSize: highlight ? 14 : 13, fontWeight: highlight ? 600 : 500,
      }}>{value}</span>
    </div>
  );
}

function OrgsScreen() {
  const me = Auth.getUser() || {};
  const isPlatform = Auth.canSeePlatform();

  const [orgs, setOrgs] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [query, setQuery] = useState('');
  const [tab, setTab] = useState('all');
  const [reload, setReload] = useState(0);
  const [addOpen, setAddOpen] = useState(false);
  const [searchFocused, setSearchFocused] = useState(false);
  /* Org-settings modal — opened when admin clicks an org card.
     Replaces the previous behaviour (hash → '#dashboard') which dropped
     the curator into the org's dashboard with no clear way back, and
     never let them edit org-level fields. Feedback id d1a9e61f
     (Oli, 2026-05-12): "clicking an org i.e. Merlin, should bring
     up a pop up window like settings with relevant settings control,
     not go into the site". */
  const [settingsOrg, setSettingsOrg] = useState(null);
  const [credentials, setCredentials] = useState(null); /* { orgName, user } */

  /* Pull live data for platform admins; everyone else gets their
     login `organisations` array (always == [user's own org]) so the
     page still renders something useful. */
  useEffect(() => {
    let cancelled = false;
    setLoading(true); setError(null);

    if (isPlatform) {
      apiFetch('/api/auth/platform/organisations')
        .then((r) => {
          if (cancelled) return;
          setOrgs(r.organisations || []);
          setLoading(false);
        })
        .catch((err) => {
          if (cancelled) return;
          setError(err.message);
          setLoading(false);
        });
    } else {
      const fallback = (me.organisations || []).map((o) => ({
        id: o.id, name: o.name, slug: o.slug,
        status: 'active',
        site_count: (me.sites || []).filter((s) => s.organisation_id === o.id).length || (me.sites || []).length,
        user_count: null, species_count: null,
      }));
      setOrgs(fallback);
      setLoading(false);
    }
    return () => { cancelled = true; };
  }, [reload]);

  const counts = useMemo(() => {
    const c = { all: orgs.length, active: 0, onboarding: 0 };
    orgs.forEach((o) => {
      const s = (o.status || 'active').toLowerCase();
      if (c[s] != null) c[s]++;
      else c.active++;
    });
    return c;
  }, [orgs]);

  const totals = useMemo(() => {
    return orgs.reduce((acc, o) => ({
      sites:    acc.sites    + (Number(o.site_count) || 0),
      users:    acc.users    + (Number(o.user_count) || 0),
      species:  acc.species  + (Number(o.species_count) || 0),
      // Species actually placed on screens across the fleet (distinct per org,
      // summed). Falls back to the library count for the non-platform path,
      // which doesn't carry deployed_species_count.
      deployed: acc.deployed + (Number(o.deployed_species_count ?? o.species_count) || 0),
    }), { sites: 0, users: 0, species: 0, deployed: 0 });
  }, [orgs]);

  const filtered = useMemo(() => {
    let list = orgs;
    if (tab !== 'all') list = list.filter((o) => (o.status || 'active').toLowerCase() === tab);
    if (query) {
      const q = query.toLowerCase();
      list = list.filter((o) =>
        (o.name || '').toLowerCase().includes(q) ||
        (o.slug || '').toLowerCase().includes(q) ||
        (o.city || '').toLowerCase().includes(q) ||
        (o.country || '').toLowerCase().includes(q)
      );
    }
    return list;
  }, [orgs, tab, query]);

  function refresh() { setReload((n) => n + 1); }

  return (
    <div className="aq-content">
      <div className="aq-content-inner" style={{ maxWidth: 1400 }}>
        <section className="aq-hero" style={{ paddingBottom: 0, marginBottom: 24 }}>
          <h1>Organisations</h1>
        </section>

        {/* KPI strip — hidden when there's nothing platform-level to show. */}
        {isPlatform && (
          <div className="aq-stats" style={{ marginBottom: 24 }}>
            <div>
              <div className="aq-stat-label">Organisations</div>
              <div className="aq-stat-value">{ORG_fmt(counts.all)}</div>
              <div className="aq-stat-sub">{counts.onboarding} onboarding</div>
            </div>
            <div>
              <div className="aq-stat-label">Sites</div>
              <div className="aq-stat-value">{ORG_fmt(totals.sites)}</div>
              <div className="aq-stat-sub">across the fleet</div>
            </div>
            <div>
              <div className="aq-stat-label">Users</div>
              <div className="aq-stat-value">{ORG_fmt(totals.users)}</div>
              <div className="aq-stat-sub">platform-wide</div>
            </div>
            <div>
              <div className="aq-stat-label">Species deployed</div>
              <div className="aq-stat-value">{ORG_fmt(totals.deployed)}</div>
              <div className="aq-stat-sub">on screens across the fleet</div>
            </div>
          </div>
        )}

        {/* Toolbar — handoff parity. Search + tabs + "Show test orgs" +
            Add organisation. Add is platform-admin only. */}
        <div className="aq-lib-toolbar" style={{ marginBottom: 14 }}>
          {/* Recessed search — borderless at rest, faint fill on focus
              (matches the Global Species toolbar). */}
          <div
            className="aq-lib-search"
            style={{
              background: searchFocused ? 'var(--aq-surface)' : 'transparent',
              border: `1px solid ${searchFocused ? 'var(--aq-line)' : 'transparent'}`,
              transition: 'background 120ms ease, border-color 120ms ease',
            }}
          >
            <Icon name="search" size={13} />
            <input
              placeholder="Search organisations…"
              value={query}
              onChange={(e) => setQuery(e.target.value)}
              onFocus={() => setSearchFocused(true)}
              onBlur={() => setSearchFocused(false)}
            />
            <span className="aq-kbd" style={{ opacity: 0.55 }}>⌘K</span>
          </div>
          <div className="aq-lib-tabs">
            {[
              ['all',        'All',        counts.all,        false],
              ['active',     'Active',     counts.active,     false],
              ['onboarding', 'Onboarding', counts.onboarding, true],
            ].map(([id, label, n, warnable]) => {
              const isWarn = warnable && n > 0;
              return (
                <button
                  key={id}
                  className={`aq-tab ${tab === id ? 'is-active' : ''}`}
                  onClick={() => setTab(id)}
                >
                  <span>{label}</span>
                  {/* Plain muted numeral; amber only on Onboarding when there's a backlog. */}
                  <span style={{
                    fontSize: 11, fontWeight: 500, fontVariantNumeric: 'tabular-nums',
                    color: isWarn ? 'var(--aq-warn)' : 'var(--aq-text-faint)',
                  }}>{n}</span>
                </button>
              );
            })}
          </div>
          <div className="aq-lib-actions">
            {isPlatform && (
              <button
                className="aq-icon-btn"
                onClick={() => setAddOpen(true)}
                title="Add organisation"
                aria-label="Add organisation"
              >
                <Icon name="plus" size={15} />
              </button>
            )}
          </div>
        </div>

        {loading && (
          <div style={{ padding: 60, textAlign: 'center', color: 'var(--aq-text-faint)' }}>
            Loading organisations…
          </div>
        )}
        {error && (
          <div style={{
            padding: '10px 14px',
            background: 'color-mix(in srgb, var(--aq-danger) 12%, transparent)',
            border: '1px solid color-mix(in srgb, var(--aq-danger) 30%, transparent)',
            borderRadius: 6, color: 'var(--aq-danger)', fontSize: 12,
          }}>{error}</div>
        )}

        {!loading && !error && filtered.length === 0 && (
          <div className="x-card" style={{ padding: '60px 24px', textAlign: 'center' }}>
            <div style={{ fontSize: 13, color: 'var(--aq-text-faint)' }}>
              {orgs.length === 0
                ? "You're not a member of any organisation yet."
                : 'No organisations match this filter.'}
            </div>
          </div>
        )}

        {!loading && !error && filtered.length > 0 && (
          <div className="aq-orgs-grid">
            {filtered.map((o) => {
              const status = (o.status || 'active').toLowerCase();
              const tone = ORG_STATUS_TONE[status] || 'neutral';
              return (
                <article
                  key={o.id}
                  className="aq-org-card"
                  onClick={() => setSettingsOrg(o)}
                  title={`Open ${o.name} settings`}
                >
                  <header className="aq-org-card-head">
                    <ORG_Avatar org={o} size={32} />
                    <div className="aq-org-card-id">
                      <div className="aq-org-card-name">{o.name}</div>
                      <div className="aq-org-card-note">{ORG_locationLine(o)}</div>
                    </div>
                    {/* Active is the expected state — leave it implicit and
                        only flag the statuses that need attention
                        (onboarding, suspended, etc.). */}
                    {status !== 'active' && (
                      <span className={`aq-status-text is-${tone}`}>
                        <span className="aq-status-dot-inline" />
                        {ORG_humanStatus(o.status)}
                      </span>
                    )}
                  </header>
                  <div className="aq-org-card-stats">
                    <div>
                      <span className="aq-org-card-v">{ORG_fmt(o.site_count)}</span>
                      <span className="aq-org-card-k">sites</span>
                    </div>
                    <div>
                      <span className="aq-org-card-v">{o.user_count == null ? '—' : ORG_fmt(o.user_count)}</span>
                      <span className="aq-org-card-k">users</span>
                    </div>
                    <div>
                      <span className="aq-org-card-v">{o.species_count == null ? '—' : ORG_fmt(o.species_count)}</span>
                      <span className="aq-org-card-k">species</span>
                    </div>
                  </div>
                  <footer className="aq-org-card-foot">
                    <span className="aq-org-card-meta">
                      {o.created_at ? `Joined ${ORG_joinedLabel(o.created_at)}` : (o.slug || '')}
                    </span>
                  </footer>
                </article>
              );
            })}
          </div>
        )}
      </div>

      {/* Create-org modal + post-create credentials handoff */}
      <CreateOrgModal
        open={addOpen}
        onClose={() => setAddOpen(false)}
        onCreated={(payload) => {
          setAddOpen(false);
          /* If the curator added an initial admin we surface the
             credentials modal so the password can be copied; otherwise
             just refresh the list quietly. */
          if (payload && payload.user) {
            setCredentials({ org: payload.org, user: payload.user });
          }
          refresh();
        }}
      />
      <CredentialsModal
        data={credentials}
        onClose={() => setCredentials(null)}
      />
      <OrgSettingsModal
        org={settingsOrg}
        onClose={() => setSettingsOrg(null)}
        onSaved={() => { setSettingsOrg(null); refresh(); }}
        onDeleted={() => { setSettingsOrg(null); refresh(); }}
      />
    </div>
  );
}

/* ══════════════════════════════════════════════════════════════════
   OrgSettingsModal — popup with org-level settings + actions.
   ──────────────────────────────────────────────────────────────────
   Curator clicks an org card → this opens with the org's editable
   fields (name, slug) plus read-only counts and the post-create
   metadata (status, created date). Save PUTs to
   /api/auth/platform/organisations/:id; delete cascades via the
   matching DELETE endpoint with a confirm prompt.

   The old hash-to-dashboard behaviour is preserved as an explicit
   "Enter site" button so curators who genuinely wanted to navigate
   into the org can still do it. Feedback id d1a9e61f.
   ══════════════════════════════════════════════════════════════════ */
/* Inline-expandable row inside OrgSettingsModal's sites list. Click
   the row to expand it into a per-screen breakdown for that site,
   each screen with a delete (X) button. Lets platform admin clean
   up allocations without navigating into the customer's #displays.
   Backend: GET /api/screens?site_id=<id> already returns the
   filtered set for aquaos_admin (their accessible_site_ids covers
   every site). DELETE /api/screens/:id already bypasses org checks
   for aquaos_admin. So this is pure UX glue. */
function SiteAllocationRow({ site, onAssignClick, onScreenChanged }) {
  const [expanded, setExpanded] = useState(false);
  const [screens, setScreens] = useState(null);
  const [loading, setLoading] = useState(false);
  const [busyDelete, setBusyDelete] = useState(null);

  function loadScreens() {
    if (loading) return;
    setLoading(true);
    apiFetch(`/api/screens?site_id=${encodeURIComponent(site.id)}&limit=200`)
      .then((r) => setScreens(r.screens || []))
      .catch(() => setScreens([]))
      .finally(() => setLoading(false));
  }

  function toggle() {
    if (!expanded && screens == null) loadScreens();
    setExpanded((v) => !v);
  }

  async function destroy(screen) {
    if (!window.confirm(`Delete ${screen.screen_code}?\n\nIf a device is currently attached, it'll drop back to a pairing screen on its next heartbeat. The screen's analytics history is removed.`)) return;
    setBusyDelete(screen.id);
    try {
      await apiFetch(`/api/screens/${screen.id}`, { method: 'DELETE' });
      if (window.toast) window.toast(`${screen.screen_code} deleted`);
      setScreens((prev) => (prev || []).filter((s) => s.id !== screen.id));
      onScreenChanged && onScreenChanged();
    } catch (e) {
      if (window.toast) window.toast(`Delete failed: ${e.message}`, 'error');
      else window.alert(`Delete failed: ${e.message}`);
    } finally {
      setBusyDelete(null);
    }
  }

  return (
    <div style={{ borderTop: '1px solid var(--aq-line)' }}>
      <div style={{
        display: 'flex', alignItems: 'center', gap: 12,
        padding: '8px 0',
      }}>
        <button
          type="button"
          onClick={toggle}
          style={{
            background: 'transparent', border: 0,
            color: 'var(--aq-text-faint)', cursor: 'pointer',
            padding: 2, fontFamily: 'inherit',
            transform: expanded ? 'rotate(90deg)' : 'none',
            transition: 'transform 120ms',
          }}
          aria-label={expanded ? 'Collapse' : 'Expand'}
        >▸</button>
        <div style={{ flex: 1, minWidth: 0, cursor: 'pointer' }} onClick={toggle}>
          <div style={{ fontWeight: 500, color: 'var(--aq-text)', fontSize: 13 }}>
            {site.name}
          </div>
          <div style={{ fontSize: 11, color: 'var(--aq-text-faint)' }}>
            {site.city || '—'}
            {site.screen_count != null && (
              <> · {site.screen_count} screen{site.screen_count === 1 ? '' : 's'} assigned</>
            )}
          </div>
        </div>
        <button
          type="button"
          className="x-btn ghost"
          style={{ padding: '4px 10px', fontSize: 12, flexShrink: 0 }}
          onClick={onAssignClick}
        >Assign screens</button>
      </div>
      {expanded && (
        <div style={{ paddingLeft: 22, paddingBottom: 8 }}>
          {loading && (
            <div style={{ fontSize: 11.5, color: 'var(--aq-text-faint)', padding: '4px 0' }}>
              Loading screens…
            </div>
          )}
          {!loading && screens && screens.length === 0 && (
            <div style={{ fontSize: 11.5, color: 'var(--aq-text-faint)', padding: '4px 0' }}>
              No screens assigned to this site yet.
            </div>
          )}
          {!loading && screens && screens.map((s) => {
            const isWaiting = s.paired_via_code && !s.paired_at;
            const isAttached = !!s.paired_at;
            return (
              <div
                key={s.id}
                style={{
                  display: 'flex', alignItems: 'center', gap: 10,
                  padding: '6px 0', fontSize: 12,
                  color: 'var(--aq-text)',
                  borderTop: '1px dashed var(--aq-line)',
                }}
              >
                <span style={{
                  fontFamily: 'var(--aq-ff-mono)', fontSize: 11.5,
                  color: 'var(--aq-text-dim)', minWidth: 90,
                }}>{s.screen_code}</span>
                <span style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                    {s.name}
                  </div>
                  <div style={{ fontSize: 10.5, color: 'var(--aq-text-faint)' }}>
                    {isAttached
                      ? <span style={{ color: 'var(--aq-success)' }}>● Attached</span>
                      : (isWaiting
                          ? <span style={{ color: '#F2C879' }}>● Waiting for hardware</span>
                          : <span>● Legacy (unpaired)</span>)}
                    {s.zone_name ? ` · ${s.zone_name}` : ''}
                  </div>
                </span>
                <button
                  type="button"
                  onClick={() => destroy(s)}
                  disabled={busyDelete === s.id}
                  title="Delete this screen"
                  style={{
                    padding: '3px 8px', fontSize: 11,
                    background: 'transparent', color: 'var(--aq-danger)',
                    border: '1px solid color-mix(in srgb, var(--aq-danger) 30%, transparent)',
                    borderRadius: 5, cursor: busyDelete === s.id ? 'wait' : 'pointer',
                    opacity: busyDelete === s.id ? 0.6 : 1,
                  }}
                >{busyDelete === s.id ? '…' : 'Delete'}</button>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

function OrgSettingsModal({ org, onClose, onSaved, onDeleted }) {
  const [name, setName] = useState('');
  const [slug, setSlug] = useState('');
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState(null);
  /* Sites within this org + their screen counts. Loaded on open
     for aquaos_admin so they can assign screens per-site without
     navigating into the customer's CMS surface. Feedback id
     f8054e9b extension (Oli, 2026-05-13). */
  const [sites, setSites] = useState([]);
  const [sitesLoading, setSitesLoading] = useState(false);
  const [allocateSite, setAllocateSite] = useState(null);
  const isPlatformAdmin = Auth && Auth.canSeePlatform && Auth.canSeePlatform();

  useEffect(() => {
    if (!org) { setErr(null); return; }
    setName(org.name || '');
    setSlug(org.slug || '');
    setErr(null);
    if (isPlatformAdmin) {
      setSitesLoading(true);
      apiFetch(`/api/auth/platform/organisations/${org.id}/sites`)
        .then((r) => setSites(r.sites || []))
        .catch(() => setSites([]))
        .finally(() => setSitesLoading(false));
    }
  }, [org && org.id, isPlatformAdmin]);

  function refreshSites() {
    if (!org || !isPlatformAdmin) return;
    apiFetch(`/api/auth/platform/organisations/${org.id}/sites`)
      .then((r) => setSites(r.sites || []))
      .catch(() => {});
  }

  useEffect(() => {
    if (!org) return;
    const onKey = (e) => { if (e.key === 'Escape' && !busy) onClose && onClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [org, onClose, busy]);

  if (!org) return null;

  async function save() {
    if (!name.trim()) { setErr('Name is required.'); return; }
    setBusy(true); setErr(null);
    try {
      const body = {};
      if (name !== org.name) body.name = name.trim();
      if (slug !== org.slug) body.slug = slug.trim();
      if (Object.keys(body).length === 0) {
        // Nothing changed — close silently.
        onClose && onClose();
        return;
      }
      await apiFetch(`/api/auth/platform/organisations/${org.id}`, {
        method: 'PUT',
        body: JSON.stringify(body),
      });
      if (window.toast) window.toast(`${name} saved`);
      onSaved && onSaved();
    } catch (e) {
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  }

  async function destroy() {
    if (!window.confirm(`Delete organisation "${org.name}"?\n\nThis is irreversible. Every site, screen, species and user belonging to this org will be removed.`)) return;
    setBusy(true); setErr(null);
    try {
      await apiFetch(`/api/auth/platform/organisations/${org.id}`, { method: 'DELETE' });
      if (window.toast) window.toast(`${org.name} deleted`);
      onDeleted && onDeleted();
    } catch (e) {
      setErr(e.message);
      setBusy(false);
    }
  }

  function enterSite() {
    // The "go to dashboard" behaviour that the old click did,
    // preserved as an explicit button for curators who really did
    // want to navigate into the org.
    window.location.hash = '#dashboard';
  }

  return (
    <div
      onClick={(e) => { if (e.target === e.currentTarget && !busy) onClose && onClose(); }}
      style={{
        position: 'fixed', inset: 0, zIndex: 1000,
        background: 'rgba(6,7,10,0.78)', backdropFilter: 'blur(6px)',
        display: 'grid', placeItems: 'center', padding: 24,
        /* Same open transition as the Settings modal — backdrop fade
           (aqModalFade) + card rise (.aq-rise = aqModalRise). */
        animation: 'aqModalFade 0.18s ease-out',
      }}
    >
      <div className="aq-rise" style={{
        width: 'min(520px, 100%)', maxHeight: '82vh',
        background: 'var(--aq-surface)',
        border: '1px solid var(--aq-line)',
        borderRadius: 14, boxShadow: 'var(--aq-shadow-2)',
        display: 'flex', flexDirection: 'column', overflow: 'hidden',
      }}>
        <header style={{
          padding: '14px 18px', borderBottom: '1px solid var(--aq-line)',
          display: 'flex', alignItems: 'center', gap: 12,
        }}>
          <ORG_Avatar org={org} size={32} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontFamily: 'var(--aq-ff-display)', fontSize: 14.5, fontWeight: 500, color: 'var(--aq-text)' }}>
              {org.name}
            </div>
            <div style={{ fontSize: 11.5, color: 'var(--aq-text-faint)' }}>
              Organisation settings · {ORG_humanStatus(org.status)}
            </div>
          </div>
          <button type="button" className="aq-icon-btn" onClick={onClose} disabled={busy}>×</button>
        </header>

        <div style={{ flex: 1, overflowY: 'auto', padding: '14px 18px', display: 'flex', flexDirection: 'column', gap: 14 }}>
          <label style={{ display: 'block' }}>
            <div style={{ fontSize: 11.5, color: 'var(--aq-text-dim)', marginBottom: 5 }}>Name</div>
            <input
              type="text"
              value={name}
              onChange={(e) => setName(e.target.value)}
              style={{
                width: '100%', padding: '9px 11px', fontSize: 13,
                background: 'var(--aq-surface-2)', border: '1px solid transparent',
                borderRadius: 8, color: 'var(--aq-text)', font: 'inherit', outline: 0,
              }}
            />
          </label>
          <label style={{ display: 'block' }}>
            <div style={{ fontSize: 11.5, color: 'var(--aq-text-dim)', marginBottom: 5 }}>Slug</div>
            <input
              type="text"
              value={slug}
              onChange={(e) => setSlug(e.target.value)}
              placeholder="acme-aquarium"
              style={{
                width: '100%', padding: '9px 11px', fontSize: 13,
                fontFamily: 'var(--aq-ff-mono)',
                background: 'var(--aq-surface-2)', border: '1px solid transparent',
                borderRadius: 8, color: 'var(--aq-text)', font: 'inherit', outline: 0,
              }}
            />
          </label>

          {/* Sites + per-site screen allocation (platform admin only).
              Each site shows how many screen rows currently exist,
              expandable to a per-screen list with delete buttons.
              Customer's site manager handles the device-attach part;
              platform admin handles the up-front allocation and the
              cleanup if a customer downsizes. */}
          {isPlatformAdmin && (
            <div>
              {/* Apple pass: no surrounding box — section label + the rows'
                  own hairlines carry the structure (settings-page style). */}
              <div style={{
                fontSize: 10.5, letterSpacing: '0.06em', textTransform: 'uppercase',
                color: 'var(--aq-text-faint)', marginBottom: 2,
              }}>Sites · screen allocation</div>
              {sitesLoading && (
                <div style={{ fontSize: 12, color: 'var(--aq-text-faint)' }}>Loading sites…</div>
              )}
              {!sitesLoading && sites.length === 0 && (
                <div style={{ fontSize: 12, color: 'var(--aq-text-faint)', lineHeight: 1.5 }}>
                  No sites yet. Create one from the org dashboard first,
                  then come back here to assign screens.
                </div>
              )}
              {!sitesLoading && sites.map((s) => (
                <SiteAllocationRow
                  key={s.id}
                  site={s}
                  onAssignClick={() => setAllocateSite(s)}
                  onScreenChanged={refreshSites}
                />
              ))}
            </div>
          )}

          {/* Read-only stats — Apple pass: no box. A single hairline
              separates them from the editable section above; the numbers
              speak for themselves. */}
          <div style={{
            display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8,
            paddingTop: 14, borderTop: '1px solid var(--aq-line)',
          }}>
            <div>
              <div style={{ fontSize: 10.5, color: 'var(--aq-text-faint)', textTransform: 'uppercase', letterSpacing: '0.06em' }}>Sites</div>
              <div style={{ fontSize: 18, fontWeight: 500, color: 'var(--aq-text)' }}>{org.site_count == null ? '—' : ORG_fmt(org.site_count)}</div>
            </div>
            <div>
              <div style={{ fontSize: 10.5, color: 'var(--aq-text-faint)', textTransform: 'uppercase', letterSpacing: '0.06em' }}>Users</div>
              <div style={{ fontSize: 18, fontWeight: 500, color: 'var(--aq-text)' }}>{org.user_count == null ? '—' : ORG_fmt(org.user_count)}</div>
            </div>
            <div>
              <div style={{ fontSize: 10.5, color: 'var(--aq-text-faint)', textTransform: 'uppercase', letterSpacing: '0.06em' }}>Species</div>
              <div style={{ fontSize: 18, fontWeight: 500, color: 'var(--aq-text)' }}>{org.species_count == null ? '—' : ORG_fmt(org.species_count)}</div>
            </div>
          </div>

          {org.created_at && (
            <div style={{ fontSize: 11, color: 'var(--aq-text-faint)' }}>
              Joined {ORG_joinedLabel(org.created_at)}
            </div>
          )}

          {err && (
            <div style={{
              padding: '8px 10px', borderRadius: 6,
              background: 'color-mix(in srgb, var(--aq-danger) 14%, transparent)',
              color: 'var(--aq-danger)', fontSize: 12,
            }}>{err}</div>
          )}
        </div>

        <footer style={{
          padding: '10px 16px 14px',
          background: 'var(--aq-surface)',
          display: 'flex', alignItems: 'center', gap: 8,
        }}>
          <button
            type="button"
            onClick={destroy}
            disabled={busy}
            style={{
              padding: '6px 12px', fontSize: 12, fontFamily: 'inherit',
              background: 'transparent', color: 'var(--aq-danger)',
              border: '1px solid color-mix(in srgb, var(--aq-danger) 30%, transparent)',
              borderRadius: 6, cursor: 'pointer',
            }}
          >Delete org</button>
          <div style={{ flex: 1 }} />
          <button type="button" className="x-btn ghost" onClick={enterSite} disabled={busy}>
            Enter site
          </button>
          <button type="button" className="x-btn ghost" onClick={onClose} disabled={busy}>Cancel</button>
          <button type="button" className="x-btn" onClick={save} disabled={busy}>
            {busy ? 'Saving…' : 'Save changes'}
          </button>
        </footer>
      </div>
      <AllocateScreensModal
        site={allocateSite}
        onClose={() => setAllocateSite(null)}
        onAllocated={() => { setAllocateSite(null); refreshSites(); }}
      />
    </div>
  );
}

/* ══════════════════════════════════════════════════════════════════
   AllocateScreensModal — platform admin grants a site N screen slots.
   ──────────────────────────────────────────────────────────────────
   Used from OrgSettingsModal's per-site "Assign screens" action.
   Takes a count, POSTs to /api/screens/allocate. The server creates
   N placeholder screen rows for the site (auto-named, auto-zoned)
   in "Waiting for hardware" state. Customer's site manager then
   sees them on their #displays page and attaches devices by typing
   the pairing code shown on each device's screen.

   Devices ship identical — no per-customer URL or imaging step.
   The platform admin's act of allocating slots and the customer's
   act of typing a code together bind device to site. Feedback id
   f8054e9b refinement (Oli, 2026-05-13).
   ══════════════════════════════════════════════════════════════════ */
function AllocateScreensModal({ site, onClose, onAllocated }) {
  const [count, setCount] = useState(5);
  const [busy, setBusy] = useState(false);
  const [err, setErr] = useState(null);

  useEffect(() => {
    if (!site) return;
    setCount(5); setErr(null);
  }, [site && site.id]);

  if (!site) return null;

  async function submit() {
    const n = Math.max(1, Math.min(50, parseInt(count, 10) || 0));
    if (!n) { setErr('Pick a number from 1 to 50.'); return; }
    setBusy(true); setErr(null);
    try {
      const r = await apiFetch('/api/screens/allocate', {
        method: 'POST',
        body: JSON.stringify({ site_id: site.id, count: n }),
      });
      if (window.toast) window.toast(`${r.allocated} screen${r.allocated === 1 ? '' : 's'} assigned to ${site.name}`);
      onAllocated && onAllocated();
    } catch (e) {
      setErr(e.message);
    } finally {
      setBusy(false);
    }
  }

  return (
    <div
      onClick={(e) => { if (e.target === e.currentTarget && !busy) onClose && onClose(); }}
      style={{
        position: 'fixed', inset: 0, zIndex: 1100,
        background: 'rgba(6,7,10,0.78)', backdropFilter: 'blur(6px)',
        display: 'grid', placeItems: 'center', padding: 24,
      }}
    >
      <div style={{
        width: 'min(420px, 100%)',
        background: 'var(--aq-surface)',
        border: '1px solid var(--aq-line)',
        borderRadius: 14, boxShadow: 'var(--aq-shadow-2)',
        display: 'flex', flexDirection: 'column', overflow: 'hidden',
      }}>
        <header style={{
          padding: '14px 18px', borderBottom: '1px solid var(--aq-line)',
          display: 'flex', alignItems: 'center', gap: 10,
        }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontFamily: 'var(--aq-ff-display)', fontSize: 14.5, fontWeight: 500, color: 'var(--aq-text)' }}>
              Assign screens
            </div>
            <div style={{ fontSize: 11.5, color: 'var(--aq-text-faint)' }}>
              to <strong style={{ color: 'var(--aq-text-dim)' }}>{site.name}</strong>
            </div>
          </div>
          <button type="button" className="aq-icon-btn" onClick={onClose} disabled={busy}>×</button>
        </header>

        <div style={{ padding: '14px 18px', display: 'flex', flexDirection: 'column', gap: 12 }}>
          <label style={{ display: 'block' }}>
            <div style={{ fontSize: 11.5, color: 'var(--aq-text-dim)', marginBottom: 5 }}>
              How many screens?
            </div>
            <input
              type="number" min="1" max="50"
              value={count}
              onChange={(e) => setCount(e.target.value)}
              style={{
                width: '100%', padding: '8px 10px', fontSize: 18,
                fontFamily: 'var(--aq-ff-mono)', textAlign: 'center',
                background: 'var(--aq-surface-2)', border: '1px solid var(--aq-line)',
                borderRadius: 7, color: 'var(--aq-text)', outline: 0,
              }}
            />
          </label>
          <div style={{
            fontSize: 11.5, color: 'var(--aq-text-faint)', lineHeight: 1.5,
            padding: '10px 12px', background: 'var(--aq-surface-2)',
            border: '1px solid var(--aq-line)', borderRadius: 7,
          }}>
            Creates {count || 'N'} placeholder screen{count == 1 ? '' : 's'} in
            an <strong>Unassigned</strong> zone for this site. The site manager
            picks the real zone for each screen when they configure it.
            They'll see the slots on <code style={{ fontFamily: 'var(--aq-ff-mono)', fontSize: 11 }}>#displays</code> in
            "Waiting for hardware" state.
          </div>
          {err && (
            <div style={{
              padding: '8px 10px', borderRadius: 6,
              background: 'color-mix(in srgb, var(--aq-danger) 14%, transparent)',
              color: 'var(--aq-danger)', fontSize: 12,
            }}>{err}</div>
          )}
        </div>

        <footer style={{
          padding: '10px 16px', borderTop: '1px solid var(--aq-line)',
          background: 'var(--aq-surface-2)',
          display: 'flex', gap: 8, justifyContent: 'flex-end',
        }}>
          <button type="button" className="x-btn ghost" onClick={onClose} disabled={busy}>Cancel</button>
          <button type="button" className="x-btn" onClick={submit} disabled={busy}>
            {busy ? 'Assigning…' : `Assign ${count || ''} screens`.trim()}
          </button>
        </footer>
      </div>
    </div>
  );
}

window.OrgsScreen = OrgsScreen;
