/**
 * @fileoverview Rule to prevent reversed link and image syntax in Markdown.
 * @author xbinaryx
 */
//-----------------------------------------------------------------------------
// Type Definitions
//-----------------------------------------------------------------------------
/**
 * @import { Heading, Paragraph, TableCell, Html, Image, ImageReference, InlineCode, LinkReference } from "mdast";
 * @import { InlineMath } from "mdast-util-math";
 * @import { MarkdownRuleDefinition } from "../types.js";
 * @typedef {"reversedSyntax"} NoReversedMediaSyntaxMessageIds
 * @typedef {[]} NoReversedMediaSyntaxOptions
 * @typedef {MarkdownRuleDefinition<{ RuleOptions: NoReversedMediaSyntaxOptions, MessageIds: NoReversedMediaSyntaxMessageIds }>} NoReversedMediaSyntaxRuleDefinition
 */
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
/** Matches reversed link/image syntax like `(text)[url]`, ignoring escaped characters like `\(text\)[url]`. */
const reversedPattern = /(?<=(?<!\\)(?:\\{2})*)\((?<label>(?:\\.|[^()\\]|\([\s\S]*\))*)\)\[(?<url>(?:\\.|[^\]\\\r\n])*)\](?!\()/gu;
//-----------------------------------------------------------------------------
// Rule Definition
//-----------------------------------------------------------------------------
export default /** @satisfies {NoReversedMediaSyntaxRuleDefinition} */ ({
    meta: {
        type: "problem",
        docs: {
            recommended: true,
            description: "Disallow reversed link and image syntax",
            url: "https://github.com/eslint/markdown/blob/main/docs/rules/no-reversed-media-syntax.md",
        },
        fixable: "code",
        messages: {
            reversedSyntax: "Unexpected reversed syntax found. Use [label](URL) syntax instead.",
        },
    },
    create(context) {
        const { sourceCode } = context;
        /** @type {string[]} */
        let buffer;
        /** @type {number} */
        let nodeStartOffset;
        return {
            "heading, paragraph, tableCell"(
            /** @type {Heading | Paragraph | TableCell} */ node) {
                // Use UTF-16 code units so the buffer stays aligned with source offsets.
                buffer = sourceCode.getText(node).split("");
                // Store the start offset of the node for later calculations.
                nodeStartOffset = node.position.start.offset;
            },
            ":matches(heading, paragraph, tableCell) :matches(html, image, imageReference, inlineCode, linkReference, inlineMath)"(
            /** @type {Html | Image | ImageReference | InlineCode | LinkReference | InlineMath} */ node) {
                const [startOffset, endOffset] = sourceCode.getRange(node);
                // Mask the content of `html`, `image`, `imageReference`, `inlineCode`, `linkReference`, and `inlineMath` nodes with whitespaces.
                for (let i = startOffset; i < endOffset; i++) {
                    buffer[i - nodeStartOffset] = " ";
                }
            },
            ":matches(heading, paragraph, tableCell):exit"() {
                const maskedText = buffer.join("");
                /** @type {RegExpExecArray | null} */
                let match;
                while ((match = reversedPattern.exec(maskedText)) !== null) {
                    const { label, url } = match.groups;
                    const startOffset = match.index + nodeStartOffset; // Adjust `reversedPattern` match index to the full source code.
                    const endOffset = startOffset + match[0].length;
                    const labelStartOffset = startOffset + 1; // Skip "("
                    const labelEndOffset = labelStartOffset + label.length;
                    const urlStartOffset = labelEndOffset + 2; // Skip ")["
                    const urlEndOffset = urlStartOffset + url.length;
                    context.report({
                        loc: {
                            start: sourceCode.getLocFromIndex(startOffset),
                            end: sourceCode.getLocFromIndex(endOffset),
                        },
                        messageId: "reversedSyntax",
                        fix(fixer) {
                            return fixer.replaceTextRange([startOffset, endOffset], `[${sourceCode.text.slice(labelStartOffset, labelEndOffset)}](${sourceCode.text.slice(urlStartOffset, urlEndOffset)})`);
                        },
                    });
                }
            },
        };
    },
});
