"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.chooseFieldOption = chooseFieldOption;
exports.readDropdownOptions = readDropdownOptions;
exports.assertDropdownOptions = assertDropdownOptions;
exports.scrollToElement = scrollToElement;
exports.openTab = openTab;
exports.isTabActive = isTabActive;
exports.readFieldValue = readFieldValue;
exports.isFieldReadOnly = isFieldReadOnly;
exports.readLocatorValue = readLocatorValue;
exports.assertLocatorValue = assertLocatorValue;
exports.resolveFieldVerificationRule = resolveFieldVerificationRule;
const locator_1 = require("./locator");
async function chooseFieldOption(scope, fieldName, optionText, index = 0, selectors) {
    const trigger = (0, locator_1.atIndex)(buildFieldLocator(scope, fieldName, selectors), index);
    await trigger.click();
    const page = getPage(scope);
    const option = page
        .getByRole('option', { name: optionText })
        .or(page.getByRole('listbox', { name: optionText }))
        .or(page.getByText(optionText, { exact: true }))
        .or(page.getByLabel(optionText))
        .first();
    await option.click();
}
async function readDropdownOptions(scope, fieldName, selectors, index = 0) {
    const trigger = (0, locator_1.atIndex)(buildFieldLocator(scope, fieldName, selectors), index);
    const exists = await trigger.count().catch(() => 0);
    if (!exists) {
        throw new Error(`Dropdown "${fieldName}" was not found.`);
    }
    const nativeOptions = await readNativeSelectOptions(trigger);
    if (nativeOptions.length > 0) {
        return nativeOptions;
    }
    await trigger.click();
    const page = getPage(scope);
    const optionLocator = page
        .getByRole('option')
        .or(page.locator('[role="option"]'))
        .or(page.locator('option'));
    const options = await readVisibleTexts(optionLocator);
    if (options.length === 0) {
        throw new Error(`Dropdown "${fieldName}" did not expose any readable options.`);
    }
    return options;
}
async function assertDropdownOptions(scope, fieldName, expected, mode, selectors, index = 0) {
    const actual = await readDropdownOptions(scope, fieldName, selectors, index);
    const normalizedExpected = normalizeOptionValues(expected);
    const normalizedActual = normalizeOptionValues(actual);
    const matches = mode === 'exact'
        ? arraysEqual(normalizedActual, normalizedExpected)
        : arraysEqual([...normalizedActual].sort(), [...normalizedExpected].sort());
    if (matches) {
        return;
    }
    throw new Error(`Dropdown "${fieldName}" values mismatch. Expected [${normalizedExpected.join(', ')}] but found [${normalizedActual.join(', ')}]. Comparison mode: ${mode}.`);
}
async function scrollToElement(page, target, selectors, index = 0) {
    const locator = (0, locator_1.atIndex)((0, locator_1.locatorByAny)(page, target, selectors), index);
    await locator.scrollIntoViewIfNeeded();
}
async function openTab(page, name, selectors, index = 0, scopeTarget) {
    const scope = resolveScope(page, scopeTarget, selectors);
    const locator = (0, locator_1.atIndex)(buildTabLocator(scope, name, selectors), index);
    if (await isLocatorTabActive(locator)) {
        return;
    }
    await locator.click();
    await locator.waitFor();
    await waitForLocatorTabActive(locator);
}
async function isTabActive(page, name, selectors, index = 0) {
    const locator = (0, locator_1.atIndex)(buildTabLocator(page, name, selectors), index);
    return isLocatorTabActive(locator);
}
async function readFieldValue(scope, fieldName, selectors, index = 0) {
    const locator = (0, locator_1.atIndex)(buildFieldLocator(scope, fieldName, selectors), index);
    return readLocatorValue(locator);
}
async function isFieldReadOnly(scope, fieldName, selectors, index = 0) {
    const locator = (0, locator_1.atIndex)(buildFieldLocator(scope, fieldName, selectors), index);
    return locator.evaluate((element) => {
        const candidate = element;
        if ('readOnly' in candidate && typeof candidate.readOnly === 'boolean' && candidate.readOnly) {
            return true;
        }
        const readonlyAttr = element.getAttribute('readonly');
        if (readonlyAttr != null) {
            return true;
        }
        const ariaReadonly = element.getAttribute('aria-readonly');
        return ariaReadonly?.toLowerCase() === 'true';
    });
}
async function readLocatorValue(locator) {
    return locator.evaluate((element) => {
        const input = element;
        const directValue = typeof input.value === 'string' ? input.value.trim() : '';
        if (directValue) {
            return directValue;
        }
        const valueLike = [
            element.getAttribute('value'),
            element.getAttribute('aria-valuetext'),
            element.getAttribute('data-value'),
            element.getAttribute('title'),
        ].find((value) => typeof value === 'string' && value.trim().length > 0);
        if (valueLike) {
            return valueLike.trim();
        }
        const shadowText = input.shadowRoot?.textContent?.trim();
        if (shadowText) {
            return shadowText;
        }
        const innerText = 'innerText' in element && typeof element.innerText === 'string'
            ? element.innerText.trim()
            : '';
        if (innerText) {
            return innerText;
        }
        return element.textContent?.trim() ?? '';
    });
}
async function assertLocatorValue(locator, expected, contextLabel = 'Field', rule) {
    if (rule?.mode === 'none') {
        return;
    }
    const actual = await readLocatorValue(locator);
    if (matchesVerificationRule(actual, expected, rule)) {
        return;
    }
    if (rule?.mode === 'pattern') {
        if (!rule.expectedPattern) {
            throw new Error(`${contextLabel} pattern verification requires expectedPattern.`);
        }
        throw new Error(`${contextLabel} value mismatch. Value "${actual}" does not match pattern "${rule.expectedPattern}".`);
    }
    if (rule?.mode === 'formatted') {
        if (!rule.expectedPattern) {
            throw new Error(`${contextLabel} formatted verification requires expectedPattern.`);
        }
        throw new Error(`${contextLabel} value mismatch. Value "${actual}" does not match formatted pattern "${rule.expectedPattern}".`);
    }
    throw new Error(`${contextLabel} value mismatch. Expected "${expected}" but found "${actual}".`);
}
function resolveFieldVerificationRule(fieldName, fieldVerification) {
    if (!fieldVerification) {
        return undefined;
    }
    const candidates = [
        fieldName,
        fieldName.replace(/\*+$/g, '').trim(),
        fieldName.trim()
    ].filter(Boolean);
    for (const candidate of candidates) {
        if (fieldVerification[candidate]) {
            return fieldVerification[candidate];
        }
    }
    return undefined;
}
function buildFieldLocator(scope, fieldName, selectors) {
    const alias = (0, locator_1.resolveSelectorAlias)(fieldName, selectors);
    const base = (0, locator_1.locatorByLabelOrText)(getPage(scope), fieldName, undefined);
    const scopedBase = 'getByLabel' in scope
        ? scope.getByLabel(fieldName)
            .or(scope.getByText(fieldName, { exact: true }))
            .or(scope.getByRole('combobox', { name: fieldName }))
        : base;
    return alias ? getPage(scope).locator(alias).or(scopedBase).or(base) : scopedBase.or(base);
}
function getPage(scope) {
    return 'page' in scope && typeof scope.page === 'function'
        ? scope.page()
        : scope;
}
function buildTabLocator(scope, name, selectors) {
    const selector = (0, locator_1.resolveSelectorAlias)(name, selectors);
    const base = scope
        .getByRole('tab', { name })
        .or(scope.getByText(name, { exact: true }))
        .or(scope.getByLabel(name));
    return selector ? scope.locator(selector).or(base) : base;
}
function resolveScope(page, scopeTarget, selectors) {
    if (!scopeTarget) {
        return page;
    }
    const selector = (0, locator_1.resolveSelectorAlias)(scopeTarget, selectors) ?? scopeTarget;
    return page.locator(selector);
}
async function isLocatorTabActive(locator) {
    return locator.evaluate((element) => {
        const values = [
            element.getAttribute('aria-selected'),
            element.getAttribute('data-active'),
            element.getAttribute('data-selected'),
            element.getAttribute('selected')
        ]
            .filter((value) => typeof value === 'string' && value.length > 0)
            .map((value) => value.toLowerCase());
        if (values.includes('true') || values.includes('selected')) {
            return true;
        }
        const className = typeof element.className === 'string'
            ? element.className.toLowerCase()
            : '';
        return /\b(active|selected|current)\b/.test(className);
    });
}
async function waitForLocatorTabActive(locator) {
    const deadline = Date.now() + 1000;
    while (Date.now() <= deadline) {
        if (await isLocatorTabActive(locator).catch(() => false)) {
            return;
        }
        await delay(50);
    }
    throw new Error('Tab did not become active after click.');
}
function delay(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
async function readNativeSelectOptions(locator) {
    const readFromElements = (elements) => {
        for (const element of elements) {
            const directSelect = hasOptions(element)
                ? element
                : element.querySelector?.('select');
            if (hasOptions(directSelect)) {
                return Array.from(directSelect.options)
                    .map((option) => option.label?.trim() || option.textContent?.trim() || '')
                    .filter(Boolean);
            }
        }
        return [];
    };
    const evaluateAll = locator.evaluateAll;
    if (typeof evaluateAll === 'function') {
        return evaluateAll.call(locator, readFromElements).catch(() => []);
    }
    return locator.evaluate((element) => readFromElements([element])).catch(() => []);
}
async function readVisibleTexts(locator) {
    const values = [];
    const count = await locator.count().catch(() => 0);
    for (let index = 0; index < count; index += 1) {
        const option = locator.nth(index);
        const visible = await option.isVisible().catch(() => false);
        if (!visible) {
            continue;
        }
        const text = await option.evaluate((element) => {
            const innerText = 'innerText' in element && typeof element.innerText === 'string'
                ? element.innerText.trim()
                : '';
            if (innerText) {
                return innerText;
            }
            const label = element.getAttribute('aria-label')?.trim();
            if (label) {
                return label;
            }
            return element.textContent?.trim() ?? '';
        }).catch(() => '');
        if (text) {
            values.push(text);
        }
    }
    return values;
}
function matchesVerificationRule(actual, expected, rule) {
    switch (rule?.mode) {
        case undefined:
            return actual === expected;
        case 'none':
            return true;
        case 'contains':
            return actual.includes(expected);
        case 'normalized':
            return normalizeValue(actual) === normalizeValue(expected);
        case 'pattern':
        case 'formatted':
            return rule.expectedPattern ? new RegExp(rule.expectedPattern).test(actual) : false;
        default:
            return actual === expected;
    }
}
function normalizeValue(value) {
    const trimmed = value.trim();
    const numericCandidate = trimmed.replace(/[,\s]/g, '');
    if (numericCandidate.length > 0 && /^-?\d+(\.\d+)?$/.test(numericCandidate)) {
        return String(Number(numericCandidate));
    }
    return trimmed.replace(/[,\s]/g, '');
}
function normalizeOptionValues(values) {
    return values
        .map((value) => value.trim())
        .filter(Boolean);
}
function arraysEqual(left, right) {
    return left.length === right.length && left.every((value, index) => value === right[index]);
}
function hasOptions(value) {
    return typeof value === 'object' && value !== null && 'options' in value;
}
