"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveStateParam = exports.createStateWithParamClassName = exports.createBooleanStateClassName = exports.isTemplateState = exports.transformPseudoClassToCustomState = exports.validateStateArgument = exports.validateRuleStateDefinition = exports.systemValidators = exports.validationErrors = exports.parseStateValue = exports.parsePseudoStates = exports.stateDiagnostics = exports.stateWithParamDelimiter = exports.booleanStateDelimiter = exports.stateMiddleDelimiter = void 0;
const postcss_value_parser_1 = __importDefault(require("postcss-value-parser"));
const cssesc_1 = __importDefault(require("cssesc"));
const diagnostics_1 = require("../diagnostics");
const selector_1 = require("./selector");
const value_1 = require("./value");
const string_1 = require("./string");
const functions_1 = require("../functions");
const features_1 = require("../features");
const native_reserved_lists_1 = require("../native-reserved-lists");
const css_value_parser_1 = require("@tokey/css-value-parser");
const css_value_seeker_1 = require("./css-value-seeker");
exports.stateMiddleDelimiter = '-';
exports.booleanStateDelimiter = '--';
exports.stateWithParamDelimiter = exports.booleanStateDelimiter + exports.stateMiddleDelimiter;
exports.stateDiagnostics = {
    MISSING_TYPE_OR_TEMPLATE: (0, diagnostics_1.createDiagnosticReporter)('08000', 'error', (name) => `pseudo-state "${name}" missing type or template`),
    UNKNOWN_STATE_TYPE: (0, diagnostics_1.createDiagnosticReporter)('08002', 'error', (name, type) => `pseudo-state "${name}" defined with unknown type: "${type}"`),
    TOO_MANY_STATE_TYPES: (0, diagnostics_1.createDiagnosticReporter)('08003', 'error', (name, types) => `pseudo-state "${name}(${types.join(', ')})" definition must be of a single type`),
    NO_STATE_TYPE_GIVEN: (0, diagnostics_1.createDiagnosticReporter)('08005', 'warning', (name) => `pseudo-state "${name}" expected a definition of a single type, but received none`),
    TOO_MANY_ARGS_IN_VALIDATOR: (0, diagnostics_1.createDiagnosticReporter)('08006', 'error', (name, validator, args) => `pseudo-state "${name}" expected "${validator}" validator to receive a single argument, but it received "${args.join(', ')}"`),
    STATE_STARTS_WITH_HYPHEN: (0, diagnostics_1.createDiagnosticReporter)('08007', 'error', (name) => `state "${name}" declaration cannot begin with a "${exports.stateMiddleDelimiter}" character`),
    RESERVED_NATIVE_STATE: (0, diagnostics_1.createDiagnosticReporter)('08008', 'warning', (name) => `state "${name}" is reserved for native pseudo-class`),
    DEFAULT_PARAM_FAILS_VALIDATION: (0, diagnostics_1.createDiagnosticReporter)('08010', 'error', (stateName, defaultValue, errors) => `pseudo-state "${stateName}" default value "${defaultValue}" failed validation:\n${errors.join('\n')}`),
    NO_STATE_ARGUMENT_GIVEN: (0, diagnostics_1.createDiagnosticReporter)('08004', 'error', (name, type) => `pseudo-state "${name}" expected argument of type "${type}" but got none`),
    FAILED_STATE_VALIDATION: (0, diagnostics_1.createDiagnosticReporter)('08009', 'error', (name, actualParam, errors) => [
        `pseudo-state "${name}" with parameter "${actualParam}" failed validation:`,
        ...errors,
    ].join('\n')),
    TEMPLATE_MISSING_PLACEHOLDER: (0, diagnostics_1.createDiagnosticReporter)('08011', 'warning', (state, template) => `pseudo-state "${state}" template "${template}" is missing a placeholder, use "$0" to set the parameter insertion place`),
    TEMPLATE_MULTI_PARAMETERS: (0, diagnostics_1.createDiagnosticReporter)('08012', 'error', (state) => `pseudo-state "${state}" template only supports a single parameter`),
    TEMPLATE_MISSING_PARAMETER: (0, diagnostics_1.createDiagnosticReporter)('08013', 'error', (state) => `pseudo-state "${state}" template expected a parameter definition`),
    UNSUPPORTED_MULTI_SELECTOR: (0, diagnostics_1.createDiagnosticReporter)('08014', 'error', (state, finalSelector) => `pseudo-state "${state}" resulted in an unsupported multi selector "${finalSelector}"`),
    UNSUPPORTED_COMPLEX_SELECTOR: (0, diagnostics_1.createDiagnosticReporter)('08015', 'error', (state, finalSelector) => `pseudo-state "${state}" resulted in an unsupported complex selector "${finalSelector}"`),
    INVALID_SELECTOR: (0, diagnostics_1.createDiagnosticReporter)('08016', 'error', (state, finalSelector) => `pseudo-state "${state}" resulted in an invalid selector "${finalSelector}"`),
    UNSUPPORTED_INITIAL_SELECTOR: (0, diagnostics_1.createDiagnosticReporter)('08017', 'error', (state, finalSelector) => `pseudo-state "${state}" result cannot start with a type or universal selector "${finalSelector}"`),
    NO_PARAM_REQUIRED: (0, diagnostics_1.createDiagnosticReporter)('08018', 'error', (name, param) => `pseudo-state "${name}" accepts no parameter, but received "${param}"`),
};
// parse
function parsePseudoStates(value, decl, diagnostics) {
    const mappedStates = {};
    const ast = (0, postcss_value_parser_1.default)(value);
    const statesSplitByComma = (0, value_1.groupValues)(ast.nodes);
    statesSplitByComma.forEach((workingState) => {
        const [stateDefinition, ...stateDefault] = workingState;
        const stateName = stateDefinition.value;
        if (!validateStateName(stateName, diagnostics, decl)) {
            return;
        }
        if (stateDefinition.type === 'function') {
            resolveStateType(stateDefinition, mappedStates, stateDefault, diagnostics, decl);
        }
        else if (stateDefinition.type === 'word') {
            resolveBooleanState(mappedStates, stateDefinition);
        }
        else {
            // TODO: Invalid state, edge case needs warning
        }
    });
    return mappedStates;
}
exports.parsePseudoStates = parsePseudoStates;
function validateStateName(name, diagnostics, node) {
    if (name.startsWith('-')) {
        diagnostics.report(exports.stateDiagnostics.STATE_STARTS_WITH_HYPHEN(name), {
            node: node,
            word: name,
        });
    }
    else if (native_reserved_lists_1.reservedFunctionalPseudoClasses.includes(name)) {
        diagnostics.report(exports.stateDiagnostics.RESERVED_NATIVE_STATE(name), {
            node: node,
            word: name,
        });
        return false;
    }
    return true;
}
function parseStateValue(value, node, diagnostics) {
    let stateName = '';
    let stateDef = null; /*boolean*/
    let amountTaken = 0;
    const customIdentResult = (0, css_value_seeker_1.findCustomIdent)(value, 0);
    const [amountToName, nameNode] = customIdentResult[0]
        ? customIdentResult
        : (0, css_value_seeker_1.findNextCallNode)(value, 0);
    if (nameNode && validateStateName(nameNode.value, diagnostics, node)) {
        amountTaken += amountToName;
        stateName = nameNode.value;
        // state with parameter
        if (nameNode.type === 'call') {
            // take all of the definition since default value takes the rest
            amountTaken = value.length;
            // ToDo: translate resolveStateType to tokey and remove the double parsing
            const postcssStateValue = (0, postcss_value_parser_1.default)((0, css_value_parser_1.stringifyCSSValue)(value.slice(amountToName - 1)));
            // get state definition
            const [stateDefinition, ...stateDefault] = postcssStateValue.nodes;
            const stateMap = {};
            resolveStateType(stateDefinition, stateMap, stateDefault, diagnostics, node // ToDo: change to accept any postcss node
            );
            if (stateMap[stateName]) {
                stateDef = stateMap[stateName];
            }
        }
    }
    if (stateName) {
        return [amountTaken, stateDef];
    }
    return [0, undefined];
}
exports.parseStateValue = parseStateValue;
function resolveBooleanState(mappedStates, stateDefinition) {
    const currentState = mappedStates[stateDefinition.value];
    if (!currentState) {
        mappedStates[stateDefinition.value] = null; // add boolean state
    }
    else {
        // TODO: warn with such name already exists
    }
}
function resolveStateType(stateDefinition, mappedStates, stateDefault, diagnostics, decl) {
    const stateName = stateDefinition.value;
    if (stateDefinition.nodes.length === 0) {
        resolveBooleanState(mappedStates, stateDefinition);
        diagnostics.report(exports.stateDiagnostics.NO_STATE_TYPE_GIVEN(stateName), {
            node: decl,
            word: decl.value,
        });
        return;
    }
    const { paramType, argsFirstNode, argsFullValue } = collectStateArgsDef(stateDefinition.nodes);
    if (!paramType) {
        diagnostics.report(exports.stateDiagnostics.MISSING_TYPE_OR_TEMPLATE(stateName), {
            node: decl,
        });
        return;
    }
    if (paramType?.type === 'string') {
        defineTemplateState(stateName, paramType, argsFirstNode, argsFullValue, mappedStates, diagnostics, decl);
    }
    else {
        if (argsFullValue.length > 1) {
            diagnostics.report(exports.stateDiagnostics.TOO_MANY_STATE_TYPES(stateName, argsFirstNode.map((argNode) => argNode ? postcss_value_parser_1.default.stringify(argNode) : '')), {
                node: decl,
                word: decl.value,
            });
        }
        defineParamState(stateName, paramType, stateDefault, mappedStates, diagnostics, stateDefinition, decl);
    }
}
function defineTemplateState(stateName, templateDef, argsFirstNode, argsFullValue, mappedStates, diagnostics, decl) {
    const template = (0, string_1.stripQuotation)(postcss_value_parser_1.default.stringify(templateDef));
    if (argsFullValue.length === 1) {
        // simple template with no params
        const selectorStr = template.trim().replace(/\\["']/g, '"');
        const selectorAst = (0, selector_1.parseSelectorWithCache)(selectorStr, { clone: true });
        if (!validateTemplateSelector({
            stateName,
            selectorStr,
            selectorAst,
            cssNode: decl,
            diagnostics,
        })) {
            return;
        }
        else {
            mappedStates[stateName] = selectorStr;
        }
    }
    else if (argsFullValue.length === 2) {
        // single parameter template
        if (!template.includes('$0')) {
            diagnostics.report(exports.stateDiagnostics.TEMPLATE_MISSING_PLACEHOLDER(stateName, template), {
                node: decl,
                word: template,
            });
        }
        const paramFullDef = argsFullValue[1];
        const paramTypeDef = argsFirstNode[1];
        if (!paramTypeDef) {
            diagnostics.report(exports.stateDiagnostics.TEMPLATE_MISSING_PARAMETER(stateName), {
                node: decl,
            });
            return;
        }
        const param = createStateParamDef(stateName + ' parameter', paramTypeDef, paramFullDef.splice(paramFullDef.indexOf(paramTypeDef) + 1), diagnostics, decl);
        if (!param) {
            // UNKNOWN_STATE_TYPE reported in createStateParamDef
            return;
        }
        const templateStateType = {
            type: 'template',
            template,
            params: [param],
        };
        mappedStates[stateName] = templateStateType;
    }
    else {
        // unsupported multiple params
        diagnostics.report(exports.stateDiagnostics.TEMPLATE_MULTI_PARAMETERS(stateName), {
            node: decl,
        });
    }
}
function defineParamState(stateName, paramType, stateDefault, mappedStates, diagnostics, stateDefinition, decl) {
    if (paramType.value === 'boolean') {
        // explicit boolean // ToDo: remove support
        resolveBooleanState(mappedStates, stateDefinition);
    }
    else {
        const stateParamDef = createStateParamDef(stateName, paramType, stateDefault, diagnostics, decl);
        if (stateParamDef) {
            mappedStates[stateName] = stateParamDef;
        }
    }
}
function createStateParamDef(stateName, typeDef, stateDefault, diagnostics, decl) {
    const type = typeDef.value;
    if (type in exports.systemValidators && (typeDef.type === 'function' || typeDef.type === 'word')) {
        const stateType = {
            type,
            arguments: [],
            defaultValue: postcss_value_parser_1.default
                .stringify(stateDefault)
                .trim(),
        };
        if (typeDef.type === 'function' && typeDef.nodes.length > 0) {
            resolveArguments(typeDef, stateType, stateName, diagnostics, decl);
        }
        return stateType;
    }
    else {
        const srcValue = postcss_value_parser_1.default.stringify(typeDef);
        diagnostics.report(exports.stateDiagnostics.UNKNOWN_STATE_TYPE(stateName, srcValue), {
            node: decl,
            word: srcValue,
        });
        return;
    }
}
function collectStateArgsDef(nodes) {
    const argsFullValue = [];
    const argsFirstNode = [];
    let collectedArg = [];
    let firstActualValue = undefined;
    for (const node of nodes) {
        if (node.type === 'div') {
            argsFullValue.push(collectedArg);
            argsFirstNode.push(firstActualValue);
            collectedArg = [];
            firstActualValue = undefined;
        }
        else {
            collectedArg.push(node);
            if (!firstActualValue && node.type !== 'space' && node.type !== 'comment') {
                firstActualValue = node;
            }
        }
    }
    if (collectedArg.length) {
        argsFullValue.push(collectedArg);
        argsFirstNode.push(firstActualValue);
    }
    const paramType = argsFirstNode[0];
    return { paramType, argsFullValue, argsFirstNode };
}
function resolveArguments(paramType, stateType, name, diagnostics, decl) {
    const separatedByComma = (0, value_1.groupValues)(paramType.nodes);
    separatedByComma.forEach((group) => {
        const validator = group[0];
        if (validator.type === 'function') {
            const args = (0, value_1.listOptions)(validator);
            if (args.length > 1) {
                diagnostics.report(exports.stateDiagnostics.TOO_MANY_ARGS_IN_VALIDATOR(name, validator.value, args), {
                    node: decl,
                    word: decl.value,
                });
            }
            else {
                stateType.arguments.push({
                    name: validator.value,
                    args,
                });
            }
        }
        else if (validator.type === 'string' || validator.type === 'word') {
            stateType.arguments.push(validator.value);
        }
    });
}
exports.validationErrors = {
    string: {
        STRING_TYPE_VALIDATION_FAILED: (actualParam) => `"${actualParam}" should be of type string`,
        REGEX_VALIDATION_FAILED: (regex, actualParam) => `expected "${actualParam}" to match regex "${regex}"`,
        CONTAINS_VALIDATION_FAILED: (shouldContain, actualParam) => `expected "${actualParam}" to contain string "${shouldContain}"`,
        MIN_LENGTH_VALIDATION_FAILED: (length, actualParam) => `expected "${actualParam}" to be of length longer than or equal to ${length}`,
        MAX_LENGTH_VALIDATION_FAILED: (length, actualParam) => `expected "${actualParam}" to be of length shorter than or equal to ${length}`,
        UKNOWN_VALIDATOR: (name) => `encountered unknown string validator "${name}"`,
    },
    number: {
        NUMBER_TYPE_VALIDATION_FAILED: (actualParam) => `expected "${actualParam}" to be of type number`,
        MIN_VALIDATION_FAILED: (actualParam, min) => `expected "${actualParam}" to be larger than or equal to ${min}`,
        MAX_VALIDATION_FAILED: (actualParam, max) => `expected "${actualParam}" to be lesser then or equal to ${max}`,
        MULTIPLE_OF_VALIDATION_FAILED: (actualParam, multipleOf) => `expected "${actualParam}" to be a multiple of ${multipleOf}`,
        UKNOWN_VALIDATOR: (name) => `encountered unknown number validator "${name}"`,
    },
    enum: {
        ENUM_TYPE_VALIDATION_FAILED: (actualParam, options) => `expected "${actualParam}" to be one of the options: "${options.join(', ')}"`,
        NO_OPTIONS_DEFINED: () => `expected enum to be defined with one option or more`,
    },
};
exports.systemValidators = {
    string: {
        validate(value, validators, resolveParam, validateDefinition, validateValue) {
            const res = value;
            const errors = [];
            if (validateValue && typeof value !== 'string') {
                errors.push(exports.validationErrors.string.STRING_TYPE_VALIDATION_FAILED(value));
            }
            if (validators.length > 0) {
                validators.forEach((validatorMeta) => {
                    if (typeof validatorMeta === 'object') {
                        if (this.subValidators && this.subValidators[validatorMeta.name]) {
                            const subValidator = this.subValidators[validatorMeta.name];
                            const validationRes = subValidator(value, resolveParam(validatorMeta.args[0]));
                            if (validateValue && validationRes.errors) {
                                errors.push(...validationRes.errors);
                            }
                        }
                        else if (validateDefinition) {
                            errors.push(exports.validationErrors.string.UKNOWN_VALIDATOR(validatorMeta.name));
                        }
                    }
                });
            }
            return { res, errors: errors.length ? errors : null };
        },
        subValidators: {
            regex: (value, regex) => {
                const r = new RegExp(regex);
                const valid = r.test(value);
                return {
                    res: value,
                    errors: valid
                        ? null
                        : [exports.validationErrors.string.REGEX_VALIDATION_FAILED(regex, value)],
                };
            },
            contains: (value, checkedValue) => {
                const valid = !!~value.indexOf(checkedValue);
                return {
                    res: value,
                    errors: valid
                        ? null
                        : [exports.validationErrors.string.CONTAINS_VALIDATION_FAILED(checkedValue, value)],
                };
            },
            minLength: (value, length) => {
                const valid = value.length >= Number(length);
                return {
                    res: value,
                    errors: valid
                        ? null
                        : [exports.validationErrors.string.MIN_LENGTH_VALIDATION_FAILED(length, value)],
                };
            },
            maxLength: (value, length) => {
                const valid = value.length <= Number(length);
                return {
                    res: value,
                    errors: valid
                        ? null
                        : [exports.validationErrors.string.MAX_LENGTH_VALIDATION_FAILED(length, value)],
                };
            },
        },
    },
    number: {
        validate(value, validators, resolveParam, validateDefinition, validateValue) {
            const res = value;
            const errors = [];
            if (isNaN(value)) {
                if (validateValue) {
                    errors.push(exports.validationErrors.number.NUMBER_TYPE_VALIDATION_FAILED(value));
                }
            }
            else if (validators.length > 0) {
                validators.forEach((validatorMeta) => {
                    if (typeof validatorMeta === 'object') {
                        if (this.subValidators && this.subValidators[validatorMeta.name]) {
                            const subValidator = this.subValidators[validatorMeta.name];
                            const validationRes = subValidator(value, resolveParam(validatorMeta.args[0]));
                            if (validateValue && validationRes.errors) {
                                errors.push(...validationRes.errors);
                            }
                        }
                        else if (validateDefinition) {
                            errors.push(exports.validationErrors.number.UKNOWN_VALIDATOR(validatorMeta.name));
                        }
                    }
                });
            }
            return { res, errors: errors.length ? errors : null };
        },
        subValidators: {
            min: (value, minValue) => {
                const valid = Number(value) >= Number(minValue);
                return {
                    res: value,
                    errors: valid
                        ? null
                        : [exports.validationErrors.number.MIN_VALIDATION_FAILED(value, minValue)],
                };
            },
            max: (value, maxValue) => {
                const valid = Number(value) <= Number(maxValue);
                return {
                    res: value,
                    errors: valid
                        ? null
                        : [exports.validationErrors.number.MAX_VALIDATION_FAILED(value, maxValue)],
                };
            },
            multipleOf: (value, multipleOf) => {
                const valid = Number(value) % Number(multipleOf) === 0;
                return {
                    res: value,
                    errors: valid
                        ? null
                        : [
                            exports.validationErrors.number.MULTIPLE_OF_VALIDATION_FAILED(value, multipleOf),
                        ],
                };
            },
        },
    },
    enum: {
        validate(value, options, resolveParam, validateDefinition, validateValue) {
            const res = value;
            const errors = [];
            const stringOptions = [];
            if (options.length) {
                const isOneOf = options.some((option) => {
                    if (typeof option === 'string') {
                        stringOptions.push(option);
                        return resolveParam(option) === value;
                    }
                    return true;
                });
                if (validateValue && !isOneOf) {
                    errors.push(exports.validationErrors.enum.ENUM_TYPE_VALIDATION_FAILED(value, stringOptions));
                }
            }
            else if (validateDefinition) {
                errors.push(exports.validationErrors.enum.NO_OPTIONS_DEFINED());
            }
            return { res, errors: errors.length ? errors : null };
        },
    },
};
function validateRuleStateDefinition(selector, selectorNode, meta, resolver, diagnostics) {
    const selectorAst = (0, selector_1.parseSelectorWithCache)(selector);
    if (selectorAst.length && selectorAst.length === 1) {
        const singleSelectorAst = selectorAst[0];
        const selectorChunk = singleSelectorAst.nodes;
        if (selectorChunk.length === 1 && selectorChunk[0].type === 'class') {
            const className = selectorChunk[0].value;
            const classMeta = features_1.CSSClass.get(meta, className);
            const states = classMeta?.[`-st-states`];
            if (states && classMeta._kind === 'class') {
                for (const stateName in states) {
                    // TODO: Sort out types
                    const state = states[stateName];
                    if (state && typeof state === 'object') {
                        const stateParam = isTemplateState(state) ? state.params[0] : state;
                        const { errors } = validateStateArgument(stateParam, meta, stateParam.defaultValue || '', resolver, diagnostics, selectorNode, true, !!stateParam.defaultValue);
                        if (errors && selectorNode.nodes) {
                            for (const node of selectorNode.nodes) {
                                if (node.type === 'decl' && node.prop === `-st-states`) {
                                    diagnostics.report(exports.stateDiagnostics.DEFAULT_PARAM_FAILS_VALIDATION(stateName, stateParam.defaultValue || '', errors), {
                                        node: node,
                                        word: node.value,
                                    });
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            else {
                // TODO: error state on non-class
            }
        }
    }
}
exports.validateRuleStateDefinition = validateRuleStateDefinition;
function validateStateArgument(stateAst, meta, value, resolver, diagnostics, selectorNode, validateDefinition, validateValue = true) {
    const resolvedValidations = {
        res: resolveParam(meta, resolver, diagnostics, selectorNode, value || stateAst.defaultValue),
        errors: null,
    };
    const { type: paramType } = stateAst;
    const validator = exports.systemValidators[paramType];
    try {
        if (resolvedValidations.res || validateDefinition) {
            const { errors } = validator.validate(resolvedValidations.res, stateAst.arguments, resolveParam.bind(null, meta, resolver, diagnostics, selectorNode), !!validateDefinition, validateValue);
            resolvedValidations.errors = errors;
        }
    }
    catch (error) {
        // TODO: warn about validation throwing exception
    }
    return resolvedValidations;
}
exports.validateStateArgument = validateStateArgument;
// transform
function transformPseudoClassToCustomState(stateDef, meta, name, stateNode, namespace, resolver, diagnostics, selectorNode) {
    if (stateDef === null || typeof stateDef === 'string') {
        if (stateNode.nodes && selectorNode) {
            diagnostics.report(exports.stateDiagnostics.NO_PARAM_REQUIRED(name, (0, selector_1.stringifySelector)(stateNode.nodes)), {
                node: selectorNode,
                word: (0, selector_1.stringifySelector)(stateNode),
            });
        }
        if (stateDef === null) {
            // boolean
            (0, selector_1.convertToClass)(stateNode).value = createBooleanStateClassName(name, namespace);
        }
        else {
            // static template selector
            // simply concat global mapped selector - ToDo: maybe change to 'selector'
            (0, selector_1.convertToInvalid)(stateNode).value = stateDef;
        }
        delete stateNode.nodes;
    }
    else if (typeof stateDef === 'object') {
        if (isTemplateState(stateDef)) {
            convertTemplateState(meta, resolver, diagnostics, selectorNode, stateNode, stateDef, name);
        }
        else {
            resolveStateValue(meta, resolver, diagnostics, selectorNode, stateNode, stateDef, name, namespace);
        }
    }
}
exports.transformPseudoClassToCustomState = transformPseudoClassToCustomState;
function isTemplateState(state) {
    return !!state && typeof state === 'object' && state.type === 'template';
}
exports.isTemplateState = isTemplateState;
function createBooleanStateClassName(stateName, namespace) {
    const escapedNamespace = (0, cssesc_1.default)(namespace, { isIdentifier: true });
    return `${escapedNamespace}${exports.booleanStateDelimiter}${stateName}`;
}
exports.createBooleanStateClassName = createBooleanStateClassName;
function createStateWithParamClassName(stateName, namespace, param) {
    const escapedNamespace = (0, cssesc_1.default)(namespace, { isIdentifier: true });
    return `${escapedNamespace}${exports.stateWithParamDelimiter}${stateName}${resolveStateParam(param, true)}`;
}
exports.createStateWithParamClassName = createStateWithParamClassName;
function resolveStateParam(param, escape = false) {
    const result = `${exports.stateMiddleDelimiter}${param.length}${exports.stateMiddleDelimiter}${param.replace(/\s/gm, '_')}`;
    // adding/removing initial `s` to indicate that it's not the first param of the identifier
    return escape ? (0, cssesc_1.default)(`s` + result, { isIdentifier: true }).slice(1) : result;
}
exports.resolveStateParam = resolveStateParam;
function convertTemplateState(meta, resolver, diagnostics, selectorNode, stateNode, stateParamDef, name) {
    const paramStateDef = stateParamDef.params[0];
    const resolvedParam = getParamInput(meta, resolver, diagnostics, selectorNode, stateNode, paramStateDef, name);
    validateParam(meta, resolver, diagnostics, selectorNode, paramStateDef, resolvedParam, name);
    const strippedParam = (0, string_1.stripQuotation)(resolvedParam);
    transformMappedStateWithParam({
        stateName: name,
        template: stateParamDef.template,
        param: strippedParam,
        node: stateNode,
        selectorNode: selectorNode,
        diagnostics,
    });
}
function getParamInput(meta, resolver, diagnostics, selectorNode, stateNode, stateParamDef, name) {
    const inputValue = stateNode.nodes && stateNode.nodes.length ? (0, selector_1.stringifySelector)(stateNode.nodes) : ``;
    const resolvedParam = resolveParam(meta, resolver, diagnostics, selectorNode, inputValue ? inputValue : stateParamDef.defaultValue);
    if (selectorNode && !inputValue && !stateParamDef.defaultValue) {
        diagnostics.report(exports.stateDiagnostics.NO_STATE_ARGUMENT_GIVEN(name, stateParamDef.type), {
            node: selectorNode,
            word: name,
        });
    }
    return resolvedParam;
}
function validateParam(meta, resolver, diagnostics, selectorNode, stateParamDef, resolvedParam, name) {
    const validator = exports.systemValidators[stateParamDef.type];
    let stateParamOutput;
    try {
        stateParamOutput = validator.validate(resolvedParam, stateParamDef.arguments, resolveParam.bind(null, meta, resolver, diagnostics, selectorNode), false, true);
    }
    catch (e) {
        // TODO: warn about validation throwing exception
    }
    if (stateParamOutput !== undefined) {
        if (stateParamOutput.res !== resolvedParam) {
            resolvedParam = stateParamOutput.res;
        }
        if (selectorNode && stateParamOutput.errors) {
            diagnostics.report(exports.stateDiagnostics.FAILED_STATE_VALIDATION(name, resolvedParam, stateParamOutput.errors), {
                node: selectorNode,
                word: resolvedParam,
            });
        }
    }
}
function resolveStateValue(meta, resolver, diagnostics, selectorNode, stateNode, stateParamDef, name, namespace) {
    const resolvedParam = getParamInput(meta, resolver, diagnostics, selectorNode, stateNode, stateParamDef, name);
    validateParam(meta, resolver, diagnostics, selectorNode, stateParamDef, resolvedParam, name);
    const strippedParam = (0, string_1.stripQuotation)(resolvedParam);
    (0, selector_1.convertToClass)(stateNode).value = createStateWithParamClassName(name, namespace, strippedParam);
    delete stateNode.nodes;
}
function transformMappedStateWithParam({ stateName, template, param, node, selectorNode, diagnostics, }) {
    const selectorStr = template.replace(/\$0/g, param);
    const selectorAst = (0, selector_1.parseSelectorWithCache)(selectorStr, { clone: true });
    if (!validateTemplateSelector({
        stateName,
        selectorStr,
        selectorAst,
        cssNode: selectorNode,
        diagnostics,
    })) {
        return;
    }
    (0, selector_1.convertToSelector)(node).nodes = selectorAst[0].nodes;
}
function validateTemplateSelector({ stateName, selectorStr, selectorAst, cssNode, diagnostics, }) {
    if (selectorAst.length > 1) {
        if (cssNode) {
            diagnostics.report(exports.stateDiagnostics.UNSUPPORTED_MULTI_SELECTOR(stateName, selectorStr), {
                node: cssNode,
            });
        }
        return false;
    }
    else {
        const firstSelector = selectorAst[0].nodes.find(({ type }) => type !== 'comment');
        if (firstSelector?.type === 'type' || firstSelector?.type === 'universal') {
            if (cssNode) {
                diagnostics.report(exports.stateDiagnostics.UNSUPPORTED_INITIAL_SELECTOR(stateName, selectorStr), {
                    node: cssNode,
                });
            }
            return false;
        }
        let unexpectedSelector = undefined;
        for (const node of selectorAst[0].nodes) {
            if (node.type === 'combinator' || node.type === 'invalid') {
                unexpectedSelector = node;
                break;
            }
        }
        if (unexpectedSelector) {
            if (cssNode) {
                switch (unexpectedSelector.type) {
                    case 'combinator':
                        diagnostics.report(exports.stateDiagnostics.UNSUPPORTED_COMPLEX_SELECTOR(stateName, selectorStr), {
                            node: cssNode,
                        });
                        break;
                    case 'invalid':
                        diagnostics.report(exports.stateDiagnostics.INVALID_SELECTOR(stateName, selectorStr), {
                            node: cssNode,
                        });
                        break;
                }
            }
            return false;
        }
    }
    return true;
}
function resolveParam(meta, resolver, diagnostics, node, nodeContent) {
    const defaultStringValue = '';
    const param = nodeContent || defaultStringValue;
    return (0, functions_1.evalDeclarationValue)(resolver, param, meta, node, undefined, undefined, diagnostics);
}
//# sourceMappingURL=custom-state.js.map