"use strict";
/**
 * Excel Dual-Feed Writer v1.2.1
 *
 * Writes customer/vehicle data to Excel file after successful API upload.
 * Features:
 * - FLEXIBLE COLUMN MAPPING: Works with existing Excel files (any column layout)
 * - Append or update based on VIN (unique identifier)
 * - Preserves all existing data and structure
 * - File locking handling with retry
 * - Background retry queue for locked files
 * - Max row limit (default 1000) - only for NEW files
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isExcelEnabled = isExcelEnabled;
exports.startRetryProcessor = startRetryProcessor;
exports.stopRetryProcessor = stopRetryProcessor;
exports.writeToExcel = writeToExcel;
exports.initExcelWriter = initExcelWriter;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const exceljs_1 = __importDefault(require("exceljs"));
const logger_1 = require("./logger");
// Configuration from environment
const EXCEL_ENABLED = process.env.EXCEL_ENABLED === 'true';
const EXCEL_OUTPUT_PATH = process.env.EXCEL_OUTPUT_PATH || 'C:\\RepairIQ-EMS\\customers.xlsx';
const EXCEL_MAX_ROWS = parseInt(process.env.EXCEL_MAX_ROWS || '1000', 10);
const RETRY_ATTEMPTS = 3;
const RETRY_DELAY_MS = 2000;
const QUEUE_RETRY_INTERVAL_MS = 30000;
// Default columns for NEW files only
const DEFAULT_COLUMNS = [
    { header: 'Customer Name', key: 'customerName', width: 25 },
    { header: 'Phone', key: 'phone', width: 15 },
    { header: 'Email', key: 'email', width: 30 },
    { header: 'Year', key: 'year', width: 8 },
    { header: 'Make', key: 'make', width: 15 },
    { header: 'Model', key: 'model', width: 15 },
    { header: 'VIN', key: 'vin', width: 20 },
    { header: 'Color', key: 'color', width: 12 },
    { header: 'License Plate', key: 'licensePlate', width: 12 },
    { header: 'Insurance Company', key: 'insuranceCompany', width: 20 },
    { header: 'Check-In Date', key: 'checkInDate', width: 12 },
];
// Flexible header mappings - maps various header names to our data fields
const HEADER_MAPPINGS = {
    // VIN variations
    'vin': ['vin'],
    'v.i.n': ['vin'],
    'v.i.n.': ['vin'],
    'vehicle vin': ['vin'],
    'vehicle identification number': ['vin'],
    // Customer name variations
    'customer name': ['customerName'],
    'customer': ['customerName'],
    'name': ['customerName'],
    'owner': ['customerName'],
    'owner name': ['customerName'],
    'insured': ['customerName'],
    'insured name': ['customerName'],
    'client': ['customerName'],
    'client name': ['customerName'],
    // Phone variations
    'phone': ['phone'],
    'phone number': ['phone'],
    'phone #': ['phone'],
    'telephone': ['phone'],
    'tel': ['phone'],
    'mobile': ['phone'],
    'cell': ['phone'],
    'contact': ['phone'],
    // Email variations
    'email': ['email'],
    'e-mail': ['email'],
    'email address': ['email'],
    // Year variations
    'year': ['year'],
    'yr': ['year'],
    'model year': ['year'],
    'vehicle year': ['year'],
    // Make variations
    'make': ['make'],
    'vehicle make': ['make'],
    'manufacturer': ['make'],
    'brand': ['make'],
    // Model variations
    'model': ['model'],
    'vehicle model': ['model'],
    // Color variations
    'color': ['color'],
    'colour': ['color'],
    'vehicle color': ['color'],
    'ext color': ['color'],
    'exterior color': ['color'],
    // License plate variations
    'license plate': ['licensePlate'],
    'license': ['licensePlate'],
    'plate': ['licensePlate'],
    'tag': ['licensePlate'],
    'plate #': ['licensePlate'],
    'license #': ['licensePlate'],
    'plate number': ['licensePlate'],
    'license number': ['licensePlate'],
    'registration': ['licensePlate'],
    // Insurance variations
    'insurance company': ['insuranceCompany'],
    'insurance': ['insuranceCompany'],
    'insurer': ['insuranceCompany'],
    'carrier': ['insuranceCompany'],
    'ins company': ['insuranceCompany'],
    'ins co': ['insuranceCompany'],
    // Date variations
    'check-in date': ['checkInDate'],
    'checkin date': ['checkInDate'],
    'check in date': ['checkInDate'],
    'date': ['checkInDate'],
    'date in': ['checkInDate'],
    'arrival date': ['checkInDate'],
    'receive date': ['checkInDate'],
    'ro date': ['checkInDate'],
};
// Retry queue for locked files
const retryQueue = [];
let retryIntervalId = null;
/**
 * Check if Excel feature is enabled
 */
