"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TableRowService = void 0;
const test_1 = require("@playwright/test");
const timeouts_1 = require("../../../timeouts");
const logger_1 = require("../../../utils/logger");
const TableRowUtils_1 = require("./TableRowUtils");
const table_scope_1 = require("./table-scope");
/**
 * Унифицированный сервис строк таблицы (scope: page | block).
 */
class TableRowService {
    constructor(page, textUtils) {
        this.page = page;
        this.textUtils = textUtils;
    }
    /**
     * Ожидает появления хотя бы одной строки данных в таблице (не считая заголовка)
     */
    async waitForTableToHaveRows(scope, customTimeout) {
        const { rowsRoot, tableLabel } = (0, table_scope_1.resolveTableRoots)(this.page, scope);
        const allRows = rowsRoot.getByRole('row');
        await test_1.expect
            .poll(async () => {
            const currentRowCount = await allRows.count();
            return currentRowCount >= 2;
        }, {
            message: `В таблице "${tableLabel}" нет строк для поиска`,
            timeout: customTimeout?.timeout ?? (0, timeouts_1.getTimeouts)().TABLE_UPDATE,
            intervals: customTimeout?.intervals ?? [1000, 1000, 2000, 3000, 3000, 5000]
        })
            .toBeTruthy();
    }
    /**
     * Получает индекс колонки по её названию с опциональной проверкой существования
     */
    async getRequiredColumnIndex(scope, columnName, logContext, throwIfNotFound = true) {
        const { structureRoot, tableLabel } = (0, table_scope_1.resolveTableRoots)(this.page, scope);
        const columnIndexes = await (0, TableRowUtils_1.getCachedTableStructure)(structureRoot, this.textUtils);
        const columnIndex = (0, TableRowUtils_1.getColumnIndex)(columnIndexes, columnName, this.textUtils);
        if (throwIfNotFound && columnIndex === undefined) {
            logger_1.logger.error(`Колонка "${columnName}" не найдена в таблице "${tableLabel}"`, logContext);
            throw new Error(`Колонка "${columnName}" не найдена в таблице "${tableLabel}"`);
        }
        return columnIndex;
    }
    /**
     * Проверяет значение одной ячейки
     */
    async verifyCellValue(cell, expectedValue, columnName, logContext) {
        const normalizedExpected = this.textUtils.normalizeTextForComparison(expectedValue);
        await cell.waitFor({ state: 'visible', timeout: (0, timeouts_1.getTimeouts)().ACTION });
        await test_1.test.step(`Проверка значения ячейки "${columnName}". Ожидаемое значение: "${expectedValue}"`, async () => {
            await test_1.expect
                .poll(async () => {
                const actualText = await cell.textContent();
                const normalizedActual = this.textUtils.normalizeTextForComparison(actualText || '');
                if (normalizedActual !== normalizedExpected) {
                    logger_1.logger.debug(`Несоответствие в ячейке "${columnName}": ожидалось "${normalizedExpected}", получено "${normalizedActual}"`, logContext);
                }
                return normalizedActual === normalizedExpected;
            }, {
                message: `Текст ячейки "${columnName}" не соответствует ожидаемому значению "${expectedValue}"`,
                timeout: (0, timeouts_1.getTimeouts)().ROW_UPDATE,
                intervals: [1000, 1000]
            })
                .toBeTruthy();
        });
    }
    /**
     * Подготавливает данные для верификации ячеек строки
     */
    async prepareCellsForVerification(scope, row, rowData, logContext) {
        const { structureRoot, tableLabel } = (0, table_scope_1.resolveTableRoots)(this.page, scope);
        const columnIndexes = await (0, TableRowUtils_1.getCachedTableStructure)(structureRoot, this.textUtils);
        const allCells = await row.getByRole('cell').all();
        const cellsToVerify = [];
        for (const [columnName, expectedValue] of Object.entries(rowData)) {
            if (!expectedValue)
                continue;
            const columnIndex = await (0, TableRowUtils_1.resolveColumnIndexForVerify)(columnIndexes, columnName, this.textUtils, `${logContext}.verifyTableRow`);
            if (columnIndex === undefined) {
                throw new Error(`Колонка "${columnName}" не найдена в таблице "${tableLabel}"`);
            }
            if (columnIndex >= allCells.length) {
                throw new Error(`Индекс колонки ${columnIndex} выходит за границы (всего ячеек: ${allCells.length}) для колонки "${columnName}"`);
            }
            cellsToVerify.push({
                columnName,
                expectedValue,
                cell: allCells[columnIndex]
            });
        }
        return cellsToVerify;
    }
    /**
     * Проверяет наличие обязательных колонок по их названиям.
     */
    async verifyRequiredColumns(scope, columnNames, logContext) {
        if (!Array.isArray(columnNames) || columnNames.length === 0) {
            throw new Error(`columnNames должен быть непустым массивом. Получено: ${JSON.stringify(columnNames)}`);
        }
        logger_1.logger.debug(`Проверка наличия необходимых колонок: ${columnNames.join(', ')}`, `${logContext}.verifyRequiredColumns`);
        await test_1.test.step(`Проверка наличия колонок грида`, async () => {
            const { structureRoot } = (0, table_scope_1.resolveTableRoots)(this.page, scope);
            const useExact = scope.kind === 'block';
            for (const columnName of columnNames) {
                await test_1.expect
                    .soft(structureRoot.getByRole('columnheader', useExact ? { name: columnName, exact: true } : { name: columnName }))
                    .toBeVisible({ timeout: (0, timeouts_1.getTimeouts)().EXPECT });
            }
        });
        logger_1.logger.info(`Проверка колонок завершена. Проверено колонок: ${columnNames.length}`, `${logContext}.verifyRequiredColumns`);
    }
    /**
     * Поиск новой строки (первая строка после заголовка).
     */
    async findNewRow(scope, keyValue, keyColumnIndex, logContext) {
        logger_1.logger.debug('Поиск новой строки по ключу', `${logContext}.findNewRow`);
        let foundRow = null;
        await test_1.expect
            .poll(async () => {
            const { rowsRoot } = (0, table_scope_1.resolveTableRoots)(this.page, scope);
            const currentRows = rowsRoot.getByRole('row');
            const currentRowCount = await currentRows.count();
            if (currentRowCount < 2) {
                logger_1.logger.debug('Таблица еще не обновилась, ждем...', `${logContext}.findNewRow`);
                foundRow = null;
                return false;
            }
            const row = currentRows.nth(1); // Первая строка после хедера (новая строка обычно сверху)
            if (await (0, TableRowUtils_1.checkRowMatch)(row, keyValue, keyColumnIndex, this.textUtils, `${logContext}.findNewRow`)) {
                logger_1.logger.debug(`Новая строка с ключом "${keyValue}" найдена`, `${logContext}.findNewRow`);
                foundRow = row;
                return true;
            }
            foundRow = null;
            return false;
        }, {
            message: `Новая строка с ключом "${keyValue}" не найдена`,
            timeout: (0, timeouts_1.getTimeouts)().TABLE_UPDATE,
            intervals: [1000, 1000, 2000, 3000, 3000, 5000]
        })
            .toBeTruthy();
        return foundRow;
    }
    /**
     * Поиск по всей таблице. Возвращает строку по ключу.
     */
    async findExistingRow(scope, keyValue, keyColumnIndex, logContext) {
        logger_1.logger.debug('Поиск существующей строки по ключу', `${logContext}.findExistingRow`);
        let foundRow = null;
        await test_1.expect
            .poll(async () => {
            const { rowsRoot } = (0, table_scope_1.resolveTableRoots)(this.page, scope);
            const currentRows = rowsRoot.getByRole('row');
            const currentRowCount = await currentRows.count();
            if (currentRowCount < 2) {
                logger_1.logger.debug('Таблица еще не обновилась, ждем...', `${logContext}.findExistingRow`);
                return false;
            }
            for (let i = 1; i < currentRowCount; i++) {
                const row = currentRows.nth(i);
                if (await (0, TableRowUtils_1.checkRowMatch)(row, keyValue, keyColumnIndex, this.textUtils, `${logContext}.findExistingRow`)) {
                    foundRow = row;
                    logger_1.logger.debug(`Строка с ключом "${keyValue}" найдена`, `${logContext}.findExistingRow`);
                    return true;
                }
            }
            return false;
        }, {
            message: `Строка с ключом "${keyValue}" не найдена в таблице`,
            timeout: (0, timeouts_1.getTimeouts)().TABLE_UPDATE,
            intervals: [1000, 1000, 2000, 3000, 3000, 5000]
        })
            .toBeTruthy();
        return foundRow;
    }
    /**
     * Поиск строки по rowKey.
     */
    async findTableRow(scope, rowKey, keyValue, isNewlyAdded, logContext, customTimeout) {
        const normalizedKeyValue = this.textUtils.normalizeTextForComparison(keyValue ?? '');
        logger_1.logger.info(`Поиск строки в таблице (rowKey: ${rowKey}, keyValue: ${normalizedKeyValue}, isNewlyAdded: ${isNewlyAdded})`, `${logContext}.findTableRow`);
        if (!normalizedKeyValue) {
            logger_1.logger.error(`keyValue имеет пустое значение для rowKey "${rowKey}"`, `${logContext}.findTableRow`);
            throw new Error(`keyValue имеет пустое значение для rowKey "${rowKey}"`);
        }
        // Ждем появления строк в таблице
        await this.waitForTableToHaveRows(scope, customTimeout);
        // Получаем индекс колонки
        const keyColumnIndex = await this.getRequiredColumnIndex(scope, rowKey, `${logContext}.findTableRow`, true);
        if (keyColumnIndex === undefined) {
            throw new Error(`Колонка "${rowKey}" не найдена в таблице для поиска строки`);
        }
        if (isNewlyAdded) {
            const newRow = await this.findNewRow(scope, normalizedKeyValue, keyColumnIndex, logContext);
            if (!newRow) {
                throw new Error(`Новая строка с ключом "${normalizedKeyValue}" не найдена`);
            }
            return newRow;
        }
        const existingRow = await this.findExistingRow(scope, normalizedKeyValue, keyColumnIndex, logContext);
        if (!existingRow) {
            throw new Error(`Строка с ключом "${normalizedKeyValue}" не найдена`);
        }
        return existingRow;
    }
    /**
     * Тихий поиск строки в таблице без выброса ошибок.
     */
    async findTableRowSilent(scope, rowKey, keyValue, logContext) {
        const normalizedKeyValue = this.textUtils.normalizeTextForComparison(keyValue);
        logger_1.logger.debug(`Тихий поиск строки в таблице (rowKey: ${rowKey}, keyValue: ${normalizedKeyValue})`, `${logContext}.findTableRowSilent`);
        if (!normalizedKeyValue) {
            logger_1.logger.debug(`keyValue имеет пустое значение для rowKey "${rowKey}", возвращаем null`, `${logContext}.findTableRowSilent`);
            return null;
        }
        const { rowsRoot } = (0, table_scope_1.resolveTableRoots)(this.page, scope);
        const rows = rowsRoot.getByRole('row');
        const rowCount = await rows.count();
        if (rowCount < 2) {
            logger_1.logger.debug('В таблице нет строк для поиска, возвращаем null', `${logContext}.findTableRowSilent`);
            return null;
        }
        const keyColumnIndex = await this.getRequiredColumnIndex(scope, rowKey, `${logContext}.findTableRowSilent`, false);
        if (keyColumnIndex === undefined) {
            logger_1.logger.debug(`Колонка "${rowKey}" не найдена в таблице, возвращаем null`, `${logContext}.findTableRowSilent`);
            return null;
        }
        logger_1.logger.debug(`Поиск строки с ключом "${normalizedKeyValue}" по всей таблице (${rowCount - 1} строк)`, `${logContext}.findTableRowSilent`);
        for (let i = 1; i < rowCount; i++) {
            const row = rows.nth(i);
            if (await (0, TableRowUtils_1.checkRowMatch)(row, normalizedKeyValue, keyColumnIndex, this.textUtils, `${logContext}.findTableRowSilent`)) {
                logger_1.logger.debug(`Строка с ключом "${normalizedKeyValue}" найдена в строке ${i}`, `${logContext}.findTableRowSilent`);
                return row;
            }
        }
        logger_1.logger.debug(`Строка с ключом "${normalizedKeyValue}" не найдена в таблице`, `${logContext}.findTableRowSilent`);
        return null;
    }
    /**
     * Проверяет соответствие строки данным без `test.step` и без `verifyCellValue` / `expect.poll`.
     * Используется в промежуточных итерациях `waitForTableRowWithRefresh`, чтобы не засорять Allure failed-шагами.
     * @param scope — область таблицы (page | block)
     * @param rowKey — ключевое поле строки
     * @param rowData — ожидаемые значения колонок
     * @param isNewlyAdded — искать среди новых строк
     * @param logContext — контекст для логирования
     * @returns `true`, если строка найдена через `findTableRow` и все ячейки совпадают
     */
    async matchesTableRow(scope, rowKey, rowData, isNewlyAdded, logContext) {
        try {
            const row = await this.findTableRow(scope, rowKey, rowData[rowKey], isNewlyAdded, logContext);
            const cellsToVerify = await this.prepareCellsForVerification(scope, row, rowData, logContext);
            for (const { expectedValue, cell } of cellsToVerify) {
                const actualText = await cell.textContent();
                const normalizedActual = this.textUtils.normalizeTextForComparison(actualText || '');
                const normalizedExpected = this.textUtils.normalizeTextForComparison(expectedValue);
                if (normalizedActual !== normalizedExpected) {
                    return false;
                }
            }
            return true;
        }
        catch {
            return false;
        }
    }
    /**
     * Проверка строки в таблице.
     */
    async verifyTableRow(scope, rowKey, rowData, isNewlyAdded, logContext, onRowFound) {
        if (!rowKey || typeof rowKey !== 'string' || rowKey.trim() === '') {
            throw new Error(`rowKey должен быть непустой строкой. Получено: ${rowKey}`);
        }
        if (!rowData || typeof rowData !== 'object' || Object.keys(rowData).length === 0) {
            throw new Error(`rowData должен быть непустым объектом. Получено: ${JSON.stringify(rowData)}`);
        }
        await test_1.test.step(`Проверка строки по ключу "${rowKey}". Значение ключа: "${rowData[rowKey]}"`, async () => {
            const row = await this.findTableRow(scope, rowKey, rowData[rowKey], isNewlyAdded, logContext);
            if (!row)
                return;
            if (onRowFound) {
                onRowFound(row);
            }
            // Подготавливаем данные для проверки
            const cellsToVerify = await this.prepareCellsForVerification(scope, row, rowData, logContext);
            // Проверяем каждую ячейку
            for (const { columnName, expectedValue, cell } of cellsToVerify) {
                await this.verifyCellValue(cell, expectedValue, columnName, logContext);
            }
        });
    }
    /**
     * Клик по ссылке в строке таблицы
     */
    async clickInTableRow(scope, key, itemToClick, isNewlyAdded, logContext) {
        const { rowKey, keyValue } = key;
        const context = logContext ?? this.constructor.name;
        const textByClick = itemToClick;
        logger_1.logger.debug(`Клик по ссылке "${textByClick}" в строке таблицы (rowKey: ${rowKey}, keyValue: ${keyValue})`, `${context}.clickInTableRow`);
        const row = await this.findTableRow(scope, rowKey, keyValue, isNewlyAdded, context);
        if (!row) {
            throw new Error(`Строка не найдена в таблице для клика по ссылке "${textByClick}" (rowKey: ${rowKey}, keyValue: ${keyValue})`);
        }
        const link = row.getByRole('link', { name: textByClick });
        const linkCount = await link.count();
        if (linkCount === 0) {
            throw new Error(`Ссылка с текстом "${textByClick}" не найдена в строке таблицы (rowKey: ${rowKey}, keyValue: ${keyValue})`);
        }
        await link.click();
        logger_1.logger.debug(`Клик по ссылке "${textByClick}" успешно выполнен`, `${context}.clickInTableRow`);
    }
    /**
     * Ожидает появления строки с заданными данными, периодически обновляя таблицу.
     * В цикле poll использует тихую проверку `matchesTableRow` (без шагов Allure);
     * после успеха один раз вызывает `verifyTableRow` для финальной записи в отчёт.
     * @param options.onEachRefresh — колбэк обновления грида перед каждой итерацией poll
     */
    async waitForTableRowWithRefresh(scope, rowKey, rowData, isNewlyAdded, logContext, options) {
        const keyValue = rowData[rowKey];
        logger_1.logger.info(`Начинаем ожидание строки: ${rowKey}=${keyValue}, статус: ${rowData['Статус']}`, `${logContext}.waitForTableRowWithRefresh`);
        await test_1.expect
            .poll(async () => {
            if (options.onEachRefresh) {
                logger_1.logger.info(`Обновление грида`, logContext);
                await options.onEachRefresh();
                await this.page.waitForLoadState('load');
            }
            const matches = await this.matchesTableRow(scope, rowKey, rowData, isNewlyAdded, logContext);
            if (matches) {
                logger_1.logger.info(`Строка с нужным статусом найдена!`, logContext);
            }
            return matches;
        }, {
            message: `Строка ${keyValue} со статусом "${rowData['Статус']}" не найдена за ${options.totalTimeout}мс`,
            timeout: options.totalTimeout,
            intervals: options.refreshIntervals
        })
            .toBeTruthy();
        await this.verifyTableRow(scope, rowKey, rowData, isNewlyAdded, logContext);
    }
    /**
     * Итерирует по всем строкам таблицы.
     */
    async forEachRow(scope, callback, logContext) {
        const { rowsRoot, tableLabel } = (0, table_scope_1.resolveTableRoots)(this.page, scope);
        logger_1.logger.debug(`Итерация по строкам таблицы "${tableLabel}"`, `${logContext}.forEachRow`);
        await this.waitForTableToHaveRows(scope);
        const allRows = rowsRoot.getByRole('row');
        const rowCount = await allRows.count();
        logger_1.logger.debug(`Найдено строк в таблице: ${rowCount - 1}`, `${logContext}.forEachRow`);
        for (let i = 1; i < rowCount; i++) {
            const row = allRows.nth(i);
            await callback(row, i);
        }
        logger_1.logger.debug(`Итерация по строкам таблицы "${tableLabel}" завершена`, `${logContext}.forEachRow`);
    }
}
exports.TableRowService = TableRowService;
