import type { Area } from "./api";

// Named colors used by the guide markup; anything else that is 3/6 hex chars
// is treated as a hex color.
const COLOR_NAMES: Record<string, string> = {
  red: "#e05555",
  lime: "#7CFC00",
  aqua: "#55e0e0",
  yellow: "#e0c84a",
  green: "#5fbf5f",
  white: "#ffffff",
  orange: "#e08a3c",
};

function resolveColor(name: string): string {
  if (COLOR_NAMES[name]) return COLOR_NAMES[name];
  if (/^[0-9a-fA-F]{6}$/.test(name) || /^[0-9a-fA-F]{3}$/.test(name)) return "#" + name;
  return "inherit";
}

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

// Icon names available under static/icons/leveling/ (from the original AHK
// "leveling tracker" assets). Only these render as real <img>; anything else
// falls back to a text chip so unknown markup never shows a broken image.
const ICONS = new Set([
  "0", "1", "2", "3", "4", "5", "6", "7",
  "arena", "artificer", "b-rune", "checkpoint", "craft", "exa", "flasks",
  "gcp", "hideout", "in-out", "in-out2", "jeweller", "lab", "portal",
  "quest", "quest_2", "regal", "ring", "rune", "skill", "skill2", "skip",
  "spirit", "spirit2", "support", "support2", "town", "waypoint",
]);

// Underscores stand in for spaces in plain guide text.
function plain(text: string, color: string): string {
  const t = escapeHtml(text.replace(/_/g, " "));
  if (!t) return "";
  if (color === "inherit") return t;
  return `<span style="color:${color}">${t}</span>`;
}

export interface RenderedLine {
  html: string;
  kind: "normal" | "hint" | "optional" | "info";
  indent: number;
}

const TOKEN_RE = /\(([a-z]+)(?::([^)]*))?\)|areaid([a-z0-9_]+)/gi;

/** Render a single guide line (with embedded markup) into HTML + metadata. */
export function renderLine(raw: string, areas: Record<string, Area>): RenderedLine {
  let line = raw;

  // Classify the line for styling.
  let kind: RenderedLine["kind"] = "normal";
  let indent = 0;
  const lower = line.toLowerCase();
  if (lower.startsWith("(hint)")) {
    kind = "hint";
    line = line.slice("(hint)".length);
    // leading underscores after (hint) indicate indentation
    const m = line.match(/^_+/);
    if (m) {
      indent = 1;
      line = line.slice(m[0].length);
    }
  } else if (lower.startsWith("optional:")) {
    kind = "optional";
  } else if (lower.includes("info:")) {
    kind = "info";
  }

  // Pull out the trailing " ;; area name" annotation.
  let areaName: string | null = null;
  const sc = line.indexOf(";;");
  if (sc >= 0) {
    areaName = line.slice(sc + 2).trim();
    line = line.slice(0, sc).trim();
  }

  let out = "";
  let color = "inherit";
  let last = 0;
  let m: RegExpExecArray | null;
  TOKEN_RE.lastIndex = 0;
  while ((m = TOKEN_RE.exec(line)) !== null) {
    out += plain(line.slice(last, m.index), color);
    last = m.index + m[0].length;

    const kindTok = m[1]?.toLowerCase();
    const val = m[2] ?? "";
    const areaId = m[3];

    if (areaId !== undefined) {
      const name = areaName || areas[areaId]?.name || areaId.replace(/_/g, " ");
      out += `<span class="area">${escapeHtml(name)}</span>`;
      areaName = null; // consume once
    } else if (kindTok === "color") {
      color = resolveColor(val.trim());
    } else if (kindTok === "img") {
      const name = val.trim();
      const label = name.replace(/_/g, " ");
      if (ICONS.has(name.toLowerCase())) {
        out += `<img class="icon" src="/icons/leveling/${encodeURIComponent(
          name.toLowerCase()
        )}.png" alt="${escapeHtml(label)}" title="${escapeHtml(label)}" />`;
      } else {
        out += `<span class="chip img" data-img="${escapeHtml(name)}">${escapeHtml(
          label
        )}</span>`;
      }
    } else if (kindTok === "quest") {
      out += `<span class="chip quest">${escapeHtml(val.replace(/_/g, " "))}</span>`;
    } else if (kindTok === "emph") {
      // ignored: treated as a plain emphasis marker
    }
    // (hint) handled at line level above
  }
  out += plain(line.slice(last), color);

  // Any leftover area name annotation with no areaid token: append it.
  if (areaName) {
    out += ` <span class="area">${escapeHtml(areaName)}</span>`;
  }

  return { html: out, kind, indent };
}
