import React, { useMemo, useState } from "react";
import type { ParsedMessage } from "../lib/types";
import { CATEGORY_LABELS } from "../lib/types";
import { CATEGORY_COLORS } from "../lib/constants";
import { renderMarkdown, highlightSearchText } from "../lib/markdown";
import { redactMessage } from "../../shared/sensitive-redactor";
import { escapeHtml } from "../../shared/escape-html";

interface Props {
  message: ParsedMessage;
  searchQuery: string;
  dimmed: boolean;
  selectedForRedaction: boolean;
  onToggleRedactionSelection: () => void;
  autoRedactEnabled: boolean;
}

/**
 * MessageBubble renders session messages using innerHTML.
 * SECURITY: This is safe because content comes only from local JSONL session files
 * owned by the user, processed through the `marked` markdown renderer.
 * This is a local-only developer tool, not exposed to untrusted input.
 * The session files are read from the user's own filesystem (~/.claude/projects/).
 */
export function MessageBubble({
  message,
  searchQuery,
  dimmed,
  selectedForRedaction,
  onToggleRedactionSelection,
  autoRedactEnabled,
}: Props) {
  const colors = CATEGORY_COLORS[message.category];
  const label = CATEGORY_LABELS[message.category];

  // Collapsible state for thinking blocks and tool calls/results
  const isCollapsible = message.category === "thinking" || message.category === "tool_call" || message.category === "tool_result";
  const [collapsed, setCollapsed] = useState(isCollapsible);
  const [linkCopied, setLinkCopied] = useState(false);

  const renderedHtml = useMemo(() => {
    // Skip expensive rendering when content is collapsed and not visible
    if (collapsed) return "";

    const msg = autoRedactEnabled ? redactMessage(message) : message;

    if (msg.category === "tool_call") {
      const inputHtml = msg.toolInput
        ? `<pre class="hljs mt-2"><code>${escapeHtml(msg.toolInput)}</code></pre>`
        : "";
      const html = `<div class="font-medium ${colors.text}">${escapeHtml(msg.toolName || "Unknown Tool")}</div>${inputHtml}`;
      return searchQuery ? highlightSearchText(html, searchQuery) : html;
    }

    // Structured data categories: render as preformatted text, not markdown.
    // Avoids expensive marked.parse() on large JSON/log blobs.
    if (msg.category === "hook_progress" || msg.category === "file_snapshot") {
      const html = `<pre class="hljs"><code>${escapeHtml(msg.content)}</code></pre>`;
      return searchQuery ? highlightSearchText(html, searchQuery) : html;
    }

    if (msg.category === "tool_result") {
      const html = isDiffContent(msg.content)
        ? renderDiffHtml(msg.content)
        : `<pre class="hljs"><code>${escapeHtml(msg.content)}</code></pre>`;
      return searchQuery ? highlightSearchText(html, searchQuery) : html;
    }

    const html = renderMarkdown(msg.content);
    return searchQuery ? highlightSearchText(html, searchQuery) : html;
  }, [message, searchQuery, autoRedactEnabled, colors.text, collapsed]);

  // Generate preview for collapsed thinking blocks
  const collapsedPreview = useMemo(() => {
    if (!isCollapsible || !collapsed) return null;
    if (message.category === "thinking") {
      const lines = message.content.split("\n").filter(l => l.trim());
      const preview = lines.slice(0, 2).join("\n");
      const totalLines = lines.length;
      return { preview, totalLines };
    }
    if (message.category === "tool_call") {
      return { preview: message.toolName || "Unknown Tool", totalLines: 0 };
    }
    if (message.category === "tool_result") {
      const lines = message.content.split("\n");
      const preview = lines[0]?.substring(0, 120) || "Result";
      return { preview, totalLines: lines.length };
    }
    return null;
  }, [isCollapsible, collapsed, message]);

  const timestamp = message.timestamp
    ? formatTimestamp(message.timestamp)
    : null;

  return (
    <div
      className={`
        group rounded-xl border bg-surface-raised
        transition-all duration-200 relative overflow-hidden
        ${colors.border}
        ${dimmed ? "message-dimmed" : ""}
        ${selectedForRedaction ? "redaction-selected" : ""}
        hover:shadow-card-hover
        shadow-card
      `}
    >
      {/* Category accent strip */}
      <div className={`absolute left-0 top-0 bottom-0 w-[3px] rounded-l-xl ${colors.dot}`} />

      {/* Header bar */}
      <div className="flex items-center gap-2 px-4 pl-5 h-10">
        {isCollapsible && (
          <button
            onClick={(e) => { e.stopPropagation(); setCollapsed(!collapsed); }}
            className="flex items-center justify-center w-5 h-5 text-foreground-muted hover:text-foreground transition-colors flex-shrink-0"
            aria-label={collapsed ? "Expand" : "Collapse"}
          >
            <svg
              className={`w-3.5 h-3.5 transition-transform duration-150 ${collapsed ? "" : "rotate-90"}`}
              fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}
            >
              <path strokeLinecap="round" strokeLinejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
            </svg>
          </button>
        )}
        <span className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${colors.dot}`} />
        <span className="text-caption font-semibold uppercase tracking-wider text-foreground-muted leading-none">
          {label}
        </span>
        {timestamp && (
          <>
            <span className="text-border leading-none">·</span>
            <span className="text-caption text-foreground-muted tabular-nums leading-none">
              {timestamp}
            </span>
          </>
        )}
        {isCollapsible && collapsed && collapsedPreview && (
          <>
            <span className="text-border leading-none">·</span>
            <span className="text-caption text-foreground-muted truncate max-w-[300px] leading-none">
              {message.category === "thinking" && collapsedPreview.totalLines > 2
                ? `${collapsedPreview.totalLines} lines`
                : collapsedPreview.preview}
            </span>
          </>
        )}

        {/* Right-aligned header actions */}
        <div className="ml-auto flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity flex-shrink-0">
          <button
            onClick={(e) => {
              e.stopPropagation();
              const url = `${window.location.origin}${window.location.pathname}#msg-${message.uuid}`;
              navigator.clipboard.writeText(url).then(() => {
                setLinkCopied(true);
                setTimeout(() => setLinkCopied(false), 1500);
              }).catch(() => {});
            }}
            className="flex items-center justify-center w-7 h-7 rounded-md text-foreground-muted hover:text-foreground hover:bg-surface-overlay/60 transition-colors"
            title="Copy link to message"
          >
            {linkCopied ? (
              <svg className="w-4 h-4 text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
                <path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
              </svg>
            ) : (
              <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
                <path strokeLinecap="round" strokeLinejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m0 0a2.625 2.625 0 115.25 0H12m-3.75 0h3.75" />
              </svg>
            )}
          </button>
          <button
            onClick={(e) => {
              e.stopPropagation();
              onToggleRedactionSelection();
            }}
            className={`flex items-center justify-center w-7 h-7 rounded-md transition-colors ${
              selectedForRedaction
                ? "text-red-400 bg-red-500/15 hover:bg-red-500/25"
                : "text-foreground-muted hover:text-red-400 hover:bg-red-500/10"
            }`}
            title={selectedForRedaction ? "Deselect for redaction" : "Select for redaction"}
          >
            <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
              <path strokeLinecap="round" strokeLinejoin="round" d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88" />
            </svg>
          </button>
        </div>
      </div>

      {/* Content — sourced from local user-owned JSONL files, not external/untrusted input */}
      {!collapsed && (
        <div
          className="prose-message text-body text-foreground px-4 pl-5 pb-4 pt-1 max-w-none break-words overflow-hidden"
          dangerouslySetInnerHTML={{ __html: renderedHtml }}
        />
      )}
      {collapsed && message.category === "thinking" && collapsedPreview && (
        <div className="px-4 pl-5 pb-3 pt-1">
          <pre className="text-caption text-foreground-muted whitespace-pre-wrap line-clamp-2 font-mono">{collapsedPreview.preview}</pre>
        </div>
      )}
    </div>
  );
}

