const CALLOUT_TYPES = new Set([
  "aside",
  "caution",
  "note",
  "tip",
  "warning",
]);

const CALLOUT_MARKERS = new Map([
  ["📝", "note"],
  ["💡", "tip"],
  ["⚡", "tip"],
  ["⚠️", "warning"],
  ["⚠", "warning"],
]);

const BRACKET_CALLOUT_PATTERN = /^\s*\[!(aside|caution|note|tip|warning)(?::\s*([^\]\r\n]+)|\|\s*([^\]\r\n]+))?\]\s*/i;

function textFromInline(node) {
  if (!node) return "";
  if (typeof node.value === "string") return node.value;
  if (!Array.isArray(node.children)) return "";
  return node.children.map(textFromInline).join("");
}

function normalizeType(value) {
  return value
    .trim()
    .replace(/^["“”]+|["“”]+$/g, "")
    .replace(/:$/, "")
    .toLowerCase();
}

function calloutType(value) {
  const type = normalizeType(value);
  return CALLOUT_TYPES.has(type) ? type : null;
}

function parseBracketCallout(value) {
  const match = value.match(BRACKET_CALLOUT_PATTERN);
  if (!match) return null;

  return {
    kind: "bracket",
    type: calloutType(match[1]),
    title: (match[2] ?? match[3] ?? "").trim(),
  };
}

function paragraphCallout(paragraph) {
  if (paragraph?.type !== "paragraph") return null;

  const first = paragraph.children?.[0];
  if (first?.type === "text") {
    const bracket = parseBracketCallout(first.value);
    if (bracket?.type) {
      return bracket;
    }

    const trimmed = first.value.trimStart();
    for (const [marker, type] of CALLOUT_MARKERS) {
      if (trimmed.startsWith(marker)) {
        return { kind: "marker", type, title: "", marker };
      }
    }
  }

  if (first?.type !== "strong") return null;

  const type = calloutType(textFromInline(first));
  if (!type) return null;

  return { kind: "strong", type, title: "" };
}

function cleanupParagraphStart(paragraph) {
  while (paragraph.children?.length > 0) {
    const first = paragraph.children[0];
    if (first.type === "text") {
      first.value = first.value.replace(/^\s+/, "");
      if (first.value) return;
      paragraph.children.shift();
      continue;
    }

    if (first.type === "break") {
      paragraph.children.shift();
      continue;
    }

    return;
  }
}

function calloutTitleParagraph(title) {
  return {
    type: "paragraph",
    data: {
      hName: "p",
      hProperties: {
        className: ["callout-title"],
      },
    },
    children: [{ type: "text", value: title }],
  };
}

function removeLeadingCallout(paragraph, callout) {
  if (paragraph?.type !== "paragraph") return;

  const first = paragraph.children?.[0];
  if (callout.kind === "bracket" && first?.type === "text") {
    first.value = first.value.replace(BRACKET_CALLOUT_PATTERN, "");
    cleanupParagraphStart(paragraph);
    return;
  }

  if (callout.kind === "marker" && first?.type === "text") {
    const escapedMarker = callout.marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    first.value = first.value.replace(new RegExp(`^\\s*${escapedMarker}\\s*`, "u"), "");
    cleanupParagraphStart(paragraph);
    return;
  }

  const firstText = calloutType(textFromInline(first));
  if (callout.kind !== "strong" || !first || first.type !== "strong" || firstText !== callout.type) {
    return;
  }

  paragraph.children.shift();

  const next = paragraph.children[0];
  if (next?.type === "text") {
    next.value = next.value.replace(/^:\s*/, "").replace(/^\s+/, "");
    if (!next.value) paragraph.children.shift();
  }

  cleanupParagraphStart(paragraph);
}

function transformBlockquote(node) {
  if (node.type !== "blockquote") return;

  const first = node.children?.[0];
  const callout = paragraphCallout(first);
  if (!callout) return;

  removeLeadingCallout(first, callout);
  if (first.children?.length === 0) {
    node.children.shift();
  }

  if (callout.title) {
    node.children.unshift(calloutTitleParagraph(callout.title));
  }

  node.data = {
    hName: "aside",
    hProperties: {
      className: ["callout", `callout-${callout.type}`],
    },
  };
}

function visit(node) {
  transformBlockquote(node);

  if (!Array.isArray(node.children)) return;
  for (const child of node.children) {
    visit(child);
  }
}

export default function remarkCallouts() {
  return function transformer(tree) {
    visit(tree);
  };
}
