import { useEffect, useMemo, useRef } from "react";

import { Badge } from "@/components/ui/badge";

export type CodeFileName = "index.html" | "styles.css" | "script.js";

interface CodeStreamPanelProps {
  streamedHtml: string;
  activeFile: CodeFileName;
  onActiveFileChange: (next: CodeFileName) => void;
  generationLoading: boolean;
  generationStatus?: string | null;
  generationLogs: string[];
  generationError?: string | null;
}

const FILE_ORDER: CodeFileName[] = ["index.html", "styles.css", "script.js"];

type HighlightTokenType =
  | "plain"
  | "comment"
  | "keyword"
  | "string"
  | "number"
  | "property"
  | "tag";

interface HighlightSegment {
  text: string;
  type: HighlightTokenType;
}

const TOKEN_CLASS: Record<HighlightTokenType, string> = {
  plain: "text-[#c9d1d9]",
  comment: "text-[#8b949e]",
  keyword: "text-[#ff7b72]",
  string: "text-[#a5d6ff]",
  number: "text-[#79c0ff]",
  property: "text-[#d2a8ff]",
  tag: "text-[#7ee787]",
};

function tokenizeCode(
  code: string,
  pattern: RegExp,
  classify: (token: string) => HighlightSegment[],
): HighlightSegment[] {
  const segments: HighlightSegment[] = [];
  let lastIndex = 0;

  for (const match of code.matchAll(pattern)) {
    const matchIndex = match.index ?? 0;
    const token = match[0] ?? "";

    if (matchIndex > lastIndex) {
      segments.push({
        text: code.slice(lastIndex, matchIndex),
        type: "plain",
      });
    }

    segments.push(...classify(token));
    lastIndex = matchIndex + token.length;
  }

  if (lastIndex < code.length) {
    segments.push({
      text: code.slice(lastIndex),
      type: "plain",
    });
  }

  return segments;
}

function highlightHtmlTag(tagToken: string): HighlightSegment[] {
  const segments: HighlightSegment[] = [];
  let lastIndex = 0;

  for (const match of tagToken.matchAll(/"[^"]*"|'[^']*'/g)) {
    const matchIndex = match.index ?? 0;
    const token = match[0] ?? "";

    if (matchIndex > lastIndex) {
      segments.push({
        text: tagToken.slice(lastIndex, matchIndex),
        type: "tag",
      });
    }

    segments.push({
      text: token,
      type: "string",
    });
    lastIndex = matchIndex + token.length;
  }

  if (lastIndex < tagToken.length) {
    segments.push({
      text: tagToken.slice(lastIndex),
      type: "tag",
    });
  }

  return segments;
}

function highlightHtml(code: string): HighlightSegment[] {
  return tokenizeCode(
    code,
    /<!--[\s\S]*?-->|<\/?[a-zA-Z!][^>]*>/g,
    (token): HighlightSegment[] => {
      if (token.startsWith("<!--")) {
        return [{ text: token, type: "comment" }];
      }

      return highlightHtmlTag(token);
    },
  );
}

function highlightCss(code: string): HighlightSegment[] {
  return tokenizeCode(
    code,
    /\/\*[\s\S]*?\*\/|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|#[\da-fA-F]{3,8}\b|\b(?:@media|@keyframes|@supports|from|to)\b|[a-zA-Z-]+(?=\s*:)|\b\d+(?:\.\d+)?(?:px|rem|em|vh|vw|%|s|ms)?\b/g,
    (token): HighlightSegment[] => {
      if (token.startsWith("/*")) {
        return [{ text: token, type: "comment" }];
      }

      if (token.startsWith('"') || token.startsWith("'")) {
        return [{ text: token, type: "string" }];
      }

      if (token.startsWith("@")) {
        return [{ text: token, type: "keyword" }];
      }

      if (/^[a-zA-Z-]+$/.test(token)) {
        return [{ text: token, type: "property" }];
      }

      if (/^#/.test(token) || /^\d/.test(token)) {
        return [{ text: token, type: "number" }];
      }

      return [{ text: token, type: "plain" }];
    },
  );
}

function highlightJavaScript(code: string): HighlightSegment[] {
  const jsKeywordPattern =
    /^(?:const|let|var|function|return|if|else|for|while|switch|case|break|continue|class|new|try|catch|finally|throw|await|async|import|from|export|default|typeof|instanceof)$/;

  return tokenizeCode(
    code,
    /\/\/.*|\/\*[\s\S]*?\*\/|`(?:\\.|[^`\\])*`|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\b(?:const|let|var|function|return|if|else|for|while|switch|case|break|continue|class|new|try|catch|finally|throw|await|async|import|from|export|default|typeof|instanceof)\b|\b\d+(?:\.\d+)?\b/g,
    (token): HighlightSegment[] => {
      if (token.startsWith("//") || token.startsWith("/*")) {
        return [{ text: token, type: "comment" }];
      }

      if (token.startsWith('"') || token.startsWith("'") || token.startsWith("`")) {
        return [{ text: token, type: "string" }];
      }

      if (jsKeywordPattern.test(token)) {
        return [{ text: token, type: "keyword" }];
      }

      if (/^\d/.test(token)) {
        return [{ text: token, type: "number" }];
      }

      return [{ text: token, type: "plain" }];
    },
  );
}

function highlightCode(file: CodeFileName, code: string): HighlightSegment[] {
  if (file === "styles.css") {
    return highlightCss(code);
  }

  if (file === "script.js") {
    return highlightJavaScript(code);
  }

  return highlightHtml(code);
}

