import { useEffect, useState } from 'react';
import { api } from '@/api/client';

// themes-v1: source of truth for the 19 presets + 3 futuristic additions.
// id and name are surfaced in the picker; family groups visually;
// supportsDark/supportsLight reflect whether the corresponding selector exists
// in styles/themes/<id>.css; anchors are the 5 dark swatches (or the light
// palette for the two light-only themes) used in the picker preview strip.
// Dark-only themes (supportsLight:false) always render with the dark class —
// see applyTheme force-dark logic below.
export type ThemeId =
  | 'obsidian'
  | 'gunmetal'
  | 'espresso'
  | 'volcanic-brown'
  | 'copper'
  | 'gold'
  | 'oxblood'
  | 'crimson'
  | 'elderflower'
  | 'plum'
  | 'steel-pink'
  | 'fuchsia-noir'
  | 'matrix'
  | 'sage'
  | 'ivory'
  | 'chalk'
  | 'cobalt'
  | 'midnight-sapphire'
  | 'ember'
  | 'boocode-plus'
  | 'boocode-classic'
  | 'boocode-override';

export type ThemeMode = 'dark' | 'light' | 'system';

export interface ThemeMeta {
  id: ThemeId;
  name: string;
  family: string;
  supportsDark: boolean;
  supportsLight: boolean;
  anchors: [string, string, string, string, string];
}

export const THEMES: readonly ThemeMeta[] = [
  { id: 'obsidian', name: 'Obsidian', family: 'Charcoal', supportsDark: true, supportsLight: true,
    anchors: ['#0c0c0e', '#15151a', '#1f1f23', '#6b6b75', '#8b5cf6'] },
  { id: 'gunmetal', name: 'Gunmetal', family: 'Charcoal', supportsDark: true, supportsLight: true,
    anchors: ['#0d1117', '#161b22', '#21262d', '#7d8590', '#388bfd'] },
  { id: 'espresso', name: 'Espresso', family: 'Brown', supportsDark: true, supportsLight: true,
    anchors: ['#1c1410', '#241a14', '#2e2218', '#8a7058', '#c8a880'] },
  { id: 'volcanic-brown', name: 'Volcanic Brown', family: 'Brown', supportsDark: true, supportsLight: true,
    anchors: ['#140906', '#1e0e0a', '#2e1610', '#7a4030', '#cc4a1a'] },
  { id: 'copper', name: 'Copper', family: 'Amber', supportsDark: true, supportsLight: true,
    anchors: ['#100800', '#1c1408', '#2e1f0a', '#8a6040', '#b87333'] },
  { id: 'gold', name: 'Gold', family: 'Amber', supportsDark: true, supportsLight: true,
    anchors: ['#0e0800', '#1a1200', '#2a1f00', '#a07c30', '#d4af37'] },
  { id: 'oxblood', name: 'Oxblood', family: 'Crimson', supportsDark: true, supportsLight: true,
    anchors: ['#0a0303', '#180606', '#2a0808', '#7a3028', '#8b1a1a'] },
  { id: 'crimson', name: 'Crimson', family: 'Crimson', supportsDark: true, supportsLight: true,
    anchors: ['#0e0404', '#1a0808', '#2e0a0a', '#8a3030', '#dc143c'] },
  { id: 'elderflower', name: 'Elderflower', family: 'Violet', supportsDark: true, supportsLight: true,
    anchors: ['#100818', '#1c1024', '#2c1830', '#8a78a0', '#b89cd8'] },
  { id: 'plum', name: 'Plum', family: 'Violet', supportsDark: true, supportsLight: true,
    anchors: ['#0c0814', '#180e20', '#241830', '#7a4878', '#8e4585'] },
  { id: 'steel-pink', name: 'Steel Pink', family: 'Magenta', supportsDark: true, supportsLight: true,
    anchors: ['#0e0408', '#1a080e', '#2e0c1a', '#9a4070', '#cc33aa'] },
  { id: 'fuchsia-noir', name: 'Fuchsia Noir', family: 'Magenta', supportsDark: true, supportsLight: true,
    anchors: ['#0a0610', '#14081a', '#2a0c2e', '#8a3878', '#ff1493'] },
  { id: 'matrix', name: 'Matrix', family: 'Green', supportsDark: true, supportsLight: true,
    anchors: ['#000a00', '#031403', '#0a200a', '#208030', '#00ff41'] },
  { id: 'sage', name: 'Sage', family: 'Green', supportsDark: true, supportsLight: true,
    anchors: ['#0a0e08', '#141a10', '#1e2e1a', '#7a8870', '#9caf88'] },
  { id: 'ivory', name: 'Ivory', family: 'Light', supportsDark: false, supportsLight: true,
    anchors: ['#fdfcf8', '#f5f2e8', '#e8e4d8', '#8a8478', '#3a3328'] },
  { id: 'chalk', name: 'Chalk', family: 'Light', supportsDark: false, supportsLight: true,
    anchors: ['#fafaf7', '#f0f0ec', '#e5e5e0', '#75756e', '#2a2a28'] },
  { id: 'cobalt', name: 'Cobalt', family: 'Blue', supportsDark: true, supportsLight: true,
    anchors: ['#020817', '#061434', '#0c2244', '#3060a0', '#0047ab'] },
  { id: 'midnight-sapphire', name: 'Midnight Sapphire', family: 'Blue', supportsDark: true, supportsLight: true,
    anchors: ['#02050e', '#060c1f', '#0e1a36', '#4a6088', '#1e3a8a'] },
  { id: 'ember', name: 'BooCode', family: 'Amber', supportsDark: true, supportsLight: true,
    anchors: ['#0c0c0e', '#15151a', '#1f1f23', '#6b6b75', '#ff7a18'] },
  // Futuristic ladder — Phase 1 registrations (final anchors + flags).
  // Token stylesheets and effects live in styles/themes/boocode-*.css.
  { id: 'boocode-plus', name: 'BooCode+', family: 'Futuristic', supportsDark: true, supportsLight: true,
    anchors: ['#0f1117', '#1a1d2e', '#242838', '#7a7f99', '#5e6ad2'] },
  { id: 'boocode-classic', name: 'BooCode Classic', family: 'Futuristic', supportsDark: true, supportsLight: false,
    anchors: ['#0a0604', '#120a06', '#1a0e08', '#9a7a5a', '#f97316'] },
  { id: 'boocode-override', name: 'BooCode Override', family: 'Futuristic', supportsDark: true, supportsLight: false,
    anchors: ['#0a0604', '#120a06', '#1a0e08', '#9a7a5a', '#f97316'] },
] as const;

