import iterateJsdoc from '../iterateJsdoc.js';

/**
 * @param {string} targetTagName
 * @param {boolean} enableFixer
 * @param {import('comment-parser').Block} jsdoc
 * @param {import('../iterateJsdoc.js').Utils} utils
 * @returns {boolean}
 */
const validatePropertyNames = (
  targetTagName,
  enableFixer,
  jsdoc, utils,
) => {
  const jsdocTypedefs = utils.getJsdocTagsDeep('typedef');
  let propertyTagGroups;
  if (jsdocTypedefs && jsdocTypedefs.length > 1) {
    propertyTagGroups = jsdocTypedefs.map(({
      idx,
    }, index) => {
      return Object.entries(jsdoc.tags).slice(idx, jsdocTypedefs[index + 1]?.idx);
    });
  } else {
    propertyTagGroups = [
      Object.entries(jsdoc.tags),
    ];
  }

  return propertyTagGroups.some((propertyTagGroup) => {
    const propertyTags = propertyTagGroup.filter(([
      , tag,
    ]) => {
      return tag.tag === targetTagName;
    });

    return propertyTags.some(([
      , tag,
    ], index) => {
      /** @type {import('../iterateJsdoc.js').Integer} */
      let tagsIndex;
      const dupeTagInfo = propertyTags.find(([
        tgsIndex,
        tg,
      ], idx) => {
        tagsIndex = Number(tgsIndex);

        return tg.name === tag.name && idx !== index;
      });
      if (dupeTagInfo) {
        utils.reportJSDoc(`Duplicate @${targetTagName} "${tag.name}"`, dupeTagInfo[1], enableFixer ? () => {
          utils.removeTag(tagsIndex);
        } : null);

        return true;
      }

      return false;
    });
  });
};

/**
 * @param {string} targetTagName
 * @param {{
 *   idx: number;
 *   name: string;
 *   type: string;
 * }[]} jsdocPropertyNames
 * @param {import('comment-parser').Block} jsdoc
 * @param {import('../iterateJsdoc.js').Report} report
 */
const validatePropertyNamesDeep = (
  targetTagName,
  jsdocPropertyNames, jsdoc, report,
) => {
  /** @type {string} */
  let lastRealProperty;

  return jsdocPropertyNames.some(({
    idx,
    name: jsdocPropertyName,
  }) => {
    const isPropertyPath = jsdocPropertyName.includes('.');

    if (isPropertyPath) {
      if (!lastRealProperty) {
        report(`@${targetTagName} path declaration ("${jsdocPropertyName}") appears before any real property.`, null, jsdoc.tags[idx]);

        return true;
      }

      let pathRootNodeName = jsdocPropertyName.slice(0, jsdocPropertyName.indexOf('.'));

      if (pathRootNodeName.endsWith('[]')) {
        pathRootNodeName = pathRootNodeName.slice(0, -2);
      }

      if (pathRootNodeName !== lastRealProperty) {
        report(
          `@${targetTagName} path declaration ("${jsdocPropertyName}") root node name ("${pathRootNodeName}") ` +
          `does not match previous real property name ("${lastRealProperty}").`,
          null,
          jsdoc.tags[idx],
        );

        return true;
      }
    } else {
      lastRealProperty = jsdocPropertyName;
    }

    return false;
  });
};

export default iterateJsdoc(({
  context,
  jsdoc,
  report,
  utils,
}) => {
  const {
    enableFixer = false,
  } = context.options[0] || {};
  const jsdocPropertyNamesDeep = utils.getJsdocTagsDeep('property');
  if (!jsdocPropertyNamesDeep || !jsdocPropertyNamesDeep.length) {
    return;
  }

  const targetTagName = /** @type {string} */ (utils.getPreferredTagName({
    tagName: 'property',
  }));
  const isError = validatePropertyNames(
    targetTagName,
    enableFixer,
    jsdoc,
    utils,
  );

  if (isError) {
    return;
  }

  validatePropertyNamesDeep(
    targetTagName, jsdocPropertyNamesDeep, jsdoc, report,
  );
}, {
  iterateAllJsdocs: true,
  meta: {
    docs: {
      description: 'Ensures that property names in JSDoc are not duplicated on the same block and that nested properties have defined roots.',
      url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-property-names.md#repos-sticky-header',
    },
    fixable: 'code',
    schema: [
      {
        additionalProperties: false,
        properties: {
          enableFixer: {
            description: `Set to \`true\` to auto-remove \`@property\` duplicates (based on
identical names).

Note that this option will remove duplicates of the same name even if
the definitions do not match in other ways (e.g., the second property will
be removed even if it has a different type or description).`,
            type: 'boolean',
          },
        },
        type: 'object',
      },
    ],
    type: 'suggestion',
  },
});