function isExcelEnabled() {
    return EXCEL_ENABLED;
}
/**
 * Sleep helper
 */
function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
/**
 * Create new workbook with default headers
 */
function createNewWorkbook() {
    const workbook = new exceljs_1.default.Workbook();
    workbook.creator = 'RepairIQ Sync Agent';
    workbook.created = new Date();
    const sheet = workbook.addWorksheet('Customers');
    sheet.columns = DEFAULT_COLUMNS;
    // Style header row
    const headerRow = sheet.getRow(1);
    headerRow.font = { bold: true };
    headerRow.fill = {
        type: 'pattern',
        pattern: 'solid',
        fgColor: { argb: 'FFE0E0E0' }
    };
    return workbook;
}
/**
 * Detect column mappings from existing header row
 */
function detectColumnMappings(sheet) {
    const mappings = [];
    const headerRow = sheet.getRow(1);
    headerRow.eachCell((cell, colNumber) => {
        const headerText = cell.value?.toString().toLowerCase().trim() || '';
        // Check if this header matches any of our known mappings
        const matchedFields = HEADER_MAPPINGS[headerText];
        if (matchedFields && matchedFields.length > 0) {
            mappings.push({
                colNumber,
                field: matchedFields[0]
            });
            (0, logger_1.log)(`Excel: Mapped column ${colNumber} "${cell.value}" -> ${matchedFields[0]}`);
        }
    });
    return mappings;
}
/**
 * Find VIN column number from mappings
 */
function findVinColumn(mappings) {
    const vinMapping = mappings.find(m => m.field === 'vin');
    return vinMapping ? vinMapping.colNumber : null;
}
/**
 * Try to read existing workbook with retry logic
 */
async function readWorkbook() {
    if (!fs.existsSync(EXCEL_OUTPUT_PATH)) {
        return null;
    }
    for (let attempt = 1; attempt <= RETRY_ATTEMPTS; attempt++) {
        try {
            const workbook = new exceljs_1.default.Workbook();
            await workbook.xlsx.readFile(EXCEL_OUTPUT_PATH);
            return workbook;
        }
        catch (error) {
            if (error.code === 'EBUSY' || error.message.includes('locked')) {
                (0, logger_1.log)(`Excel file locked, attempt ${attempt}/${RETRY_ATTEMPTS}...`, 'WARN');
                if (attempt < RETRY_ATTEMPTS) {
                    await sleep(RETRY_DELAY_MS);
                }
            }
            else {
                (0, logger_1.log)(`Error reading Excel file: ${error.message}`, 'ERROR');
                return null;
            }
        }
    }
    return null; // File is locked after all retries
}
/**
 * Try to write workbook with retry logic
 */
async function writeWorkbook(workbook) {
    // Ensure directory exists
    const dir = path.dirname(EXCEL_OUTPUT_PATH);
    if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true });
    }
    for (let attempt = 1; attempt <= RETRY_ATTEMPTS; attempt++) {
        try {
            await workbook.xlsx.writeFile(EXCEL_OUTPUT_PATH);
            return true;
        }
        catch (error) {
            if (error.code === 'EBUSY' || error.message.includes('locked')) {
                (0, logger_1.log)(`Excel file locked for write, attempt ${attempt}/${RETRY_ATTEMPTS}...`, 'WARN');
                if (attempt < RETRY_ATTEMPTS) {
                    await sleep(RETRY_DELAY_MS);
                }
            }
            else {
                (0, logger_1.log)(`Error writing Excel file: ${error.message}`, 'ERROR');
                return false;
            }
        }
    }
    return false; // File is locked after all retries
}
/**
 * Write data to Excel file
 * Handles both NEW files and EXISTING files with flexible column mapping
 */
