/* Hearth — routing, the fixture in state, and the settings the product acts on.
   The commits live in HouseCommits.jsx behind makeHouseCommits(ctx); this file
   is the router and the commit index.

   Auth renders outside the shell (§7.1): it has no navigation owner, so it is
   returned before HearthShell is reached rather than nested inside it. */
function App(props) {
  const options = props || {};
  const [path, setPath] = React.useState(options.startPath || "/");
  const [dark, setDark] = React.useState(options.theme === "dark");
  const [read, setRead] = React.useState(false);
  const [toasts, setToasts] = React.useState([]);
  const [confirm, setConfirm] = React.useState(null);
  const [form, setForm] = React.useState(null);
  const [period, setPeriod] = React.useState("14");
  const [authEmail, setAuthEmail] = React.useState(HEARTH_RESET_LINK.email);

  const [data, setData] = React.useState({
    house: HOUSE_STATE, members: MEMBERS, zones: ZONES_STATE, entries: ENTRIES, devices: DEVICES,
    hub: HUB, links: DEVICE_LINK, discovered: DISCOVERED, cameras: CAMERAS_STATE, clips: CLIPS,
    energy: ENERGY_DAYS, water: WATER_DAYS, battery: BATTERY, solar: SOLAR,
    bills: BILLS, contracts: CONTRACTS, jobs: JOBS, providers: PROVIDERS, grants: GRANTS,
    routines: ROUTINES, requests: REQUESTS, events: EVENTS,
  });
  /* The settings the product acts on. Each one is read by a screen that does
     something with it, never stored twice. */
  const [residence, setResidence] = React.useState(RESIDENCE);
  const [policy, setPolicy] = React.useState(HOUSE_POLICY);
  const [tariff, setTariff] = React.useState(TARIFF);
  const [response, setResponse] = React.useState(RESPONSE);
  const [prefs, setPrefs] = React.useState(NOTIFICATION_PREFS);
  const [dataPrefs, setDataPrefs] = React.useState(DATA_PREFS);

  const now = HEARTH_NOW;
  /* The signed-in person is a member record, not a second literal: the profile
     screen edits the same entry every permission predicate reads. */
  const viewer = data.members.find((m) => m.id === HEARTH_SIGNED_IN);
  const palette = useCommandPalette(!!confirm || !!form);

  React.useEffect(() => { if (options.startPath) setPath(options.startPath); }, [options.startPath]);
  React.useEffect(() => { setDark(options.theme === "dark"); }, [options.theme]);
  React.useEffect(() => {
    document.documentElement.classList.toggle("dark", dark);
    document.body.classList.toggle("dark", dark);
  }, [dark]);
  /* Anything scoped to one record closes when the route changes: a live commit
     closure must not outlive the screen it was opened from. */
  React.useEffect(() => { setConfirm(null); setForm(null); }, [path]);

  const dismiss = (id) => setToasts((list) => list.filter((t) => t.id !== id));
  const pushToast = (toast) => {
    const id = "t" + Date.now() + Math.random().toString(36).slice(2, 6);
    setToasts((list) => list.concat([Object.assign({ id: id }, toast)]));
    setTimeout(() => dismiss(id), 7000);
  };

  const patch = (key, id, fields) => setData((d) => Object.assign({}, d, { [key]: d[key].map((r) => (r.id === id ? Object.assign({}, r, fields) : r)) }));
  const restore = (key, snapshot) => { if (snapshot) setData((d) => Object.assign({}, d, { [key]: d[key].map((r) => (r.id === snapshot.id ? snapshot : r)) })); };
  const logEvent = (kind, subject, line, note) => {
    const id = "evt_" + Math.random().toString(36).slice(2, 7);
    setData((d) => Object.assign({}, d, { events: [{ id: id, at: now, kind: kind, actorId: viewer.id, subject: subject, line: line, note: note || null }].concat(d.events) }));
    return id;
  };
  const unlogEvent = (id) => setData((d) => Object.assign({}, d, { events: d.events.filter((e) => e.id !== id) }));

  const house = makeHouseCommits({
    data: data, setData: setData, patch: patch, restore: restore, pushToast: pushToast,
    setForm: setForm, setConfirm: setConfirm, viewer: viewer, now: now, setPath: setPath,
    logEvent: logEvent, unlogEvent: unlogEvent,
  });

  /* ── The staged saves ──────────────────────────────────────────────────── */
  const saveResidence = (values) => {
    const beforeResidence = residence, beforePolicy = policy;
    setResidence(Object.assign({}, residence, { name: values.name.trim(), short: values.short.trim(), address: values.address.trim(), timezone: values.timezone }));
    setPolicy(Object.assign({}, policy, { awaySetbackC: values.away, nightSetbackC: values.night, frostC: values.frost, boostMinutes: Number(values.boost), lowBatteryPercent: Number(values.lowBattery) }));
    /* The policy object is what enums.js reads at render, so writing into it is
       what makes the setting real rather than decorative. */
    Object.assign(HOUSE_POLICY, { awaySetbackC: values.away, nightSetbackC: values.night, frostC: values.frost, boostMinutes: Number(values.boost), lowBatteryPercent: Number(values.lowBattery) });
    pushToast({
      variant: "success", title: "Residence saved",
      description: values.short.trim() + " · away " + formatTemp(values.away) + "°, night " + formatTemp(values.night) + "° · " + devicesLowBattery(data.devices).length + " devices now read low",
      onUndo: () => { setResidence(beforeResidence); setPolicy(beforePolicy); Object.assign(HOUSE_POLICY, beforePolicy); },
    });
  };

  const saveTariff = (values) => {
    const before = tariff, beforePolicy = policy;
    const next = Object.assign({}, tariff, {
      supplier: values.supplier.trim(), name: values.name.trim(),
      exportPencePerKwh: Number(values.exportRate),
      windows: [
        Object.assign({}, tariff.windows[0], { from: values.offFrom, to: values.offTo, pencePerKwh: Number(values.offRate) }),
        Object.assign({}, tariff.windows[1], { from: values.peakFrom, to: values.peakTo, pencePerKwh: Number(values.peakRate) }),
        Object.assign({}, tariff.windows[2], { pencePerKwh: Number(values.standardRate) }),
      ],
    });
    setTariff(next);
    /* metrics.costOf reads TARIFF and HOUSE_POLICY directly — one source, so a
       saved rate re-prices every day on Energy rather than a copy of it. */
    Object.assign(TARIFF, next);
    Object.assign(HOUSE_POLICY, { standingChargePence: Number(values.standing) });
    setPolicy(Object.assign({}, policy, { standingChargePence: Number(values.standing) }));
    pushToast({
      variant: "success", title: "Tariff saved",
      description: "The last " + period + " days now cost " + formatPounds(costSum(data.energy.slice(-Number(period)))) + " · " + batteryAdvice(data.battery, now).line.toLowerCase(),
      onUndo: () => { setTariff(before); Object.assign(TARIFF, before); Object.assign(HOUSE_POLICY, beforePolicy); setPolicy(beforePolicy); },
    });
  };

  const saveSecuritySettings = (values) => {
    const beforePolicy = policy, beforeResponse = response, beforeResidence = residence;
    Object.assign(HOUSE_POLICY, { darkAfter: values.darkAfter });
    setPolicy(Object.assign({}, policy, { darkAfter: values.darkAfter }));
    setResidence(Object.assign({}, residence, { responseNumber: values.responseNumber.trim() }));
    setResponse(Object.assign({}, response, { order: values.order, autoArm: values.autoArm, autoArmAt: values.autoArmAt, notifyPolice: values.notifyPolice }));
    Object.assign(RESPONSE, { order: values.order, autoArm: values.autoArm, autoArmAt: values.autoArmAt, notifyPolice: values.notifyPolice });
    const insecure = entriesInsecure(data, now);
    pushToast({
      variant: "success", title: "Alarm settings saved",
      description: "Dark after " + values.darkAfter + " · " + insecure.length + (insecure.length === 1 ? " way in is" : " ways in are") + " insecure on the new rules · " + memberName(data.members, values.order[0]).split(" ")[0] + " is called first",
      onUndo: () => { Object.assign(HOUSE_POLICY, beforePolicy); setPolicy(beforePolicy); setResponse(beforeResponse); Object.assign(RESPONSE, beforeResponse); setResidence(beforeResidence); },
    });
  };

  const saveNotifications = (values) => {
    const before = prefs;
    setPrefs(Object.assign({}, prefs, { rows: values.rows, quietFrom: values.from, quietTo: values.to }));
    const silent = values.rows.filter((r) => !r.push && !r.sms && !r.email);
    pushToast({
      variant: "success", title: "Notifications saved",
      description: (values.rows.length - silent.length) + " of " + values.rows.length + " events reach somebody · quiet " + values.from + "–" + values.to,
      onUndo: () => setPrefs(before),
    });
  };

  const saveDataPrefs = (values) => {
    const before = dataPrefs;
    setDataPrefs({ keepLogDays: Number(values.keepLogDays), keepClipsDays: Number(values.keepClipsDays), shareWithProviders: values.shareWithProviders, presenceHistory: values.presenceHistory });
    pushToast({
      variant: "success", title: "Data settings saved",
      description: "Log kept " + values.keepLogDays + " days, clips " + values.keepClipsDays + " · " + (values.presenceHistory ? "who was home is recorded" : "who was home is no longer recorded"),
      onUndo: () => setDataPrefs(before),
    });
  };

  const saveProfile = (values) => {
    const before = viewer;
    patch("members", viewer.id, { name: values.name.trim(), email: values.email.trim(), phone: values.phone.trim() || null, avatar: values.avatar });
    const uploaded = values.avatar && values.avatar.indexOf("data:") === 0;
    pushToast({
      variant: "success", title: "Profile saved",
      description: values.name.trim() + " · " + (uploaded ? "your uploaded photograph" : values.avatar ? "your household photograph" : "no photograph") + " is now beside your name everywhere the house records it",
      onUndo: () => restore("members", before),
    });
  };

  /* Harness chrome, not product chrome: the floating page menu reaches every
     registered screen, including the auth ones the shell cannot render. */
  const preview = (
    <TemplateMenu product="Hearth" path={path} onNavigate={setPath} groups={HEARTH_PAGES(data)}
      dark={dark} onToggleTheme={() => setDark(!dark)} />
  );

  if (HEARTH_AUTH_PATHS.indexOf(path) > -1) {
    const authScreen = () => {
      if (path === "/forgot-password") return <ForgotPasswordScreen email={authEmail} onSubmit={(value) => { setAuthEmail(value); setPath("/forgot-password/sent"); }} onNavigate={setPath} />;
      if (path === "/forgot-password/sent") return <ResetLinkSentScreen email={authEmail} onNavigate={setPath} />;
      if (path === "/reset-password") return <SetNewPasswordScreen now={now} onSubmit={() => setPath("/reset-password/done")} onNavigate={setPath} />;
      if (path === "/reset-password/expired") return <ResetLinkExpiredScreen onNavigate={setPath} />;
      if (path === "/reset-password/done") return <PasswordResetDoneScreen email={HEARTH_RESET_LINK.email} onNavigate={setPath} />;
      return <SignInScreen onSignIn={() => setPath("/")} onNavigate={setPath} />;
    };
    return (<>{authScreen()}{preview}</>);
  }

  const match = (pattern) => {
    const m = path.match(pattern);
    return m ? m[1] : null;
  };
  const deviceId = match(/^\/devices\/([^/]+)$/);
  const personId = match(/^\/people\/([^/]+)$/);
  const providerId = match(/^\/providers\/([^/]+)$/);
  const jobId = match(/^\/upkeep\/([^/]+)$/);

  const screen = () => {
    if (path === "/") {
      return <TodayScreen data={data} now={now} viewer={viewer} onNavigate={setPath}
        onArm={house.onArm} onToggleDevice={house.toggleDevice} onDeviceTransition={house.onDeviceTransition}
        onRequest={house.onRequest} onJobTransition={house.onJobTransition} onExport={house.onExport} />;
    }
    if (path === "/rooms") {
      return <RoomsScreen data={data} now={now} viewer={viewer} onNavigate={setPath}
        onToggleDevice={house.toggleDevice} onDeviceTransition={house.onDeviceTransition}
        onZoneStep={house.zoneStep} onAllOff={house.allOffDownstairs} onNewDevice={house.newDevice} />;
    }
    if (path === "/cameras") {
      return <CamerasScreen data={data} now={now} viewer={viewer} onNavigate={setPath}
        onCameraAction={house.cameraAction} onCameraPrivacy={house.cameraPrivacy}
        onDeviceTransition={house.onDeviceTransition} onExport={house.onExport} />;
    }
    if (deviceId && data.devices.some((d) => d.id === deviceId)) {
      return <DeviceScreen data={data} now={now} viewer={viewer} deviceId={deviceId} onNavigate={setPath}
        onToggleDevice={house.toggleDevice} onDeviceTransition={house.onDeviceTransition}
        onTestDevice={house.testDevice} onUpdateFirmware={house.updateFirmware} onExport={house.onExport} />;
    }
    if (path === "/devices") {
      return <DevicesScreen data={data} now={now} viewer={viewer} onNavigate={setPath}
        onToggleDevice={house.toggleDevice} onDeviceTransition={house.onDeviceTransition}
        onPairDevice={house.pairDevice} onRunHouseTest={house.runHouseTest} onTestDevice={house.testDevice}
        onUpdateFirmware={house.updateFirmware} onExport={house.onExport} />;
    }
    if (path === "/climate") {
      return <ClimateScreen data={data} now={now} viewer={viewer} onNavigate={setPath}
        onZoneStep={house.zoneStep} onZoneMode={house.zoneMode} onEditPreference={house.editPreference} onExport={house.onExport} />;
    }
    if (path === "/security") {
      return <SecurityScreen data={data} now={now} viewer={viewer} onNavigate={setPath}
        onArm={house.onArm} onEntry={house.onEntry} onCallResponse={house.callResponse}
        onDeviceTransition={house.onDeviceTransition} onExport={house.onExport} />;
    }
    if (path === "/energy") {
      return <EnergyScreen data={data} now={now} viewer={viewer} period={period} onPeriodChange={setPeriod}
        onBatteryMode={house.setBatteryMode} onNavigate={setPath} onExport={house.onExport} />;
    }
    if (path === "/spend") {
      return <SpendScreen data={data} now={now} viewer={viewer} onNavigate={setPath}
        onBillTransition={house.onBillTransition} onNewBill={house.newBill} onExport={house.onExport} />;
    }
    if (jobId && data.jobs.some((j) => j.id === jobId)) {
      return <JobScreen data={data} now={now} viewer={viewer} jobId={jobId} onNavigate={setPath}
        onJobTransition={house.onJobTransition} onExport={house.onExport} />;
    }
    if (path === "/upkeep") {
      return <UpkeepScreen data={data} now={now} viewer={viewer} onNavigate={setPath}
        onJobTransition={house.onJobTransition} onNewJob={house.newJob} onExport={house.onExport} />;
    }
    if (providerId && data.providers.some((p) => p.id === providerId)) {
      return <ProviderScreen data={data} now={now} viewer={viewer} providerId={providerId} onNavigate={setPath}
        onJobTransition={house.onJobTransition} onAccessTransition={house.onAccessTransition} onExport={house.onExport} />;
    }
    if (path === "/providers") {
      return <ProvidersScreen data={data} now={now} viewer={viewer} onNavigate={setPath}
        onNewProvider={house.newProvider} onExport={house.onExport} />;
    }
    if (personId && data.members.some((m) => m.id === personId)) {
      return <PersonScreen data={data} now={now} viewer={viewer} memberId={personId} onNavigate={setPath}
        onSavePreferences={house.savePreferences} onMemberTransition={house.onMemberTransition} onRequest={house.onRequest} />;
    }
    if (path === "/people") {
      return <PeopleScreen data={data} now={now} viewer={viewer} onNavigate={setPath}
        onRequest={house.onRequest} onMemberTransition={house.onMemberTransition} onInvite={house.invitePerson} onExport={house.onExport} />;
    }
    if (path === "/access") {
      return <AccessScreen data={data} now={now} viewer={viewer} onNavigate={setPath}
        onAccessTransition={house.onAccessTransition} onNewGrant={house.newGrant} onExport={house.onExport} />;
    }
    if (path === "/routines") {
      return <RoutinesScreen data={data} now={now} viewer={viewer} onNavigate={setPath}
        onRoutineTransition={house.onRoutineTransition} onNewRoutine={house.newRoutine} onExport={house.onExport} />;
    }
    if (path === "/log") return <LogScreen data={data} now={now} viewer={viewer} onNavigate={setPath} onExport={house.onExport} />;
    if (path === "/account/profile") return <ProfileScreen data={data} now={now} viewer={viewer} onNavigate={setPath} onSave={saveProfile} />;
    if (path === "/settings") return <ResidenceSettingsScreen data={data} now={now} residence={residence} policy={policy} onNavigate={setPath} onSave={saveResidence} />;
    if (path === "/settings/tariff") return <TariffSettingsScreen data={data} now={now} tariff={tariff} policy={policy} onNavigate={setPath} onSave={saveTariff} />;
    if (path === "/settings/security") return <SecuritySettingsScreen data={data} now={now} policy={policy} response={response} onNavigate={setPath} onSave={saveSecuritySettings} />;
    if (path === "/settings/notifications") return <NotificationSettingsScreen data={data} prefs={prefs} residence={residence} onNavigate={setPath} onSave={saveNotifications} />;
    if (path === "/settings/data") return <DataSettingsScreen data={data} now={now} dataPrefs={dataPrefs} onNavigate={setPath} onSave={saveDataPrefs} onExportAll={(format) => house.onExport("Everything", "The whole record", data.events.length + data.devices.length + data.bills.length, format)} />;
    return <RouteNotBuilt path={path} onNavigate={setPath} />;
  };

  const recordGroups = [
    { label: "Not secure", items: entriesInsecure(data, now).map((e) => ({ value: e.id, label: e.label + " · " + statusValue(ENTRY_STATUS, e.status).label.toLowerCase(), route: "/security" })) },
    { label: "Waiting on you", items: requestsWaiting(data.requests).map((r) => ({ value: r.id, label: memberName(data.members, r.memberId) + " · " + r.body.slice(0, 44) + "…", route: "/people" })) },
    /* The four registers are off the sidebar, so the palette is the one place
       that names them as destinations: derived from the route table — an
       offSidebar route whose PARENT is a sidebar destination — never a second
       list to keep in step. The auth flow fails that test on its parent, which
       is how it stays out of the palette (§7.1). */
    { label: "Registers", items: HEARTH_NAV.filter((r) => r.offSidebar && r.parent && !r.group && r.path.indexOf(":") < 0 && HEARTH_ROUTES[r.parent] && !HEARTH_ROUTES[r.parent].offSidebar).map((r) => ({ value: r.path, label: r.label + " · under " + HEARTH_ROUTES[r.parent].label, route: r.path })) },
    { label: "Rooms", items: ROOMS.map((room) => ({ value: room.value, label: room.label, route: "/rooms" })) },
    { label: "Cameras", items: cameraRows(data).map((c) => ({ value: c.id, label: c.name + " · " + (c.watching ? "watching" : c.shuttered ? "shutter closed" : statusValue(DEVICE_STATUS, c.device ? c.device.status : "offline").label.toLowerCase()), route: "/cameras" })) },
    { label: "Devices", items: data.devices.map((d) => ({ value: d.id, label: d.name + " · " + roomName(d.room), route: "/devices/" + d.id })) },
    { label: "People", items: data.members.map((m) => ({ value: m.id, label: m.name + " · " + labelOf(HOUSEHOLD_ROLES, m.role), route: "/people/" + m.id })) },
    { label: "Jobs", items: data.jobs.map((j) => ({ value: j.id, label: j.title + " · " + statusValue(JOB_STATUS, j.status).label, route: "/upkeep/" + j.id })) },
    { label: "Providers", items: data.providers.map((p) => ({ value: p.id, label: p.name + " · " + labelOf(TRADES, p.trade), route: "/providers/" + p.id })) },
  ];

  return (
    <HearthShell
      path={path}
      onNavigate={setPath}
      dark={dark}
      onToggleTheme={() => setDark(!dark)}
      data={data}
      now={now}
      viewer={viewer}
      notifications={NOTIFICATIONS}
      unread={read ? 0 : NOTIFICATIONS.length}
      onRead={() => setRead(true)}
      palette={palette}
      toasts={toasts}
      onDismissToast={dismiss}
    >
      {screen()}
      <CommandPalette
        open={palette.open}
        onClose={() => palette.setOpen(false)}
        routes={HEARTH_ROUTES}
        recordGroups={recordGroups}
        onNavigate={setPath}
        placeholder="Search devices, rooms, people, jobs…"
      />
      <ConfirmDialog request={confirm} onCancel={() => setConfirm(null)} onConfirm={() => { confirm.run(); setConfirm(null); }} />
      <FormDialog request={form} onCancel={() => setForm(null)} onSubmit={(values) => { form.run(values); setForm(null); }} />
      {preview}
    </HearthShell>
  );
}
window.__hearthReal = App;
if (!window.HearthApp) window.HearthApp = App;
