import {
    AttributeAction,
    type AttributeSelector,
    isTraversal as isTraversalBase,
    SelectorType,
    type Traversal,
} from "css-what";
import type { InternalSelector } from "../types.js";

export function isTraversal(token: InternalSelector): token is Traversal {
    return token.type === "_flexibleDescendant" || isTraversalBase(token);
}

/**
 * Sort the parts of the passed selector, as there is potential for
 * optimization (some types of selectors are faster than others).
 *
 * @param arr Selector to sort
 */
export function sortRules(arr: InternalSelector[]): void {
    const ratings = arr.map(getQuality);
    for (let i = 1; i < arr.length; i++) {
        const procNew = ratings[i];

        if (procNew < 0) {
            continue;
        }

        // Use insertion sort to move the token to the correct position.
        for (let j = i; j > 0 && procNew < ratings[j - 1]; j--) {
            const token = arr[j];
            arr[j] = arr[j - 1];
            arr[j - 1] = token;
            ratings[j] = ratings[j - 1];
            ratings[j - 1] = procNew;
        }
    }
}

function getAttributeQuality(token: AttributeSelector): number {
    switch (token.action) {
        case AttributeAction.Exists: {
            return 10;
        }
        case AttributeAction.Equals: {
            // Prefer ID selectors (eg. #ID)
            return token.name === "id" ? 9 : 8;
        }
        case AttributeAction.Not: {
            return 7;
        }
        case AttributeAction.Start: {
            return 6;
        }
        case AttributeAction.End: {
            return 6;
        }
        case AttributeAction.Any: {
            return 5;
        }
        case AttributeAction.Hyphen: {
            return 4;
        }
        case AttributeAction.Element: {
            return 3;
        }
    }
}

/**
 * Determine the quality of the passed token. The higher the number, the
 * faster the token is to execute.
 *
 * @param token Token to get the quality of.
 * @returns The token's quality.
 */
export function getQuality(token: InternalSelector): number {
    switch (token.type) {
        case SelectorType.Universal: {
            return 50;
        }
        case SelectorType.Tag: {
            return 30;
        }
        case SelectorType.Attribute: {
            return Math.floor(
                getAttributeQuality(token) /
                    // `ignoreCase` adds some overhead, half the result if applicable.
                    (token.ignoreCase ? 2 : 1),
            );
        }
        case SelectorType.Pseudo: {
            return !token.data
                ? 3
                : token.name === "has" ||
                    token.name === "contains" ||
                    token.name === "icontains"
                  ? // Expensive in any case — run as late as possible.
                    0
                  : Array.isArray(token.data)
                    ? // Eg. `:is`, `:not`
                      Math.max(
                          // If we have traversals, try to avoid executing this selector
                          0,
                          Math.min(
                              ...token.data.map((d) =>
                                  Math.min(...d.map(getQuality)),
                              ),
                          ),
                      )
                    : 2;
        }
        default: {
            return -1;
        }
    }
}

export function includesScopePseudo(t: InternalSelector): boolean {
    return (
        t.type === SelectorType.Pseudo &&
        (t.name === "scope" ||
            (Array.isArray(t.data) &&
                t.data.some((data) => data.some(includesScopePseudo))))
    );
}