async function writeToExcelInternal(data) {
    try {
        // Try to read existing workbook
        let workbook = await readWorkbook();
        let isNewFile = false;
        let useDefaultColumns = false;
        if (!workbook) {
            // No existing file
            if (fs.existsSync(EXCEL_OUTPUT_PATH)) {
                // File exists but is locked
                return false;
            }
            // Create new workbook with default structure
            workbook = createNewWorkbook();
            isNewFile = true;
            useDefaultColumns = true;
            (0, logger_1.log)('Excel: Creating new file with default structure');
        }
        // Get first worksheet (or create if none exists)
        let sheet = workbook.worksheets[0];
        if (!sheet) {
            sheet = workbook.addWorksheet('Customers');
            sheet.columns = DEFAULT_COLUMNS;
            const headerRow = sheet.getRow(1);
            headerRow.font = { bold: true };
            useDefaultColumns = true;
            (0, logger_1.log)('Excel: Created new worksheet with default structure');
        }
        // Detect column mappings from existing headers
        let columnMappings = [];
        let vinColNumber = null;
        if (!useDefaultColumns && sheet.rowCount > 0) {
            // Existing file - detect column structure
            columnMappings = detectColumnMappings(sheet);
            vinColNumber = findVinColumn(columnMappings);
            if (columnMappings.length === 0) {
                (0, logger_1.log)('Excel: WARNING - No recognized columns found in existing file', 'WARN');
                (0, logger_1.log)('Excel: Will append data but may not update existing rows', 'WARN');
            }
            else {
                (0, logger_1.log)(`Excel: Detected ${columnMappings.length} mapped columns`);
            }
        }
        else {
            // New file with default columns
            columnMappings = [
                { colNumber: 1, field: 'customerName' },
                { colNumber: 2, field: 'phone' },
                { colNumber: 3, field: 'email' },
                { colNumber: 4, field: 'year' },
                { colNumber: 5, field: 'make' },
                { colNumber: 6, field: 'model' },
                { colNumber: 7, field: 'vin' },
                { colNumber: 8, field: 'color' },
                { colNumber: 9, field: 'licensePlate' },
                { colNumber: 10, field: 'insuranceCompany' },
                { colNumber: 11, field: 'checkInDate' },
            ];
            vinColNumber = 7;
        }
        // Find existing row by VIN
        let existingRowNumber = null;
        if (vinColNumber && data.vin) {
            sheet.eachRow((row, rowNumber) => {
                if (rowNumber > 1) { // Skip header
                    const cellVin = row.getCell(vinColNumber).value?.toString().trim() || '';
                    if (cellVin.toLowerCase() === data.vin.toLowerCase()) {
                        existingRowNumber = rowNumber;
                    }
                }
            });
        }
        if (existingRowNumber) {
            // Update existing row - only update mapped columns
            const row = sheet.getRow(existingRowNumber);
            for (const mapping of columnMappings) {
                const value = data[mapping.field];
                if (value !== undefined && value !== '') {
                    row.getCell(mapping.colNumber).value = value;
                }
            }
            row.commit();
            (0, logger_1.log)(`Excel: Updated existing row ${existingRowNumber} for VIN ${data.vin}`);
        }
        else {
            // Append new row
            if (useDefaultColumns || isNewFile) {
                // New file - use structured add
                sheet.addRow({
                    customerName: data.customerName,
                    phone: data.phone,
                    email: data.email,
                    year: data.year,
                    make: data.make,
                    model: data.model,
                    vin: data.vin,
                    color: data.color,
                    licensePlate: data.licensePlate,
                    insuranceCompany: data.insuranceCompany,
                    checkInDate: data.checkInDate,
                });
            }
            else {
                // Existing file - add row matching existing column structure
                const newRowNumber = sheet.rowCount + 1;
                const newRow = sheet.getRow(newRowNumber);
                for (const mapping of columnMappings) {
                    const value = data[mapping.field];
                    if (value !== undefined) {
                        newRow.getCell(mapping.colNumber).value = value;
                    }
                }
                newRow.commit();
            }
            (0, logger_1.log)(`Excel: Added new row for VIN ${data.vin}`);
            // Enforce max rows limit ONLY for new files
            if (isNewFile) {
                const rowCount = sheet.rowCount;
                if (rowCount > EXCEL_MAX_ROWS + 1) { // +1 for header
                    const rowsToDelete = rowCount - EXCEL_MAX_ROWS - 1;
                    for (let i = 0; i < rowsToDelete; i++) {
                        sheet.spliceRows(2, 1); // Delete row 2 (oldest data after header)
                    }
                    (0, logger_1.log)(`Excel: Trimmed ${rowsToDelete} old rows to maintain ${EXCEL_MAX_ROWS} limit`);
                }
            }
        }
        // Write workbook
        const written = await writeWorkbook(workbook);
        if (!written) {
            return false;
        }
        (0, logger_1.log)(`Excel: Successfully saved to ${EXCEL_OUTPUT_PATH}`);
        return true;
    }
    catch (error) {
        (0, logger_1.log)(`Excel write error: ${error.message}`, 'ERROR');
        return false;
    }
}
/**
 * Add data to retry queue
 */
