"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;
var _index = _interopRequireDefault(require("../index.cjs"));
var _decamelize = _interopRequireDefault(require("decamelize"));
var _gitdown = _interopRequireDefault(require("gitdown"));
var _glob = require("glob");
var _nodeFs = _interopRequireDefault(require("node:fs"));
var _nodePath = _interopRequireDefault(require("node:path"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } /**
 * This script is used to inline assertions into the README.md documents.
 */
const dirname = __dirname;

/**
 * @param {string} str
 */
const escapeDescription = str => {
  return str.replaceAll(/(?<!`|\* +|'|\/\/ )@\w+/gv, '<code>$&</code>');
};

/**
 * @param {string} code
 * @returns {string}
 */
const trimCode = code => {
  let lines = code.replace(/^\n/v, '').trimEnd().split('\n');
  const firsLineIndentation = lines[0].match(/^\s+/v);
  const lastLineIndentation = lines[lines.length - 1].match(/^\s+/v);
  const firstIndentSize = firsLineIndentation ? firsLineIndentation[0].length : 0;
  const lastIndentSize = lastLineIndentation ? lastLineIndentation[0].length : 0;
  lines = lines.map((line, index) => {
    const lineIndentSize = firstIndentSize !== 0 || index === 0 ? Math.min(firstIndentSize, lastIndentSize) : lastIndentSize;
    return line.slice(lineIndentSize);
  });
  return lines.join('\n').replaceAll('\r', '\\r');
};

/**
 * @param {import('eslint').RuleTester.InvalidTestCase|import('eslint').RuleTester.ValidTestCase} setup
 * @param {string} ruleName
 * @returns {string}
 */
const formatCodeSnippet = (setup, ruleName) => {
  const paragraphs = [];
  paragraphs.push(trimCode(setup.code));
  if (setup.settings) {
    paragraphs.push(`// Settings: ${JSON.stringify(setup.settings)}`);
  }
  if (setup.options) {
    paragraphs.push(`// "jsdoc/${ruleName}": ["error"|"warn", ${JSON.stringify(setup.options).slice(1)}`);
  }
  if ('errors' in setup) {
    paragraphs.push(`// Message: ${ /** @type {Array<import('eslint').RuleTester.TestCaseError>} */setup.errors[0].message}`);
  }
  return paragraphs.join('\n');
};
const getAssertions = async () => {
  const assertionFiles = (await (0, _glob.glob)(_nodePath.default.resolve(dirname, '../../test/rules/assertions/*.js'))).filter(file => {
    return !file.includes('flatConfig');
  }).toReversed();
  const assertionNames = assertionFiles.map(filePath => {
    return _nodePath.default.basename(filePath, '.js');
  });
  const assertionCodes = await Promise.all(assertionFiles.map(async (filePath, idx) => {
    /**
     * @type {{
     *   invalid: (import('eslint').RuleTester.InvalidTestCase & {ignoreReadme?: true})[],
     *   valid: (import('eslint').RuleTester.ValidTestCase & {ignoreReadme?: true})[]
     * }}
     */
    const codes = (await (specifier => new Promise(r => r(`${specifier}`)).then(s => _interopRequireWildcard(require(s))))(filePath)).default;
    const ruleName = (0, _decamelize.default)(assertionNames[idx], {
      separator: '-'
    });
    return {
      invalid: codes.invalid.filter(({
        ignoreReadme
      }) => {
        return !ignoreReadme;
      }).map(setup => {
        return formatCodeSnippet(setup, ruleName);
      }),
      valid: codes.valid.filter(({
        ignoreReadme
      }) => {
        return !ignoreReadme;
      }).map(setup => {
        return formatCodeSnippet(setup, ruleName);
      })
    };
  }));
  return {
    assertionNames,
    assertions: Object.fromEntries(assertionNames.map((assertionName, index) => {
      return [assertionName, assertionCodes[index]];
    }))
  };
};
const getSomeBranch = () => {
  const gitConfig = _nodeFs.default.readFileSync(_nodePath.default.join(dirname, '../../.git/config')).toString();
  const [, branch] = /\[branch "([^"]+)"\]/v.exec(gitConfig) || [];
  return branch;
};

// Scan the directory for these instead?
const extraFiles = ['settings.md', 'advanced.md', 'processors.md', 'README.md'];
const otherPaths = extraFiles.map(extraFile => {
  return _nodePath.default.join(dirname, '..', '..', '.README', extraFile);
});
const generateDocs = async () => {
  const {
    assertionNames,
    assertions
  } = await getAssertions();

  /** @type {import('json-schema').JSONSchema4[][]} */
  const schemas = [];

  /**
   * @type {{
   *   decamelized: string,
   *   row: string
   * }[]}
   */
  const tableRows = [];
  const docContents = await Promise.all([...assertionNames.map(assertionName => {
    const decamelized = (0, _decamelize.default)(assertionName, {
      separator: '-'
    });
    schemas.push(/** @type {import('json-schema').JSONSchema4[]} */
    _index.default.rules?.[decamelized].meta?.schema);
    const ruleDescription = _index.default.rules?.[decamelized]?.meta?.docs?.description;
    if (ruleDescription === undefined) {
      throw new Error(`Rule ${assertionName} missing description`);
    }
    const fixable = _index.default.rules?.[decamelized]?.meta?.fixable ?? null;
    const recommended = _index.default.configs['flat/recommended'].rules?.['jsdoc/' + decamelized] !== 'off';
    const tsRecommended = _index.default.configs['flat/recommended-typescript'].rules?.['jsdoc/' + decamelized] !== 'off';
    const tsRecommendedFlavor = _index.default.configs['flat/recommended-typescript-flavor'].rules?.['jsdoc/' + decamelized] !== 'off';
    tableRows.push({
      decamelized,
      row: `|${recommended ? tsRecommended && tsRecommendedFlavor ? ':heavy_check_mark:' : ':heavy_check_mark: (' + (tsRecommended ? 'On in TS' : 'Off in TS') + '; ' + (tsRecommendedFlavor ? 'On in TS flavor' : 'Off in TS flavor') + ')' : tsRecommended || tsRecommendedFlavor ? (tsRecommended ? 'On in TS' : 'Off in TS') + '; ' + (tsRecommendedFlavor ? 'On in TS flavor' : 'Off in TS flavor') : ''}|${fixable ? ':wrench:' : ''}| [${decamelized}](./docs/rules/${decamelized}.md#readme) | ${ruleDescription} |`
    });
    return _nodePath.default.join(dirname, '..', '..', '.README', 'rules', decamelized + '.md');
  }), ...otherPaths].map(async (docPath, idx) => {
    const gitdown = await _gitdown.default.readFile(docPath);
    gitdown.setConfig({
      gitinfo: {
        defaultBranchName: getSomeBranch() || 'master',
        gitPath: _nodePath.default.join(dirname, '../../.git')
      }
    });
    gitdown.registerHelper('rules-table', {
      compile() {
        return tableRows.toSorted(({
          decamelized
        }, {
          decamelized: dc
        }) => {
          return decamelized < dc ? -1 : decamelized > dc ? 1 : 0;
        }).map(({
          row
        }) => {
          return row;
        }).join('\n');
      }
    });
    gitdown.registerHelper('options', {
      compile() {
        if (!schemas[idx]) {
          return '';
        }

        /**
         * @param {import('json-schema').JSONSchema4} schema
         * @param {number} jIdx
         * @param {import('json-schema').JSONSchema4[]} arr
         * @param {number} [nesting]
         */
        const convertFromSchema = (schema, jIdx, arr, nesting = 3) => {
          let ret = '';
          switch (schema.type) {
            case 'array':
              ret += convertFromSchema(/** @type {import('json-schema').JSONSchema4} */schema.items, 0, [], nesting + 1);
              break;
            case 'object':
              if (!schema.properties) {
                break;
              }
              if (jIdx === 0) {
                ret += (nesting > 3 ? '\n' : '') + (arr.length <= 1 ? 'A single' : 'An') + ' options object has the following properties.\n';
              } else {
                ret += '\n\nThe next option is an object with the following properties.\n';
              }
              if (schema.description) {
                ret += `\n${escapeDescription(schema.description)}\n`;
              }
              for (const [property, innerSchema] of Object.entries(schema.properties)) {
                const {
                  description,
                  type
                } = innerSchema;
                if (!description) {
                  throw new Error('Missing description for property ' + property + ' for rule ' + assertionNames[idx] + ' with schema ' + JSON.stringify(innerSchema));
                }
                ret += '\n' + '#'.repeat(nesting) + ` \`${property}\`

${type === 'object' && innerSchema.properties ? '' : escapeDescription(description) + '\n'}`;
                if (type === 'object' || type === 'array') {
                  ret += convertFromSchema(innerSchema, 0, [], nesting + 1);
                }
              }
              break;
            case 'string':
              if (jIdx !== 0) {
                throw new Error('Unexpected string schema');
              }

              // If a simple string, should be documented by parent
              if (schema.enum) {
                ret += 'The first option is a string with the following possible values: ';
                ret += schema.enum?.map(val => {
                  return `"${val}"`;
                }).join(', ') + '.\n';
              }
              if (schema.description) {
                ret += escapeDescription(schema.description);
              }
              break;
            default:
              // Describe on parent object
              if (schema.anyOf) {
                break;
              }
              throw new Error('Unrecognized type ' + schema.type + ' for schema: ' + JSON.stringify(schema));
          }
          return ret;
        };
        return schemas[idx].map((schema, jIdx, arr) => {
          return convertFromSchema(schema, jIdx, arr);
        }).join('');
      }
    });
    return gitdown.get();
  }));
  return docContents.map(docContent => {
    return docContent.replaceAll(/<!-- assertions-(passing|failing) ([a-z]+?) -->/gvi,
    /**
     * @param {string} _assertionsBlock
     * @param {string} passingFailing
     * @param {string} ruleName
     * @returns {string}
     */
    (_assertionsBlock, passingFailing, ruleName) => {
      const ruleAssertions = assertions[ruleName];
      if (!ruleAssertions) {
        throw new Error(`No assertions available for rule "${ruleName}".`);
      }
      return passingFailing === 'failing' ? 'The following patterns are considered problems:\n\n````ts\n' + ruleAssertions.invalid.join('\n\n') + '\n````\n\n' : 'The following patterns are not considered problems:\n\n````ts\n' + ruleAssertions.valid.join('\n\n') + '\n````\n';
    }
    // Allow relative paths in source for #902 but generate compiled file in
    //   manner compatible with GitHub and npmjs.com
    ).replaceAll('(../#', '(#user-content-eslint-plugin-jsdoc-');
  });
};

/**
 * @returns {string[]}
 */
const getDocPaths = () => {
  const basePath = _nodePath.default.join(dirname, '..', '..', '.README');
  const writeBasePath = _nodePath.default.join(dirname, '..', '..', 'docs');
  const docPaths = /** @type {string[]} */_nodeFs.default.readdirSync(basePath).flatMap(docFile => {
    if (extraFiles.includes(docFile)) {
      // Will get path separately below
      return null;
    }
    if (docFile === '.DS_Store') {
      return null;
    }
    const innerBasePath = _nodePath.default.join(basePath, docFile);
    const writeInnerBasePath = _nodePath.default.join(writeBasePath, docFile);
    const stat = _nodeFs.default.statSync(innerBasePath);
    if (stat.isFile()) {
      // Currently settings and advanced
      return writeInnerBasePath;
    }
    if (stat.isDirectory()) {
      return _nodeFs.default.readdirSync(innerBasePath).map(innerDocFile => {
        return _nodePath.default.join(writeInnerBasePath, innerDocFile);
      }).toSorted((a, b) => {
        const newA = a.replace(/\.md/v, '');
        const newB = b.replace(/\.md/v, '');
        return newA < newB ? -1 : newB > newA ? 1 : 0;
      });
    }
    return null;
  }).filter(Boolean);
  return [...docPaths, ...extraFiles.slice(0, -1).map(extraFile => {
    return _nodePath.default.join(dirname, '..', '..', 'docs', extraFile);
  }), _nodePath.default.join(dirname, '..', '..', 'README.md')];
};
const generateDocsAndWriteToDisk = async () => {
  const [docContents, docPaths] = await Promise.all([generateDocs(), getDocPaths()]);
  for (const [idx, docContent] of docContents.entries()) {
    const destPath = docPaths[idx];
    _nodeFs.default.writeFileSync(destPath, docContent);
  }
};
const assertDocsAreUpToDate = async () => {
  const [docContents, docPaths] = await Promise.all([generateDocs(), getDocPaths()]);
  for (const [idx, docContent] of docContents.entries()) {
    const docPath = docPaths[idx];
    const isUpToDate = _nodeFs.default.readFileSync(docPath, 'utf8') === docContent;
    if (!isUpToDate) {
      throw new Error('Docs are not up to date, please run `pnpm run create-docs` to update it.');
    }
  }
};
const main = async () => {
  try {
    const hasCheckFlag = process.argv.includes('--check');
    if (hasCheckFlag) {
      await assertDocsAreUpToDate();
    } else {
      await generateDocsAndWriteToDisk();
    }
  } catch (error) {
    /* eslint-disable-next-line no-console */
    console.error(error);
    process.exit(1);
  }
};
main();
var _default = exports.default = generateDocs;
module.exports = exports.default;
//# sourceMappingURL=generateDocs.cjs.map