const CHESS_LABELS = {
  "♔": "white king",
  "♕": "white queen",
  "♖": "white rook",
  "♗": "white bishop",
  "♘": "white knight",
  "♙": "white pawn",
  "♚": "black king",
  "♛": "black queen",
  "♜": "black rook",
  "♝": "black bishop",
  "♞": "black knight",
  "♟": "black pawn",
};

const CARD_LABELS = {
  "♠": "spade",
  "♤": "spade",
  "♣": "club",
  "♧": "club",
  "♥": "heart",
  "♡": "heart",
  "♦": "diamond",
  "♢": "diamond",
};

function escapeHtml(value) {
  return String(value)
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;");
}

function isVariationSelector(codePoint) {
  return codePoint === 0xfe0e || codePoint === 0xfe0f;
}

function isSkinTone(codePoint) {
  return codePoint >= 0x1f3fb && codePoint <= 0x1f3ff;
}

function isEmojiBase(codePoint) {
  return (
    (codePoint >= 0x1f000 && codePoint <= 0x1faff) ||
    (codePoint >= 0x2600 && codePoint <= 0x27bf) ||
    codePoint === 0x00a9 ||
    codePoint === 0x00ae ||
    codePoint === 0x203c ||
    codePoint === 0x2049 ||
    codePoint === 0x2122 ||
    codePoint === 0x2139 ||
    codePoint === 0x3030 ||
    codePoint === 0x303d ||
    codePoint === 0x3297 ||
    codePoint === 0x3299
  );
}

function readCluster(chars, index) {
  let value = chars[index];
  let next = index + 1;

  while (next < chars.length) {
    const codePoint = chars[next].codePointAt(0);
    if (isVariationSelector(codePoint) || isSkinTone(codePoint)) {
      value += chars[next];
      next += 1;
      continue;
    }

    if (codePoint === 0x200d && next + 1 < chars.length) {
      value += chars[next] + chars[next + 1];
      next += 2;
      continue;
    }

    break;
  }

  return { value, next };
}

function symbolHtml(cluster) {
  if (CHESS_LABELS[cluster]) {
    return `<span class="symbol symbol-chess" data-symbol="${escapeHtml(CHESS_LABELS[cluster].replaceAll(" ", "-"))}" aria-label="${escapeHtml(CHESS_LABELS[cluster])}">${escapeHtml(cluster)}</span>`;
  }

  if (CARD_LABELS[cluster]) {
    return `<span class="symbol symbol-card" data-symbol="${escapeHtml(CARD_LABELS[cluster])}" aria-label="${escapeHtml(CARD_LABELS[cluster])}">${escapeHtml(cluster)}</span>`;
  }

  const codePoint = cluster.codePointAt(0);
  if ((codePoint >= 0x1f0a0 && codePoint <= 0x1f0ff) || isEmojiBase(codePoint)) {
    return `<span class="symbol symbol-emoji">${escapeHtml(cluster)}</span>`;
  }

  return null;
}

function splitText(value) {
  const chars = Array.from(value);
  const nodes = [];
  let text = "";

  function flushText() {
    if (text) {
      nodes.push({ type: "text", value: text });
      text = "";
    }
  }

  for (let index = 0; index < chars.length; ) {
    const codePoint = chars[index].codePointAt(0);
    const shouldCluster =
      CHESS_LABELS[chars[index]] ||
      CARD_LABELS[chars[index]] ||
      isEmojiBase(codePoint);

    if (!shouldCluster) {
      text += chars[index];
      index += 1;
      continue;
    }

    const { value: cluster, next } = readCluster(chars, index);
    const html = symbolHtml(cluster);
    if (html) {
      flushText();
      nodes.push({ type: "html", value: html });
    } else {
      text += cluster;
    }
    index = next;
  }

  flushText();
  return nodes;
}

function transformChildren(parent) {
  if (!Array.isArray(parent?.children)) return;

  const nextChildren = [];
  for (const child of parent.children) {
    if (child.type === "text") {
      nextChildren.push(...splitText(child.value));
      continue;
    }

    transformChildren(child);
    nextChildren.push(child);
  }

  parent.children = nextChildren;
}

export default function remarkSymbols() {
  return (tree) => transformChildren(tree);
}