function addToRetryQueue(data) {
    // Check if VIN already in queue (avoid duplicates)
    const exists = retryQueue.some(item => item.vin === data.vin);
    if (!exists) {
        retryQueue.push(data);
        (0, logger_1.log)(`Excel: Added VIN ${data.vin} to retry queue (queue size: ${retryQueue.length})`);
    }
}
/**
 * Process retry queue
 */
async function processRetryQueue() {
    if (retryQueue.length === 0)
        return;
    (0, logger_1.log)(`Excel: Processing retry queue (${retryQueue.length} items)...`);
    const itemsToProcess = [...retryQueue];
    retryQueue.length = 0; // Clear queue
    for (const item of itemsToProcess) {
        const success = await writeToExcelInternal(item);
        if (!success) {
            // Re-add to queue if still failing
            addToRetryQueue(item);
        }
    }
}
/**
 * Start background retry processor
 */
function startRetryProcessor() {
    if (retryIntervalId)
        return; // Already running
    retryIntervalId = setInterval(() => {
        processRetryQueue();
    }, QUEUE_RETRY_INTERVAL_MS);
    (0, logger_1.log)(`Excel: Started retry processor (every ${QUEUE_RETRY_INTERVAL_MS / 1000}s)`);
}
/**
 * Stop background retry processor
 */
function stopRetryProcessor() {
    if (retryIntervalId) {
        clearInterval(retryIntervalId);
        retryIntervalId = null;
        (0, logger_1.log)('Excel: Stopped retry processor');
    }
}
/**
 * Main function to write data to Excel
 * Returns immediately, queues for retry if file is locked
 */
async function writeToExcel(data) {
    if (!EXCEL_ENABLED) {
        return;
    }
    const success = await writeToExcelInternal(data);
    if (!success) {
        addToRetryQueue(data);
    }
}
/**
 * Initialize Excel writer
 */
function initExcelWriter() {
    if (!EXCEL_ENABLED) {
        (0, logger_1.log)('Excel: Feature disabled (EXCEL_ENABLED=false)');
        return;
    }
    (0, logger_1.log)('=====================================');
    (0, logger_1.log)('Excel Dual-Feed Enabled (v1.2.1)');
    (0, logger_1.log)('=====================================');
    (0, logger_1.log)(`Output file: ${EXCEL_OUTPUT_PATH}`);
    (0, logger_1.log)(`Mode: ${fs.existsSync(EXCEL_OUTPUT_PATH) ? 'UPDATE existing file' : 'CREATE new file'}`);
    (0, logger_1.log)(`Max rows: ${EXCEL_MAX_ROWS} (new files only)`);
    (0, logger_1.log)(`Retry queue interval: ${QUEUE_RETRY_INTERVAL_MS / 1000}s`);
    (0, logger_1.log)('Flexible column mapping: ENABLED');
    (0, logger_1.log)('=====================================');
    // Start background retry processor
    startRetryProcessor();
}
//# sourceMappingURL=excel-writer.js.map