import { useCallback, useEffect, useRef, useState } from 'react';
import { Check, Copy, X, Paperclip } from 'lucide-react';
import { codeToHtml } from 'shiki';
import { sessionEvents } from '@/hooks/sessionEvents';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';

interface Props {
  path: string;
  content: string;
  lang: string | null;
  onClose: () => void;
}

const SHIKI_THEME = 'github-dark';

function splitShikiLines(html: string): string[] {
  const match = html.match(/<code[^>]*>([\s\S]*)<\/code>/);
  if (!match) return [];
  const inner = match[1]!;
  const lines = inner.split(/(?=<span class="line">)/);
  // Keep every line segment (including blank `<span class="line"></span>`
  // entries) so lineHtmls.length matches the raw line count and line-number
  // indexing stays in sync. The first element before the first line span is
  // discarded (it's always empty or whitespace).
  return lines.filter(l => l.startsWith('<span class="line">'));
}

function basename(path: string): string {
  const parts = path.split('/');
  return parts[parts.length - 1] ?? path;
}

export function FileViewerOverlay({ path, content, lang, onClose }: Props) {
  const [copied, setCopied] = useState(false);
  const [lineHtmls, setLineHtmls] = useState<string[] | null>(null);
  const [selectedLines, setSelectedLines] = useState<Set<number>>(new Set());
  const [showAttachPopover, setShowAttachPopover] = useState(false);
  const draggingRef = useRef(false);
  const dragStartRef = useRef<number | null>(null);
  const overlayRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    setSelectedLines(new Set());
    setShowAttachPopover(false);
    if (!lang) { setLineHtmls(null); return; }
    let cancelled = false;
    (async () => {
      try {
        const result = await codeToHtml(content, { lang, theme: SHIKI_THEME });
        if (!cancelled) {
          const lines = splitShikiLines(result);
          setLineHtmls(lines.length > 0 ? lines : null);
        }
      } catch { if (!cancelled) setLineHtmls(null); }
    })();
    return () => { cancelled = true; };
  }, [content, lang]);

  const plainLines = content.split('\n');
  const totalLines = lineHtmls ? lineHtmls.length : plainLines.length;

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

  function handleLineMouseDown(lineNo: number, e: React.MouseEvent) {
    if (e.shiftKey && dragStartRef.current !== null) {
      const start = dragStartRef.current;
      const min = Math.min(start, lineNo);
      const max = Math.max(start, lineNo);
      const next = new Set<number>();
      for (let i = min; i <= max; i++) next.add(i);
      setSelectedLines(next);
      setShowAttachPopover(true);
      return;
    }
    draggingRef.current = true;
    dragStartRef.current = lineNo;
    setSelectedLines(new Set([lineNo]));
    setShowAttachPopover(false);
  }

  function handleLineMouseEnter(lineNo: number) {
    if (!draggingRef.current || dragStartRef.current === null) return;
    const start = dragStartRef.current;
    const min = Math.min(start, lineNo);
    const max = Math.max(start, lineNo);
    const next = new Set<number>();
    for (let i = min; i <= max; i++) next.add(i);
    setSelectedLines(next);
  }

  const handleMouseUp = useCallback(() => {
    if (draggingRef.current) {
      draggingRef.current = false;
      if (selectedLines.size > 0) setShowAttachPopover(true);
    }
  }, [selectedLines.size]);

  useEffect(() => {
    document.addEventListener('mouseup', handleMouseUp);
    return () => document.removeEventListener('mouseup', handleMouseUp);
  }, [handleMouseUp]);

  useEffect(() => {
    function handleClick(e: MouseEvent) {
      if (overlayRef.current && !overlayRef.current.contains(e.target as Node)) {
        onClose();
      }
    }
    document.addEventListener('mousedown', handleClick);
    return () => document.removeEventListener('mousedown', handleClick);
  }, [onClose]);

  useEffect(() => {
    function handleKey(e: KeyboardEvent) {
      if (e.key === 'Escape') onClose();
    }
    document.addEventListener('keydown', handleKey);
    return () => document.removeEventListener('keydown', handleKey);
  }, [onClose]);

  function getSelectionRange(): { min: number; max: number } | null {
    if (selectedLines.size === 0) return null;
    let min = Infinity;
    let max = -Infinity;
    for (const n of selectedLines) {
      if (n < min) min = n;
      if (n > max) max = n;
    }
    return { min, max };
  }

  function handleAttach() {
    const range = getSelectionRange();
    if (!range) return;
    const lines = content.split('\n').slice(range.min - 1, range.max);
    sessionEvents.emit({
      type: 'attach_chat_file',
      attachment: {
        kind: 'lines',
        filename: path,
        language: lang,
        content: lines.join('\n'),
        range: [range.min, range.max],
        source: 'line-select',
      },
    });
    setSelectedLines(new Set());
    setShowAttachPopover(false);
  }

  const range = getSelectionRange();
  const attachLabel = range
    ? range.min === range.max
      ? `Attach line ${range.min} to chat`
      : `Attach lines ${range.min}–${range.max} to chat`
    : '';

  return (
    <div className="fixed inset-0 z-50 flex items-start justify-center pt-12 pb-12">
      <div className="absolute inset-0 bg-black/40" />
      <div
        ref={overlayRef}
        className="relative bg-background border rounded-lg shadow-xl flex flex-col w-[80vw] max-w-[1000px] max-h-[80vh] overflow-hidden"
      >
        <div className="flex items-center gap-2 px-4 py-2 border-b shrink-0">
          <span className="text-sm font-medium truncate flex-1" title={path}>
            {basename(path)}
          </span>
          <span className="text-xs text-muted-foreground truncate">{path}</span>
          <button
            type="button"
            onClick={() => void copyAll()}
            className="flex items-center gap-1 text-xs px-2 py-1 rounded hover:bg-muted"
          >
            {copied ? <Check size={12} /> : <Copy size={12} />}
            {copied ? 'Copied' : 'Copy'}
          </button>
          <button
            type="button"
            onClick={onClose}
            className="p-1 rounded hover:bg-muted"
            aria-label="Close"
          >
            <X size={16} />
          </button>
        </div>

        {/* Shiki-highlighted code lines are generated from source code files, not user content */}
        <div className="flex-1 overflow-auto text-sm font-mono select-none">
          {Array.from({ length: totalLines }, (_, i) => {
            const lineNo = i + 1;
            const isSelected = selectedLines.has(lineNo);
            return (
              <div
                key={lineNo}
                className={cn('flex', isSelected && 'bg-blue-500/10')}
                onMouseDown={(e) => handleLineMouseDown(lineNo, e)}
                onMouseEnter={() => handleLineMouseEnter(lineNo)}
              >
                <div
                  className="shrink-0 w-[3.5ch] text-right pr-2 text-xs text-muted-foreground select-none cursor-pointer hover:text-foreground"
                  style={{ fontVariantNumeric: 'tabular-nums' }}
                >
                  {lineNo}
                </div>
                {lineHtmls ? (
                  <div
                    className="flex-1 min-w-0 text-xs leading-relaxed [&>.line]:!bg-transparent"
                    dangerouslySetInnerHTML={{ __html: lineHtmls[i] ?? '' }}
                  />
                ) : (
                  <span className="flex-1 min-w-0 text-xs leading-relaxed whitespace-pre">
                    {plainLines[i] ?? ''}
                  </span>
                )}
              </div>
            );
          })}
        </div>

        {showAttachPopover && range && (
          <div className="sticky bottom-0 border-t bg-background px-4 py-2 flex items-center gap-2">
            <Paperclip size={14} className="text-muted-foreground" />
            <span className="text-xs flex-1">{attachLabel}</span>
            <Button size="sm" onClick={handleAttach}>
              Attach
            </Button>
            <Button size="sm" variant="ghost" onClick={() => { setSelectedLines(new Set()); setShowAttachPopover(false); }}>
              Cancel
            </Button>
          </div>
        )}
      </div>
    </div>
  );
}
