"use client"

import * as React from "react"
import { useTheme } from "next-themes"

import { useT } from "@/components/locale-provider"
import { cn } from "@/lib/utils"
import type { MessageKey } from "@/lib/i18n/messages"
import { IconDeviceDesktop, IconMoon, IconSun } from "@tabler/icons-react"

/**
 * Light, dark, or whatever the machine says, in the sidebar beside the
 * language.
 *
 * It is here for the same reason the language switch is here rather than inside
 * the person's own menu: somebody who cannot see the screen should not have to
 * find a menu they cannot see. Glare on a bright bay and a dark corner of the
 * factory are both real, and neither is fixed by a setting three presses deep.
 *
 * Until now the only way to change this was an undocumented `d` keypress in
 * `components/theme-provider.tsx`. Nobody on a factory floor discovers a
 * hotkey.
 *
 * **It stays in the browser, and that is the opposite of the language, on
 * purpose.** The language lives on the person because a preference left on a
 * shared terminal is inherited by whoever walks up next, and with 183 of 193
 * people reading Thai the inheritor is usually the one it is wrong for. The
 * theme is not like that: it is a fact about the light in the room, and the
 * light at a terminal is the same for everybody who stands at it. So it belongs
 * to the machine, which is where `next-themes` already keeps it, and it must
 * not be moved onto the person.
 *
 * Three choices and not a toggle, for the same reason. `system` is the machine
 * saying what the room is like, and it is the default; a two-way toggle would
 * be a one-way door out of it.
 */

type Choice = {
  value: "light" | "dark" | "system"
  label: MessageKey
  icon: React.ReactNode
}

const CHOICES: Choice[] = [
  { value: "light", label: "theme.light", icon: <IconSun size={18} /> },
  { value: "dark", label: "theme.dark", icon: <IconMoon size={18} /> },
  {
    value: "system",
    label: "theme.system",
    icon: <IconDeviceDesktop size={18} />,
  },
]

export function ThemeSwitch({
  /** Sidebar colours and collapse behaviour, or a page's. Matches `LanguageSwitch`. */
  variant = "sidebar",
}: {
  variant?: "sidebar" | "page"
} = {}) {
  const { theme, setTheme } = useTheme()
  const t = useT()
  const inSidebar = variant === "sidebar"

  /*
    Nothing is drawn as chosen until the browser has told us what it chose. The
    server cannot know the theme: it is kept by the machine, which is the whole
    point of this control. Marking a button pressed on the first pass would mark
    whichever one the server guessed and then correct itself, which reads as the
    control changing on its own the moment you look at it.

    `useSyncExternalStore` and not a `useState` set from an effect. It says
    exactly this, "false on the server and true once hydrated", in one
    expression and with no render to undo; a state set in an effect is also the
    thing ESLint refuses here, and it is right to.
  */
  const ready = React.useSyncExternalStore(
    () => () => {},
    () => true,
    () => false,
  )

  return (
    <div
      role="group"
      aria-label={t("theme.label")}
      className={cn(
        "flex",
        inSidebar
          ? "gap-1 px-1 pb-1 group-data-[collapsible=icon]:hidden"
          : "gap-2",
      )}
    >
      {CHOICES.map((choice) => {
        const active = ready && theme === choice.value
        return (
          <button
            key={choice.value}
            type="button"
            /*
              `aria-pressed` rather than `disabled` on the chosen one, the same
              way the language switch does it: a button that disables itself
              hands focus somewhere the person did not ask for, and here that
              somewhere is another theme one Enter away.
            */
            aria-pressed={active}
            aria-label={t(choice.label)}
            title={t(choice.label)}
            onClick={() => setTheme(choice.value)}
            className={cn(
              "flex items-center justify-center rounded-md transition-colors",
              "focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-none",
              inSidebar ? "flex-1 px-2 py-1.5" : "min-w-20 px-4 py-2",
              inSidebar &&
                (active
                  ? "bg-sidebar-accent text-sidebar-accent-foreground"
                  : "text-sidebar-foreground/70 hover:bg-sidebar-accent/60"),
              !inSidebar &&
                (active
                  ? "bg-accent text-accent-foreground"
                  : "text-foreground/70 hover:bg-accent/60"),
            )}
          >
            {choice.icon}
          </button>
        )
      })}
    </div>
  )
}
