'use client';

import { useEffect, useState } from 'react';

/** Background used for the editor and its loading/error placeholders. */
export const EDITOR_BACKGROUND = '#0a0a0a';

/**
 * Hook that registers and returns the Monaco editor theme.
 *
 * The editor is always dark — devtool-style — regardless of the app theme.
 * Colors are read from the document's CSS variables so the editor still
 * matches the design tokens, with a dark fallback for SSR.
 *
 * @param monaco - Monaco namespace (null during loading)
 * @param themeOverride - Optional explicit theme name (skips the app theme)
 * @returns Resolved Monaco theme name to pass to editor options
 */
export function useEditorTheme(
  monaco: typeof import('monaco-editor') | null,
  themeOverride?: string,
): string {
  // State (not ref) so consumers re-render once the custom theme is registered.
  const [registered, setRegistered] = useState(false);

  useEffect(() => {
    if (!monaco) return;

    try {
      const colors = readEditorColors();

      monaco.editor.defineTheme('app-dark', {
        base: 'vs-dark',
        inherit: true,
        rules: [
          { token: 'comment', foreground: '6A9955', fontStyle: 'italic' },
          { token: 'keyword', foreground: 'C586C0' },
          { token: 'string', foreground: 'CE9178' },
          { token: 'number', foreground: 'B5CEA8' },
          { token: 'type', foreground: '4EC9B0' },
          { token: 'function', foreground: 'DCDCAA' },
          { token: 'variable', foreground: '9CDCFE' },
        ],
        colors: {
          'editor.background': colors.background,
          'editor.foreground': colors.foreground,
          'editor.lineHighlightBackground': colors.lineHighlight,
          'editor.selectionBackground': colors.selection,
          'editorCursor.foreground': colors.foreground,
          'editorLineNumber.foreground': colors.mutedForeground,
          'editorWidget.background': colors.card,
          'editorWidget.border': colors.border,
          'input.background': colors.card,
          'dropdown.background': colors.card,
        },
      });

      setRegistered(true);
    } catch {
      // Fallback — use the built-in dark theme.
      setRegistered(false);
    }
  }, [monaco]);

  if (themeOverride) return themeOverride;
  return registered ? 'app-dark' : 'vs-dark';
}

/**
 * Read editor colors from the document's dark theme CSS variables.
 *
 * Tokens in `tokens.css` are stored wrapped, e.g. `--background: hsl(0 0% 4%)`,
 * so we resolve them through the browser by painting onto a probe element and
 * reading back the computed (already-rgb) color. This works for any color
 * syntax (hsl/oklch/hex).
 */
function readEditorColors() {
  // Dark defaults — used during SSR and as a safety net.
  const fallback = {
    background: EDITOR_BACKGROUND,
    foreground: '#f5f5f5',
    card: '#141414',
    border: '#262626',
    mutedForeground: '#858585',
    lineHighlight: '#1a1a1a',
    selection: '#264F78',
  };

  if (typeof document === 'undefined' || typeof window === 'undefined') {
    return fallback;
  }

  const probe = document.createElement('div');
  probe.style.position = 'absolute';
  probe.style.pointerEvents = 'none';
  probe.style.opacity = '0';
  document.body.appendChild(probe);

  /** Resolve a CSS var to a hex color via computed style; '' on failure. */
  const resolve = (varName: string): string => {
    probe.style.color = `var(${varName})`;
    const computed = getComputedStyle(probe).color;
    return rgbToHex(computed);
  };

  try {
    const background = resolve('--background') || fallback.background;
    const foreground = resolve('--foreground') || fallback.foreground;
    const card = resolve('--card') || fallback.card;
    const border = resolve('--border') || fallback.border;
    const mutedForeground = resolve('--muted-foreground') || fallback.mutedForeground;
    const primary = resolve('--primary');

    return {
      background,
      foreground,
      card,
      border,
      mutedForeground,
      lineHighlight: mixHex(background, foreground, 0.06),
      selection: primary ? mixHex(primary, background, 0.55) : fallback.selection,
    };
  } finally {
    probe.remove();
  }
}

/** Convert a computed `rgb(r, g, b)` / `rgba(...)` string to `#rrggbb`. */
function rgbToHex(rgb: string): string {
  const match = rgb.match(/(\d+(?:\.\d+)?)/g);
  if (!match || match.length < 3) return '';
  const [r, g, b] = match.slice(0, 3).map((n) => Math.round(parseFloat(n)));
  const toHex = (v: number) => Math.min(255, Math.max(0, v)).toString(16).padStart(2, '0');
  return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}

/** Linearly blend `a` toward `b` by `t` (0..1). Both must be `#rrggbb`. */
function mixHex(a: string, b: string, t: number): string {
  const pa = parseInt(a.slice(1), 16);
  const pb = parseInt(b.slice(1), 16);
  if (Number.isNaN(pa) || Number.isNaN(pb)) return a;
  const lerp = (shift: number) => {
    const ca = (pa >> shift) & 0xff;
    const cb = (pb >> shift) & 0xff;
    return Math.round(ca + (cb - ca) * t);
  };
  const r = lerp(16);
  const g = lerp(8);
  const bch = lerp(0);
  return `#${((r << 16) | (g << 8) | bch).toString(16).padStart(6, '0')}`;
}
