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": "color: var(--black)",
  "ansi-red": "color: var(--red)",
  "ansi-green": "color: var(--green)",
  "ansi-yellow": "color: var(--brown)",
  "ansi-blue": "color: var(--blue)",
  "ansi-magenta": "color: var(--magenta)",
  "ansi-cyan": "color: var(--cyan)",
  "ansi-white": "color: var(--light-gray)",
  "ansi-bright-black": "color: var(--dark-gray)",
  "ansi-bright-red": "color: var(--light-red)",
  "ansi-bright-green": "color: var(--light-green)",
  "ansi-bright-yellow": "color: var(--yellow)",
  "ansi-bright-blue": "color: var(--light-blue)",
  "ansi-bright-magenta": "color: var(--light-magenta)",
  "ansi-bright-cyan": "color: var(--light-cyan)",
  "ansi-bright-white": "color: var(--white)",
};

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

const decorationMap: Record<string, string> = {
  bold: "font-weight: 700",
  dim: "opacity: 0.5",
  italic: "font-style: italic",
  hidden: "visibility: hidden",
  strikethrough: "text-decoration: line-through",
  underline: "text-decoration: underline",
  blink: "animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite",
};

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 createStyle(bundle: AnserJsonEntry): string | null {
  const declarations: string[] = [];
  if (bundle.bg && bgColorMap[bundle.bg]) {
    declarations.push(bgColorMap[bundle.bg]!);
  }
  if (bundle.fg && colorMap[bundle.fg]) {
    declarations.push(colorMap[bundle.fg]!);
  }
  if (bundle.decoration && decorationMap[bundle.decoration]) {
    declarations.push(decorationMap[bundle.decoration]!);
  }
  return declarations.length ? declarations.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 style = createStyle(bundle);
      const content = escapeHtml(bundle.content);
      if (style) {
        return `<span style="${style}">${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)};`;
    },
  };
}
