/**
 * API for parsing SHAAM uniform format files
 */
import { z } from 'zod';
import { parseA000, parseA000Sum } from '../generator/records/index.js';
import { parseDataFile } from '../parser/data-parser.js';
import { AccountSchema, BusinessMetadataSchema, DocumentSchema, InventoryItemSchema, JournalEntrySchema, } from '../types/index.js';
/**
 * Parses SHAAM uniform format files (INI.TXT and BKMVDATA.TXT)
 * back into structured JSON objects with comprehensive validation.
 *
 * @param iniContent - Content of the INI.TXT file
 * @param dataContent - Content of the BKMVDATA.TXT file
 * @param options - Parse options for validation and error handling
 * @returns Parsed report input data with validation summary
 */
export function parseUniformFormatFiles(iniContent, dataContent, options = {}) {
    const { validationMode = 'lenient', skipUnknownRecords = true, allowPartialData = true, } = options;
    const errors = [];
    const recordCounts = {};
    // Parse files independently
    const parsedData = parseFilesSeparately(iniContent, dataContent, errors, recordCounts, {
        validationMode,
        skipUnknownRecords,
    });
    // Convert parsed records to structured data
    const structuredData = convertToStructuredData(parsedData, errors, validationMode);
    // Perform individual validation
    performRecordsValidation(structuredData, errors, validationMode);
    // Perform cross-validation
    const crossValidationPassed = performCrossFileValidation(parsedData, errors, validationMode);
    // Handle validation errors based on mode
    if (validationMode === 'strict' &&
        errors.some(e => e.severity === 'error') &&
        !allowPartialData) {
        throw new Error(`Validation failed: ${errors.map(e => e.message).join(', ')}`);
    }
    const totalRecords = Object.values(recordCounts).reduce((sum, count) => sum + count, 0);
    return {
        data: structuredData,
        summary: {
            totalRecords,
            perType: recordCounts,
            errors,
            crossValidationPassed,
        },
    };
}
/**
 * Parse INI and data files separately and extract all records
 */
function parseFilesSeparately(iniContent, dataContent, errors, recordCounts, options) {
    const parsedData = {
        iniData: {
            a000: null,
            a000Sum: [],
        },
        dataRecords: {
            a100: null,
            b100: [],
            b110: [],
            c100: [],
            d110: [],
            d120: [],
            m100: [],
            z900: null,
        },
    };
    // Parse INI file
    const iniLines = iniContent.split('\n').filter(line => line.trim().length > 0);
    for (let i = 0; i < iniLines.length; i++) {
        const line = iniLines[i];
        if (line.length < 4)
            continue;
        const recordType = line.substring(0, 4);
        const cleanLine = line.replace(/\r?\n?$/, '').replace(/\r$/, '');
        try {
            if (recordType === 'A000') {
                parsedData.iniData.a000 = parseA000(cleanLine);
                recordCounts['A000'] = (recordCounts['A000'] || 0) + 1;
            }
            else if (cleanLine.length === 19) {
                // A000Sum records
                const summaryRecord = parseA000Sum(cleanLine);
                parsedData.iniData.a000Sum.push(summaryRecord);
                recordCounts['A000Sum'] = (recordCounts['A000Sum'] || 0) + 1;
            }
            else if (!options.skipUnknownRecords) {
                errors.push({
                    recordType: 'UNKNOWN_INI',
                    recordIndex: i,
                    field: 'recordType',
                    message: `Unknown INI record type: ${recordType}`,
                    severity: options.validationMode === 'strict' ? 'error' : 'warning',
                });
            }
        }
        catch (error) {
            errors.push({
                recordType,
                recordIndex: i,
                field: 'parsing',
                message: `Failed to parse ${recordType} record: ${error instanceof Error ? error.message : 'Unknown error'}`,
                severity: 'error',
            });
        }
    }
    // Parse data file
    const parsedDataFile = parseDataFile(dataContent);
    parsedData.dataRecords = parsedDataFile.records;
    for (const [recordType, count] of Object.entries(parsedDataFile.summary.perType)) {
        recordCounts[recordType] = (recordCounts[recordType] || 0) + count;
    }
    for (const parseError of parsedDataFile.errors) {
        errors.push({
            recordType: parseError.recordType,
            recordIndex: parseError.lineNumber - 1,
            field: 'parsing',
            message: `Failed to parse ${parseError.recordType} record: ${parseError.message}`,
            severity: 'error',
        });
    }
    if (!options.skipUnknownRecords) {
        for (const unknownRecord of parsedDataFile.unknownRecords) {
            errors.push({
                recordType: 'UNKNOWN_DATA',
                recordIndex: unknownRecord.lineNumber - 1,
                field: 'recordType',
                message: `Unknown data record type: ${unknownRecord.recordType}`,
                severity: options.validationMode === 'strict' ? 'error' : 'warning',
            });
        }
    }
    return parsedData;
}
/**
 * Convert parsed records to structured ReportInput format
 */
