// ============ APP ROOT: shell, routing, state ============
const { useState, useEffect } = React;

const ACCENTS = {
  teal:  { a: "#0F9B8E", ink: "#0A6F66", soft: "#E2F1EE", da: "#3FC9B9", dink: "#7CDDD0", dsoft: "#173430" },
  blue:  { a: "#2A6FDB", ink: "#1E54AC", soft: "#E4ECFB", da: "#5B9BFF", dink: "#9CC0FF", dsoft: "#16243F" },
  terra: { a: "#C56A43", ink: "#9E4F2D", soft: "#F6E7DE", da: "#E08A5F", dink: "#EFA985", dsoft: "#3A2419" },
  plum:  { a: "#7C5CBF", ink: "#5E429B", soft: "#ECE6F7", da: "#A488DD", dink: "#C3AEEC", dsoft: "#251B3A" },
};

function applyAccent(accent, theme) {
  const c = ACCENTS[accent] || ACCENTS.teal;
  const r = document.documentElement.style;
  const dark = theme === "dark";
  r.setProperty("--accent", dark ? c.da : c.a);
  r.setProperty("--accent-ink", dark ? c.dink : c.ink);
  r.setProperty("--accent-soft", dark ? c.dsoft : c.soft);
}

const NAV = [
  { group: "Pilotage", items: [
    { id: "dashboard", label: "Tableau de bord", icon: "dashboard" },
    { id: "remote", label: "Télécommandes", icon: "remote" },
    { id: "sensors", label: "Capteurs", icon: "thermo" },
    { id: "energy", label: "Énergie", icon: "bolt" },
    { id: "calendar", label: "Programmation", icon: "calendar" },
    // Scripted, not AI: it understands a handful of phrasings and proposes an
    // action to confirm. Kept to administrators until it is worth more.
    { id: "assistant", label: "Assistant", icon: "assistant", superadmin: true },
  ]},
  { group: "Administration", admin: true, items: [
    { id: "fleet", label: "Supervision", icon: "fleet" },
    { id: "devices", label: "Gestion du parc", icon: "devices" },
  ]},
  // Only a superadmin sees other organisations, so this group is theirs alone.
  { group: "Plateforme", superadmin: true, items: [
    { id: "clients", label: "Clients", icon: "building" },
    { id: "requests", label: "Demandes & contact", icon: "send" },
  ]},
];

