import { useEffect, useRef, useState } from 'react';
import { Check, Copy } from 'lucide-react';
import { codeToHtml } from 'shiki';

// NOTE: spec calls for syntax-highlighted code blocks. Added Shiki in v1.1.
// Shiki output is compiler-generated and does not contain user input; setting
// it via a ref is safe here.
interface Props {
  code: string;
  lang?: string;
}

const LANG_MAP: Record<string, string> = {
  ts: 'typescript',
  tsx: 'tsx',
  typescript: 'typescript',
  js: 'javascript',
  jsx: 'jsx',
  javascript: 'javascript',
  py: 'python',
  python: 'python',
  go: 'go',
  rs: 'rust',
  rust: 'rust',
  rb: 'ruby',
  ruby: 'ruby',
  java: 'java',
  c: 'c',
  cpp: 'cpp',
  cs: 'csharp',
  csharp: 'csharp',
  php: 'php',
  sh: 'bash',
  bash: 'bash',
  shell: 'bash',
  yaml: 'yaml',
  yml: 'yaml',
  json: 'json',
  toml: 'toml',
  md: 'markdown',
  markdown: 'markdown',
  sql: 'sql',
  dockerfile: 'dockerfile',
  html: 'html',
  css: 'css',
};

const SHIKI_THEME = 'github-dark';

export function CodeBlock({ code, lang }: Props) {
  const [copied, setCopied] = useState(false);
  const [html, setHtml] = useState<string | null>(null);
  const highlightRef = useRef<HTMLDivElement | null>(null);

  useEffect(() => {
    let cancelled = false;
    const mappedLang = (lang && LANG_MAP[lang.toLowerCase()]) ?? null;
    if (!mappedLang) {
      setHtml(null);
      return;
    }
    (async () => {
      try {
        const result = await codeToHtml(code, { lang: mappedLang, theme: SHIKI_THEME });
        if (!cancelled) setHtml(result);
      } catch (err) {
        console.warn('shiki failed', err);
        if (!cancelled) setHtml(null);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [code, lang]);

  // Inject Shiki HTML via ref; output is compiler-generated, not user input.
  useEffect(() => {
    if (highlightRef.current) {
      // Shiki generates sanitized HTML spans — not user-supplied content.
      // eslint-disable-next-line no-unsanitized/property
      highlightRef.current.innerHTML = html ?? '';
    }
  }, [html]);

  async function copy() {
    try {
      await navigator.clipboard.writeText(code);
      setCopied(true);
      setTimeout(() => setCopied(false), 1200);
    } catch {
      /* ignore */
    }
  }

  return (
    <div className="rounded-md border bg-muted/40 overflow-hidden text-sm my-1">
      <div className="flex items-center justify-between px-2 py-1 border-b text-xs text-muted-foreground">
        <span className="font-mono">{lang || 'code'}</span>
        <button
          type="button"
          onClick={() => void copy()}
          className="flex items-center gap-1 px-1.5 py-0.5 rounded hover:bg-muted text-foreground"
          aria-label="Copy code"
        >
          {copied ? <Check className="size-3" /> : <Copy className="size-3" />}
          <span>{copied ? 'Copied' : 'Copy'}</span>
        </button>
      </div>
      {html !== null ? (
        <div
          ref={highlightRef}
          className="overflow-x-auto px-3 py-2 font-mono text-xs leading-relaxed [&>pre]:!bg-transparent [&>pre]:!m-0 [&>pre]:!p-0"
        />
      ) : (
        <pre className="overflow-x-auto px-3 py-2 font-mono text-xs leading-relaxed">
          {code}
        </pre>
      )}
    </div>
  );
}
