import remarkGfm from 'remark-gfm';
import remarkParse from 'remark-parse';
import { unified } from 'unified';

const markdownParser = unified().use(remarkParse).use(remarkGfm);
const STANDALONE_BREAK_LINE = /^[\t ]*<br\s*\/?>[\t ]*$/im;
const LEADING_FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;

/**
 * Compares Markdown by parsed GFM structure so harmless marker formatting is
 * accepted while list, table, heading, code, and raw HTML changes are rejected.
 */
export function hasEquivalentMarkdownSemantics(
  source: string,
  normalized: string,
) {
  try {
    return semanticTree(source) === semanticTree(normalized);
  } catch {
    return false;
  }
}

/**
 * Removes blank-paragraph markers generated by the live editor without
 * changing an explicit HTML break that already exists in the source.
 */
export function normalizeLiveMarkdownUpdate(source: string, markdown: string) {
  const orderedMarkdown = keepFrontmatterFirst(source, markdown);
  if (STANDALONE_BREAK_LINE.test(source)) return orderedMarkdown;
  return orderedMarkdown
    .replaceAll(/^[\t ]*<br\s*\/?>[\t ]*\n{2,}/gi, '')
    .replaceAll(/\n{2,}[\t ]*<br\s*\/?>[\t ]*\n{2,}/gi, '\n\n')
    .replaceAll(/\n{2,}[\t ]*<br\s*\/?>[\t ]*$/gi, '\n');
}

/**
 * Keeps immutable YAML frontmatter at the start when an editor selection is
 * placed before its atom node. The typed body is preserved immediately after
 * the metadata block, so the saved file remains valid standard Markdown.
 */
function keepFrontmatterFirst(source: string, markdown: string) {
  const sourceMatch = LEADING_FRONTMATTER.exec(source);
  if (!sourceMatch) return markdown;

  const normalizedMarkdown = markdown
    .replaceAll('\r\n', '\n')
    .replaceAll('\r', '\n');
  const frontmatter = `---\n${(sourceMatch[1] || '')
    .replaceAll('\r\n', '\n')
    .replaceAll('\r', '\n')}\n---`;
  const frontmatterIndex = normalizedMarkdown.indexOf(frontmatter);
  if (frontmatterIndex <= 0) return normalizedMarkdown;

  const leadingBody = normalizedMarkdown.slice(0, frontmatterIndex).trim();
  const trailingBody = normalizedMarkdown
    .slice(frontmatterIndex + frontmatter.length)
    .replace(/^\n+/, '');
  const body = [leadingBody, trailingBody]
    .filter((part) => part.length > 0)
    .join('\n\n');
  return body ? `${frontmatter}\n\n${body}` : `${frontmatter}\n`;
}

function semanticTree(markdown: string) {
  const normalizedLineEndings = markdown
    .replaceAll('\r\n', '\n')
    .replaceAll('\r', '\n');
  return JSON.stringify(
    markdownParser.parse(normalizedLineEndings),
    (key, value) => (key === 'position' ? undefined : value),
  );
}