function splitSegmentsByLine(segments: HighlightSegment[]): HighlightSegment[][] {
  const lines: HighlightSegment[][] = [[]];

  for (const segment of segments) {
    const rawText = segment.text;
    if (!rawText) {
      continue;
    }

    const parts = rawText.split("\n");
    parts.forEach((part, index) => {
      if (part.length > 0) {
        lines[lines.length - 1]?.push({
          text: part,
          type: segment.type,
        });
      }

      if (index < parts.length - 1) {
        lines.push([]);
      }
    });
  }

  return lines;
}

function extractStyleCode(html: string): string {
  const blocks = [...html.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style>/gi)];
  return blocks.map((block) => block[1]?.trim() ?? "").filter(Boolean).join("\n\n");
}

function extractInlineScriptCode(html: string): string {
  const blocks = [
    ...html.matchAll(/<script\b(?![^>]*\bsrc=)[^>]*>([\s\S]*?)<\/script>/gi),
  ];
  return blocks.map((block) => block[1]?.trim() ?? "").filter(Boolean).join("\n\n");
}

function getDisplayCode(file: CodeFileName, html: string): string {
  if (file === "styles.css") {
    return extractStyleCode(html);
  }

  if (file === "script.js") {
    return extractInlineScriptCode(html);
  }

  return html;
}

function getEmptyState(file: CodeFileName): string {
  if (file === "styles.css") {
    return "No closed <style> blocks streamed yet.";
  }

  if (file === "script.js") {
    return "No closed inline <script> blocks streamed yet.";
  }

  return "Streaming output will appear here.";
}

export function CodeStreamPanel({
  streamedHtml,
  activeFile,
  onActiveFileChange,
  generationLoading,
  generationStatus,
  generationLogs,
  generationError,
}: CodeStreamPanelProps) {
  const scrollContainerRef = useRef<HTMLDivElement | null>(null);
  const displayCode = getDisplayCode(activeFile, streamedHtml).trim();
  const showPlaceholder = displayCode.length === 0;
  const highlightedCode = useMemo(
    () => highlightCode(activeFile, displayCode),
    [activeFile, displayCode],
  );
  const highlightedLines = useMemo(
    () => splitSegmentsByLine(highlightedCode),
    [highlightedCode],
  );
  const lineNumberWidth = useMemo(
    () => Math.max(2, String(highlightedLines.length).length),
    [highlightedLines.length],
  );

  useEffect(() => {
    if (!generationLoading || showPlaceholder) {
      return;
    }

    const container = scrollContainerRef.current;
    if (!container) {
      return;
    }

    container.scrollTop = container.scrollHeight;
  }, [displayCode, generationLoading, showPlaceholder]);

  return (
    <div className="flex h-full min-h-[62vh] flex-col lg:min-h-0">
      <div className="flex flex-wrap items-center justify-between gap-3 border-b border-border bg-sidebar px-4 py-2">
        <div className="inline-flex rounded-lg bg-muted p-0.5">
          {FILE_ORDER.map((file) => {
            const isActive = activeFile === file;
            return (
              <button
                key={file}
                type="button"
                onClick={() => onActiveFileChange(file)}
                className={`rounded-md px-3 py-1 text-xs font-medium transition-colors ${
                  isActive
                    ? "bg-background text-foreground shadow-sm"
                    : "text-muted-foreground hover:text-foreground"
                }`}
              >
                {file}
              </button>
            );
          })}
        </div>

        <div className="flex items-center gap-2">
          {generationLoading ? <Badge variant="default">streaming</Badge> : null}
          {generationError ? <Badge variant="destructive">error</Badge> : null}
          {generationStatus ? (
            <p className="text-xs text-muted-foreground">{generationStatus}</p>
          ) : null}
        </div>
      </div>

      <div
        ref={scrollContainerRef}
        data-testid="code-stream-scroll"
        className="min-h-0 flex-1 overflow-auto bg-[#0d1117] dark:bg-[#0d1117]"
      >
        <div className="min-h-full px-4 py-4 font-mono text-xs leading-6 text-[#c9d1d9]">
          {showPlaceholder ? (
            <span className="text-[#484f58]">{getEmptyState(activeFile)}</span>
          ) : (
            <div className="min-w-full whitespace-pre-wrap">
              {highlightedLines.map((lineSegments, lineIndex) => (
                <div
                  key={`line-${lineIndex + 1}`}
                  className="grid grid-cols-[auto_1fr] gap-4"
                >
                  <span
                    data-testid="code-line-number"
                    className="select-none text-right text-[#6e7681]"
                    style={{ minWidth: `${lineNumberWidth}ch` }}
                  >
                    {lineIndex + 1}
                  </span>
                  <span className="whitespace-pre-wrap">
                    {lineSegments.length === 0 ? "\u00A0" : null}
                    {lineSegments.map((segment, segmentIndex) => (
                      <span
                        key={`${lineIndex + 1}-${segment.type}-${segmentIndex}`}
                        className={TOKEN_CLASS[segment.type]}
                      >
                        {segment.text}
                      </span>
                    ))}
                  </span>
                </div>
              ))}
            </div>
          )}
        </div>
      </div>

      <div className="border-t border-border bg-sidebar px-4 py-2.5">
        <p className="mb-1.5 text-[11px] font-medium uppercase tracking-wider text-muted-foreground">
          Activity
        </p>
        {generationLogs.length === 0 ? (
          <p className="text-xs text-muted-foreground/50">No events yet.</p>
        ) : (
          <ul className="space-y-0.5 font-mono text-[11px] text-muted-foreground">
            {generationLogs.map((entry, index) => (
              <li key={`${entry}-${index}`}>{entry}</li>
            ))}
          </ul>
        )}
      </div>
    </div>
  );
}
