// The page: a header with the menu, the chosen theme, and the presets'
// foot. Everything it keeps is in this browser (engine/store.ts), read
// before anything is drawn, so the page opens as it was left rather than
// on the defaults and then jumping.

import { StrictMode, useState, type JSX } from "react";
import { createRoot } from "react-dom/client";
import { Menu, Monitor, Moon, Sun } from "lucide-react";
import { useMetronome, type MetronomeApi } from "./engine/useMetronome";
import { load, loadAppearance, loadLook, saveAppearance, saveLook, type Appearance, type Saved } from "./engine/store";
import type { Look } from "./looks";
import { LookContext } from "./controls/ThemePicker";
import { Footer } from "./controls/PresetMenu";
import { Segmented } from "./controls/controls";
import { AppMenu } from "./menu/AppMenu";
import { Pendulum } from "./variants/Pendulum";
import { Pads } from "./variants/Pads";
import { Dial } from "./variants/Dial";
import { List } from "./variants/List";
import "./base.css";
import "./controls/controls.css";
import "./page.css";

const DRAWN: Record<Look, (props: { m: MetronomeApi }) => JSX.Element> = {
  orbit: Dial,
  classic: Pendulum,
  pads: Pads,
  simple: List,
};

/** The ground colour of each appearance, for the browser's own bars
 *  (`theme-color`): what index.html says for the system's choice, and the
 *  person's own choice over both when there is one. */
const GROUND = { light: "#ffffff", dark: "#18181a" } as const;

/** Light or dark on the root: none for the system's choice. index.html
 *  does the same before the page's script has loaded, so a page kept dark
 *  never flashes light first. */
function wear(appearance: Appearance) {
  const root = document.documentElement;
  if (appearance === "auto") delete root.dataset.theme;
  else root.dataset.theme = appearance;
  for (const meta of document.querySelectorAll<HTMLMetaElement>('meta[name="theme-color"]')) {
    const system = meta.media.includes("dark") ? "dark" : "light";
    meta.content = GROUND[appearance === "auto" ? system : appearance];
  }
}

function Page({ initial, saved }: { initial: { look: Look; appearance: Appearance }; saved: Saved }) {
  // The metronome lives here, above the theme, so switching themes
  // changes the picture and nothing playing.
  const m = useMetronome(saved);
  const [look, setLook] = useState(initial.look);
  const [appearance, setAppearance] = useState(initial.appearance);
  const [menu, setMenu] = useState(false);
  const choose = (next: Look) => {
    setLook(next);
    document.documentElement.dataset.look = next;
    saveLook(next);
  };
  const appear = (next: Appearance) => {
    setAppearance(next);
    wear(next);
    saveAppearance(next);
  };
  const Drawn = DRAWN[look];
  return (
    <LookContext.Provider value={{ look, choose }}>
      <div className="m-page">
        <header className="m-header">
          <img className="m-header-icon" src={`${import.meta.env.BASE_URL}icon.svg`} alt="" width={22} height={22} />
          <h1>Metronome</h1>
          {/* Light or dark one press away, on the page itself: the menu's
              Appearance is the same choice, and the two never disagree. */}
          <Segmented
            label="Appearance"
            className="m-appearance"
            value={appearance}
            onChange={appear}
            options={[
              { value: "auto", label: <Monitor size={15} strokeWidth={1.8} aria-hidden="true" />, title: "Match the system" },
              { value: "light", label: <Sun size={15} strokeWidth={1.8} aria-hidden="true" />, title: "Light" },
              { value: "dark", label: <Moon size={15} strokeWidth={1.8} aria-hidden="true" />, title: "Dark" },
            ]}
          />
          <button
            type="button"
            className="m-icon-button bare m-header-menu"
            aria-label="Themes, appearance and about"
            aria-haspopup="dialog"
            data-tip="Themes, appearance and about"
            onClick={() => setMenu(true)}
          >
            <Menu size={18} strokeWidth={1.8} aria-hidden="true" />
          </button>
        </header>
        {/* The foot is the page's, not the theme's: it stays on the window's
            last line while a theme taller than the window scrolls above
            it, so the presets and the theme are always a press away. */}
        <main className="m-page-body">
          <Drawn m={m} />
        </main>
        <Footer m={m} />
      </div>
      <AppMenu open={menu} onClose={() => setMenu(false)} appearance={appearance} onAppearance={appear} />
    </LookContext.Provider>
  );
}

const initial = { look: loadLook(), appearance: loadAppearance() };
wear(initial.appearance);
document.documentElement.dataset.look = initial.look;
createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <Page initial={initial} saved={load()} />
  </StrictMode>,
);

// Offline after the first visit: the service worker keeps every file the
// page needs (build/service-worker.ts). Never in `pnpm dev`, where it would
// serve yesterday's code to today's edits.
if (import.meta.env.PROD && "serviceWorker" in navigator) {
  addEventListener("load", () => {
    navigator.serviceWorker.register(`${import.meta.env.BASE_URL}sw.js`).catch(() => {
      // No worker (a private window, a browser setting): the page still
      // works, only not offline.
    });
  });
}
