import { Visitor } from "@herb-tools/core";
/**
 * Base visitor class that provides common functionality for rule visitors
 */
export class BaseRuleVisitor extends Visitor {
    offenses = [];
    ruleName;
    constructor(ruleName) {
        super();
        this.ruleName = ruleName;
    }
    /**
     * Helper method to create a lint offense
     */
    createOffense(message, location, severity = "error") {
        return {
            rule: this.ruleName,
            code: this.ruleName,
            source: "Herb Linter",
            message,
            location,
            severity,
        };
    }
    /**
     * Helper method to add an offense to the offenses array
     */
    addOffense(message, location, severity = "error") {
        this.offenses.push(this.createOffense(message, location, severity));
    }
}
/**
 * Gets attributes from either an HTMLOpenTagNode or HTMLSelfCloseTagNode
 */
export function getAttributes(node) {
    return node.type === "AST_HTML_SELF_CLOSE_TAG_NODE"
        ? node.attributes
        : node.children;
}
/**
 * Gets the tag name from an HTML tag node (lowercased)
 */
export function getTagName(node) {
    return node.tag_name?.value.toLowerCase() || null;
}
/**
 * Gets the attribute name from an HTMLAttributeNode (lowercased)
 */
export function getAttributeName(attributeNode) {
    if (attributeNode.name?.type === "AST_HTML_ATTRIBUTE_NAME_NODE") {
        const nameNode = attributeNode.name;
        return nameNode.name?.value.toLowerCase() || null;
    }
    return null;
}
/**
 * Gets the attribute value content from an HTMLAttributeValueNode
 */
export function getAttributeValue(attributeNode) {
    if (attributeNode.value?.type === "AST_HTML_ATTRIBUTE_VALUE_NODE") {
        const valueNode = attributeNode.value;
        if (valueNode.children && valueNode.children.length > 0) {
            return valueNode.children
                .filter(child => child.type === "AST_LITERAL_NODE")
                .map(child => child.content)
                .join("");
        }
    }
    return null;
}
/**
 * Checks if an attribute has a value
 */
export function hasAttributeValue(attributeNode) {
    return attributeNode.value?.type === "AST_HTML_ATTRIBUTE_VALUE_NODE";
}
/**
 * Gets the quote type used for an attribute value
 */
export function getAttributeValueQuoteType(attributeNode) {
    if (attributeNode.value?.type === "AST_HTML_ATTRIBUTE_VALUE_NODE") {
        const valueNode = attributeNode.value;
        if (valueNode.quoted && valueNode.open_quote) {
            return valueNode.open_quote.value === '"' ? "double" : "single";
        }
        return "none";
    }
    return null;
}
/**
 * Finds an attribute by name in a list of attributes
 */
export function findAttributeByName(attributes, attributeName) {
    for (const child of attributes) {
        if (child.type === "AST_HTML_ATTRIBUTE_NODE") {
            const attributeNode = child;
            const name = getAttributeName(attributeNode);
            if (name === attributeName.toLowerCase()) {
                return attributeNode;
            }
        }
    }
    return null;
}
/**
 * Checks if a tag has a specific attribute
 */
export function hasAttribute(node, attributeName) {
    const attributes = getAttributes(node);
    return findAttributeByName(attributes, attributeName) !== null;
}
/**
 * Common HTML element categorization
 */
export const HTML_INLINE_ELEMENTS = new Set([
    "a", "abbr", "acronym", "b", "bdo", "big", "br", "button", "cite", "code",
    "dfn", "em", "i", "img", "input", "kbd", "label", "map", "object", "output",
    "q", "samp", "script", "select", "small", "span", "strong", "sub", "sup",
    "textarea", "time", "tt", "var"
]);
export const HTML_BLOCK_ELEMENTS = new Set([
    "address", "article", "aside", "blockquote", "canvas", "dd", "div", "dl",
    "dt", "fieldset", "figcaption", "figure", "footer", "form", "h1", "h2",
    "h3", "h4", "h5", "h6", "header", "hr", "li", "main", "nav", "noscript",
    "ol", "p", "pre", "section", "table", "tfoot", "ul", "video"
]);
export const HTML_BOOLEAN_ATTRIBUTES = new Set([
    "autofocus", "autoplay", "checked", "controls", "defer", "disabled", "hidden",
    "loop", "multiple", "muted", "readonly", "required", "reversed", "selected",
    "open", "default", "formnovalidate", "novalidate", "itemscope", "scoped",
    "seamless", "allowfullscreen", "async", "compact", "declare", "nohref",
    "noresize", "noshade", "nowrap", "sortable", "truespeed", "typemustmatch"
]);
export const HEADING_TAGS = new Set(["h1", "h2", "h3", "h4", "h5", "h6"]);
/**
 * Checks if an element is inline
 */
export function isInlineElement(tagName) {
    return HTML_INLINE_ELEMENTS.has(tagName.toLowerCase());
}
/**
 * Checks if an element is block-level
 */
export function isBlockElement(tagName) {
    return HTML_BLOCK_ELEMENTS.has(tagName.toLowerCase());
}
/**
 * Checks if an attribute is a boolean attribute
 */
export function isBooleanAttribute(attributeName) {
    return HTML_BOOLEAN_ATTRIBUTES.has(attributeName.toLowerCase());
}
/**
 * Abstract base class for rules that need to check individual attributes on HTML tags
 * Eliminates duplication of visitHTMLOpenTagNode/visitHTMLSelfCloseTagNode patterns
 * and attribute iteration logic. Provides simplified interface with extracted attribute info.
 */
export class AttributeVisitorMixin extends BaseRuleVisitor {
    visitHTMLOpenTagNode(node) {
        this.checkAttributesOnNode(node);
        super.visitHTMLOpenTagNode(node);
    }
    visitHTMLSelfCloseTagNode(node) {
        this.checkAttributesOnNode(node);
        super.visitHTMLSelfCloseTagNode(node);
    }
    checkAttributesOnNode(node) {
        forEachAttribute(node, (attributeNode) => {
            const attributeName = getAttributeName(attributeNode);
            const attributeValue = getAttributeValue(attributeNode);
            if (attributeName) {
                this.checkAttribute(attributeName, attributeValue, attributeNode, node);
            }
        });
    }
}
/**
 * Checks if an attribute value is quoted
 */
export function isAttributeValueQuoted(attributeNode) {
    if (attributeNode.value?.type === "AST_HTML_ATTRIBUTE_VALUE_NODE") {
        const valueNode = attributeNode.value;
        return !!valueNode.quoted;
    }
    return false;
}
/**
 * Iterates over all attributes of a tag node, calling the callback for each attribute
 */
export function forEachAttribute(node, callback) {
    const attributes = getAttributes(node);
    for (const child of attributes) {
        if (child.type === "AST_HTML_ATTRIBUTE_NODE") {
            callback(child);
        }
    }
}
//# sourceMappingURL=rule-utils.js.map