import { motion } from "framer-motion";
import { User, Bot, Copy, Check, ChevronDown, ChevronUp, FileText, Eye, Code2 } from "lucide-react";
import { useState, type ReactNode } from "react";
import type { AgentScopeMessage } from "@/hooks/use-agentscope";

interface ChatMessageProps {
  message: AgentScopeMessage;
  isStreaming?: boolean;
  isLast?: boolean;
}

export function ChatMessage({ message, isStreaming, isLast }: ChatMessageProps) {
  const [copied, setCopied] = useState(false);
  const [showRaw, setShowRaw] = useState(false);
  const isUser = message.role === "user";
  const isAssistant = message.role === "assistant";

  const text = message.content
    .filter((c) => c.type === "text")
    .map((c) => c.text)
    .join("\n");

  const images = message.content.filter((c) => c.type === "image" && c.image_url);
  const files = message.content.filter((c) => c.type === "file");

  const codeBlocks = extractCodeBlocks(text);
  const hasCode = codeBlocks.length > 0;

  const handleCopy = async (textToCopy: string) => {
    try {
      await navigator.clipboard.writeText(textToCopy);
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    } catch {
      const textarea = document.createElement("textarea");
      textarea.value = textToCopy;
      document.body.appendChild(textarea);
      textarea.select();
      document.execCommand("copy");
      document.body.removeChild(textarea);
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    }
  };

  return (
    <motion.div
      initial={{ opacity: 0, y: 10 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.25, ease: "easeOut" }}
      className={`flex w-full gap-3 px-4 py-5 ${
        isLast && isStreaming ? "border-l-2 border-foreground/20" : ""
      }`}
    >
      {/* Avatar */}
      <div
        className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${
          isUser ? "bg-foreground text-background" : "bg-secondary text-secondary-foreground"
        }`}
      >
        {isUser ? (
          <User className="w-4 h-4" />
        ) : (
          <Bot className="w-4 h-4" />
        )}
      </div>

      {/* Content */}
      <div className="flex-1 min-w-0 space-y-3">
        {/* Role label */}
        <div className="flex items-center justify-between">
          <div className="flex items-center gap-2">
            <span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
              {isUser ? "You" : "Agent"}
            </span>
            {!isStreaming && (text || files.length > 0) && (
              <span
                className="text-[10px] text-muted-foreground/40 font-mono"
                title="Estimated token count (~4 chars/token, not exact)"
              >
                ~{estimateTokens(text, files)} tok
              </span>
            )}
          </div>
          {isAssistant && text && !isStreaming && (
            <button
              onClick={() => handleCopy(text)}
              className="p-1 rounded-md text-muted-foreground hover:text-foreground hover:bg-secondary transition-colors"
              title="Copy response"
            >
              {copied ? (
                <Check className="w-3.5 h-3.5 text-green-500" />
              ) : (
                <Copy className="w-3.5 h-3.5" />
              )}
            </button>
          )}
        </div>

        {/* Attached images */}
        {images.length > 0 && (
          <div className="flex flex-wrap gap-2">
            {images.map((img, i) => (
              <img
                key={i}
                src={img.image_url}
                alt={img.file_name || "Attached image"}
                className="max-w-[200px] max-h-[200px] rounded-lg border border-border object-cover"
              />
            ))}
          </div>
        )}

        {/* Attached files */}
        {files.length > 0 && (
          <div className="flex flex-wrap gap-2">
            {files.map((f, i) => (
              <FileChip key={i} name={f.file_name || "Attached file"} text={f.text} />
            ))}
          </div>
        )}

        {/* Text content */}
        {text ? (
          <div className="text-sm leading-relaxed text-foreground/90 space-y-2">
            {renderFormattedText(text)}
          </div>
        ) : isStreaming ? (
          <div className="flex gap-1.5 py-2">
            <span className="w-1.5 h-1.5 bg-muted-foreground/50 rounded-full animate-bounce" style={{ animationDelay: "0ms" }} />
            <span className="w-1.5 h-1.5 bg-muted-foreground/50 rounded-full animate-bounce" style={{ animationDelay: "150ms" }} />
            <span className="w-1.5 h-1.5 bg-muted-foreground/50 rounded-full animate-bounce" style={{ animationDelay: "300ms" }} />
          </div>
        ) : null}

        {/* Code blocks */}
        {hasCode && !isStreaming && (
          <div className="space-y-3">
            {codeBlocks.map((block, i) => (
              <CodeBlock key={i} language={block.language} code={block.code} onCopy={handleCopy} copied={copied} />
            ))}
          </div>
        )}

        {/* Toggle raw view */}
        {isAssistant && hasCode && !isStreaming && (
          <button
            onClick={() => setShowRaw(!showRaw)}
            className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
          >
            {showRaw ? (
              <ChevronUp className="w-3 h-3" />
            ) : (
              <ChevronDown className="w-3 h-3" />
            )}
            {showRaw ? "Hide raw" : "Show raw"}
          </button>
        )}

        {showRaw && (
          <pre className="p-3 rounded-lg bg-background border border-border text-xs text-muted-foreground overflow-x-auto">
            {text}
          </pre>
        )}

        {/* Streaming cursor */}
        {isStreaming && isLast && (
          <span className="inline-block w-2 h-4 bg-foreground/60 animate-pulse rounded-sm" />
        )}
      </div>
    </motion.div>
  );
}

/* ─── Token estimation (rough, provider-agnostic) ───
   No provider here reliably surfaces real usage through AgentScope's
   abstraction, so we estimate using the common "~4 chars per token"
   rule of thumb for English text. It's not exact, but it's good enough
   for a lightweight visual indicator of relative message size/cost. */

function estimateTokens(text: string, files: { text?: string }[] = []): number {
  const fileChars = files.reduce((sum, f) => sum + (f.text?.length || 0), 0);
  const totalChars = text.length + fileChars;
  return Math.max(1, Math.ceil(totalChars / 4));
}

/* ─── Code block: syntax highlighting + live preview for HTML ─── */

const PREVIEWABLE_LANGS = new Set(["html", "htm"]);

function looksLikeHtmlDoc(code: string): boolean {
  return /<!DOCTYPE html>|<html[\s>]/i.test(code);
}

function CodeBlock({
  language,
  code,
  onCopy,
  copied,
}: {
  language: string;
  code: string;
  onCopy: (text: string) => void;
  copied: boolean;
}) {
  const [showPreview, setShowPreview] = useState(false);
  const lang = (language || "").toLowerCase();
  const canPreview = PREVIEWABLE_LANGS.has(lang) || (lang === "code" && looksLikeHtmlDoc(code)) || looksLikeHtmlDoc(code);

  return (
    <div className="rounded-lg overflow-hidden border border-border bg-background/50">
      <div className="flex items-center justify-between px-3 py-1.5 bg-secondary/50 border-b border-border">
        <span className="text-xs text-muted-foreground font-mono">
          {language || "code"}
        </span>
        <div className="flex items-center gap-1">
          {canPreview && (
            <button
              onClick={() => setShowPreview((p) => !p)}
              className="flex items-center gap-1 px-2 py-1 rounded text-[10px] text-muted-foreground hover:text-foreground hover:bg-secondary transition-colors"
              title={showPreview ? "Show code" : "Preview"}
            >
              {showPreview ? <Code2 className="w-3 h-3" /> : <Eye className="w-3 h-3" />}
              {showPreview ? "Code" : "Preview"}
            </button>
          )}
          <button
            onClick={() => onCopy(code)}
            className="p-1 rounded text-muted-foreground hover:text-foreground transition-colors"
            title="Copy code"
          >
            {copied ? (
              <Check className="w-3 h-3 text-green-500" />
            ) : (
              <Copy className="w-3 h-3" />
            )}
          </button>
        </div>
      </div>

      {showPreview && canPreview ? (
        <iframe
          title="Code preview"
          srcDoc={code}
          sandbox="allow-scripts"
          className="w-full h-80 bg-white"
        />
      ) : (
        <pre className="p-3 overflow-x-auto text-xs leading-relaxed text-foreground/80 font-mono">
          <code>{highlightCode(code, lang)}</code>
        </pre>
      )}
    </div>
  );
}

/* ─── Minimal dependency-free syntax highlighter ───
   Not a full tokenizer — just enough regex-based coloring (comments,
   strings, keywords, tags, numbers) to make code blocks readable without
   pulling in a highlighting library. */

const KEYWORDS_BY_LANG: Record<string, string[]> = {
  js: ["const", "let", "var", "function", "return", "if", "else", "for", "while", "class", "extends", "import", "export", "default", "from", "async", "await", "new", "this", "try", "catch", "finally", "throw", "typeof", "instanceof", "null", "undefined", "true", "false"],
  jsx: [],
  ts: [],
  tsx: [],
  python: ["def", "return", "if", "elif", "else", "for", "while", "class", "import", "from", "as", "try", "except", "finally", "raise", "with", "lambda", "None", "True", "False", "and", "or", "not", "in", "is", "yield", "async", "await"],
  py: [],
  css: [],
  json: [],
};
KEYWORDS_BY_LANG.jsx = KEYWORDS_BY_LANG.js;
KEYWORDS_BY_LANG.ts = KEYWORDS_BY_LANG.js;
KEYWORDS_BY_LANG.tsx = KEYWORDS_BY_LANG.js;
KEYWORDS_BY_LANG.py = KEYWORDS_BY_LANG.python;

function highlightCode(code: string, lang: string): ReactNode {
  if (lang === "html" || lang === "htm" || (lang === "code" && looksLikeHtmlDoc(code))) {
    return highlightHtml(code);
  }
  if (lang === "css") return highlightGeneric(code, [], /(".*?"|'.*?')/g, /(\/\*[\s\S]*?\*\/)/g);
  const keywords = KEYWORDS_BY_LANG[lang] || [];
  return highlightGeneric(code, keywords, /(".*?"|'.*?'|`[\s\S]*?`)/g, /(\/\/.*$|#.*$)/gm);
}

function highlightGeneric(
  code: string,
  keywords: string[],
  stringRe: RegExp,
  commentRe: RegExp,
): ReactNode {
  // Split into lines first so comments (which run to end-of-line) work.
  const lines = code.split("\n");
  return (
    <>
      {lines.map((line, li) => (
        <div key={li}>{highlightLine(line, keywords, stringRe, commentRe)}</div>
      ))}
    </>
  );
}

function highlightLine(
  line: string,
  keywords: string[],
  stringRe: RegExp,
  commentRe: RegExp,
): ReactNode {
  // Comment takes priority: everything after a comment marker is dimmed.
  commentRe.lastIndex = 0;
  const commentMatch = commentRe.exec(line);
  const codePart = commentMatch ? line.slice(0, commentMatch.index) : line;
  const commentPart = commentMatch ? line.slice(commentMatch.index) : "";

  const parts: ReactNode[] = [];
  let lastIndex = 0;
  stringRe.lastIndex = 0;
  let m: RegExpExecArray | null;
  while ((m = stringRe.exec(codePart)) !== null) {
    if (m.index > lastIndex) {
      parts.push(...highlightKeywords(codePart.slice(lastIndex, m.index), keywords));
    }
    parts.push(
      <span key={`str-${m.index}`} className="text-emerald-400/90">
        {m[0]}
      </span>,
    );
    lastIndex = m.index + m[0].length;
  }
  if (lastIndex < codePart.length) {
    parts.push(...highlightKeywords(codePart.slice(lastIndex), keywords));
  }
  if (commentPart) {
    parts.push(
      <span key="comment" className="text-muted-foreground/50 italic">
        {commentPart}
      </span>,
    );
  }
  return <>{parts}</>;
}

function highlightKeywords(text: string, keywords: string[]): ReactNode[] {
  if (keywords.length === 0) return [text];
  const re = new RegExp(`\\b(${keywords.join("|")})\\b`, "g");
  const parts: ReactNode[] = [];
  let lastIndex = 0;
  let m: RegExpExecArray | null;
  while ((m = re.exec(text)) !== null) {
    if (m.index > lastIndex) parts.push(text.slice(lastIndex, m.index));
    parts.push(
      <span key={`kw-${m.index}`} className="text-sky-400 font-medium">
        {m[0]}
      </span>,
    );
    lastIndex = m.index + m[0].length;
  }
  if (lastIndex < text.length) parts.push(text.slice(lastIndex));
  return parts;
}

function highlightHtml(code: string): ReactNode {
  const lines = code.split("\n");
  const tagRe = /(&lt;\/?[a-zA-Z][\w-]*|\/?&gt;|<\/?[a-zA-Z][\w-]*|\/?>)/g;
  const attrRe = /([\w-]+)(=)("[^"]*"|'[^']*')/g;
  return (
    <>
      {lines.map((line, li) => {
        const attrParts: ReactNode[] = [];
        let lastIndex = 0;
        let m: RegExpExecArray | null;
        attrRe.lastIndex = 0;
        while ((m = attrRe.exec(line)) !== null) {
          if (m.index > lastIndex) attrParts.push(line.slice(lastIndex, m.index));
          attrParts.push(
            <span key={`a-${m.index}`}>
              <span className="text-amber-400/90">{m[1]}</span>
              <span className="text-foreground/60">{m[2]}</span>
              <span className="text-emerald-400/90">{m[3]}</span>
            </span>,
          );
          lastIndex = m.index + m[0].length;
        }
        if (lastIndex < line.length) attrParts.push(line.slice(lastIndex));

        return (
          <div key={li}>
            {attrParts.map((part, pi) =>
              typeof part === "string" ? (
                <span key={pi}>
                  {part.split(tagRe).map((seg, si) =>
                    /^(<\/?[a-zA-Z][\w-]*|\/?>)$/.test(seg) ? (
                      <span key={si} className="text-sky-400 font-medium">
                        {seg}
                      </span>
                    ) : (
                      <span key={si}>{seg}</span>
                    ),
                  )}
                </span>
              ) : (
                part
              ),
            )}
          </div>
        );
      })}
    </>
  );
}

function FileChip({ name, text }: { name: string; text?: string }) {
  const [open, setOpen] = useState(false);

  return (
    <div className="rounded-lg border border-border bg-secondary/40 overflow-hidden max-w-full">
      <button
        onClick={() => text && setOpen(!open)}
        className="flex items-center gap-1.5 px-2.5 py-1.5 text-xs text-foreground/80 hover:bg-secondary/60 transition-colors"
        title={name}
      >
        <FileText className="w-3.5 h-3.5 flex-shrink-0" />
        <span className="truncate max-w-[180px]">{name}</span>
        {text && (open ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />)}
      </button>
      {open && text && (
        <pre className="px-2.5 pb-2.5 text-[10px] text-muted-foreground max-h-40 overflow-y-auto whitespace-pre-wrap">
          {text}
        </pre>
      )}
    </div>
  );
}

/* ─── Markdown-like rendering (no dangerouslySetInnerHTML) ─── */

function renderFormattedText(text: string): ReactNode[] {
  const parts: ReactNode[] = [];
  const codeBlockRegex = /```(\w*)\n?([\s\S]*?)```/g;
  let lastIndex = 0;
  let match: RegExpExecArray | null;

  while ((match = codeBlockRegex.exec(text)) !== null) {
    if (match.index > lastIndex) {
      parts.push(renderInlineText(text.slice(lastIndex, match.index)));
    }
    lastIndex = match.index + match[0].length;
  }

  if (lastIndex < text.length) {
    parts.push(renderInlineText(text.slice(lastIndex)));
  }

  return parts.length > 0 ? parts : [renderInlineText(text)];
}

type InlineToken =
  | { type: "text"; value: string }
  | { type: "bold"; value: string }
  | { type: "italic"; value: string }
  | { type: "code"; value: string };

function tokenizeInline(line: string): InlineToken[] {
  const tokens: InlineToken[] = [];
  const regex = /(\*\*(.+?)\*\*)|(\*(.+?)\*)|(`([^`]+)`)/g;
  let lastIndex = 0;
  let m: RegExpExecArray | null;

  while ((m = regex.exec(line)) !== null) {
    if (m.index > lastIndex) {
      tokens.push({ type: "text", value: line.slice(lastIndex, m.index) });
    }
    if (m[1]) tokens.push({ type: "bold", value: m[2] });
    else if (m[3]) tokens.push({ type: "italic", value: m[4] });
    else if (m[5]) tokens.push({ type: "code", value: m[6] });
    lastIndex = m.index + m[0].length;
  }

  if (lastIndex < line.length) {
    tokens.push({ type: "text", value: line.slice(lastIndex) });
  }

  return tokens;
}

function renderInlineTokens(tokens: InlineToken[]): ReactNode {
  return (
    <>
      {tokens.map((t, i) => {
        switch (t.type) {
          case "bold":
            return <strong key={i}>{t.value}</strong>;
          case "italic":
            return <em key={i}>{t.value}</em>;
          case "code":
            return (
              <code
                key={i}
                className="bg-secondary px-1 py-0.5 rounded text-xs font-mono text-foreground/80"
              >
                {t.value}
              </code>
            );
          default:
            return <span key={i}>{t.value}</span>;
        }
      })}
    </>
  );
}

function renderInlineText(text: string): ReactNode {
  const lines = text.split("\n").map((line, i) => {
    const trimmed = line.trim();

    if (trimmed === "") {
      return <div key={i} className="h-3" />;
    }

    // Headers
    if (line.startsWith("### ")) {
      return (
        <p key={i} className="text-sm font-semibold text-foreground pt-2 first:pt-0">
          {line.slice(4)}
        </p>
      );
    }
    if (line.startsWith("## ")) {
      return (
        <p key={i} className="text-base font-bold text-foreground pt-3 first:pt-0">
          {line.slice(3)}
        </p>
      );
    }
    if (line.startsWith("# ")) {
      return (
        <p key={i} className="text-lg font-bold text-foreground pt-3 first:pt-0">
          {line.slice(2)}
        </p>
      );
    }

    // Unordered list items
    if (line.startsWith("- ") || line.startsWith("* ")) {
      const content = line.slice(2);
      return (
        <li key={i} className="text-sm text-foreground/80 ml-4 list-disc">
          {renderInlineTokens(tokenizeInline(content))}
        </li>
      );
    }

    // Ordered list items
    if (/^\d+\. /.test(line)) {
      const content = line.replace(/^\d+\. /, "");
      return (
        <li key={i} className="text-sm text-foreground/80 ml-4 list-decimal">
          {renderInlineTokens(tokenizeInline(content))}
        </li>
      );
    }

    // Regular paragraph with inline formatting
    const tokens = tokenizeInline(line);
    return (
      <p key={i} className="text-sm text-foreground/80">
        {renderInlineTokens(tokens)}
      </p>
    );
  });

  return <>{lines}</>;
}

function extractCodeBlocks(text: string): { language: string; code: string }[] {
  const blocks: { language: string; code: string }[] = [];
  const regex = /```(\w*)\n?([\s\S]*?)```/g;
  let match: RegExpExecArray | null;
  while ((match = regex.exec(text)) !== null) {
    blocks.push({
      language: match[1] || "text",
      code: match[2].trim(),
    });
  }
  return blocks;
}
