/**
 * @file wally-rule-helpers.js
 * @description Shared helper functions used by NBR 17.225, IF-THEN, and custom
 *              rule modules. Eliminates duplication across rule engines.
 * @module WallyMonitor.RuleHelpers
 * @since 1.4.0
 */
window.WallyMonitor = window.WallyMonitor || {};

(function() {
    'use strict';

    var H = WallyMonitor.RuleHelpers = {};

    /**
     * Build a concise CSS selector for a given DOM element.
     * Walks up the tree collecting tag.class segments until an id is found.
     *
     * @param {Element} el
     * @returns {string}
     */
    H.getSelector = function(el) {
        if (!el || !el.tagName) { return ''; }
        var parts = [];
        while (el && el.nodeType === 1) {
            var seg = el.tagName.toLowerCase();
            if (el.id) {
                seg += '#' + el.id;
                parts.unshift(seg);
                break;
            }
            if (el.className && typeof el.className === 'string') {
                seg += '.' + el.className.trim().split(/\s+/).slice(0, 3).join('.');
            }
            // Disambiguate siblings with the same tag+class combination
            // so each element gets a unique selector (avoids report dedup
            // collapsing distinct elements into one).
            if (el.parentNode && el.parentNode.children) {
                var siblings = el.parentNode.children;
                var sameTag = 0, pos = 0;
                for (var i = 0; i < siblings.length; i++) {
                    if (siblings[i].tagName === el.tagName) {
                        sameTag++;
                        if (siblings[i] === el) { pos = sameTag; }
                    }
                }
                if (sameTag > 1) {
                    seg += ':nth-of-type(' + pos + ')';
                }
            }
            parts.unshift(seg);
            el = el.parentNode;
        }
        return parts.join(' > ');
    };

    /**
     * Return outer HTML truncated to a reasonable length.
     *
     * @param {Element} el
     * @param {number}  [max=250]
     * @returns {string}
     */
    H.outerSnippet = function(el, max) {
        if (!el || !el.outerHTML) { return ''; }
        max = max || 250;
        var h = el.outerHTML;
        return h.length > max ? h.substring(0, max) + '…' : h;
    };

    /**
     * Safely get computed style for an element.
     *
     * @param {Element} el
     * @returns {CSSStyleDeclaration|null}
     */
    H.getComputed = function(el) {
        try { return window.getComputedStyle(el); } catch (e) { return null; }
    };

    /**
     * Create a standard violation node entry.
     *
     * @param {Element} el
     * @param {string}  impact
     * @param {string}  message
     * @returns {{target: string[], html: string, impact: string, message: string}}
     */
    H.makeNode = function(el, impact, message) {
        return {
            target: [H.getSelector(el)],
            html:   H.outerSnippet(el),
            impact: impact,
            message: message
        };
    };

    /**
     * Create a complete violation result object.
     * Returns null when no nodes are provided (rule passed).
     *
     * @param {string}   id
     * @param {string}   impact
     * @param {string[]} tags
     * @param {string}   description
     * @param {string}   help
     * @param {string}   helpUrl
     * @param {object[]} nodes
     * @returns {object|null}
     */
    H.violation = function(id, impact, tags, description, help, helpUrl, nodes) {
        if (!nodes || nodes.length === 0) { return null; }
        return {
            id: id,
            impact: impact,
            tags: tags,
            description: description,
            help: help,
            helpUrl: helpUrl,
            nodes: nodes
        };
    };

    /**
     * Check whether an element is hidden via display/visibility.
     *
     * @param {Element} el
     * @returns {boolean}
     */
    H.isHidden = function(el) {
        var cs = H.getComputed(el);
        return cs && (cs.display === 'none' || cs.visibility === 'hidden');
    };

    /**
     * Check whether an element has an aria-label or aria-labelledby attribute.
     *
     * @param {Element} el
     * @returns {boolean}
     */
    H.hasAriaLabel = function(el) {
        return !!(el.getAttribute('aria-label') || el.getAttribute('aria-labelledby'));
    };

})();
