"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.compareJsonRowsFromFiles = compareJsonRowsFromFiles;
exports.compareJsonRows = compareJsonRows;
const promises_1 = __importDefault(require("node:fs/promises"));
const node_path_1 = __importDefault(require("node:path"));
async function compareJsonRowsFromFiles(options) {
    const cwd = options.cwd ?? process.cwd();
    const actual = await loadJsonRows(options.actualPath, cwd);
    const expected = await loadJsonRows(options.expectedPath, cwd);
    compareJsonRows(actual, expected, options.keyColumns, options.mode);
}
function compareJsonRows(actual, expected, keyColumns, mode) {
    if (keyColumns.length === 0) {
        throw new Error('At least one key column is required for unordered JSON row comparison.');
    }
    const actualByKey = indexRows(actual, keyColumns, 'actual');
    const expectedByKey = indexRows(expected, keyColumns, 'expected');
    const missingKeys = [];
    const unexpectedKeys = [];
    const differences = [];
    for (const [key, expectedRow] of expectedByKey) {
        const actualRow = actualByKey.get(key);
        if (!actualRow) {
            missingKeys.push(key);
            continue;
        }
        for (const [column, expectedValue] of Object.entries(expectedRow)) {
            const actualValue = actualRow[column];
            if (!valuesEqual(actualValue, expectedValue)) {
                differences.push(`${key}: column "${column}" expected ${formatValue(expectedValue)} but found ${formatValue(actualValue)}`);
            }
        }
    }
    if (mode === 'exact') {
        for (const key of actualByKey.keys()) {
            if (!expectedByKey.has(key)) {
                unexpectedKeys.push(key);
            }
        }
    }
    if (missingKeys.length || unexpectedKeys.length || differences.length) {
        throw new Error(buildComparisonError(missingKeys, unexpectedKeys, differences));
    }
}
async function loadJsonRows(filePath, cwd) {
    const resolvedPath = node_path_1.default.resolve(cwd, filePath);
    const raw = await promises_1.default.readFile(resolvedPath, 'utf8');
    const parsed = JSON.parse(raw);
    if (!Array.isArray(parsed) || !parsed.every(isJsonRow)) {
        throw new Error(`${filePath} must contain a JSON array of row objects.`);
    }
    return parsed;
}
function indexRows(rows, keyColumns, label) {
    const indexed = new Map();
    for (const [index, row] of rows.entries()) {
        const key = buildRowKey(row, keyColumns);
        if (indexed.has(key)) {
            throw new Error(`${label} JSON contains duplicate row key ${key} at row ${index}.`);
        }
        indexed.set(key, row);
    }
    return indexed;
}
function buildRowKey(row, keyColumns) {
    return keyColumns.map((column) => {
        if (!(column in row)) {
            throw new Error(`JSON row is missing key column "${column}".`);
        }
        return `${column}=${normalizeValue(row[column])}`;
    }).join('|');
}
function buildComparisonError(missingKeys, unexpectedKeys, differences) {
    const parts = ['JSON rows did not match.'];
    if (missingKeys.length) {
        parts.push(`Missing rows: ${missingKeys.join(', ')}`);
    }
    if (unexpectedKeys.length) {
        parts.push(`Unexpected rows: ${unexpectedKeys.join(', ')}`);
    }
    if (differences.length) {
        parts.push(`Different values: ${differences.join('; ')}`);
    }
    return parts.join(' ');
}
function valuesEqual(left, right) {
    return normalizeValue(left) === normalizeValue(right);
}
function normalizeValue(value) {
    if (value === null || value === undefined) {
        return '';
    }
    if (typeof value === 'string') {
        return value.replace(/\s+/g, ' ').trim();
    }
    return String(value);
}
function formatValue(value) {
    return JSON.stringify(normalizeValue(value));
}
function isJsonRow(value) {
    return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
}