// BooCode 2.0: orange-on-black "BooCode Ember" is the out-of-the-box signature
// (was 'obsidian' / purple). Also the dark fallback for the light-only themes.
export const DEFAULT_THEME_ID: ThemeId = 'ember';
export const DEFAULT_THEME_MODE: ThemeMode = 'dark';
export const STORAGE_KEY = 'boocode.theme';

const THEME_IDS_SET: ReadonlySet<string> = new Set(THEMES.map((t) => t.id));

export function isThemeId(s: string): s is ThemeId {
  return THEME_IDS_SET.has(s);
}

function resolvedMode(mode: ThemeMode): 'dark' | 'light' {
  if (mode !== 'system') return mode;
  if (typeof window === 'undefined') return 'dark';
  return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}

// Light-only themes (ivory, chalk) can't render dark — fall back to obsidian
// dark per spec §8 decision 1. Keeps the fallback explicit so the caller
// doesn't accidentally apply theme-ivory.dark (which has no rule block).
function effectiveThemeId(id: ThemeId, mode: 'dark' | 'light'): ThemeId {
  if (mode === 'dark') {
    const meta = THEMES.find((t) => t.id === id);
    if (meta && !meta.supportsDark) return DEFAULT_THEME_ID;
  }
  return id;
}

export function applyTheme(id: ThemeId, mode: ThemeMode): void {
  if (typeof document === 'undefined') return;
  const resolved = resolvedMode(mode);
  const effective = effectiveThemeId(id, resolved);
  // Dark-only themes (supportsLight:false) always get the dark class so
  // dark: utilities and .dark selectors render correctly under any mode pref.
  const meta = THEMES.find((t) => t.id === effective);
  const isDark = resolved === 'dark' || (meta !== undefined && !meta.supportsLight);
  document.documentElement.className =
    `theme-${effective}${isDark ? ' dark' : ''}`;
  try {
    localStorage.setItem(STORAGE_KEY, JSON.stringify({ id, mode }));
  } catch {
    // quota / disabled storage — apply still succeeds, persistence falls
    // back to the next /api/settings round-trip.
  }
}