function App() {
  const D = window.STData;
  const [user, setUser] = useState(null);
  const [theme, setThemeState] = useState(() => localStorage.getItem("st-theme") || "light");
  const [accent, setAccentState] = useState(() => localStorage.getItem("st-accent") || "teal");
  const [route, setRoute] = useState("dashboard");
  const [devices, setDevices] = useState([]);
  const [units, setUnits] = useState([]);
  const [loading, setLoading] = useState(true);
  const [authBusy, setAuthBusy] = useState(false);
  const [authError, setAuthError] = useState(null);
  const [schedules, setSchedules] = useState([]);
  const [orgs, setOrgs] = useState([]);
  const [openDevice, setOpenDevice] = useState(null);
  const [editDevice, setEditDevice] = useState(null);
  const [openClient, setOpenClient] = useState(null);
  const [maintDevice, setMaintDevice] = useState(null);
  const [newReq, setNewReq] = useState(0);
  const [demo, setDemo] = useState(null);
  const [dashView, setDashView] = useState("rooms");
  const [toast, setToast] = useState(null);
  const [navOpen, setNavOpen] = useState(false);

  useEffect(() => { document.documentElement.setAttribute("data-theme", theme); applyAccent(accent, theme); localStorage.setItem("st-theme", theme); }, [theme, accent]);
  const setTheme = (t) => setThemeState(t);
  const setAccent = (a) => { setAccentState(a); localStorage.setItem("st-accent", a); };

  // Restore a session on reload so a refresh does not bounce you to login.
  useEffect(() => {
    let alive = true;
    (async () => {
      if (!STApi.hasSession()) {
        // /app?demo signs a visitor straight into the public demo.
        if (/[?&]demo\b/.test(location.search)) {
          try {
            const info = await STApi.demoInfo();
            if (info.enabled && info.accounts && info.accounts[0]) {
              const me = await STApi.login(info.accounts[0].email, info.password);
              if (alive) { setUser(me); setRoute("dashboard"); }
            }
          } catch { /* the sign-in page is the fallback */ }
          history.replaceState(null, "", location.pathname);
        }
        if (alive) setLoading(false);
        return;
      }
      try {
        const me = await STApi.me();
        if (alive) setUser(me);
      } catch {
        /* expired or revoked — the login screen is the right answer */
      } finally {
        if (alive) setLoading(false);
      }
    })();
    return () => { alive = false; };
  }, []);

  // Poll the real fleet. The backend also emits device.state over socket.io;
  // polling is used here because it needs no extra client library and is
  // immune to a dropped socket going unnoticed.
  // Rooms and schedules were fetched once at sign-in, so a room created a
  // minute ago showed as "—" on the dashboard and never appeared as a
  // schedule scope. Refresh them with the devices.
  const refresh = React.useCallback(async () => {
    try {
      const [list, us, sc] = await Promise.all([
        STApi.devices(),
        STApi.allUnits().catch(() => null),
        STApi.schedules().catch(() => null),
      ]);
      setDevices(list);
      if (us) setUnits(us);
      if (sc) setSchedules(sc);
      return list;
    } catch (e) {
      if (e.status === 401) { setUser(null); clearTimeout(window._tt); }
      return null;
    }
  }, []);

  useEffect(() => {
    if (!user) return;
    let alive = true;
    refresh();
    // allUnits() rather than units(user.orgId): a superadmin has no org, so
    // the scoped call returned an empty list and every room dropdown was
    // blank — including the one that assigns a device to a room.
    STApi.allUnits().then(u => { if (alive) setUnits(u); }).catch(() => {});
    // Needed by Programmation so a superadmin can pick which client a scenario
    // belongs to — and so its room list can be narrowed to that client.
    if (user.isAdmin) STApi.orgs().then(o => { if (alive) setOrgs(o); }).catch(() => {});
    STApi.demoInfo().then(d => { if (alive) setDemo(d); }).catch(() => {});
    // The menu badge on "Demandes & contact": requests nobody has looked at yet.
    const pollReq = () => {
      if (user.rawRole !== "superadmin") return;
      STApi.contactSummary().then(s => { if (alive) setNewReq(s.new || 0); }).catch(() => {});
    };
    pollReq();
    const ivReq = setInterval(pollReq, 30000);
    const iv = setInterval(() => { if (alive) refresh(); }, 4000);
    return () => { alive = false; clearInterval(iv); clearInterval(ivReq); };
  }, [user, refresh]);

  const showToast = (msg) => { setToast(msg); clearTimeout(window._tt); window._tt = setTimeout(() => setToast(null), 2200); };

  const onCommand = async (deviceId, patch, silent) => {
    const before = devices.find(x => x.id === deviceId);

    // Optimistic: the button should react immediately. The device gets ~1 s to
    // answer, so waiting first would feel broken.
    setDevices(ds => ds.map(d => d.id === deviceId ? { ...d, ...patch, pending: true } : d));

    try {
      const state = await STApi.command(deviceId, patch);
      setDevices(ds => ds.map(d => d.id === deviceId
        ? { ...d, ...(state || patch), pending: false }
        : d));
      if (!silent) {
        let msg = "Commande envoyée";
        if ("power" in patch) msg = patch.power ? "Appareil allumé" : "Appareil éteint";
        else if ("target" in patch) msg = `Consigne réglée à ${patch.target}°C`;
        else if ("mode" in patch) msg = `Mode ${D.modeLabel(patch.mode)}`;
        else if ("fan" in patch) msg = `Ventilation ${D.fanLabel(patch.fan)}`;
        showToast(msg);
      }
    } catch (e) {
      // Put the old value back: the AC never received anything, so showing it
      // as changed would be a lie.
      setDevices(ds => ds.map(d => d.id === deviceId
        ? { ...(before || d), pending: false }
        : d));
      showToast(e.message || "Commande refusée");
      refresh();
    }
  };

  const login = async (email, password) => {
    setAuthBusy(true);
    setAuthError(null);
    try {
      const me = await STApi.login(email, password);
      setUser(me);
      setRoute("dashboard");
    } catch (e) {
      setAuthError(e.message || "Connexion impossible");
    } finally {
      setAuthBusy(false);
    }
  };

  const logout = async () => {
    await STApi.logout();
    setUser(null);
    setOpenDevice(null);
    setDevices([]);
  };

  if (loading) {
    return React.createElement("div", {
      style: { display: "grid", placeItems: "center", minHeight: "100vh", color: "var(--ink-3)" },
      className: "mono",
    }, "Chargement…");
  }

  if (!user) return React.createElement(Login, {
    onLogin: login, theme, busy: authBusy, error: authError,
    toggleTheme: () => setTheme(theme === "dark" ? "light" : "dark"),
  });

  const openDev = openDevice ? devices.find(d => d.id === openDevice) : null;
  const isDemoUser = !!(demo && demo.enabled && user.orgId && user.orgId === demo.orgId);
  const canSee = (x) => (!x.admin || user.isAdmin) && (!x.superadmin || user.rawRole === "superadmin");
  const navGroups = NAV.filter(canSee)
    .map(g => ({ ...g, items: g.items.filter(canSee) }))
    .filter(g => g.items.length);

  let content;
  if (route === "dashboard") content = React.createElement(Dashboard, { devices, units, user, onCommand, onOpen: setOpenDevice, view: dashView, setView: setDashView });
  else if (route === "remote") content = React.createElement(RemoteGrid, { devices, units, onCommand, showToast });
  else if (route === "sensors") content = React.createElement(SensorsPage, { devices, units, user, showToast, onRefresh: refresh, onCommand });
  else if (route === "energy") content = React.createElement(EnergyPage, { devices, units, user, showToast, onRefresh: refresh });
  else if (route === "calendar") content = React.createElement(Schedules, {
    schedules, units, orgs, user,
    onToggle: async (id) => {
      const s = schedules.find(x => x.id === id);
      try { await STApi.updateSchedule(id, { enabled: !s.enabled }); refresh(); }
      catch (e) { showToast(e.message || "Modification impossible"); }
    },
    onDelete: async (id) => {
      try { await STApi.deleteSchedule(id); showToast("Scénario supprimé"); refresh(); }
      catch (e) { showToast(e.message || "Suppression impossible"); }
    },
    onCreate: async (body) => {
      try {
        await STApi.createSchedule({ ...body, orgId: body.orgId || user.orgId });
        showToast("Scénario créé"); refresh();
      } catch (e) { showToast(e.message || "Création impossible"); }
    },
    onSave: async (id, body) => {
      try {
        await STApi.updateSchedule(id, body);
        showToast("Scénario modifié"); refresh();
      } catch (e) { showToast(e.message || "Modification impossible"); }
    },
  });
  // The menu hides it from clients; this keeps a remembered route from
  // bringing it back for them.
  else if (route === "assistant" && user.rawRole === "superadmin") content = React.createElement(Assistant, { devices, onCommand });
  else if (route === "settings") content = React.createElement(Settings, { theme, setTheme, accent, setAccent, user });
  else if (route === "fleet" && user.isAdmin) {
    const md = maintDevice ? devices.find(d => d.id === maintDevice) : null;
    content = md
      ? React.createElement(DeviceMaintenance, {
          device: md, devices, units, showToast,
          onBack: () => setMaintDevice(null), onChanged: refresh,
        })
      : React.createElement(Fleet, { devices, units, onOpen: setMaintDevice });
  }
  else if (route === "devices" && user.isAdmin) content = React.createElement(AdminDevices, { devices, units, onEdit: setEditDevice, onDeleted: refresh, showToast });
  else if (route === "clients" && user.rawRole === "superadmin") {
    content = openClient
      ? React.createElement(ClientDetail, {
          orgId: openClient, showToast, onCommand,
          onBack: () => setOpenClient(null),
        })
      : React.createElement(Clients, { user, showToast, onOpenClient: setOpenClient });
  }
  else if (route === "requests" && user.rawRole === "superadmin") content = React.createElement(Requests, {
    showToast,
    onChanged: () => STApi.contactSummary().then(s => setNewReq(s.new || 0)).catch(() => {}),
  });
  else content = React.createElement(Dashboard, { devices, units, user, onCommand, onOpen: setOpenDevice, view: dashView, setView: setDashView });

  const go = (id) => {
    if (id !== "clients") setOpenClient(null);
    if (id !== "fleet") setMaintDevice(null);
    setRoute(id);
    setNavOpen(false);
  };

  return React.createElement("div", { className: "shell" + (navOpen ? " nav-open" : "") },
    // sidebar
    React.createElement("aside", { className: "sidebar" },
      React.createElement("div", { className: "side-brand" },
        React.createElement("span", { className: "mark" }, React.createElement(Icon, { name: "home", size: 18, stroke: 2.1, style: { color: "#fff" } })),
        React.createElement("div", null,
          React.createElement("div", { className: "side-brand-name" }, "Smart Tiguemi"),
          React.createElement("div", { className: "side-brand-org mono" }, user.orgName || "Espace de gestion")
        )
      ),
      React.createElement("nav", { className: "side-nav" },
        navGroups.map(g => React.createElement("div", { key: g.group, className: "nav-group" },
          React.createElement("div", { className: "nav-glabel mono" }, g.group),
          g.items.map(it => React.createElement("button", {
            key: it.id, className: "nav-item" + (route === it.id ? " act" : ""), onClick: () => go(it.id),
          },
            React.createElement(Icon, { name: it.icon, size: 18 }),
            React.createElement("span", null, it.label),
            it.id === "requests" && newReq > 0 &&
              React.createElement("span", { className: "nav-count mono", title: newReq + " nouvelle(s) demande(s)" }, newReq)
          ))
        ))
      ),
      React.createElement("div", { className: "side-foot" },
        React.createElement("button", { className: "nav-item" + (route === "settings" ? " act" : ""), onClick: () => go("settings") },
          React.createElement(Icon, { name: "settings", size: 18 }), React.createElement("span", null, "Réglages")),
        React.createElement("div", { className: "side-user" },
          React.createElement("span", { className: "demo-av " + (user.isAdmin ? "admin" : "client") }, user.initials),
          React.createElement("div", { className: "su-meta" },
            React.createElement("div", { className: "su-name" }, user.name),
            React.createElement("div", { className: "su-role mono" }, user.role)
          ),
          React.createElement("button", { className: "icon-btn-sm", onClick: logout, title: "Déconnexion" }, React.createElement(Icon, { name: "logout", size: 16 }))
        )
      )
    ),
    // main
    React.createElement("div", { className: "main" },
      isDemoUser && React.createElement("div", { className: "demo-banner" },
        React.createElement("span", null, "Mode démo : les climatiseurs sont simulés et les données sont remises à zéro chaque nuit."),
        React.createElement("a", { href: "/#contact", className: "demo-banner-cta" }, "Demander un devis")),
      React.createElement("header", { className: "topbar" },
        React.createElement("button", { className: "icon-btn burger", onClick: () => setNavOpen(o => !o) }, React.createElement(Icon, { name: "menu", size: 18 })),
        React.createElement("div", { className: "search" },
          React.createElement(Icon, { name: "search", size: 16, className: "search-ic" }),
          React.createElement("input", { placeholder: "Rechercher un logement, un appareil…" })
        ),
        React.createElement("div", { className: "top-right" },
          React.createElement("span", { className: "live-pill", title: "Appareils en ligne / total" },
            React.createElement("span", { className: "live-dot" }),
            `${devices.filter(d => d.online).length}/${devices.length} en ligne`),
          React.createElement(LangPicker, null),
          React.createElement("button", { className: "icon-btn", onClick: () => setTheme(theme === "dark" ? "light" : "dark"), title: "Thème" },
            React.createElement(Icon, { name: theme === "dark" ? "sun" : "moon", size: 17 }))
        )
      ),
      React.createElement("main", { className: "content" }, content)
    ),
    // An air conditioner opens its remote; a sensor or a meter its readings.
    openDev && (openDev.kind === "ir"
      ? React.createElement(DeviceDetail, { device: openDev, onCommand, onClose: () => setOpenDevice(null) })
      : React.createElement(MeasurePanel, {
          device: openDev, units, devices, isAdmin: user.isAdmin, showToast,
          onClose: () => setOpenDevice(null),
          onChanged: () => { setOpenDevice(null); refresh(); },
        })),
    editDevice && React.createElement(DeviceSettings, {
      device: editDevice, units, showToast,
      onClose: () => setEditDevice(null),
      onSaved: () => { setEditDevice(null); refresh(); },
      onDelete: async () => {
        if (!confirm(`Retirer ${editDevice.name} du parc ?`)) return;
        try { await STApi.deleteDevice(editDevice.id); showToast("Appareil retiré"); }
        catch (e) { showToast(e.message || "Suppression impossible"); }
        setEditDevice(null); refresh();
      },
    }),
    navOpen && React.createElement("div", { className: "nav-scrim", onClick: () => setNavOpen(false) }),
    toast && React.createElement("div", { className: "toast" }, React.createElement(Icon, { name: "check", size: 15, stroke: 3 }), toast)
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(React.createElement(App));