function convertToStructuredData(parsedData, errors, validationMode) {
    const result = {
        business: {
            businessId: 'unknown',
            name: 'Unknown Business',
            taxId: '0',
            reportingPeriod: {
                startDate: '1900-01-01',
                endDate: '1900-01-01',
            },
        },
        documents: [],
        journalEntries: [],
        accounts: [],
        inventory: [],
    };
    // Extract business metadata from A000 or A100
    if (parsedData.iniData.a000) {
        const a000 = parsedData.iniData.a000;
        let startDate = '1900-01-01';
        let endDate = '1900-01-01';
        try {
            startDate = formatDateFromShaam(a000.startDate);
        }
        catch (error) {
            errors.push({
                recordType: 'A000',
                recordIndex: 0,
                field: 'startDate',
                message: `Failed to convert A000 startDate: ${error instanceof Error ? error.message : 'Unknown error'}`,
                severity: validationMode === 'strict' ? 'error' : 'warning',
            });
        }
        try {
            endDate = formatDateFromShaam(a000.endDate);
        }
        catch (error) {
            errors.push({
                recordType: 'A000',
                recordIndex: 0,
                field: 'endDate',
                message: `Failed to convert A000 endDate: ${error instanceof Error ? error.message : 'Unknown error'}`,
                severity: validationMode === 'strict' ? 'error' : 'warning',
            });
        }
        result.business = {
            businessId: a000.primaryIdentifier,
            name: a000.businessName || 'Unknown Business',
            taxId: a000.vatId,
            reportingPeriod: {
                startDate,
                endDate,
            },
        };
        // Add business address if available
        if (a000.businessStreet || a000.businessHouseNum || a000.businessCity || a000.businessZip) {
            result.business.address = {
                street: a000.businessStreet || undefined,
                houseNumber: a000.businessHouseNum || undefined,
                city: a000.businessCity || undefined,
                zip: a000.businessZip || undefined,
            };
        }
    }
    else if (parsedData.dataRecords.a100) {
        const a100 = parsedData.dataRecords.a100;
        result.business = {
            ...result.business,
            businessId: a100.primaryIdentifier.toString(),
            taxId: a100.vatId,
        };
    }
    // Convert journal entries
    for (const b100 of parsedData.dataRecords.b100) {
        try {
            const journalEntry = {
                id: `JE_${b100.transactionNumber.toString()}`,
                date: formatDateFromShaam(b100.date),
                amount: b100.transactionAmount, // Use transactionAmount as-is (it already contains the correct sign)
                accountId: b100.accountKey,
                description: b100.details,
                // Include extended B100 fields
                transactionNumber: b100.transactionNumber,
                transactionLineNumber: b100.transactionLineNumber,
                batchNumber: b100.batchNumber,
                transactionType: b100.transactionType,
                referenceDocument: b100.referenceDocument,
                referenceDocumentType: b100.referenceDocumentType,
                referenceDocument2: b100.referenceDocument2,
                referenceDocumentType2: b100.referenceDocumentType2,
                valueDate: b100.valueDate ? formatDateFromShaam(b100.valueDate) : undefined,
                counterAccountKey: b100.counterAccountKey,
                debitCreditIndicator: b100.debitCreditIndicator,
                currencyCode: b100.currencyCode,
                transactionAmount: b100.transactionAmount, // Preserve original signed amount
                foreignCurrencyAmount: b100.foreignCurrencyAmount,
                quantityField: b100.quantityField,
                matchingField1: b100.matchingField1,
                matchingField2: b100.matchingField2,
                branchId: b100.branchId,
                entryDate: b100.entryDate ? formatDateFromShaam(b100.entryDate) : undefined,
                operatorUsername: b100.operatorUsername,
                reserved: b100.reserved,
            };
            result.journalEntries.push(journalEntry);
        }
        catch (error) {
            errors.push({
                recordType: 'B100',
                recordIndex: result.journalEntries.length,
                field: 'conversion',
                message: `Failed to convert B100 to JournalEntry: ${error instanceof Error ? error.message : 'Unknown error'}`,
                severity: validationMode === 'strict' ? 'error' : 'warning',
            });
        }
    }
    // Convert accounts
    for (const b110 of parsedData.dataRecords.b110) {
        try {
            const account = {
                id: b110.accountKey,
                name: b110.accountName?.trim(),
                sortCode: {
                    key: b110.trialBalanceCode,
                    name: b110.trialBalanceCodeDescription,
                },
                accountOpeningBalance: b110.accountOpeningBalance,
                // Include extended fields if available
                countryCode: b110.countryCode,
                parentAccountKey: b110.parentAccountKey,
                vatId: b110.supplierCustomerTaxId,
                totalDebits: b110.totalDebits,
                totalCredits: b110.totalCredits,
                accountingClassificationCode: b110.accountingClassificationCode?.toString(),
                branchId: b110.branchId,
                openingBalanceForeignCurrency: b110.openingBalanceForeignCurrency,
                foreignCurrencyCode: b110.foreignCurrencyCode,
            };
            // Add address if available
            if (b110.customerSupplierAddressStreet ||
                b110.customerSupplierAddressCity ||
                b110.customerSupplierAddressZip ||
                b110.customerSupplierAddressCountry) {
                account.address = {
                    street: b110.customerSupplierAddressStreet,
                    houseNumber: b110.customerSupplierAddressHouseNumber,
                    city: b110.customerSupplierAddressCity,
                    zip: b110.customerSupplierAddressZip,
                    country: b110.customerSupplierAddressCountry,
                };
            }
            result.accounts.push(account);
        }
        catch (error) {
            errors.push({
                recordType: 'B110',
                recordIndex: result.accounts.length,
                field: 'conversion',
                message: `Failed to convert B110 to Account: ${error instanceof Error ? error.message : 'Unknown error'}`,
                severity: validationMode === 'strict' ? 'error' : 'warning',
            });
        }
    }
    // Convert documents
    for (const c100 of parsedData.dataRecords.c100) {
        try {
            // Find the corresponding D110 record for this document
            const d110 = parsedData.dataRecords.d110.find(d => d.documentType === c100.documentType && d.documentNumber === c100.documentId);
            const document = {
                id: c100.documentId,
                type: c100.documentType,
                date: formatDateFromShaam(c100.documentIssueDate),
                amount: parseFloat(c100.amountIncludingVat || '0'),
                description: d110?.goodsServiceDescription || c100.customerName || 'Document',
            };
            result.documents.push(document);
        }
        catch (error) {
            errors.push({
                recordType: 'C100',
                recordIndex: result.documents.length,
                field: 'conversion',
                message: `Failed to convert C100 to Document: ${error instanceof Error ? error.message : 'Unknown error'}`,
                severity: validationMode === 'strict' ? 'error' : 'warning',
            });
        }
    }
    // Convert inventory
    for (const m100 of parsedData.dataRecords.m100) {
        try {
            const inventoryItem = {
                id: m100.internalItemCode,
                name: m100.itemName,
                quantity: parseInt(m100.totalStockOut || '0', 10),
            };
            result.inventory.push(inventoryItem);
        }
        catch (error) {
            errors.push({
                recordType: 'M100',
                recordIndex: result.inventory.length,
                field: 'conversion',
                message: `Failed to convert M100 to InventoryItem: ${error instanceof Error ? error.message : 'Unknown error'}`,
                severity: validationMode === 'strict' ? 'error' : 'warning',
            });
        }
    }
    return result;
}
/**
 * Perform record-level validation using Zod schemas
 */
