import createDOMPurify from "dompurify";
import { marked } from "marked";
import type { ChatMessage, TraceEvent } from "@/types";
import { firstNonEmptyString } from "@/utils";

marked.setOptions({ async: false, breaks: true, gfm: true });

export function renderSafeMarkdown(source: string | null | undefined): string {
  const text = source?.trim();
  if (!text) return "";
  const rendered = marked.parse(text) as string;
  return sanitizeHtml(rendered, {
    ADD_ATTR: ["target", "rel"],
    USE_PROFILES: { html: true }
  });
}

interface HtmlSanitizeConfig {
  ADD_ATTR?: string[];
  USE_PROFILES?: { html?: boolean };
}

interface HtmlSanitizer {
  sanitize(dirty: string, config?: HtmlSanitizeConfig): string;
}

type DomPurifyFactory = (targetWindow: Window) => HtmlSanitizer;
type DomPurifyExport = HtmlSanitizer | DomPurifyFactory;

function sanitizeHtml(html: string, config: HtmlSanitizeConfig): string {
  const purifierExport = createDOMPurify as unknown as DomPurifyExport;
  if (hasSanitize(purifierExport)) return purifierExport.sanitize(html, config);
  if (typeof window !== "undefined" && typeof purifierExport === "function") {
    const purifier = purifierExport(window);
    if (hasSanitize(purifier)) return purifier.sanitize(html, config);
  }
  return conservativeSanitize(html);
}

function hasSanitize(value: unknown): value is HtmlSanitizer {
  if (typeof value !== "function" && (value === null || typeof value !== "object")) return false;
  return typeof (value as { sanitize?: unknown }).sanitize === "function";
}

function conservativeSanitize(html: string): string {
  return html
    .replace(/<script\b[^>]*>[\s\S]*?<\/script>/giu, "")
    .replace(/\son[a-z]+=("[^"]*"|'[^']*'|[^\s>]+)/giu, "")
    .replace(/\s(href|src)=("javascript:[^"]*"|'javascript:[^']*'|javascript:[^\s>]+)/giu, "");
}

export function messageTraceId(message: ChatMessage): string | null {
  return firstNonEmptyString(message.traceId, message.runnerTrace?.traceId);
}

export function canCancelMessage(message: ChatMessage): boolean {
  return message.role === "agent" && String(message.status ?? "").toLowerCase() === "running" && Boolean(messageTraceId(message));
}

export function canRetryMessage(message: ChatMessage): boolean {
  if (message.role !== "agent" || !message.retryInput) return false;
  return ["running", "failed", "timeout", "canceled", "cancelled", "blocked"].includes(String(message.status ?? "").toLowerCase());
}

export function traceIdentityText(message: ChatMessage): string {
  return [
    messageTraceId(message) ? `traceId=${messageTraceId(message)}` : null,
    message.sessionId ? `sessionId=${message.sessionId}` : null,
    message.threadId ? `threadId=${message.threadId}` : null
  ].filter((item): item is string => Boolean(item)).join("\n");
}

export function isNoiseTraceEvent(event: TraceEvent): boolean {
  const label = String(event.label ?? event.type ?? event.kind ?? "").toLowerCase();
  return label.includes("delta") || label.includes("chunk") || label.includes("token");
}

export function visibleTraceEvents(events: TraceEvent[], compact: boolean): TraceEvent[] {
  return compact ? events.filter((event) => !isNoiseTraceEvent(event)) : events;
}

export function traceEventLabel(event: TraceEvent): string {
  return firstNonEmptyString(event.label, event.type, event.kind) ?? "event";
}

export function traceEventBody(event: TraceEvent): string {
  const body = firstNonEmptyString(event.message, event.text, event.outputSummary, event.stdoutSummary, event.stderrSummary, event.command, event.status);
  if (body) return body;
  if (event.exitCode != null) return `exitCode=${event.exitCode}`;
  return "已记录";
}

export function traceEventMeta(event: TraceEvent): string {
  return [event.ts, event.elapsedMs == null ? null : `${event.elapsedMs}ms`, event.exitCode == null ? null : `exit=${event.exitCode}`].filter((item): item is string => Boolean(item)).join(" / ");
}