interface ThemeState {
  id: ThemeId;
  mode: ThemeMode;
}

// Module-level singleton, mirrors the useChatStatus / useSidebar pattern.
// One shared state across every useTheme() consumer; setTheme() mutates it
// and notifies subscribers so the App-level hook (which owns the matchMedia
// listener) and the Settings picker stay in lockstep without prop drilling.
function readCache(): ThemeState | null {
  if (typeof localStorage === 'undefined') return null;
  try {
    const raw = localStorage.getItem(STORAGE_KEY);
    if (!raw) return null;
    const parsed = JSON.parse(raw) as { id?: unknown; mode?: unknown };
    if (typeof parsed.id !== 'string' || !isThemeId(parsed.id)) return null;
    const m = parsed.mode;
    if (m !== 'dark' && m !== 'light' && m !== 'system') return null;
    return { id: parsed.id, mode: m };
  } catch {
    return null;
  }
}

let _state: ThemeState = readCache() ?? { id: DEFAULT_THEME_ID, mode: DEFAULT_THEME_MODE };
let _initialized = false;
const _subscribers = new Set<(s: ThemeState) => void>();

function notify(): void {
  for (const sub of _subscribers) {
    try {
      sub(_state);
    } catch {
      // swallow — one bad subscriber shouldn't break others
    }
  }
}

// Optimistic update: applies immediately, PATCHes server, reverts on failure
// so the picker can show a toast without manual state juggling. Throws on
// failure so the caller can surface the error.
export async function setTheme(id: ThemeId, mode: ThemeMode): Promise<void> {
  const prev = _state;
  _state = { id, mode };
  applyTheme(id, mode);
  notify();
  try {
    await api.settings.patch({ theme_id: id, theme_mode: mode });
  } catch (err) {
    _state = prev;
    applyTheme(prev.id, prev.mode);
    notify();
    throw err;
  }
}

// useTheme — mounts as many times as needed across the tree. The first mount
// (initialized=false) triggers a single /api/settings fetch to reconcile the
// local cache with the server. Every mount installs the matchMedia listener
// when mode === 'system'; cleanup runs on unmount or when mode flips away.
export function useTheme(): ThemeState {
  const [state, setState] = useState<ThemeState>(_state);

  useEffect(() => {
    _subscribers.add(setState);
    // Ensure the DOM reflects current state on mount — the FOUC script in
    // index.html runs before this hook, but we re-apply in case the cache
    // was stale relative to a fresh fetch above.
    applyTheme(_state.id, _state.mode);
    if (!_initialized) {
      _initialized = true;
      api.settings
        .get()
        .then((s) => {
          const rawId = s['theme_id'];
          const rawMode = s['theme_mode'];
          const id =
            typeof rawId === 'string' && isThemeId(rawId) ? rawId : DEFAULT_THEME_ID;
          const mode: ThemeMode =
            rawMode === 'dark' || rawMode === 'light' || rawMode === 'system'
              ? rawMode
              : DEFAULT_THEME_MODE;
          _state = { id, mode };
          applyTheme(id, mode);
          notify();
        })
        .catch(() => {
          // Settings fetch failed — keep whatever the FOUC script applied.
          // The picker still works; PATCH will retry on next selection.
        });
    }
    return () => {
      _subscribers.delete(setState);
    };
  }, []);

  useEffect(() => {
    if (state.mode !== 'system') return;
    if (typeof window === 'undefined') return;
    const mql = window.matchMedia('(prefers-color-scheme: dark)');
    const onChange = () => applyTheme(state.id, 'system');
    mql.addEventListener('change', onChange);
    return () => mql.removeEventListener('change', onChange);
  }, [state.id, state.mode]);

  return state;
}
