import fs from 'fs';
import { resolve } from 'path';
import assert from 'assert';

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

const source = resolve(process.argv[2]);

const skipDeprecationComment = /^<!-- md-lint skip-deprecation (DEP\d{4}) -->$/;

const generateDeprecationCode = (codeAsNumber) =>
  `DEP${codeAsNumber.toString().padStart(4, '0')}`;

const addMarkdownPathToErrorStack = (error, node) => {
  const { line, column } = node.position.start;
  const [header, ...lines] = error.stack.split('\n');
  error.stack =
          header +
          `\n    at <anonymous> (${source}:${line}:${column})\n` +
          lines.join('\n');
  return error;
};

const testHeading = (headingNode, expectedDeprecationCode) => {
  try {
    assert.strictEqual(
      headingNode?.children[0]?.value.substring(0, 9),
      `${expectedDeprecationCode}: `,
      'Ill-formed or out-of-order deprecation code.',
    );
  } catch (e) {
    throw addMarkdownPathToErrorStack(e, headingNode);
  }
};

const testYAMLComment = (commentNode) => {
  try {
    assert.match(
      commentNode?.value?.substring(0, 21),
      /^<!-- YAML\r?\nchanges:\r?\n/,
      'Missing or ill-formed YAML comment.',
    );
  } catch (e) {
    throw addMarkdownPathToErrorStack(e, commentNode);
  }
};

const testDeprecationType = (paragraphNode) => {
  try {
    assert.strictEqual(
      paragraphNode?.children[0]?.value?.substring(0, 6),
      'Type: ',
      'Missing deprecation type.',
    );
  } catch (e) {
    throw addMarkdownPathToErrorStack(e, paragraphNode);
  }
};

const tree = unified()
  .use(remarkParse)
  .parse(fs.readFileSync(source));

let expectedDeprecationCodeNumber = 0;
for (let i = 0; i < tree.children.length; i++) {
  const node = tree.children[i];
  if (node.type === 'html' && skipDeprecationComment.test(node.value)) {
    const expectedDeprecationCode =
          generateDeprecationCode(++expectedDeprecationCodeNumber);
    const deprecationCodeAsText = node.value.match(skipDeprecationComment)[1];

    try {
      assert.strictEqual(
        deprecationCodeAsText,
        expectedDeprecationCode,
        'Deprecation codes are not ordered correctly.',
      );
    } catch (e) {
      throw addMarkdownPathToErrorStack(e, node);
    }
  }
  if (node.type === 'heading' && node.depth === 3) {
    const expectedDeprecationCode =
          generateDeprecationCode(++expectedDeprecationCodeNumber);

    testHeading(node, expectedDeprecationCode);

    testYAMLComment(tree.children[i + 1]);
    testDeprecationType(tree.children[i + 2]);
  }
}