function performRecordsValidation(data, errors, validationMode) {
    if (validationMode === 'none')
        return;
    // Validate business metadata
    try {
        BusinessMetadataSchema.parse(data.business);
    }
    catch (error) {
        if (error instanceof z.ZodError) {
            for (const issue of error.issues) {
                errors.push({
                    recordType: 'BusinessMetadata',
                    recordIndex: 0,
                    field: issue.path.join('.'),
                    message: issue.message,
                    severity: validationMode === 'strict' ? 'error' : 'warning',
                });
            }
        }
    }
    // Validate documents
    for (let i = 0; i < data.documents.length; i++) {
        try {
            DocumentSchema.parse(data.documents[i]);
        }
        catch (error) {
            if (error instanceof z.ZodError) {
                for (const issue of error.issues) {
                    errors.push({
                        recordType: 'Document',
                        recordIndex: i,
                        field: issue.path.join('.'),
                        message: issue.message,
                        severity: validationMode === 'strict' ? 'error' : 'warning',
                    });
                }
            }
        }
    }
    // Validate journal entries
    for (let i = 0; i < data.journalEntries.length; i++) {
        try {
            JournalEntrySchema.parse(data.journalEntries[i]);
        }
        catch (error) {
            if (error instanceof z.ZodError) {
                for (const issue of error.issues) {
                    errors.push({
                        recordType: 'JournalEntry',
                        recordIndex: i,
                        field: issue.path.join('.'),
                        message: issue.message,
                        severity: validationMode === 'strict' ? 'error' : 'warning',
                    });
                }
            }
        }
    }
    // Validate accounts
    for (let i = 0; i < data.accounts.length; i++) {
        try {
            AccountSchema.parse(data.accounts[i]);
        }
        catch (error) {
            if (error instanceof z.ZodError) {
                for (const issue of error.issues) {
                    errors.push({
                        recordType: 'Account',
                        recordIndex: i,
                        field: issue.path.join('.'),
                        message: issue.message,
                        severity: validationMode === 'strict' ? 'error' : 'warning',
                    });
                }
            }
        }
    }
    // Validate inventory
    for (let i = 0; i < data.inventory.length; i++) {
        try {
            InventoryItemSchema.parse(data.inventory[i]);
        }
        catch (error) {
            if (error instanceof z.ZodError) {
                for (const issue of error.issues) {
                    errors.push({
                        recordType: 'InventoryItem',
                        recordIndex: i,
                        field: issue.path.join('.'),
                        message: issue.message,
                        severity: validationMode === 'strict' ? 'error' : 'warning',
                    });
                }
            }
        }
    }
}
/**
 * Perform cross-validation between INI and data files
 */
