"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateStepSemantics = validateStepSemantics;
exports.validateSemantics = validateSemantics;
const errors_1 = require("../errors");
function isElementFindTarget(target) {
    if (typeof target !== 'object' || !target) {
        return false;
    }
    const t = target;
    return 'findType' in t && 'findTarget' in t;
}
function hasKind(target, kind) {
    if (typeof target !== 'object' || !target) {
        return false;
    }
    return target.kind === kind;
}
function getConditionType(condition) {
    if (typeof condition !== 'object' || !condition) {
        return undefined;
    }
    return condition.conditionType;
}
function getGeneratorType(generator) {
    if (typeof generator !== 'object' || !generator) {
        return undefined;
    }
    return generator.type;
}
function validateAssertSemantics(descriptor, path, options) {
    const results = [];
    const { target, condition, extract, count } = descriptor;
    const condType = getConditionType(condition);
    if (!target && condition === undefined) {
        results.push((0, errors_1.errorResult)(path, 'Assertion steps must have at least a target or a condition. Both are missing.', 'semantic.assert_missing_target_and_condition'));
    }
    if (hasKind(target, 'Viewport') && condType && condType !== 'ai_prompt') {
        results.push((0, errors_1.errorResult)([...path, 'target'], 'Viewport assertions only support AI prompt conditions. Switch to an AI assertion or use a different target.', 'semantic.viewport_non_ai_condition'));
    }
    if (condType === 'presence' && extract !== undefined) {
        results.push((0, errors_1.errorResult)([...path, 'extract'], 'Presence conditions do not support extraction. Remove the extract action or switch to a comparison condition.', 'semantic.presence_with_extract'));
    }
    if (count !== undefined && extract !== undefined) {
        results.push((0, errors_1.errorResult)([...path, 'extract'], 'Count assertions count elements, not attribute values. Remove the extract action when using count.', 'semantic.count_with_extract'));
    }
    if (hasKind(target, 'GetVariable') && extract !== undefined) {
        results.push((0, errors_1.errorResult)([...path, 'extract'], 'Variables are scalar values and do not support extraction. Remove the extract action.', 'semantic.variable_with_extract'));
    }
    if (options.strict && hasKind(target, 'Viewport') && extract !== undefined) {
        results.push((0, errors_1.errorResult)([...path, 'extract'], 'Viewport assertions do not support extraction. Remove the extract action.', 'semantic.viewport_with_extract'));
    }
    if (count !== undefined && !isElementFindTarget(target)) {
        results.push((0, errors_1.errorResult)([...path, 'count'], 'Count assertions only work with element find targets. Use a find descriptor that returns elements.', 'semantic.count_non_element_target'));
    }
    if (condType === 'presence' &&
        target !== undefined &&
        !isElementFindTarget(target)) {
        results.push((0, errors_1.warningResult)([...path, 'condition'], 'Presence conditions are designed for DOM elements. Consider using a comparison condition for variables, URLs, or script results.', 'semantic.presence_non_element_target'));
    }
    if (condType === 'ai_prompt' &&
        hasKind(target, 'Viewport') &&
        !descriptor.observationScope) {
        results.push((0, errors_1.warningResult)([...path, 'observationScope'], 'AI prompt assertions on the viewport should set observationScope to indicate which area is being evaluated.', 'semantic.ai_prompt_missing_observation_scope'));
    }
    return results.length > 0 ? (0, errors_1.mergeResults)(...results) : (0, errors_1.emptyResult)();
}
function validateHttpRequestSemantics(descriptor, path, options) {
    const results = [];
    const request = descriptor.request;
    if (!request) {
        return (0, errors_1.emptyResult)();
    }
    const method = (request.method || '').toUpperCase();
    const body = request.body;
    if (['GET', 'HEAD', 'DELETE'].includes(method) && body !== undefined) {
        results.push((0, errors_1.warningResult)([...path, 'request', 'body'], `${method} requests should not have a request body. The body may be ignored by the server.`, 'semantic.http_body_on_bodyless_method'));
    }
    const auth = request.authorization;
    if (auth?.type === 'basic') {
        const props = auth.properties;
        if (!props?.username && !props?.password) {
            const result = options.strict ? errors_1.errorResult : errors_1.warningResult;
            results.push(result([...path, 'request', 'authorization', 'properties'], 'Basic authentication requires username and password properties.', 'semantic.http_basic_auth_missing_credentials'));
        }
    }
    return results.length > 0 ? (0, errors_1.mergeResults)(...results) : (0, errors_1.emptyResult)();
}
function validateCreateVariableSemantics(descriptor, path) {
    const results = [];
    const generator = descriptor.generator;
    if (!generator) {
        return (0, errors_1.emptyResult)();
    }
    const genType = getGeneratorType(generator);
    if (genType === 'ATTRIBUTE' && !descriptor.find) {
        results.push((0, errors_1.errorResult)([...path, 'find'], 'ATTRIBUTE generator requires a find property to locate the element whose attribute will be extracted.', 'semantic.attribute_generator_missing_find'));
    }
    if (genType === 'ELEMENT_COUNT' && !descriptor.find) {
        results.push((0, errors_1.errorResult)([...path, 'find'], 'ELEMENT_COUNT generator requires a find property to locate the elements to count.', 'semantic.element_count_generator_missing_find'));
    }
    return results.length > 0 ? (0, errors_1.mergeResults)(...results) : (0, errors_1.emptyResult)();
}
const DOWNLOAD_SIZE_FIELDS = ['sizeBytes', 'sizeBytesMin', 'sizeBytesMax'];
function validateDownloadSemantics(descriptor, path, options) {
    if (!options.strict) {
        return (0, errors_1.emptyResult)();
    }
    const results = [];
    for (const field of DOWNLOAD_SIZE_FIELDS) {
        if (typeof descriptor[field] === 'string') {
            results.push((0, errors_1.errorResult)([...path, field], `${field} must be a number, not a string.`, 'semantic.download_size_string'));
        }
    }
    return results.length > 0 ? (0, errors_1.mergeResults)(...results) : (0, errors_1.emptyResult)();
}
const ASSERT_STEP_TYPES = new Set([
    'AssertEquals',
    'AssertPresent',
    'AssertNotPresent',
    'AssertDoesNotEqual',
    'AssertContains',
    'AssertDoesNotContain',
    'AssertStartsWith',
    'AssertStartsWithout',
    'AssertEndsWith',
    'AssertEndsWithout',
    'AssertGreaterThan',
    'AssertLessThan',
    'AssertGreaterThanOrEquals',
    'AssertLessThanOrEquals',
    'AssertMatchesRegex',
    'AssertAIPrompt',
    'If',
    'ElseIf',
    'WaitUntil',
]);
function validateStepSemantics(step, stepIndex, options) {
    if (typeof step !== 'object' || !step || Array.isArray(step)) {
        return (0, errors_1.emptyResult)();
    }
    const keys = Object.keys(step);
    if (keys.length !== 1) {
        return (0, errors_1.emptyResult)();
    }
    const stepTypeName = keys[0];
    const descriptor = step[stepTypeName];
    if (typeof descriptor !== 'object' || !descriptor) {
        return (0, errors_1.emptyResult)();
    }
    const path = ['steps', stepIndex, stepTypeName];
    const desc = descriptor;
    if (ASSERT_STEP_TYPES.has(stepTypeName)) {
        return validateAssertSemantics(desc, path, options);
    }
    if (stepTypeName === 'SendHttpRequest') {
        return validateHttpRequestSemantics(desc, path, options);
    }
    if (stepTypeName === 'CreateVariable' ||
        stepTypeName === 'GenerateEmailAddress') {
        return validateCreateVariableSemantics(desc, path);
    }
    if (stepTypeName === 'DownloadStep' || stepTypeName === 'DownloadPdfStep') {
        return validateDownloadSemantics(desc, path, options);
    }
    return (0, errors_1.emptyResult)();
}
function validateSemantics(steps, options) {
    const results = [];
    for (let i = 0; i < steps.length; i++) {
        const result = validateStepSemantics(steps[i], i, options);
        if (!result.valid || result.warnings.length > 0) {
            results.push(result);
        }
        const step = steps[i];
        if (typeof step === 'object' && step && !Array.isArray(step)) {
            const keys = Object.keys(step);
            if (keys[0] === 'StepGroup') {
                const descriptor = step.StepGroup;
                if (descriptor && Array.isArray(descriptor.steps)) {
                    const nestedResult = validateSemantics(descriptor.steps, options);
                    for (const err of nestedResult.errors) {
                        err.path = ['steps', i, 'StepGroup', ...err.path];
                    }
                    for (const warn of nestedResult.warnings) {
                        warn.path = ['steps', i, 'StepGroup', ...warn.path];
                    }
                    results.push(nestedResult);
                }
            }
        }
    }
    return results.length > 0 ? (0, errors_1.mergeResults)(...results) : (0, errors_1.emptyResult)();
}
