import Anser, { type AnserJsonEntry } from "anser";
import { escapeCarriageReturn } from "escape-carriage";
import fs from "node:fs";
import type { Plugin } from "vite";

const colorMap: Record<string, string> = {
  "ansi-black": "text-[var(--black)]",
  "ansi-red": "text-[var(--red)]",
  "ansi-green": "text-[var(--green)]",
  "ansi-yellow": "text-[var(--brown)]",
  "ansi-blue": "text-[var(--blue)]",
  "ansi-magenta": "text-[var(--magenta)]",
  "ansi-cyan": "text-[var(--cyan)]",
  "ansi-white": "text-[var(--light-gray)]",
  "ansi-bright-black": "text-[var(--dark-gray)]",
  "ansi-bright-red": "text-[var(--light-red)]",
  "ansi-bright-green": "text-[var(--light-green)]",
  "ansi-bright-yellow": "text-[var(--yellow)]",
  "ansi-bright-blue": "text-[var(--light-blue)]",
  "ansi-bright-magenta": "text-[var(--light-magenta)]",
  "ansi-bright-cyan": "text-[var(--light-cyan)]",
  "ansi-bright-white": "text-[var(--white)]",
};

const bgColorMap: Record<string, string> = {
  "ansi-black": "bg-transparent",
  "ansi-red": "bg-[var(--red)]",
  "ansi-green": "bg-[var(--green)]",
  "ansi-yellow": "bg-[var(--brown)]",
  "ansi-blue": "bg-[var(--blue)]",
  "ansi-magenta": "bg-[var(--magenta)]",
  "ansi-cyan": "bg-[var(--cyan)]",
  "ansi-white": "bg-[var(--light-gray)]",
  "ansi-bright-black": "bg-[var(--dark-gray)]",
  "ansi-bright-red": "bg-[var(--light-red)]",
  "ansi-bright-green": "bg-[var(--light-green)]",
  "ansi-bright-yellow": "bg-[var(--yellow)]",
  "ansi-bright-blue": "bg-[var(--light-blue)]",
  "ansi-bright-magenta": "bg-[var(--light-magenta)]",
  "ansi-bright-cyan": "bg-[var(--light-cyan)]",
  "ansi-bright-white": "bg-[var(--white)]",
};

const decorationMap: Record<string, string> = {
  bold: "font-bold",
  dim: "opacity-50",
  italic: "italic",
  hidden: "invisible",
  strikethrough: "line-through",
  underline: "underline",
  blink: "animate-pulse",
};

function fixBackspace(txt: string): string {
  let tmp = txt;
  do {
    txt = tmp;
    tmp = txt.replace(/[^\n]\x08/gm, "");
  } while (tmp.length < txt.length);
  return txt;
}

function createClass(bundle: AnserJsonEntry): string | null {
  const classes: string[] = [];
  if (bundle.bg && bgColorMap[bundle.bg]) {
    classes.push(bgColorMap[bundle.bg]!);
  }
  if (bundle.fg && colorMap[bundle.fg]) {
    classes.push(colorMap[bundle.fg]!);
  }
  if (bundle.decoration && decorationMap[bundle.decoration]) {
    classes.push(decorationMap[bundle.decoration]!);
  }
  return classes.length ? classes.join(" ") : null;
}

function escapeHtml(str: string): string {
  return str
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;");
}

function renderAnsiToHtml(raw: string): string {
  const input = escapeCarriageReturn(fixBackspace(raw));
  const bundles = Anser.ansiToJson(input, {
    json: true,
    remove_empty: true,
    use_classes: true,
  });

  const spans = bundles
    .map((bundle) => {
      const cls = createClass(bundle);
      const content = escapeHtml(bundle.content);
      if (cls) {
        return `<span class="${cls}">${content}</span>`;
      }
      return `<span>${content}</span>`;
    })
    .join("");

  return `<div class="flex justify-center"><pre style="text-align:left"><code>${spans}</code></pre></div>`;
}

const ANSI_SUFFIX = "?ansi";

export default function ansiPlugin(): Plugin {
  return {
    name: "vite-plugin-ansi",
    enforce: "pre",
    async resolveId(source, importer, options) {
      if (!source.endsWith(ANSI_SUFFIX)) return;
      const bare = source.slice(0, -ANSI_SUFFIX.length);
      const resolved = await this.resolve(bare, importer, {
        ...options,
        skipSelf: true,
      });
      if (resolved) {
        return resolved.id + ANSI_SUFFIX;
      }
    },
    load(id) {
      if (!id.endsWith(ANSI_SUFFIX)) return;
      const filePath = id.slice(0, -ANSI_SUFFIX.length);
      const raw = fs.readFileSync(filePath, "utf-8");
      const html = renderAnsiToHtml(raw);
      return `export default ${JSON.stringify(html)};`;
    },
  };
}