function performCrossFileValidation(parsedData, errors, validationMode) {
    if (validationMode === 'none')
        return true;
    let crossValidationPassed = true;
    // Validate record counts from A000Sum against actual data
    for (const summaryRecord of parsedData.iniData.a000Sum) {
        const expectedCount = parseInt(summaryRecord.recordCount);
        let actualCount;
        switch (summaryRecord.code) {
            case 'A100':
                actualCount = parsedData.dataRecords.a100 ? 1 : 0;
                break;
            case 'B100':
                actualCount = parsedData.dataRecords.b100.length;
                break;
            case 'B110':
                actualCount = parsedData.dataRecords.b110.length;
                break;
            case 'C100':
                actualCount = parsedData.dataRecords.c100.length;
                break;
            case 'D110':
                actualCount = parsedData.dataRecords.d110.length;
                break;
            case 'D120':
                actualCount = parsedData.dataRecords.d120.length;
                break;
            case 'M100':
                actualCount = parsedData.dataRecords.m100.length;
                break;
            case 'Z900':
                actualCount = parsedData.dataRecords.z900 ? 1 : 0;
                break;
            default:
                // Unknown record type in summary
                errors.push({
                    recordType: 'A000Sum',
                    recordIndex: 0,
                    field: 'code',
                    message: `Unknown record type in summary: ${summaryRecord.code}`,
                    severity: validationMode === 'strict' ? 'error' : 'warning',
                });
                crossValidationPassed = false;
                continue;
        }
        if (expectedCount !== actualCount) {
            errors.push({
                recordType: 'CrossValidation',
                recordIndex: 0,
                field: `${summaryRecord.code}_count`,
                message: `Record count mismatch for ${summaryRecord.code}: expected ${expectedCount}, found ${actualCount}`,
                severity: validationMode === 'strict' ? 'error' : 'warning',
            });
            crossValidationPassed = false;
        }
    }
    // Validate Z900 total records count
    if (parsedData.dataRecords.z900) {
        const z900TotalRecords = parsedData.dataRecords.z900.totalRecords;
        const actualDataRecords = (parsedData.dataRecords.a100 ? 1 : 0) +
            parsedData.dataRecords.b100.length +
            parsedData.dataRecords.b110.length +
            parsedData.dataRecords.c100.length +
            parsedData.dataRecords.d110.length +
            parsedData.dataRecords.d120.length +
            parsedData.dataRecords.m100.length;
        // Z900 doesn't count itself
        if (z900TotalRecords !== actualDataRecords) {
            errors.push({
                recordType: 'CrossValidation',
                recordIndex: 0,
                field: 'z900_total_records',
                message: `Z900 total records mismatch: expected ${z900TotalRecords}, found ${actualDataRecords}`,
                severity: validationMode === 'strict' ? 'error' : 'warning',
            });
            crossValidationPassed = false;
        }
    }
    // Validate business data consistency between A000 and A100
    if (parsedData.iniData.a000 && parsedData.dataRecords.a100) {
        const a000 = parsedData.iniData.a000;
        const a100 = parsedData.dataRecords.a100;
        if (a000.vatId !== a100.vatId) {
            errors.push({
                recordType: 'CrossValidation',
                recordIndex: 0,
                field: 'vat_id_consistency',
                message: `VAT ID mismatch between A000 (${a000.vatId}) and A100 (${a100.vatId})`,
                severity: validationMode === 'strict' ? 'error' : 'warning',
            });
            crossValidationPassed = false;
        }
    }
    return crossValidationPassed;
}
/**
 * Formats a SHAAM date string (YYYYMMDD) to ISO format (YYYY-MM-DD)
 */
function formatDateFromShaam(shaamDate) {
    // if shaamDate is not 8 digits string, throw an error
    if (!shaamDate ||
        typeof shaamDate !== 'string' ||
        shaamDate.length !== 8 ||
        !/^\d{8}$/.test(shaamDate)) {
        throw new Error(`Invalid SHAAM date format: "${shaamDate}" (expected YYYYMMDD)`);
    }
    const year = shaamDate.substring(0, 4);
    const month = shaamDate.substring(4, 6);
    const day = shaamDate.substring(6, 8);
    return `${year}-${month}-${day}`;
}
//# sourceMappingURL=parse-files.js.map