import iterateJsdoc from '../iterateJsdoc.js';
import {
  parse as commentParser,
} from 'comment-parser';

// Neither a single nor 3+ asterisks are valid JSDoc per
//  https://jsdoc.app/about-getting-started.html#adding-documentation-comments-to-your-code
const commentRegexp = /^\/\*(?!\*)/v;
const extraAsteriskCommentRegexp = /^\/\*{3,}/v;

export default iterateJsdoc(({
  allComments,
  context,
  makeReport,
  sourceCode,
}) => {
  const [
    {
      ignore = [
        'ts-check',
        'ts-expect-error',
        'ts-ignore',
        'ts-nocheck',
      ],
      preventAllMultiAsteriskBlocks = false,
    } = {},
  ] = context.options;

  let extraAsterisks = false;
  const nonJsdocNodes = /** @type {import('estree').Node[]} */ (
    allComments
  ).filter((comment) => {
    const commentText = sourceCode.getText(comment);

    const initialText = commentText.replace(commentRegexp, '').trimStart();
    if ([
      'eslint',
    ].some((directive) => {
      return initialText.startsWith(directive);
    })) {
      return false;
    }

    let sliceIndex = 2;
    if (!commentRegexp.test(commentText)) {
      const multiline = extraAsteriskCommentRegexp.exec(commentText)?.[0];
      if (!multiline) {
        return false;
      }

      sliceIndex = multiline.length;
      extraAsterisks = true;
      if (preventAllMultiAsteriskBlocks) {
        return true;
      }
    }

    const tags = (commentParser(
      `${commentText.slice(0, 2)}*${commentText.slice(sliceIndex)}`,
    )[0] || {}).tags ?? [];

    return tags.length && !tags.some(({
      tag,
    }) => {
      return ignore.includes(tag);
    });
  });

  if (!nonJsdocNodes.length) {
    return;
  }

  for (const node of nonJsdocNodes) {
    const report = /** @type {import('../iterateJsdoc.js').MakeReport} */ (
      makeReport
    )(context, node);

    // eslint-disable-next-line no-loop-func
    const fix = /** @type {import('eslint').Rule.ReportFixer} */ (fixer) => {
      const text = sourceCode.getText(node);

      return fixer.replaceText(
        node,
        extraAsterisks ?
          text.replace(extraAsteriskCommentRegexp, '/**') :
          text.replace('/*', '/**'),
      );
    };

    report('Expected JSDoc-like comment to begin with two asterisks.', fix);
  }
}, {
  checkFile: true,
  meta: {
    docs: {
      description: 'This rule checks for multi-line-style comments which fail to meet the criteria of a JSDoc block.',
      url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/no-bad-blocks.md#repos-sticky-header',
    },
    fixable: 'code',
    schema: [
      {
        additionalProperties: false,
        properties: {
          ignore: {
            description: `An array of directives that will not be reported if present at the beginning of
a multi-comment block and at-sign \`/* @\`.

Defaults to \`['ts-check', 'ts-expect-error', 'ts-ignore', 'ts-nocheck']\`
(some directives [used by TypeScript](https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html#ts-check)).`,
            items: {
              type: 'string',
            },
            type: 'array',
          },
          preventAllMultiAsteriskBlocks: {
            description: `A boolean (defaulting to \`false\`) which if \`true\` will prevent all
JSDoc-like blocks with more than two initial asterisks even those without
apparent tag content.`,
            type: 'boolean',
          },
        },
        type: 'object',
      },
    ],
    type: 'layout',
  },
});