function isDiffContent(content: string): boolean {
  const lines = content.split("\n").slice(0, 30);
  let hunkHeaders = 0;
  let diffLines = 0;
  for (const line of lines) {
    if (line.startsWith("@@") || line.startsWith("diff --")) {
      hunkHeaders++;
    } else if (line.startsWith("+++") || line.startsWith("---")) {
      hunkHeaders++;
    } else if (line.startsWith("+") || line.startsWith("-")) {
      diffLines++;
    }
  }
  // Require at least one hunk header AND some +/- lines to avoid false positives
  // on YAML lists, markdown lists, or other content with leading dashes
  return hunkHeaders >= 1 && diffLines >= 2;
}

function renderDiffHtml(content: string): string {
  const lines = content.split("\n");
  const htmlLines = lines.map((line) => {
    const escaped = escapeHtml(line);
    if (line.startsWith("@@")) {
      return `<span class="diff-hunk">${escaped}</span>`;
    }
    if (line.startsWith("+++") || line.startsWith("---")) {
      return `<span class="diff-meta">${escaped}</span>`;
    }
    if (line.startsWith("diff --")) {
      return `<span class="diff-header">${escaped}</span>`;
    }
    if (line.startsWith("+")) {
      return `<span class="diff-add">${escaped}</span>`;
    }
    if (line.startsWith("-")) {
      return `<span class="diff-del">${escaped}</span>`;
    }
    return escaped;
  });
  return `<pre class="hljs diff-view"><code>${htmlLines.join("\n")}</code></pre>`;
}

function formatTimestamp(ts: string): string {
  const d = new Date(ts);
  if (isNaN(d.getTime())) return "";
  return d.toLocaleTimeString(undefined, {
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
  });
}
