import * as React from 'react';
import { useTheme } from 'next-themes';
import { Laptop, Moon, Sun } from 'lucide-react';

import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';

type Mode = 'light' | 'dark' | 'system';

const ORDER: readonly Mode[] = ['light', 'dark', 'system'] as const;

const LABEL: Record<Mode, string> = {
  light: 'Light',
  dark: 'Dark',
  system: 'Match system',
};

/**
 * Cycles light -> dark -> system.
 *
 * The mounted guard is not ceremony: `theme` is `undefined` on the server and
 * on the first client render, so painting an icon before that resolves renders
 * the wrong one and then swaps it — the flash this component exists to avoid.
 * The placeholder is the same size as the button so the header does not reflow
 * when it resolves.
 */
export function ThemeToggle({ className }: { className?: string }) {
  const { theme, setTheme } = useTheme();
  const [mounted, setMounted] = React.useState(false);
  React.useEffect(() => setMounted(true), []);

  if (!mounted) {
    return <div className={cn('size-11 lg:size-9', className)} aria-hidden="true" />;
  }

  const current: Mode = theme === 'dark' || theme === 'light' ? theme : 'system';
  const next = ORDER[(ORDER.indexOf(current) + 1) % ORDER.length] ?? 'system';
  const Icon = current === 'light' ? Sun : current === 'dark' ? Moon : Laptop;

  return (
    <Button
      type="button"
      variant="ghost"
      size="icon-touch"
      className={cn('lg:size-9', className)}
      onClick={() => setTheme(next)}
      // The label states where you ARE and where the press takes you, because
      // an icon-only toggle announced as just "Theme" tells a screen-reader
      // user nothing about what pressing it will do.
      aria-label={`Theme: ${LABEL[current]}. Switch to ${LABEL[next].toLowerCase()}.`}
      title={`Theme: ${LABEL[current]}`}
    >
      <Icon aria-hidden="true" />
    </Button>
  );
}
