/**
 * @fileoverview Rule to prevent non-complaint link references.
 * @author Nicholas C. Zakas
 */
//-----------------------------------------------------------------------------
// Imports
//-----------------------------------------------------------------------------
import { illegalShorthandTailPattern } from "../util.js";
//-----------------------------------------------------------------------------
// Type Definitions
//-----------------------------------------------------------------------------
/**
 * @import { Position } from "unist";
 * @import { Text } from "mdast";
 * @import { MarkdownRuleDefinition } from "../types.js";
 * @import { MarkdownSourceCode } from "../language/markdown-source-code.js";
 * @typedef {"invalidLabelRef"} NoInvalidLabelRefsMessageIds
 * @typedef {[]} NoInvalidLabelRefsOptions
 * @typedef {MarkdownRuleDefinition<{ RuleOptions: NoInvalidLabelRefsOptions, MessageIds: NoInvalidLabelRefsMessageIds }>} NoInvalidLabelRefsRuleDefinition
 */
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
/** matches i.e., `[foo][bar]` */
const labelPattern = /\]\[([^\]]+)\]/u;
/**
 * Finds missing references in a node.
 * @param {Text} node The node to check.
 * @param {MarkdownSourceCode} sourceCode The Markdown source code object.
 * @returns {Array<{label:string,position:Position}>} The missing references.
 */
function findInvalidLabelReferences(node, sourceCode) {
    const nodeText = sourceCode.getText(node);
    const docText = sourceCode.text;
    const invalid = [];
    let startIndex = 0;
    /*
     * This loop works by searching the string inside the node for the next
     * label reference. If it finds one, it checks to see if there is any
     * white space between the [ and ]. If there is, it reports an error.
     * It then moves the start index to the end of the label reference and
     * continues searching the text until the end of the text is found.
     */
    while (startIndex < nodeText.length) {
        const value = nodeText.slice(startIndex);
        const match = value.match(labelPattern);
        if (!match) {
            break;
        }
        if (!illegalShorthandTailPattern.test(match[0])) {
            startIndex += match.index + match[0].length;
            continue;
        }
        /*
         * Adjust `labelPattern` match index to the full source code.
         */
        const startOffset = startIndex + match.index + node.position.start.offset;
        const endOffset = startOffset + match[0].length;
        /*
         * Search the entire document text to find the preceding open bracket.
         */
        const lastOpenBracketIndex = docText.lastIndexOf("[", startOffset);
        if (lastOpenBracketIndex === -1) {
            startIndex += match.index + match[0].length;
            continue;
        }
        /*
         * Note: `label` can contain leading and trailing newlines, so we need to
         * take that into account when calculating the line and column offsets.
         */
        const label = docText
            .slice(lastOpenBracketIndex, endOffset)
            .match(/!?\[([^\]]+)\]/u)[1];
        invalid.push({
            label: label.trim(),
            position: {
                start: sourceCode.getLocFromIndex(startOffset + 1),
                end: sourceCode.getLocFromIndex(endOffset),
            },
        });
        startIndex += match.index + match[0].length;
    }
    return invalid;
}
//-----------------------------------------------------------------------------
// Rule Definition
//-----------------------------------------------------------------------------
export default /** @satisfies {NoInvalidLabelRefsRuleDefinition} */ ({
    meta: {
        type: "problem",
        docs: {
            recommended: true,
            description: "Disallow invalid label references",
            url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-invalid-label-refs.md",
        },
        messages: {
            invalidLabelRef: "Label reference '{{label}}' is invalid due to white space between [ and ].",
        },
    },
    create(context) {
        const { sourceCode } = context;
        return {
            text(node) {
                const invalidReferences = findInvalidLabelReferences(node, sourceCode);
                for (const invalidReference of invalidReferences) {
                    context.report({
                        loc: invalidReference.position,
                        messageId: "invalidLabelRef",
                        data: {
                            label: invalidReference.label,
                        },
                    });
                }
            },
        };
    },
});
