// @ts-nocheck
import * as fs from 'fs';
import * as path from 'path';
import { format, getMonth, getYear, addMonths, addYears, isBefore, startOfMonth, endOfMonth, isEqual, getDaysInMonth, startOfYear, isSameMonth, startOfWeek, addDays, parseISO } from 'date-fns';
import type { Employee, LeaveRequest, FinancialRecord, Installment, StatementRow, Customer, CustomerCategory, ProductionItem, ProductionReport, CustomerPricing, SalesInvoice, CustomerPayment, SupplierPayment, JournalEntry, JournalEntryLine, ChartOfAccount, Supplier, PurchaseOrder, PurchaseOrderItem, PurchaseReturn, IncomingCheck, IssuedCheck, Bank, Currency, ItemGroup, SalesRep, Warehouse, Shelf, InternalTransfer, StockLocationBalance, MaterialMovementEntry, MaterialStockReport, MaterialExpiryFifoRow, TransferLock, InternalTransferDocument, ItemUnit, ItemUnitStatus, Shipment, ShipmentSequence, ShipmentType, InventoryMovement, DailyExchangeRate, ManagedUnit, CheckReturnEvent, PurchaseCreditNote, PurchaseCreditNoteItem, Coupon, Campaign, CampaignType, CampaignStatus } from './types';
import { getNetSalary, getMonthlyDeductions, getMonthlyBonuses, getEmployeeStatement as getEmployeeStatementHelper, getShiftForEmployee as getShiftForEmployeeHelper } from './data-helpers';
import { isPostgresProviderEnabled, isPostgresStrictModeEnabled } from './config';

const MOCK_EMPLOYEES: Employee[] = [];

const MOCK_SETTINGS: Record<string, any> = {
    approvedIpAddress: '',
    showEditingMessageToUser: false,
    deploymentMode: 'local',
    documentStoragePath: '',
    posShowSuppliers: false,
    businessMode: 'inventory',
    realEstateEnabled: false,
    warehousesEnabled: false,
    allowOutOfStockSales: false,
    blockSalesBelowCost: false,
    purchaseShelfTaskEnabled: false,
    purchasePostingFollowupEnabled: false,
    posShowItemNumberColumn: true,
    posShowUnitColumn: true,
    posShowDescriptionColumn: true,
    posShowBarcodeColumn: true,
    posShowQuantityColumn: true,
    posShowPriceColumn: true,
    posShowItemDiscountColumn: true,
    posShowBonusColumn: true,
    posShowTotalColumn: true,
    posShowActionsColumn: true,
    defaultSalesWarehouseId: '',
    defaultSalesShelfId: '',
    defaultPurchaseWarehouseId: '',
    defaultPurchaseShelfId: '',
    defaultTaxMethod: 'tax-inclusive', // 'tax-inclusive' | 'invoice-level' | 'line-level'
    defaultInvoiceTaxRate: 0.1, // 10% default tax rate
    defaultLineTaxRate: 0.1, // 10% default line-item tax rate
    scaleMode: 'manual', // 'manual' | 'hid' | 'serial'
    scaleReadRegex: '([0-9]+(?:[\\.,][0-9]+)?)',
    scaleDecimalSeparator: 'auto', // 'auto' | 'dot' | 'comma'
    scaleHidMinLength: 3,
    scaleSerialBaudRate: 9600,
    scaleSerialDataBits: 8,
    scaleSerialStopBits: 1,
    scaleSerialParity: 'none', // 'none' | 'even' | 'odd'
    scaleSerialAutoConnect: false,
};

// --- Persistence Layer ---
const dbPath = path.join(process.cwd(), 'db');
if (!fs.existsSync(dbPath)) {
  fs.mkdirSync(dbPath, { recursive: true });
}

const dbLockPath = path.join(dbPath, '.db.lock');
let dbLockDepth = 0;
let dbLockFd: number | null = null;

function sleepSync(ms: number) {
    try {
        Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
    } catch {
        const endAt = Date.now() + ms;
        while (Date.now() < endAt) {
            // busy wait fallback
        }
    }
}

function withDbLock<T>(operation: () => T): T {
    if (dbLockDepth > 0) {
        dbLockDepth += 1;
        try {
            return operation();
        } finally {
            dbLockDepth -= 1;
        }
    }

    const startedAt = Date.now();
    while (dbLockFd === null) {
        try {
            dbLockFd = fs.openSync(dbLockPath, 'wx');
        } catch (error: any) {
            if (error?.code !== 'EEXIST') {
                throw error;
            }
            if (Date.now() - startedAt > 10000) {
                throw new Error('Database is busy. Please retry.');
            }
            sleepSync(10);
        }
    }

    dbLockDepth = 1;
    try {
        return operation();
    } finally {
        dbLockDepth = 0;
        if (dbLockFd !== null) {
            fs.closeSync(dbLockFd);
            dbLockFd = null;
        }
        try {
            fs.unlinkSync(dbLockPath);
        } catch {
            // ignore
        }
    }
}

function nextSequenceValue(sequenceKey: string): number {
    
    const current = Number(postgresSequenceFallbacks.get(sequenceKey) || 0);
    const next = (Number.isFinite(current) ? current : 0) + 1;
    postgresSequenceFallbacks.set(sequenceKey, next);
    return next;
    

    return withDbLock(() => {
        const counters = readData<Record<string, number>>('_sequences.json', {});
        const current = Number(counters[sequenceKey] || 0);
        const next = Number.isFinite(current) ? current + 1 : 1;
        counters[sequenceKey] = next;
        // Never write _sequences.json when postgres is active
        return next;
    });
}

export function nextNamedSequence(sequenceKey: string): number {
    const normalized = String(sequenceKey || '').trim();
    if (!normalized) {
        throw new Error('Sequence key is required.');
    }
    return nextSequenceValue(normalized);
}

export function nextPrefixedId(prefix: string): string {
    
    // In postgres mode, use short UUID to avoid writing _sequences.json and prevent ID collisions
    const { randomUUID } = require('crypto') as typeof import('crypto');
    return `${prefix}-${randomUUID().slice(0, 8)}`;
    
    return `${prefix}-${nextSequenceValue(prefix)}`;
}

// --- Shipment Numbering & Sequences ---
const shipmentPrefixMap: Record<ShipmentType, string> = {
    internal: 'ITD',
    adjustment: 'ADJ',
    reverse: 'REV',
    inventory_count: 'INV',
};

function formatShipmentNumber(shipmentType: ShipmentType, fiscalYear: number, sequenceNumber: number): string {
    const prefix = shipmentPrefixMap[shipmentType] || 'SHP';
    const padded = String(sequenceNumber).padStart(6, '0');
    return `${prefix}-${fiscalYear}-${padded}`;
}

export function getShipmentSequences(): ShipmentSequence[] {
    return readData<ShipmentSequence[]>('shipment_sequences.json', []);
}

function saveShipmentSequences(sequences: ShipmentSequence[]): void {
    writeData('shipment_sequences.json', sequences);
}

export function allocateShipmentNumber(shipmentType: ShipmentType, fiscalYear: number): { shipmentNumber: string; sequenceNumber: number } {
    return withDbLock(() => {
        const sequences = getShipmentSequences();
        const matchIndex = sequences.findIndex((seq) => seq.shipmentType === shipmentType && seq.fiscalYear === fiscalYear);
        const base: ShipmentSequence = matchIndex >= 0
            ? sequences[matchIndex]
            : { id: nextPrefixedId('SHSEQ'), shipmentType, fiscalYear, lastNumber: 0 };
        const nextNumber = base.lastNumber + 1;
        base.lastNumber = nextNumber;
        if (matchIndex >= 0) {
            sequences[matchIndex] = base;
        } else {
            sequences.push(base);
        }
        saveShipmentSequences(sequences);
        return {
            shipmentNumber: formatShipmentNumber(shipmentType, fiscalYear, nextNumber),
            sequenceNumber: nextNumber,
        };
    });
}

export function getShipments(): Shipment[] {
    return readData<Shipment[]>('shipments.json', []);
}

function saveShipments(shipments: Shipment[]): void {
    writeData('shipments.json', shipments);
}

export function createShipment(shipment: Omit<Shipment, 'id' | 'shipmentNumber' | 'sequenceNumber' | 'fiscalYear' | 'status'> & { status?: Shipment['status'] }): Shipment {
    const fiscalYear = getYear(new Date(shipment.date || Date.now()));
    const { shipmentNumber, sequenceNumber } = allocateShipmentNumber(shipment.shipmentType, fiscalYear);
    const status = shipment.status || 'pending';
    assertNotLockedByPeriod(shipment.date, shipment.periodCloseDate, shipment.inventoryCloseDate);
    const record: Shipment = {
        ...shipment,
        id: nextPrefixedId('SHP'),
        shipmentNumber,
        sequenceNumber,
        fiscalYear,
        status,
    };

    return withDbLock(() => {
        const shipments = getShipments();
        shipments.push(record);
        saveShipments(shipments);
        return record;
    });
}

export function updateShipmentStatus(shipmentId: string, status: Shipment['status']): Shipment | null {
    return withDbLock(() => {
        const shipments = getShipments();
        const idx = shipments.findIndex((s) => s.id === shipmentId);
        if (idx === -1) return null;
        shipments[idx] = { ...shipments[idx], status };
        saveShipments(shipments);
        return shipments[idx];
    });
}

export function cancelShipment(shipmentId: string): { success: boolean; error?: string } {
    return withDbLock(() => {
        const shipments = getShipments();
        const idx = shipments.findIndex((s) => s.id === shipmentId);
        if (idx === -1) return { success: false, error: 'NOT_FOUND' };
        const target = shipments[idx];
        if (target.hasInventoryMovement) {
            return { success: false, error: 'HAS_INVENTORY_MOVEMENT' };
        }
        shipments[idx] = { ...target, status: 'cancelled' };
        saveShipments(shipments);
        return { success: true };
    });
}

export function assertNotLockedByPeriod(dateIso: string, periodCloseDate?: string, inventoryCloseDate?: string): void {
    const shipmentDate = new Date(dateIso);
    if (periodCloseDate && shipmentDate <= new Date(periodCloseDate)) {
        throw new Error('PERIOD_LOCKED');
    }
    if (inventoryCloseDate && shipmentDate <= new Date(inventoryCloseDate)) {
        throw new Error('INVENTORY_LOCKED');
    }
}

export function getAvailableStock(materialId: string, warehouseId: string, shelfId: string): number {
    const balances = getStockBalances();
    const entry = balances.find((b) => b.materialId === materialId && b.warehouseId === warehouseId && b.shelfId === shelfId);
    return entry ? entry.quantity : 0;
}

export function assertReverseQuantity(materialId: string, warehouseId: string, shelfId: string, reverseQty: number): void {
    const available = getAvailableStock(materialId, warehouseId, shelfId);
    const after = available - Math.max(0, reverseQty);
    if (after < 0) {
        throw new Error('INSUFFICIENT_STOCK_FOR_REVERSE');
    }
}

// JSON reviver to convert ISO date strings back to Date objects
const dateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/;
function jsonReviver(key: string, value: any) {
  if (typeof value === 'string' && dateRegex.test(value)) {
    return new Date(value);
  }
  return value;
}

const warnedLegacyJsonAccess = new Set<string>();

function warnLegacyJsonAccess(filename: string, action: 'read' | 'write') {
        if (!isPostgresProviderEnabled() || !isPostgresStrictModeEnabled()) return;
        if (filename !== 'employees.json') return;

        const warningKey = `${action}:${filename}`;
        if (warnedLegacyJsonAccess.has(warningKey)) return;
        warnedLegacyJsonAccess.add(warningKey);

        console.warn(
                `[Postgres Strict] Legacy JSON ${action} detected for ${filename}. This flow still depends on file storage and should be migrated to PostgreSQL.`
        );
}

function readData<T>(filename: string, defaultValue: T): T {
    warnLegacyJsonAccess(filename, 'read');
  const filePath = path.join(dbPath, filename);
  try {
    if (fs.existsSync(filePath)) {
      const fileContent = fs.readFileSync(filePath, 'utf-8');
      // If file has content, parse it. Otherwise, fall through to write default.
      if (fileContent.trim()) {
        return JSON.parse(fileContent, jsonReviver);
      }
    }
  } catch (error) {
    console.error(`Error reading or parsing ${filename}:`, error);
  }
  // If file doesn't exist, is empty, or is corrupt, write the default value and return it.
    // When postgres is active, never auto-create JSON fallback files.
  return defaultValue;
}

type DataCacheEntry = {
    expiresAt: number;
    value: unknown;
};

const dataCache = new Map<string, DataCacheEntry>();
const postgresSequenceFallbacks = new Map<string, number>();
const postgresTransferLocks = new Map<string, TransferLock>();

function invalidateDataCache(filename?: string): void {
    if (!filename) {
        dataCache.clear();
        return;
    }
    dataCache.delete(filename);
}

function readDataCached<T>(filename: string, defaultValue: T, ttlMs: number): T {
    const now = Date.now();
    const cached = dataCache.get(filename);
    if (cached && cached.expiresAt > now) {
        return cached.value as T;
    }

    const value = readData<T>(filename, defaultValue);
    dataCache.set(filename, {
        value,
        expiresAt: now + Math.max(0, ttlMs),
    });
    return value;
}

export function getInventoryMovements(): InventoryMovement[] {
    return readData<InventoryMovement[]>('inventory_movements.json', []);
}

function saveInventoryMovements(movements: InventoryMovement[]): void {
    writeData('inventory_movements.json', movements);
}

export function addInventoryMovements(entries: Array<Omit<InventoryMovement, 'id'>>): InventoryMovement[] {
    if (!entries.length) return [];
    return withDbLock(() => {
        const existing = getInventoryMovements();
        const created: InventoryMovement[] = entries.map((entry) => ({
            ...entry,
            id: nextPrefixedId('inv-move'),
        }));
        existing.push(...created);
        saveInventoryMovements(existing);
        return created;
    });
}

type JournalCtx = {
    shipmentType: ShipmentType;
    invoiceType?: 'purchase' | 'sale' | 'inventory';
    supplierId?: string;
    customerId?: string;
    periodLocked?: boolean;
    reasonCode?: 'normal' | 'employee_error' | 'natural_loss';
    referenceType?: string;
    referenceId?: string;
    createdBy?: string;
};

export function generateJournalForInventoryAdjustment(movements: InventoryMovement[], ctx: JournalCtx): JournalEntry | null {
    if (!movements.length) return null;

    const accounts = getChartOfAccounts();
    const suppliers = getSuppliers();
    const materials = getMaterials();
    const materialById = new Map((materials || []).map((material) => [String(material?.id || ''), material]));

    const findSupplierAccount = (supplierId?: string) => {
        if (!supplierId) return undefined;
        const supplier = suppliers.find((s) => s.id === supplierId);
        if (!supplier?.accountCode) return undefined;
        return accounts.find((acc) => acc.code === supplier.accountCode);
    };

    const findAccount = (code: string) => accounts.find((acc: any) => String(acc?.code || '') === String(code || ''));
    const defaultInventoryAccount = findAccount('1170') || findAccount('1171') || findAccount('1173');
    const supplierAccount = findSupplierAccount(ctx.supplierId) || findAccount('2110');
    const defaultPurchaseAccount = findAccount('5110') || findAccount('5111') || supplierAccount;
    const purchaseDiffAccount = findAccount('5111') || defaultPurchaseAccount || supplierAccount;
    const inventoryDiffAccount = findAccount('5110'); // فروقات جرد (استعمال 5110 إذا لم يوجد حساب منفصل)
    const defaultCogsAccount = findAccount('5110');
    const employeeErrorAccount = findAccount('6160') || purchaseDiffAccount; // أخطاء الموظفين
    const naturalLossAccount = findAccount('6150') || purchaseDiffAccount; // هلاك/فاقد طبيعي

    if (!defaultInventoryAccount || !purchaseDiffAccount) {
        console.warn('Required accounts not found for inventory journal');
        throw new Error('JOURNAL_ACCOUNTS_MISSING');
    }

    const resolveMaterialAccount = (materialId: string, key: 'inventoryAccountCode' | 'purchaseAccountCode' | 'cogsAccountCode', fallback: any) => {
        const material = materialById.get(String(materialId || '')) as Record<string, any> | undefined;
        const configuredCode = String(material?.[key] || '').trim();
        if (configuredCode) {
            const configured = findAccount(configuredCode);
            if (configured) return configured;
        }
        return fallback;
    };

    const rawLines: JournalEntryLine[] = [];

    movements.forEach((mv) => {
        const qtyIn = mv.quantityIn || 0;
        const qtyOut = mv.quantityOut || 0;
        const isIncrease = qtyIn > 0;
        const qty = isIncrease ? qtyIn : qtyOut;
        if (!qty) return;
        const cost = Math.abs(mv.costPerUnitOriginal ?? 0);
        const value = qty * cost;
        if (value === 0) return;
        const periodLocked = !!ctx.periodLocked;
        const isPurchase = ctx.invoiceType === 'purchase';
        const isInventory = ctx.invoiceType === 'inventory';
        const isSale = ctx.invoiceType === 'sale';
        const reason = ctx.reasonCode || 'normal';
        const inventoryAccount = resolveMaterialAccount(mv.materialId, 'inventoryAccountCode', defaultInventoryAccount);
        const purchaseAccount = resolveMaterialAccount(mv.materialId, 'purchaseAccountCode', defaultPurchaseAccount);
        const cogsAccount = resolveMaterialAccount(mv.materialId, 'cogsAccountCode', defaultCogsAccount);

        if (isPurchase) {
            if (!inventoryAccount || (!supplierAccount && !purchaseAccount && !purchaseDiffAccount)) throw new Error('JOURNAL_ACCOUNTS_MISSING');
            if (!isIncrease) {
                // نقص المخزون (مردود/إخراج)
                if (reason === 'employee_error' && employeeErrorAccount) {
                    rawLines.push({ accountId: employeeErrorAccount.id, accountCode: employeeErrorAccount.code, accountName: employeeErrorAccount.nameAr, debit: value, credit: 0 });
                    rawLines.push({ accountId: inventoryAccount.id, accountCode: inventoryAccount.code, accountName: inventoryAccount.nameAr, debit: 0, credit: value });
                } else if (reason === 'natural_loss' && naturalLossAccount) {
                    rawLines.push({ accountId: naturalLossAccount.id, accountCode: naturalLossAccount.code, accountName: naturalLossAccount.nameAr, debit: value, credit: 0 });
                    rawLines.push({ accountId: inventoryAccount.id, accountCode: inventoryAccount.code, accountName: inventoryAccount.nameAr, debit: 0, credit: value });
                } else if (!periodLocked && supplierAccount) {
                    rawLines.push({ accountId: supplierAccount.id, accountCode: supplierAccount.code, accountName: supplierAccount.nameAr, debit: value, credit: 0 });
                    rawLines.push({ accountId: inventoryAccount.id, accountCode: inventoryAccount.code, accountName: inventoryAccount.nameAr, debit: 0, credit: value });
                } else {
                    const debitAccount = purchaseAccount || purchaseDiffAccount;
                    rawLines.push({ accountId: debitAccount.id, accountCode: debitAccount.code, accountName: debitAccount.nameAr, debit: value, credit: 0 });
                    rawLines.push({ accountId: inventoryAccount.id, accountCode: inventoryAccount.code, accountName: inventoryAccount.nameAr, debit: 0, credit: value });
                }
            } else {
                // زيادة المخزون (تصحيح بعد شراء)
                if (!periodLocked) {
                    rawLines.push({ accountId: inventoryAccount.id, accountCode: inventoryAccount.code, accountName: inventoryAccount.nameAr, debit: value, credit: 0 });
                    rawLines.push({ accountId: supplierAccount.id, accountCode: supplierAccount.code, accountName: supplierAccount.nameAr, debit: 0, credit: value });
                } else {
                    rawLines.push({ accountId: inventoryAccount.id, accountCode: inventoryAccount.code, accountName: inventoryAccount.nameAr, debit: value, credit: 0 });
                    const creditAccount = purchaseAccount || purchaseDiffAccount;
                    rawLines.push({ accountId: creditAccount.id, accountCode: creditAccount.code, accountName: creditAccount.nameAr, debit: 0, credit: value });
                }
            }
        } else if (isInventory) {
            // فروقات جرد: حساب 5110
            if (!inventoryDiffAccount || !inventoryAccount) return;
            if (isIncrease) {
                rawLines.push({ accountId: inventoryAccount.id, accountCode: inventoryAccount.code, accountName: inventoryAccount.nameAr, debit: value, credit: 0 });
                rawLines.push({ accountId: inventoryDiffAccount.id, accountCode: inventoryDiffAccount.code, accountName: inventoryDiffAccount.nameAr, debit: 0, credit: value });
            } else {
                rawLines.push({ accountId: inventoryDiffAccount.id, accountCode: inventoryDiffAccount.code, accountName: inventoryDiffAccount.nameAr, debit: value, credit: 0 });
                rawLines.push({ accountId: inventoryAccount.id, accountCode: inventoryAccount.code, accountName: inventoryAccount.nameAr, debit: 0, credit: value });
            }
        } else if (isSale) {
            // حركة بيع/مردود على المخزون: تكلفة مقابل المخزون
            if (!inventoryAccount || !cogsAccount) return;
            if (isIncrease) {
                rawLines.push({ accountId: inventoryAccount.id, accountCode: inventoryAccount.code, accountName: inventoryAccount.nameAr, debit: value, credit: 0 });
                rawLines.push({ accountId: cogsAccount.id, accountCode: cogsAccount.code, accountName: cogsAccount.nameAr, debit: 0, credit: value });
            } else {
                rawLines.push({ accountId: cogsAccount.id, accountCode: cogsAccount.code, accountName: cogsAccount.nameAr, debit: value, credit: 0 });
                rawLines.push({ accountId: inventoryAccount.id, accountCode: inventoryAccount.code, accountName: inventoryAccount.nameAr, debit: 0, credit: value });
            }
        }
    });

    if (!rawLines.length) return null;

    // Aggregate duplicate accounts and remove zero-value lines
    const aggregated = new Map<string, JournalEntryLine>();
    rawLines.forEach((line) =>
        aggregated.set(line.accountCode, {
            ...line,
            debit: (aggregated.get(line.accountCode)?.debit || 0) + line.debit,
            credit: (aggregated.get(line.accountCode)?.credit || 0) + line.credit,
        })
    );

    const lines = Array.from(aggregated.values()).filter((line) =>
        Math.abs(line.debit) > 0 || Math.abs(line.credit) > 0
    );

    const totalDebit = lines.reduce((sum, line) => sum + line.debit, 0);
    const totalCredit = lines.reduce((sum, line) => sum + line.credit, 0);
    const diff = Math.abs(totalDebit - totalCredit);
    if (!lines.length || diff > 0.01) {
        console.error('JOURNAL_NOT_BALANCED', { totalDebit, totalCredit, lines });
        throw new Error('JOURNAL_NOT_BALANCED');
    }

    const entry: JournalEntry = {
        id: nextPrefixedId('je-adjust'),
        date: new Date().toISOString(),
        description: 'Inventory adjustment',
        referenceType: (ctx.referenceType as JournalEntry['referenceType']) || 'manual',
        referenceId: ctx.referenceId || 'inventory-adjustment',
        shipmentId: movements[0]?.shipmentId,
        shipmentType: ctx.shipmentType,
        invoiceId: movements[0]?.invoiceId,
        lines,
        createdBy: ctx.createdBy || 'system',
        createdAt: new Date().toISOString(),
    };

    const journalEntries = getJournalEntries();
    journalEntries.unshift(entry);
    writeData('journal_entries.json', journalEntries);
    return entry;
}

type ReconcileOpts = {
    orderId: string;
    date?: string;
    periodCloseDate?: string;
    inventoryCloseDate?: string;
};

type SimpleItem = {
    materialId: string;
    quantity: number;
    warehouseId?: string;
    shelfId?: string;
};

export function reconcilePurchaseOrderInventory(existingItems: SimpleItem[], newItems: SimpleItem[], opts: ReconcileOpts) {
    const date = opts.date || new Date().toISOString();
    assertNotLockedByPeriod(date, opts.periodCloseDate, opts.inventoryCloseDate);

    const aggregate = (items: SimpleItem[]) => {
        const map = new Map<string, number>();
        items.forEach((item) => {
            const key = `${item.materialId}||${item.warehouseId || ''}||${item.shelfId || ''}`;
            map.set(key, (map.get(key) || 0) + Number(item.quantity || 0));
        });
        return map;
    };

    const oldMap = aggregate(existingItems || []);
    const newMap = aggregate(newItems || []);

    const diffs: Array<{ key: string; diff: number }> = [];
    const keys = new Set([...oldMap.keys(), ...newMap.keys()]);
    keys.forEach((key) => {
        const oldQty = oldMap.get(key) || 0;
        const newQty = newMap.get(key) || 0;
        const diff = newQty - oldQty;
        if (diff !== 0) {
            diffs.push({ key, diff });
        }
    });

    if (!diffs.length) return { movements: [] as InventoryMovement[], shipmentId: undefined as string | undefined };

    // تحقق من توفر المخزون لأي إخراج
    diffs.forEach(({ key, diff }) => {
        if (diff < 0) {
            const [materialId, warehouseId, shelfId] = key.split('||');
            assertReverseQuantity(materialId, warehouseId, shelfId, Math.abs(diff));
        }
    });

    const shipment = createShipment({
        shipmentType: 'adjustment',
        date,
        invoiceId: opts.orderId,
        hasInventoryMovement: true,
        periodCloseDate: opts.periodCloseDate,
        inventoryCloseDate: opts.inventoryCloseDate,
    });

    const costMap = new Map<string, number>();
    existingItems.forEach((item) => {
        const key = `${item.materialId}||${item.warehouseId || ''}||${item.shelfId || ''}`;
        if (item && (item as any).unitPrice !== undefined) {
            costMap.set(key, Number((item as any).unitPrice));
        }
    });
    newItems.forEach((item) => {
        const key = `${item.materialId}||${item.warehouseId || ''}||${item.shelfId || ''}`;
        if (!costMap.has(key) && (item as any).unitPrice !== undefined) {
            costMap.set(key, Number((item as any).unitPrice));
        }
    });

    const movements: InventoryMovement[] = diffs.map(({ key, diff }) => {
        const [materialId, warehouseId, shelfId] = key.split('||');
        const costPerUnitOriginal = costMap.get(key);
        return {
            id: '', // will be overwritten in addInventoryMovements
            date,
            materialId,
            warehouseId,
            shelfId,
            quantityIn: diff > 0 ? diff : 0,
            quantityOut: diff < 0 ? Math.abs(diff) : 0,
            referenceType: 'purchase_order_adjustment',
            referenceId: opts.orderId,
            shipmentId: shipment.id,
            invoiceId: opts.orderId,
            costPerUnitOriginal,
        };
    });

    const created = addInventoryMovements(movements.map(({ id, ...rest }) => rest));

    return { movements: created, shipmentId: shipment.id };
}

/** Files that have a PostgreSQL equivalent and must NOT be written locally when Postgres is active. */
const POSTGRES_GUARDED_WRITE_FILES = new Set([
    '_sequences.json',
    'coupons.json',
    'internal_transfer_docs.json',
    'materials.json',
    'item_units.json',
    'inventory_movements.json',
    'shipments.json',
    'shipment_sequences.json',
    'employees.json',
    'transfer_locks.json',
]);

export function writeData<T>(filename: string, data: T): void {
    if (POSTGRES_GUARDED_WRITE_FILES.has(filename)) {
        // Guard against creating/updating local JSON when PostgreSQL is active for these files.
        return;
    }

    warnLegacyJsonAccess(filename, 'write');

  const filePath = path.join(dbPath, filename);
  try {
    fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
        invalidateDataCache(filename);
  } catch (error) {
    console.error(`Error writing to ${filename}:`, error);
  }
}

// --- Data Access Functions ---

export function getEmployees() {
    if (isPostgresStrictModeEnabled()) {
        warnLegacyJsonAccess('employees.json', 'read');
        return [];
    }

    const employees = readData<Employee[]>('employees.json', MOCK_EMPLOYEES);
    const usedIds = new Set<string>();
    let hasChanges = false;

    const normalized = employees.map((employee, index) => {
        const rawId = String(employee.id || '').trim();
        const isInvalidId = !rawId || rawId.toLowerCase() === 'nan';

        let nextId = rawId;
        if (isInvalidId || usedIds.has(rawId)) {
            hasChanges = true;
            let attempt = 1;
            nextId = `emp-auto-${index + 1}`;
            while (usedIds.has(nextId)) {
                attempt += 1;
                nextId = `emp-auto-${index + 1}-${attempt}`;
            }
        }

        usedIds.add(nextId);

        return {
            ...employee,
            id: nextId,
            role: employee.role || 'employee',
        };
    });

    if (hasChanges) {
        writeData('employees.json', normalized);
    }

    return normalized;
}

export function getSettings() {
    return readDataCached<Record<string, any>>('settings.json', MOCK_SETTINGS, 60_000);
}

export function getPosShiftSessions() {
    return readDataCached<any[]>('posShiftSessions.json', [], 300_000); // Cache for 5 minutes (read-heavy POS operations)
}

export function getPosShiftSessionsFresh() {
    return readData<any[]>('posShiftSessions.json', []);
}

export function savePosShiftSessions(sessions: any[]): void {
    writeData('posShiftSessions.json', sessions);
}

export function getShiftOverrides() {
    return readData<{ [date: string]: { [employeeId: string]: 'first' | 'second' | 'none' } }>('shiftOverrides.json', {});
}

// --- Production Section Data ---
export function getCustomerCategories(): CustomerCategory[] {
    const categories = readData<CustomerCategory[]>('customer_categories.json', [{ id: 'general', name: 'general' }]);
    const usedIds = new Set<string>();
    let maxNumericId = 0;
    let hasChanges = false;

    categories.forEach((category) => {
        const rawId = String(category?.id || '').trim();
        const numericId = Number.parseInt(rawId, 10);
        if (Number.isFinite(numericId) && rawId !== 'general') {
            maxNumericId = Math.max(maxNumericId, numericId);
        }
    });

    const normalized = categories.map((category, index) => {
        const rawId = String(category?.id || '').trim();
        const rawName = String(category?.name || '').trim();
        const isGeneral = rawName.toLowerCase() === 'general' || rawId === 'general';

        let nextId = isGeneral ? 'general' : rawId;
        const invalidId = !nextId || nextId.toLowerCase() === 'nan' || usedIds.has(nextId);
        if (!isGeneral && invalidId) {
            do {
                maxNumericId += 1;
                nextId = String(maxNumericId);
            } while (usedIds.has(nextId) || nextId === 'general');
        }

        if (!isGeneral) {
            const numericId = Number.parseInt(nextId, 10);
            if (Number.isFinite(numericId)) {
                maxNumericId = Math.max(maxNumericId, numericId);
            }
        }

        const nextName = rawName || (nextId === 'general' ? 'general' : `category-${index + 1}`);
        if (nextId !== rawId || nextName !== rawName) {
            hasChanges = true;
        }

        usedIds.add(nextId);
        return {
            ...category,
            id: nextId,
            name: nextName,
            categoryNumber: String(category?.categoryNumber || '').trim(),
            nameEn: String(category?.nameEn || '').trim() || undefined,
            discountPercent: Number.isFinite(Number(category?.discountPercent)) ? Number(category?.discountPercent) : 0,
            description: String(category?.description || '').trim() || undefined,
        };
    });

    if (hasChanges) {
        writeData('customer_categories.json', normalized);
    }

    return normalized;
}

// --- Item Groups ---
export function getItemGroups() {
    return readData<ItemGroup[]>('item_groups.json', []);
}

export function getManagedUnits(): ManagedUnit[] {
    return readData<ManagedUnit[]>('managed_units.json', []).map((unit) => ({
        ...unit,
        unitNumber: String(unit.unitNumber || '').trim(),
        description: String(unit.description || '').trim(),
        unitType: String((unit as any).unitType || '').trim() === 'secondary' ? 'secondary' : 'primary',
        shortCode: String(unit.shortCode || '').trim(),
        notes: String(unit.notes || '').trim(),
    }));
}

function normalizeManagedUnitDescription(value: string): string {
    return String(value || '').trim().replace(/\s+/g, ' ');
}

function normalizeManagedUnitNotes(value?: string): string {
    return String(value || '').trim();
}

function normalizeManagedUnitShortCode(value?: string): string {
    return String(value || '').trim();
}

function normalizeManagedUnitType(value?: string): 'primary' | 'secondary' {
    return String(value || '').trim() === 'secondary' ? 'secondary' : 'primary';
}

export function addManagedUnit(data: { description: string; unitType?: 'primary' | 'secondary'; shortCode?: string; notes?: string }): ManagedUnit {
    return withDbLock(() => {
        const description = normalizeManagedUnitDescription(data.description);
        const unitType = normalizeManagedUnitType(data.unitType);
        const shortCode = normalizeManagedUnitShortCode(data.shortCode);
        const notes = normalizeManagedUnitNotes(data.notes);
        if (!description) {
            throw new Error('Unit description is required');
        }

        const units = getManagedUnits();
        const existing = units.find((unit) => normalizeManagedUnitDescription(unit.description).toLowerCase() === description.toLowerCase());
        if (existing) {
            return existing;
        }

        const nextNumber = String(nextNamedSequence('managed-unit-ref'));

        const newUnit: ManagedUnit = {
            id: nextPrefixedId('munit'),
            unitNumber: nextNumber,
            description,
            unitType,
            shortCode,
            notes,
        };

        units.push(newUnit);
        writeData('managed_units.json', units);
        return newUnit;
    });
}

export function updateManagedUnit(id: string, data: { description?: string; unitType?: 'primary' | 'secondary'; shortCode?: string; notes?: string }): boolean {
    return withDbLock(() => {
        const units = getManagedUnits();
        const index = units.findIndex((unit) => unit.id === id);
        if (index === -1) return false;

        const nextDescription = normalizeManagedUnitDescription(data.description ?? units[index].description);
        const nextUnitType = normalizeManagedUnitType(data.unitType ?? (units[index] as any).unitType);
        const nextShortCode = normalizeManagedUnitShortCode(data.shortCode ?? units[index].shortCode);
        const nextNotes = normalizeManagedUnitNotes(data.notes ?? units[index].notes);
        if (!nextDescription) {
            throw new Error('Unit description is required');
        }

        const duplicate = units.find((unit) => unit.id !== id && normalizeManagedUnitDescription(unit.description).toLowerCase() === nextDescription.toLowerCase());
        if (duplicate) {
            throw new Error('Unit already exists');
        }

        units[index] = {
            ...units[index],
            description: nextDescription,
            unitType: nextUnitType,
            shortCode: nextShortCode,
            notes: nextNotes,
        };
        writeData('managed_units.json', units);
        return true;
    });
}

export function deleteManagedUnit(id: string): boolean {
    return withDbLock(() => {
        const units = getManagedUnits();
        const filtered = units.filter((unit) => unit.id !== id);
        if (filtered.length === units.length) return false;
        writeData('managed_units.json', filtered);
        return true;
    });
}

export function ensureManagedUnit(description: string, unitType: 'primary' | 'secondary' = 'primary'): ManagedUnit | null {
    const normalized = normalizeManagedUnitDescription(description);
    if (!normalized) return null;
    return addManagedUnit({ description: normalized, unitType, shortCode: '', notes: '' });
}

// --- Warehouses & Shelves ---
export function getWarehouses(): Warehouse[] {
    const warehouses = readData<Warehouse[]>('warehouses.json', []);
    return warehouses.map((warehouse, index) => ({
        ...warehouse,
        warehouseNumber: String((warehouse as any)?.warehouseNumber || '').trim() || `WH-${String(index + 1).padStart(4, '0')}`,
        branchName: String((warehouse as any)?.branchName || '').trim() || '',
        departments: Array.isArray((warehouse as any)?.departments)
            ? (warehouse as any).departments.map((entry: any) => String(entry || '').trim()).filter(Boolean)
            : [],
        warehouseType: (['main', 'branch', 'returns', 'transit'].includes(String((warehouse as any)?.warehouseType || '').trim())
            ? String((warehouse as any).warehouseType).trim()
            : 'branch') as Warehouse['warehouseType'],
        managerEmployeeId: String((warehouse as any)?.managerEmployeeId || '').trim() || '',
        managerName: String((warehouse as any)?.managerName || '').trim() || '',
        operatingStatus: (['active', 'stopped', 'inventory'].includes(String((warehouse as any)?.operatingStatus || '').trim())
            ? String((warehouse as any).operatingStatus).trim()
            : (warehouse?.inactive ? 'stopped' : 'active')) as Warehouse['operatingStatus'],
        city: String((warehouse as any)?.city || '').trim() || '',
        address: String((warehouse as any)?.address || '').trim() || String((warehouse as any)?.location || '').trim() || '',
        contactPhone: String((warehouse as any)?.contactPhone || '').trim() || '',
        allowIssueWithoutStock: (warehouse as any)?.allowIssueWithoutStock === true,
        useInPos: (warehouse as any)?.useInPos !== false,
        location: String((warehouse as any)?.location || '').trim() || '',
        notes: String((warehouse as any)?.notes || '').trim() || '',
        inactive: warehouse?.inactive === true,
        shelves: (Array.isArray(warehouse?.shelves) ? warehouse.shelves : []).map((shelf, shelfIndex) => ({
            ...shelf,
            shelfNumber: String((shelf as any)?.shelfNumber || '').trim() || `SH-${String(index + 1).padStart(4, '0')}-${String(shelfIndex + 1).padStart(4, '0')}`,
            notes: String((shelf as any)?.notes || '').trim() || '',
            inactive: shelf?.inactive === true,
        })),
    }));
}

export function getActiveWarehouses(): Warehouse[] {
    return getWarehouses()
        .filter((wh) => !wh.inactive)
        .map((wh) => ({
            ...wh,
            shelves: wh.shelves.filter((shelf) => !shelf.inactive),
        }));
}

export function addWarehouse(data: string | { name: string; branchName?: string; departments?: string[]; warehouseType?: Warehouse['warehouseType']; managerEmployeeId?: string; managerName?: string; operatingStatus?: Warehouse['operatingStatus']; city?: string; address?: string; contactPhone?: string; allowIssueWithoutStock?: boolean; useInPos?: boolean; location?: string; notes?: string }): Warehouse {
    return withDbLock(() => {
        const warehouses = getWarehouses();
        const payload = typeof data === 'string' ? { name: data } : data;
        const trimmed = String(payload?.name || '').trim();
        const branchName = String((payload as any)?.branchName || '').trim().toLowerCase();
        const exists = warehouses.some((wh) => String(wh.name || '').trim().toLowerCase() === trimmed.toLowerCase() && String((wh as any).branchName || '').trim().toLowerCase() === branchName);
        if (exists) {
            throw new Error('Warehouse already exists in this branch');
        }
        const newWarehouse: Warehouse = {
            id: nextPrefixedId('wh'),
            warehouseNumber: `WH-${String(nextNamedSequence('warehouse-ref')).padStart(4, '0')}`,
            name: trimmed,
            branchName: String((payload as any)?.branchName || '').trim(),
            departments: Array.isArray((payload as any)?.departments)
                ? (payload as any).departments.map((entry: any) => String(entry || '').trim()).filter(Boolean)
                : [],
            warehouseType: (['main', 'branch', 'returns', 'transit'].includes(String((payload as any)?.warehouseType || '').trim())
                ? String((payload as any).warehouseType).trim()
                : 'branch') as Warehouse['warehouseType'],
            managerEmployeeId: String((payload as any)?.managerEmployeeId || '').trim(),
            managerName: String((payload as any)?.managerName || '').trim(),
            operatingStatus: (['active', 'stopped', 'inventory'].includes(String((payload as any)?.operatingStatus || '').trim())
                ? String((payload as any).operatingStatus).trim()
                : 'active') as Warehouse['operatingStatus'],
            city: String((payload as any)?.city || '').trim(),
            address: String((payload as any)?.address || '').trim(),
            contactPhone: String((payload as any)?.contactPhone || '').trim(),
            allowIssueWithoutStock: (payload as any)?.allowIssueWithoutStock === true,
            useInPos: (payload as any)?.useInPos !== false,
            location: String(payload?.location || '').trim(),
            notes: String(payload?.notes || '').trim(),
            inactive: String((payload as any)?.operatingStatus || '').trim() && String((payload as any)?.operatingStatus || '').trim() !== 'active',
            shelves: [],
        };
        warehouses.push(newWarehouse);
        writeData('warehouses.json', warehouses);
        return newWarehouse;
    });
}

export function updateWarehouseData(id: string, data: { name?: string; branchName?: string; departments?: string[]; warehouseType?: Warehouse['warehouseType']; managerEmployeeId?: string; managerName?: string; operatingStatus?: Warehouse['operatingStatus']; city?: string; address?: string; contactPhone?: string; allowIssueWithoutStock?: boolean; useInPos?: boolean; location?: string; notes?: string; inactive?: boolean }): Warehouse | null {
    return withDbLock(() => {
        const warehouses = getWarehouses();
        const index = warehouses.findIndex((warehouse) => warehouse.id === id);
        if (index === -1) return null;

        const current = warehouses[index];
        const nextName = String(data.name ?? current.name).trim();
        if (!nextName) {
            throw new Error('Warehouse name is required');
        }

        const nextBranchName = String(data.branchName ?? (current as any).branchName ?? '').trim();
        const duplicate = warehouses.some((warehouse) =>
            warehouse.id !== id
            && String(warehouse.name || '').trim().toLowerCase() === nextName.toLowerCase()
            && String((warehouse as any).branchName || '').trim().toLowerCase() === nextBranchName.toLowerCase()
        );
        if (duplicate) {
            throw new Error('Warehouse already exists in this branch');
        }

        const nextWarehouse: Warehouse = {
            ...current,
            warehouseNumber: current.warehouseNumber,
            name: nextName,
            branchName: nextBranchName,
            departments: Array.isArray(data.departments)
                ? data.departments.map((entry) => String(entry || '').trim()).filter(Boolean)
                : (Array.isArray((current as any).departments)
                    ? (current as any).departments.map((entry: any) => String(entry || '').trim()).filter(Boolean)
                    : []),
            warehouseType: (['main', 'branch', 'returns', 'transit'].includes(String(data.warehouseType ?? (current as any).warehouseType ?? '').trim())
                ? String(data.warehouseType ?? (current as any).warehouseType).trim()
                : 'branch') as Warehouse['warehouseType'],
            managerEmployeeId: String(data.managerEmployeeId ?? (current as any).managerEmployeeId ?? '').trim(),
            managerName: String(data.managerName ?? (current as any).managerName ?? '').trim(),
            operatingStatus: (['active', 'stopped', 'inventory'].includes(String(data.operatingStatus ?? (current as any).operatingStatus ?? '').trim())
                ? String(data.operatingStatus ?? (current as any).operatingStatus).trim()
                : ((current.inactive === true ? 'stopped' : 'active'))) as Warehouse['operatingStatus'],
            city: String(data.city ?? (current as any).city ?? '').trim(),
            address: String(data.address ?? (current as any).address ?? '').trim(),
            contactPhone: String(data.contactPhone ?? (current as any).contactPhone ?? '').trim(),
            allowIssueWithoutStock: data.allowIssueWithoutStock !== undefined ? data.allowIssueWithoutStock : ((current as any).allowIssueWithoutStock === true),
            useInPos: data.useInPos !== undefined ? data.useInPos : ((current as any).useInPos !== false),
            location: String(data.location ?? current.location ?? '').trim(),
            notes: String(data.notes ?? current.notes ?? '').trim(),
            inactive: data.inactive !== undefined
                ? data.inactive
                : (String(data.operatingStatus ?? (current as any).operatingStatus ?? '').trim()
                    ? String(data.operatingStatus ?? (current as any).operatingStatus).trim() !== 'active'
                    : current.inactive === true),
        };

        warehouses[index] = nextWarehouse;
        writeData('warehouses.json', warehouses);
        return nextWarehouse;
    });
}

export function deleteWarehouseData(id: string): boolean {
    return withDbLock(() => {
        const warehouses = getWarehouses();
        const filtered = warehouses.filter((warehouse) => warehouse.id !== id);
        if (filtered.length === warehouses.length) return false;
        writeData('warehouses.json', filtered);
        return true;
    });
}

export function addShelfToWarehouse(warehouseId: string, data: { name: string; notes?: string }): Shelf {
    return withDbLock(() => {
        const warehouses = getWarehouses();
        const index = warehouses.findIndex((warehouse) => warehouse.id === warehouseId);
        if (index === -1) throw new Error('Warehouse not found');

        const warehouse = warehouses[index];
        const shelves = Array.isArray(warehouse.shelves) ? warehouse.shelves : [];
        const shelfName = String(data.name || '').trim();
        if (!shelfName) throw new Error('Shelf name is required');
        const duplicate = shelves.some((shelf) => String(shelf.name || '').trim().toLowerCase() === shelfName.toLowerCase());
        if (duplicate) throw new Error('Shelf already exists');

        const nextSequence = shelves.reduce((max, shelf) => Math.max(max, Number(shelf.sequence || 0)), 0) + 1;
        const newShelf: Shelf = {
            id: `${warehouseId}-s${nextSequence}`,
            shelfNumber: `SH-${String(nextNamedSequence('shelf-ref')).padStart(5, '0')}`,
            name: shelfName,
            sequence: nextSequence,
            notes: String(data.notes || '').trim(),
            inactive: false,
        };

        warehouses[index] = {
            ...warehouse,
            shelves: [...shelves, newShelf],
        };
        writeData('warehouses.json', warehouses);
        return newShelf;
    });
}

export function updateShelfInWarehouse(warehouseId: string, shelfId: string, data: { name?: string; notes?: string; inactive?: boolean }): Shelf | null {
    return withDbLock(() => {
        const warehouses = getWarehouses();
        const warehouseIndex = warehouses.findIndex((warehouse) => warehouse.id === warehouseId);
        if (warehouseIndex === -1) return null;

        const warehouse = warehouses[warehouseIndex];
        const shelves = Array.isArray(warehouse.shelves) ? warehouse.shelves : [];
        const shelfIndex = shelves.findIndex((shelf) => shelf.id === shelfId);
        if (shelfIndex === -1) return null;

        const current = shelves[shelfIndex];
        const nextName = String(data.name ?? current.name).trim();
        if (!nextName) throw new Error('Shelf name is required');
        const duplicate = shelves.some((shelf) => shelf.id !== shelfId && String(shelf.name || '').trim().toLowerCase() === nextName.toLowerCase());
        if (duplicate) throw new Error('Shelf already exists');

        const nextShelf: Shelf = {
            ...current,
            shelfNumber: current.shelfNumber,
            name: nextName,
            notes: String(data.notes ?? current.notes ?? '').trim(),
            inactive: data.inactive !== undefined ? data.inactive : current.inactive === true,
        };

        shelves[shelfIndex] = nextShelf;
        warehouses[warehouseIndex] = { ...warehouse, shelves: [...shelves] };
        writeData('warehouses.json', warehouses);
        return nextShelf;
    });
}

export function deleteShelfFromWarehouse(warehouseId: string, shelfId: string): boolean {
    return withDbLock(() => {
        const warehouses = getWarehouses();
        const warehouseIndex = warehouses.findIndex((warehouse) => warehouse.id === warehouseId);
        if (warehouseIndex === -1) return false;

        const warehouse = warehouses[warehouseIndex];
        const shelves = Array.isArray(warehouse.shelves) ? warehouse.shelves : [];
        const filtered = shelves.filter((shelf) => shelf.id !== shelfId);
        if (filtered.length === shelves.length) return false;

        warehouses[warehouseIndex] = { ...warehouse, shelves: filtered };
        writeData('warehouses.json', warehouses);
        return true;
    });
}

export function addShelvesToWarehouse(
    warehouseId: string,
    data: { prefix: string; separator?: string; startNumber: number; count: number }
): { added: Shelf[]; skipped: string[] } {
    return withDbLock(() => {
        const warehouses = getWarehouses();
        const idx = warehouses.findIndex((wh) => wh.id === warehouseId);
        if (idx === -1) {
            throw new Error('Warehouse not found');
        }

        const warehouse = warehouses[idx];
        const shelves = Array.isArray(warehouse.shelves) ? warehouse.shelves : [];
        const existingNames = new Set(shelves.map((shelf) => String(shelf.name || '').trim().toLowerCase()));
        const existingMaxSequence = shelves.reduce((max, shelf) => Math.max(max, shelf.sequence || 0), 0);
        let nextSequence = existingMaxSequence;

        const prefix = String(data.prefix || '').trim();
        const separator = data.separator ? String(data.separator) : '';
        const startNumber = Number(data.startNumber);
        const count = Number(data.count);

        const added: Shelf[] = [];
        const skipped: string[] = [];

        for (let i = 0; i < count; i += 1) {
            const number = startNumber + i;
            const shelfName = `${prefix}${separator}${number}`.trim();
            if (!shelfName) continue;
            if (existingNames.has(shelfName.toLowerCase())) {
                skipped.push(shelfName);
                continue;
            }
            nextSequence += 1;
            const newShelf: Shelf = {
                id: `${warehouseId}-s${nextSequence}`,
                shelfNumber: `SH-${String(nextNamedSequence('shelf-ref')).padStart(5, '0')}`,
                name: shelfName,
                sequence: nextSequence,
                notes: '',
                inactive: false,
            };
            existingNames.add(shelfName.toLowerCase());
            added.push(newShelf);
        }

        if (added.length > 0) {
            warehouse.shelves = [...shelves, ...added];
            warehouses[idx] = warehouse;
            writeData('warehouses.json', warehouses);
        }

        return { added, skipped };
    });
}

// --- Internal Transfers & Stock Balances ---
export function getInternalTransferDocuments(): InternalTransferDocument[] {
    const docs = readData<InternalTransferDocument[]>('internal_transfer_docs.json', []);
    return docs.map((doc) => ({
        ...doc,
        receiptStatus: doc.receiptStatus ?? 'pending',
    }));
}

export function getInternalTransferDocumentById(id: string): InternalTransferDocument | undefined {
    return getInternalTransferDocuments().find((doc) => doc.id === id);
}

export function getPendingInternalTransferDocumentsForWarehouses(warehouseIds: string[]): InternalTransferDocument[] {
    const allowedIds = new Set(warehouseIds.filter(Boolean));
    if (allowedIds.size === 0) return [];

    return getInternalTransferDocuments().filter((doc) => {
        if (doc.status !== 'active') return false;
        const receiptStatus = doc.receiptStatus ?? 'pending';
        if (receiptStatus !== 'pending') return false; // لا نظهر المستلمة جزئياً أو بانتظار اعتماد زيادة
        return doc.items.some((item) => allowedIds.has(item.toWarehouseId));
    });
}

export function getPendingOverageApprovalsForWarehouses(warehouseIds: string[]): InternalTransferDocument[] {
    const allowedIds = new Set(warehouseIds.filter(Boolean));
    if (allowedIds.size === 0) return [];

    return getInternalTransferDocuments().filter((doc) => {
        if (doc.status !== 'active') return false;
        return doc.items.some(
            (item) => item.overageStatus === 'pending' && allowedIds.has(item.fromWarehouseId)
        );
    });
}

export function confirmInternalTransferReceipt(
    id: string,
    data: {
        items: Array<{
            lineId?: string;
            receivedQuantity: number;
            toShelfId?: string;
            receiptDistributions?: Array<{ shelfId: string; quantity: number }>;
        }>;
        receivedBy: string;
        receiptNote?: string;
    }
): InternalTransferDocument | null {
    const docs = getInternalTransferDocuments();
    const index = docs.findIndex((doc) => doc.id === id);
    if (index === -1 || docs[index].status !== 'active') return null;

    const itemByLineId = new Map<string, {
        receivedQuantity: number;
        toShelfId?: string;
        receiptDistributions?: Array<{ shelfId: string; quantity: number }>;
    }>();
    data.items.forEach((item) => {
        if (item.lineId) {
            itemByLineId.set(item.lineId, {
                receivedQuantity: item.receivedQuantity,
                toShelfId: item.toShelfId,
                receiptDistributions: item.receiptDistributions,
            });
        }
    });

    const nextItems = docs[index].items.map((item, itemIndex) => {
        const lineId = item.lineId || `line-${itemIndex + 1}`;
        const incoming = itemByLineId.get(lineId);
        const reported = incoming ? incoming.receivedQuantity : item.receivedQuantity ?? 0;
        const baseReceived = Math.max(0, Math.min(item.quantity, reported));
        const overReceivedQuantity = Math.max(0, reported - item.quantity);
        const rejectedQuantity = Math.max(0, item.quantity - baseReceived);
        const overageStatus = overReceivedQuantity > 0 ? ('pending' as const) : undefined;
        const overApprovedQuantity = overReceivedQuantity > 0 ? item.overApprovedQuantity ?? 0 : 0;
        const receiptDistributions = (incoming?.receiptDistributions || [])
            .map((entry) => ({
                shelfId: String(entry.shelfId || '').trim(),
                quantity: Number(entry.quantity || 0),
            }))
            .filter((entry) => entry.shelfId && Number.isFinite(entry.quantity) && entry.quantity > 0);
        const nextToShelfId = receiptDistributions[0]?.shelfId || incoming?.toShelfId || item.toShelfId;
        return {
            ...item,
            lineId,
            toShelfId: nextToShelfId,
            receiptDistributions,
            receivedQuantity: baseReceived,
            rejectedQuantity,
            overReceivedQuantity,
            overApprovedQuantity,
            overageStatus,
        };
    });

    const hasPendingOverage = nextItems.some((item) => item.overageStatus === 'pending');
    const hasShortage = nextItems.some((item) => (item.rejectedQuantity ?? 0) > 0);
    const receiptStatus: InternalTransferDocument['receiptStatus'] = hasPendingOverage
        ? 'pending-approval'
        : hasShortage
        ? 'partial'
        : 'received';

    docs[index] = {
        ...docs[index],
        items: nextItems,
        receiptStatus,
        receivedBy: data.receivedBy,
        receivedAt: new Date().toISOString(),
        receiptNote: data.receiptNote,
    };

    writeData('internal_transfer_docs.json', docs);
    return docs[index];
}

export function approveInternalTransferOverage(
    id: string,
    data: { approved: boolean; approvedBy: string; lineItems?: Array<{ lineId?: string; sourceShelfId?: string }> }
): InternalTransferDocument | null {
    const docs = getInternalTransferDocuments();
    const index = docs.findIndex((doc) => doc.id === id);
    if (index === -1 || docs[index].status !== 'active') return null;

    const allowedLineIds = new Set(data.lineItems?.map((item) => item.lineId).filter(Boolean) ?? []);
    const shelfByLineId = new Map<string, string>();
    data.lineItems?.forEach((item) => {
        if (item.lineId && item.sourceShelfId) {
            shelfByLineId.set(item.lineId, item.sourceShelfId);
        }
    });

    const nextItems = docs[index].items.map((item) => {
        if (item.overageStatus !== 'pending') return item;
        if (allowedLineIds.size > 0 && (!item.lineId || !allowedLineIds.has(item.lineId))) {
            return item;
        }
        const approved = data.approved;
        const sourceShelfId = item.lineId ? shelfByLineId.get(item.lineId) : undefined;
        return {
            ...item,
            overageStatus: approved ? ('approved' as const) : ('rejected' as const),
            overApprovedQuantity: approved ? item.overReceivedQuantity ?? 0 : 0,
            overageSourceShelfId: approved ? (sourceShelfId || item.overageSourceShelfId || item.fromShelfId) : undefined,
        };
    });

    const hasPendingOverage = nextItems.some((item) => item.overageStatus === 'pending');
    const hasShortage = nextItems.some((item) => (item.rejectedQuantity ?? 0) > 0);
    const receiptStatus: InternalTransferDocument['receiptStatus'] = hasPendingOverage
        ? 'pending-approval'
        : hasShortage
        ? 'partial'
        : 'received';

    docs[index] = {
        ...docs[index],
        items: nextItems,
        receiptStatus,
        receivedBy: docs[index].receivedBy || data.approvedBy,
    };

    writeData('internal_transfer_docs.json', docs);
    return docs[index];
}

export function resolveInternalTransferShortage(
    id: string,
    data: { resolutionType: 'supplier' | 'employee' | 'wastage' | 'resend'; note?: string; resolvedBy: string }
):
    | { success: true; journalEntryId?: string; totalValue?: number }
    | { error: string; missingCostItems?: Array<{ materialId: string; materialName: string }> } {
    const docs = getInternalTransferDocuments();
    const index = docs.findIndex((doc) => doc.id === id);
    if (index === -1) return { error: 'Document not found' };
    const doc = docs[index];
    if (doc.status !== 'active') return { error: 'Document is not active' };

    const materials = getMaterials();
    const materialById = new Map(materials.map((m) => [m.id, m]));
    const sourceOrder = doc.sourcePurchaseOrderId ? getPurchaseOrderById(doc.sourcePurchaseOrderId) : undefined;
    const sourceOrderItemByMaterial = new Map(
        (sourceOrder?.items || []).map((item) => [String(item.materialId || ''), item])
    );

    const shortages = doc.items.map((item, idx) => {
        const quantity = item.rejectedQuantity ?? (item.receivedQuantity === undefined
            ? item.quantity
            : Math.max(0, item.quantity - (item.receivedQuantity || 0)));
        const material = materialById.get(item.materialId);
        const snapshotUnitCost = Number(item.unitCostSnapshot || 0);
        const sourceOrderUnitCost = Number(sourceOrderItemByMaterial.get(item.materialId)?.unitPrice || 0);
        const liveUnitCost = Number(material?.purchasePrice ?? material?.salePrice ?? 0) || 0;
        const unitCost = snapshotUnitCost > 0
            ? snapshotUnitCost
            : sourceOrderUnitCost > 0
                ? sourceOrderUnitCost
                : liveUnitCost;
        return {
            quantity,
            materialId: item.materialId,
            lineId: item.lineId || `line-${idx + 1}`,
            unitCost,
        };
    }).filter((item) => (item.quantity || 0) > 0);

    const totalValue = shortages.reduce((sum, item) => sum + item.quantity * item.unitCost, 0);

    if (shortages.length === 0) {
        return { error: 'No shortage detected for this document.' };
    }

    if (data.resolutionType !== 'resend' && totalValue <= 0) {
        const missingCostMap = new Map<string, { materialId: string; materialName: string }>();
        shortages.forEach((item) => {
            if (item.unitCost > 0) return;
            const material = materialById.get(item.materialId);
            missingCostMap.set(item.materialId, {
                materialId: item.materialId,
                materialName: material?.name || item.materialId,
            });
        });

        return {
            error: 'TRANSFER_SHORTAGE_MISSING_COST_VALUES',
            missingCostItems: Array.from(missingCostMap.values()),
        };
    }

    let journalEntryId: string | undefined;

    if (data.resolutionType !== 'resend') {
        const entry = createInventoryShortageJournalEntry({
            doc,
            totalValue,
            createdBy: data.resolvedBy,
            resolutionType: data.resolutionType,
            note: data.note,
        });
        journalEntryId = entry.id;
    } else {
        // إعادة الإرسال تعني إعادة فتح الارسالية للاستلام مجدداً
        doc.receiptStatus = 'pending';
    }

    docs[index] = {
        ...doc,
        shortageResolutionType: data.resolutionType,
        shortageResolutionNote: data.note,
        shortageResolvedBy: data.resolvedBy,
        shortageResolvedAt: new Date().toISOString(),
        shortageJournalEntryId: journalEntryId,
        shortageValue: totalValue,
    };

    writeData('internal_transfer_docs.json', docs);

    return { success: true, journalEntryId, totalValue };
}

function generateInternalTransferDocumentNumber(): string {
    const docs = getInternalTransferDocuments();
    const now = new Date();
    const year = now.getFullYear();
    const currentYearPrefix = `ITD/${year}/`;

    const currentYearDocs = docs.filter((doc) => doc.transferNumber?.startsWith(currentYearPrefix));

    let maxSeq = 0;
    currentYearDocs.forEach((doc) => {
        const parts = doc.transferNumber.split('/');
        if (parts.length === 3) {
            const seq = parseInt(parts[2], 10);
            if (!isNaN(seq) && seq > maxSeq) {
                maxSeq = seq;
            }
        }
    });

    const newSeq = maxSeq + 1;
    return `${currentYearPrefix}${newSeq.toString().padStart(4, '0')}`;
}

export function addInternalTransferDocument(
    data: Omit<InternalTransferDocument, 'id' | 'transferNumber' | 'status' | 'createdAt'> & { createdBy: string }
): InternalTransferDocument {
    return withDbLock(() => {
        const docs = getInternalTransferDocuments();
        const newDoc: InternalTransferDocument = {
            ...data,
            id: nextPrefixedId('itd'),
            transferNumber: generateInternalTransferDocumentNumber(),
            status: 'active',
            receiptStatus: data.receiptStatus ?? 'pending',
            createdAt: new Date().toISOString(),
        };
        docs.unshift(newDoc);
        writeData('internal_transfer_docs.json', docs);
        return newDoc;
    });
}

export function updateInternalTransferDocument(
    id: string,
    data: Partial<Omit<InternalTransferDocument, 'id' | 'transferNumber' | 'status' | 'createdBy' | 'createdAt' | 'cancelledBy' | 'cancelledAt' | 'cancelledReason'>>
): InternalTransferDocument | null {
    const docs = getInternalTransferDocuments();
    const index = docs.findIndex((doc) => doc.id === id);
    if (index === -1) return null;
    if (docs[index].status !== 'active') return null;

    docs[index] = {
        ...docs[index],
        ...data,
    };
    writeData('internal_transfer_docs.json', docs);
    return docs[index];
}

export function cancelInternalTransferDocument(id: string, userId: string, reason?: string): boolean {
    const docs = getInternalTransferDocuments();
    const index = docs.findIndex((doc) => doc.id === id);
    if (index === -1 || docs[index].status !== 'active') return false;

    docs[index].status = 'cancelled';
    docs[index].cancelledBy = userId;
    docs[index].cancelledAt = new Date().toISOString();
    docs[index].cancelledReason = reason;
    writeData('internal_transfer_docs.json', docs);
    return true;
}

export function getInternalTransfers(): InternalTransfer[] {
    return readData<InternalTransfer[]>('internal_transfers.json', []);
}

function generateTransferNumber(): string {
    const transfers = getInternalTransfers();
    const now = new Date();
    const year = now.getFullYear();
    const currentYearPrefix = `IT/${year}/`;
    
    const currentYearTransfers = transfers.filter((t) => t.transferNumber?.startsWith(currentYearPrefix));
    
    let maxSeq = 0;
    currentYearTransfers.forEach((t) => {
        const parts = t.transferNumber.split('/');
        if (parts.length === 3) {
            const seq = parseInt(parts[2], 10);
            if (!isNaN(seq) && seq > maxSeq) {
                maxSeq = seq;
            }
        }
    });
    
    const newSeq = maxSeq + 1;
    return `${currentYearPrefix}${newSeq.toString().padStart(4, '0')}`;
}

export function addInternalTransfer(data: Omit<InternalTransfer, 'id' | 'createdAt' | 'transferNumber'> & { createdBy: string }): InternalTransfer {
    return withDbLock(() => {
        const transfers = getInternalTransfers();
        const newTransfer: InternalTransfer = {
            ...data,
            id: nextPrefixedId('it'),
            transferNumber: generateTransferNumber(),
            createdAt: new Date().toISOString(),
        };
        transfers.unshift(newTransfer);
        writeData('internal_transfers.json', transfers);
        return newTransfer;
    });
}

export function updateInternalTransfer(id: string, data: Partial<Omit<InternalTransfer, 'id' | 'createdAt' | 'transferNumber'>>): InternalTransfer | null {
    const transfers = getInternalTransfers();
    const index = transfers.findIndex((t) => t.id === id);
    if (index === -1) return null;
    
    transfers[index] = {
        ...transfers[index],
        ...data,
    };
    writeData('internal_transfers.json', transfers);
    return transfers[index];
}

export function deleteInternalTransfer(id: string): boolean {
    const transfers = getInternalTransfers();
    const filtered = transfers.filter((t) => t.id !== id);
    if (filtered.length === transfers.length) return false;
    writeData('internal_transfers.json', filtered);
    return true;
}

// --- Transfer Locks ---
export function getTransferLocks(): TransferLock[] {
    
    const now = new Date().toISOString();
    return Array.from(postgresTransferLocks.values()).filter((lock) => lock.expiresAt > now);
    
    return readData<TransferLock[]>('transfer_locks.json', []);
}

function cleanExpiredLocks(): void {
    const now = new Date().toISOString();
    Array.from(postgresTransferLocks.entries()).forEach(([materialId, lock]) => {
        if (lock.expiresAt <= now) {
            postgresTransferLocks.delete(materialId);
        }
    });
}

export function acquireTransferLock(materialId: string, userId: string, userName: string): { success: boolean; lockedBy?: string } {
    cleanExpiredLocks();

    
    const existingLock = postgresTransferLocks.get(materialId);
    if (existingLock) {
        if (existingLock.userId === userId) {
            const now = new Date();
            existingLock.expiresAt = new Date(now.getTime() + 5 * 60 * 1000).toISOString();
            postgresTransferLocks.set(materialId, existingLock);
            return { success: true };
        }
        return { success: false, lockedBy: existingLock.userName };
    }

    const now = new Date();
    postgresTransferLocks.set(materialId, {
        materialId,
        userId,
        userName,
        lockedAt: now.toISOString(),
        expiresAt: new Date(now.getTime() + 5 * 60 * 1000).toISOString(),
    });
    return { success: true };
}

export function releaseTransferLock(materialId: string, userId: string): void {
    const existingLock = postgresTransferLocks.get(materialId);
    if (existingLock?.userId === userId) {
        postgresTransferLocks.delete(materialId);
    }
}

type SupplierPaymentLock = {
    beneficiaryId: string;
    userId: string;
    userName: string;
    lockedAt: string;
    expiresAt: string;
};

export function getSupplierPaymentLocks(): SupplierPaymentLock[] {
    return readData<SupplierPaymentLock[]>('supplier_payment_locks.json', []);
}

function cleanExpiredSupplierPaymentLocks(): void {
    const locks = getSupplierPaymentLocks();
    const now = new Date().toISOString();
    const validLocks = locks.filter((lock) => lock.expiresAt > now);
    if (validLocks.length !== locks.length) {
        writeData('supplier_payment_locks.json', validLocks);
    }
}

export function getSupplierPaymentLockByBeneficiary(beneficiaryId: string): SupplierPaymentLock | null {
    return withDbLock(() => {
        cleanExpiredSupplierPaymentLocks();
        const locks = getSupplierPaymentLocks();
        return locks.find((lock) => lock.beneficiaryId === beneficiaryId) || null;
    });
}

export function acquireSupplierPaymentLock(beneficiaryId: string, userId: string, userName: string): { success: boolean; lockedBy?: string } {
    return withDbLock(() => {
        cleanExpiredSupplierPaymentLocks();
        const locks = getSupplierPaymentLocks();
        const existing = locks.find((lock) => lock.beneficiaryId === beneficiaryId);
        if (existing) {
            if (existing.userId !== userId) {
                return { success: false, lockedBy: existing.userName };
            }
            existing.expiresAt = new Date(Date.now() + (5 * 60 * 1000)).toISOString();
            writeData('supplier_payment_locks.json', locks);
            return { success: true };
        }

        locks.push({
            beneficiaryId,
            userId,
            userName,
            lockedAt: new Date().toISOString(),
            expiresAt: new Date(Date.now() + (5 * 60 * 1000)).toISOString(),
        });
        writeData('supplier_payment_locks.json', locks);
        return { success: true };
    });
}

export function releaseSupplierPaymentLock(beneficiaryId: string, userId: string): void {
    return withDbLock(() => {
        const locks = getSupplierPaymentLocks();
        const filtered = locks.filter((lock) => !(lock.beneficiaryId === beneficiaryId && lock.userId === userId));
        if (filtered.length !== locks.length) {
            writeData('supplier_payment_locks.json', filtered);
        }
    });
}

type StockKey = string;

function makeStockKey(materialId: string, warehouseId: string, shelfId: string): StockKey {
    return `${materialId}||${warehouseId}||${shelfId}`;
}

export function getStockBalances(): Array<{ materialId: string; warehouseId: string; shelfId: string; quantity: number }> {
    const balances = new Map<StockKey, number>();

    const addQty = (materialId: string, warehouseId?: string, shelfId?: string, qty?: number) => {
        const wh = String(warehouseId || '').trim();
        const shelf = String(shelfId || '').trim();
        const amount = Number(qty || 0);
        if (!materialId || !wh || !Number.isFinite(amount) || amount === 0) return;
        const key = makeStockKey(materialId, wh, shelf);
        balances.set(key, (balances.get(key) || 0) + amount);
    };

    const purchaseOrders = getInvoicesPurchase().filter((o) => o.status !== 'cancelled');
    purchaseOrders.forEach((order) => {
        order.items.forEach((item) => {
            addQty(item.materialId, item.warehouseId, item.shelfId, item.quantity);
        });
    });

    const purchaseReturns = getPurchaseReturns().filter((ret) => ret.status !== 'draft');
    purchaseReturns.forEach((ret) => {
        ret.items.forEach((item) => {
            addQty(item.materialId, item.warehouseId, item.shelfId, -item.quantity);
        });
    });

    const salesInvoices = getInvoices().filter((inv) => inv.status !== 'cancelled');
    salesInvoices.forEach((inv) => {
        const sign = inv.type === 'return' ? 1 : -1;
        inv.items.forEach((item) => {
            addQty(item.materialId, item.warehouseId, item.shelfId, item.quantity * sign);
        });
    });

    const transfers = getInternalTransfers();
    transfers.forEach((tr) => {
        addQty(tr.materialId, tr.fromWarehouseId, tr.fromShelfId, -tr.quantity);
        addQty(tr.materialId, tr.toWarehouseId, tr.toShelfId, tr.quantity);
    });

    const transferDocs = getInternalTransferDocuments();
    transferDocs.forEach((doc) => {
        if (doc.status !== 'active') return;
        doc.items.forEach((item) => {
            const toWarehouseId = doc.enableWarehouseTransfer ? item.toWarehouseId : item.fromWarehouseId;
            const toShelfId = doc.enableShelfTransfer ? item.toShelfId : item.fromShelfId;
            const receiptStatus = doc.receiptStatus ?? 'pending';
            const baseReceived = receiptStatus === 'pending'
                ? item.quantity
                : (item.receivedQuantity ?? item.quantity);
            const overApproved = item.overApprovedQuantity ??
                (item.overageStatus === 'approved' ? item.overReceivedQuantity ?? 0 : 0);
            const safeBaseReceived = Math.max(0, baseReceived);
            const safeOverApproved = Math.max(0, overApproved);
            const transferQty = safeBaseReceived + safeOverApproved;
            const overageSourceShelfId = item.overageSourceShelfId || item.fromShelfId;
            addQty(item.materialId, item.fromWarehouseId, item.fromShelfId, -safeBaseReceived);
            if (safeOverApproved > 0) {
                addQty(item.materialId, item.fromWarehouseId, overageSourceShelfId, -safeOverApproved);
            }

            const savedDistributions = Array.isArray(item.receiptDistributions)
                ? item.receiptDistributions
                    .map((entry) => ({
                        shelfId: String(entry?.shelfId || '').trim(),
                        quantity: Number(entry?.quantity || 0),
                    }))
                    .filter((entry) => entry.shelfId && Number.isFinite(entry.quantity) && entry.quantity > 0)
                : [];

            if (savedDistributions.length > 0 && receiptStatus !== 'pending') {
                const distributedTotal = savedDistributions.reduce((sum, entry) => sum + entry.quantity, 0);
                const factor = distributedTotal > 0 ? transferQty / distributedTotal : 0;
                savedDistributions.forEach((entry) => {
                    addQty(item.materialId, toWarehouseId, entry.shelfId, entry.quantity * factor);
                });
            } else {
                addQty(item.materialId, toWarehouseId, toShelfId, transferQty);
            }
        });
    });

    return Array.from(balances.entries())
        .map(([key, quantity]) => {
            const [materialId, warehouseId, shelfId] = key.split('||');
            return { materialId, warehouseId, shelfId, quantity };
        })
        .filter((entry) => entry.quantity !== 0);
}

export function getStockLocationsByMaterial(materialIds: string[]): MaterialStockReport[] {
    const balances = getStockBalances();
    const warehouses = getWarehouses();
    const warehouseById = new Map(warehouses.map((wh) => [wh.id, wh]));
    const shelfById = new Map<string, { name: string }>();
    warehouses.forEach((wh) => {
        wh.shelves.forEach((shelf) => {
            shelfById.set(shelf.id, { name: shelf.name });
        });
    });

    const filterSet = new Set(materialIds);
    const byMaterial = new Map<string, StockLocationBalance[]>();

    balances.forEach((entry) => {
        if (!filterSet.has(entry.materialId)) return;
        const warehouseName = warehouseById.get(entry.warehouseId)?.name || entry.warehouseId;
        const shelfName = shelfById.get(entry.shelfId)?.name || entry.shelfId;
        const location: StockLocationBalance = {
            materialId: entry.materialId,
            warehouseId: entry.warehouseId,
            warehouseName,
            shelfId: entry.shelfId,
            shelfName,
            quantity: entry.quantity,
        };
        const list = byMaterial.get(entry.materialId) || [];
        list.push(location);
        byMaterial.set(entry.materialId, list);
    });

    return Array.from(filterSet).map((materialId) => ({
        materialId,
        locations: byMaterial.get(materialId) || [],
    }));
}

export function getMaterialMovementReport(materialId?: string): MaterialMovementEntry[] {
    const materials = getMaterials();
    const materialById = new Map(materials.map((material) => [material.id, material]));
    const warehouses = getWarehouses();
    const warehouseById = new Map(warehouses.map((warehouse) => [warehouse.id, warehouse.name]));
    const purchaseOrdersById = new Map(getInvoicesPurchase().map((order) => [order.id, order]));
    const purchaseReturnsById = new Map(getPurchaseReturns().map((ret) => [ret.id, ret]));
    const disassemblyRecords = Array.isArray((getSettings() as any)?.disassemblyRecords)
        ? (((getSettings() as any).disassemblyRecords as any[]).filter((entry) => entry && typeof entry === 'object'))
        : [];
    const disassemblyRecordById = new Map(disassemblyRecords.map((record) => [String(record.id || '').trim(), record]));
    const shelfById = new Map<string, string>();
    warehouses.forEach((warehouse) => {
        (warehouse.shelves || []).forEach((shelf) => {
            shelfById.set(shelf.id, shelf.name);
        });
    });

    const filterMaterialId = String(materialId || '').trim();
    const shouldInclude = (candidateMaterialId: string) => !filterMaterialId || candidateMaterialId === filterMaterialId;

    const movements: MaterialMovementEntry[] = [];
    const pushMovement = (entry: Omit<MaterialMovementEntry, 'id' | 'materialName' | 'warehouseName' | 'shelfName'>) => {
        if (!shouldInclude(entry.materialId)) return;
        const materialName = materialById.get(entry.materialId)?.name || entry.materialId;
        const warehouseName = entry.warehouseId ? (warehouseById.get(entry.warehouseId) || entry.warehouseId) : undefined;
        const shelfName = entry.shelfId ? (shelfById.get(entry.shelfId) || entry.shelfId) : undefined;
        movements.push({
            ...entry,
            id: `${entry.referenceType}-${entry.referenceNumber}-${entry.materialId}-${entry.direction}-${movements.length + 1}`,
            materialName,
            warehouseName,
            shelfName,
        });
    };

    const purchaseOrders = getInvoicesPurchase().filter((order) => order.status !== 'cancelled');
    purchaseOrders.forEach((order) => {
        order.items.forEach((item) => {
            pushMovement({
                date: order.date,
                materialId: item.materialId,
                referenceType: 'purchase',
                referenceNumber: order.orderNumber,
                direction: 'in',
                quantity: Number(item.quantity || 0),
                warehouseId: item.warehouseId,
                shelfId: item.shelfId,
                note: order.supplierName,
            });
        });
    });

    const salesInvoices = getInvoices().filter((invoice) => invoice.status !== 'cancelled');
    salesInvoices.forEach((invoice) => {
        invoice.items.forEach((item) => {
            const isSale = (invoice.type || 'sale') !== 'return';
            pushMovement({
                date: invoice.date,
                materialId: item.materialId,
                referenceType: isSale ? 'sale' : 'sale-return',
                referenceNumber: invoice.invoiceNumber,
                direction: isSale ? 'out' : 'in',
                quantity: Number(item.quantity || 0),
                warehouseId: item.warehouseId,
                shelfId: item.shelfId,
                note: invoice.customerName,
            });
        });
    });

    const transfers = getInternalTransfers();
    transfers.forEach((transfer) => {
        const qty = Number(transfer.quantity || 0);
        pushMovement({
            date: transfer.date,
            materialId: transfer.materialId,
            referenceType: 'transfer',
            referenceNumber: transfer.transferNumber,
            direction: 'out',
            quantity: qty,
            warehouseId: transfer.fromWarehouseId,
            shelfId: transfer.fromShelfId,
            note: transfer.note,
        });
        pushMovement({
            date: transfer.date,
            materialId: transfer.materialId,
            referenceType: 'transfer',
            referenceNumber: transfer.transferNumber,
            direction: 'in',
            quantity: qty,
            warehouseId: transfer.toWarehouseId,
            shelfId: transfer.toShelfId,
            note: transfer.note,
        });
    });

    const transferDocs = getInternalTransferDocuments().filter((doc) => doc.status === 'active');
    transferDocs.forEach((doc) => {
        const receiptStatus = doc.receiptStatus ?? 'pending';
        doc.items.forEach((item) => {
            const baseReceived = receiptStatus === 'pending'
                ? Number(item.quantity || 0)
                : Number(item.receivedQuantity ?? item.quantity ?? 0);
            const overApproved = Number(
                item.overApprovedQuantity ?? (item.overageStatus === 'approved' ? item.overReceivedQuantity ?? 0 : 0)
            );
            const safeBaseReceived = Math.max(0, baseReceived);
            const safeOverApproved = Math.max(0, overApproved);
            const toWarehouseId = doc.enableWarehouseTransfer ? item.toWarehouseId : item.fromWarehouseId;
            const toShelfId = doc.enableShelfTransfer ? item.toShelfId : item.fromShelfId;
            const savedDistributions = Array.isArray(item.receiptDistributions)
                ? item.receiptDistributions
                    .map((entry) => ({
                        shelfId: String(entry?.shelfId || '').trim(),
                        quantity: Number(entry?.quantity || 0),
                    }))
                    .filter((entry) => entry.shelfId && Number.isFinite(entry.quantity) && entry.quantity > 0)
                : [];

            if (safeBaseReceived > 0) {
                pushMovement({
                    date: doc.date,
                    materialId: item.materialId,
                    referenceType: 'transfer-doc',
                    referenceNumber: doc.transferNumber,
                    direction: 'out',
                    quantity: safeBaseReceived,
                    warehouseId: item.fromWarehouseId,
                    shelfId: item.fromShelfId,
                    note: doc.note,
                });

                if (savedDistributions.length > 0 && receiptStatus !== 'pending') {
                    const distributedBaseTotal = savedDistributions.reduce((sum, entry) => sum + entry.quantity, 0);
                    const factor = distributedBaseTotal > 0 ? safeBaseReceived / distributedBaseTotal : 0;
                    savedDistributions.forEach((entry) => {
                        pushMovement({
                            date: doc.date,
                            materialId: item.materialId,
                            referenceType: 'transfer-doc',
                            referenceNumber: doc.transferNumber,
                            direction: 'in',
                            quantity: entry.quantity * factor,
                            warehouseId: toWarehouseId,
                            shelfId: entry.shelfId,
                            note: doc.note,
                        });
                    });
                } else {
                    pushMovement({
                        date: doc.date,
                        materialId: item.materialId,
                        referenceType: 'transfer-doc',
                        referenceNumber: doc.transferNumber,
                        direction: 'in',
                        quantity: safeBaseReceived,
                        warehouseId: toWarehouseId,
                        shelfId: toShelfId,
                        note: doc.note,
                    });
                }
            }

            if (safeOverApproved > 0) {
                const overageSourceShelfId = item.overageSourceShelfId || item.fromShelfId;
                pushMovement({
                    date: doc.date,
                    materialId: item.materialId,
                    referenceType: 'transfer-doc',
                    referenceNumber: doc.transferNumber,
                    direction: 'out',
                    quantity: safeOverApproved,
                    warehouseId: item.fromWarehouseId,
                    shelfId: overageSourceShelfId,
                    note: doc.note,
                });

                if (savedDistributions.length > 0 && receiptStatus !== 'pending') {
                    const distributedTotal = savedDistributions.reduce((sum, entry) => sum + entry.quantity, 0);
                    const factor = distributedTotal > 0 ? safeOverApproved / distributedTotal : 0;
                    savedDistributions.forEach((entry) => {
                        pushMovement({
                            date: doc.date,
                            materialId: item.materialId,
                            referenceType: 'transfer-doc',
                            referenceNumber: doc.transferNumber,
                            direction: 'in',
                            quantity: entry.quantity * factor,
                            warehouseId: toWarehouseId,
                            shelfId: entry.shelfId,
                            note: doc.note,
                        });
                    });
                } else {
                    pushMovement({
                        date: doc.date,
                        materialId: item.materialId,
                        referenceType: 'transfer-doc',
                        referenceNumber: doc.transferNumber,
                        direction: 'in',
                        quantity: safeOverApproved,
                        warehouseId: toWarehouseId,
                        shelfId: toShelfId,
                        note: doc.note,
                    });
                }
            }
        });
    });

    const purchaseReturnMovements = getInventoryMovements().filter(
        (movement) => movement.referenceType === 'purchase_return' || movement.referenceType === 'purchase_return_cancel'
    );

    purchaseReturnMovements.forEach((movement) => {
        const qtyIn = Number(movement.quantityIn || 0);
        const qtyOut = Number(movement.quantityOut || 0);
        const quantity = qtyIn > 0 ? qtyIn : qtyOut;
        if (quantity <= 0) return;

        const linkedReturn = purchaseReturnsById.get(String(movement.referenceId || ''));
        const linkedOrder = linkedReturn ? purchaseOrdersById.get(linkedReturn.purchaseOrderId) : undefined;
        const isCancel = movement.referenceType === 'purchase_return_cancel';

        pushMovement({
            date: movement.date,
            materialId: movement.materialId,
            referenceType: isCancel ? 'purchase-return-cancel' : 'purchase-return',
            referenceNumber: linkedReturn?.returnNumber || String(movement.referenceId || '-'),
            direction: qtyIn > 0 ? 'in' : 'out',
            quantity,
            warehouseId: movement.warehouseId,
            shelfId: movement.shelfId,
            note: linkedOrder?.supplierName,
        });
    });

    const disassemblyMovements = getInventoryMovements().filter((movement) => movement.referenceType === 'disassembly');
    disassemblyMovements.forEach((movement) => {
        const qtyIn = Number(movement.quantityIn || 0);
        const qtyOut = Number(movement.quantityOut || 0);
        const quantity = qtyIn > 0 ? qtyIn : qtyOut;
        if (quantity <= 0) return;

        const recordId = String(movement.sourceParentRecordId || movement.referenceId || '').trim();
        const linkedRecord = disassemblyRecordById.get(recordId);
        const sourceParentItemId = String(movement.sourceParentItemId || linkedRecord?.parentItemId || '').trim() || undefined;
        const sourceParentItemName = sourceParentItemId
            ? (materialById.get(sourceParentItemId)?.name || linkedRecord?.parentItemName || sourceParentItemId)
            : undefined;

        pushMovement({
            date: movement.date,
            materialId: movement.materialId,
            referenceType: 'disassembly',
            referenceNumber: String(linkedRecord?.recordNumber || movement.referenceId || '-'),
            direction: qtyIn > 0 ? 'in' : 'out',
            quantity,
            warehouseId: movement.warehouseId,
            shelfId: movement.shelfId,
            note: movement.note || sourceParentItemName,
            sourceParentItemId,
            sourceParentItemName,
            sourceParentRecordId: recordId || undefined,
        });
    });

    return movements
        .filter((entry) => Number(entry.quantity || 0) > 0)
        .sort((a, b) => {
            const dateDiff = new Date(b.date).getTime() - new Date(a.date).getTime();
            if (dateDiff !== 0) return dateDiff;
            return b.id.localeCompare(a.id);
        });
}

export function getSalesReps(): SalesRep[] {
    return readData<SalesRep[]>('sales_reps.json', []);
}

export function addSalesRep(data: { name: string; phone?: string; region?: string; commissionRate?: number }): SalesRep {
    return withDbLock(() => {
        const reps = getSalesReps();
        const numericIds = reps
            .map((rep) => Number.parseInt(String(rep.id || ''), 10))
            .filter((id) => Number.isFinite(id));
        const newId = String((numericIds.length > 0 ? Math.max(...numericIds) : 0) + 1);
        const newRep: SalesRep = {
            id: newId,
            name: data.name,
            phone: data.phone || '',
            region: data.region || '',
            commissionRate: typeof data.commissionRate === 'number' ? data.commissionRate : 0,
        };
        reps.push(newRep);
        writeData('sales_reps.json', reps);
        return newRep;
    });
}

export function updateSalesRep(id: string, data: { name?: string; phone?: string; region?: string; commissionRate?: number }): boolean {
    const reps = getSalesReps();
    const index = reps.findIndex(r => r.id === id);
    if (index === -1) return false;
    reps[index] = {
        ...reps[index],
        name: typeof data.name === 'string' ? data.name : reps[index].name,
        ...data,
        phone: typeof data.phone === 'string' ? data.phone : reps[index].phone,
        region: typeof data.region === 'string' ? data.region : reps[index].region,
        commissionRate: typeof data.commissionRate === 'number' ? data.commissionRate : reps[index].commissionRate,
    };
    writeData('sales_reps.json', reps);
    return true;
}

export function deleteSalesRep(id: string): boolean {
    let reps = getSalesReps();
    const initialLength = reps.length;
    reps = reps.filter(r => r.id !== id);
    if (reps.length < initialLength) {
        writeData('sales_reps.json', reps);
        return true;
    }
    return false;
}

export function addItemGroup(data: { name: string; isPosEnabled: boolean; usesDefaultTax?: boolean; taxRate?: number }): ItemGroup {
        return withDbLock(() => {
                const groups = getItemGroups();
                const nextGroupNumber = groups.reduce((max, group) => {
                    const numeric = parseInt(String((group as any)?.groupNumber || '').replace(/\D+/g, ''), 10);
                    return Number.isFinite(numeric) ? Math.max(max, numeric) : max;
                }, 0) + 1;
                const newGroup: ItemGroup = {
                    id: nextPrefixedId('grp'),
                    groupNumber: String(nextGroupNumber),
                    name: data.name,
                    isPosEnabled: data.isPosEnabled,
                    usesDefaultTax: data.usesDefaultTax ?? true,
                };
                if (data.usesDefaultTax === false && data.taxRate !== undefined) {
                    newGroup.taxRate = data.taxRate;
                }
                groups.unshift(newGroup);
                writeData('item_groups.json', groups);
                return newGroup;
        });
}

export function updateItemGroup(id: string, data: Partial<ItemGroup>): boolean {
    const groups = getItemGroups();
    const idx = groups.findIndex(g => g.id === id);
    if (idx === -1) return false;
    
    // Build the updated group carefully to avoid storing undefined values
    const updated: ItemGroup = {
      ...groups[idx],
      name: data.name !== undefined ? data.name : groups[idx].name,
      isPosEnabled: data.isPosEnabled !== undefined ? data.isPosEnabled : groups[idx].isPosEnabled,
      usesDefaultTax: data.usesDefaultTax !== undefined ? data.usesDefaultTax : (groups[idx].usesDefaultTax ?? true),
    };
    
    // Only include taxRate if NOT using default and taxRate is provided
    if (data.hasOwnProperty('taxRate') && updated.usesDefaultTax === false && data.taxRate !== undefined) {
      updated.taxRate = data.taxRate;
    } else if (updated.usesDefaultTax === true && 'taxRate' in updated) {
      // Remove taxRate if switching back to default
      delete updated.taxRate;
    }
    
    groups[idx] = updated;
    writeData('item_groups.json', groups);
    return true;
}

export function deleteItemGroup(id: string): boolean {
    let groups = getItemGroups();
    const initialLength = groups.length;
    groups = groups.filter(g => g.id !== id);
    if (groups.length < initialLength) {
        writeData('item_groups.json', groups);
        return true;
    }
    return false;
}

export function addCustomerCategory(payload: string | { name: string; nameEn?: string; discountPercent?: number; description?: string }): CustomerCategory {
    return withDbLock(() => {
        const categories = getCustomerCategories();
        const name = typeof payload === 'string' ? payload : String(payload?.name || '');
        const normalizedName = String(name || '').trim();
        const normalizedNameEn = typeof payload === 'string' ? '' : String(payload?.nameEn || '').trim();
        const normalizedDescription = typeof payload === 'string' ? '' : String(payload?.description || '').trim();
        const normalizedDiscount = typeof payload === 'string' ? 0 : Number(payload?.discountPercent || 0);

        const exists = categories.find(c => c.name.toLowerCase() === normalizedName.toLowerCase());
        if (exists) return exists;

        const categoryNumber = `CAT-${String(nextNamedSequence('customer-category-ref')).padStart(4, '0')}`;
        const newCategory: CustomerCategory = {
            id: nextPrefixedId('cat'),
            categoryNumber,
            name: normalizedName,
            nameEn: normalizedNameEn || undefined,
            discountPercent: Number.isFinite(normalizedDiscount) ? Math.max(0, normalizedDiscount) : 0,
            description: normalizedDescription || undefined,
        };
        categories.push(newCategory);
        writeData('customer_categories.json', categories);
        return newCategory;
    });
}

export function deleteCustomerCategory(id: string): boolean {
    let categories = getCustomerCategories();
    if (id === 'general') return false; // Can't delete general
    const initialLength = categories.length;
    categories = categories.filter(c => c.id !== id);
    if (categories.length < initialLength) {
        writeData('customer_categories.json', categories);
        return true;
    }
    return false;
}

export function getCustomers(): Customer[] {
    const customers = readDataCached<Customer[]>('customers.json', [], 60_000);
    // Backfill category for legacy records
    return customers.map((c, idx) => ({
        ...c,
        category: c.category || 'general',
        id: c.id || String(idx + 1),
        customerNumber: c.customerNumber || c.id || String(idx + 1),
        phone: c.phone || '',
        creditLimit: typeof c.creditLimit === 'number' ? c.creditLimit : 0,
        address: c.address || '',
        allowedDiscount: typeof c.allowedDiscount === 'number' ? c.allowedDiscount : 0,
        salesRepId: typeof c.salesRepId === 'string' ? c.salesRepId : '',
    }));
}

export function getMaterials(): ProductionItem[] {
    const items = readDataCached<ProductionItem[]>('materials.json', [], 60_000);
    // Backfill legacy records to new shape
    const normalized = items.map((item, idx) => ({
        ...item,
        itemCategory: ['inventory', 'service', 'non_stock', 'bundle'].includes(String((item as any).itemCategory || '').trim())
            ? (String((item as any).itemCategory || '').trim() as ProductionItem['itemCategory'])
            : 'inventory',
        reorderMinimumQty: typeof (item as any).reorderMinimumQty === 'number' && Number.isFinite((item as any).reorderMinimumQty)
            ? Math.max(0, Number((item as any).reorderMinimumQty))
            : undefined,
        itemNumber: item.itemNumber || item.id || `mat-${idx + 1}`,
        id: item.itemNumber || item.id || `mat-${idx + 1}`,
        salePrice: typeof item.salePrice === 'number' ? item.salePrice : 0,
        categoryPrices: item.categoryPrices || {},
        additionalBarcodes: Array.isArray(item.additionalBarcodes) ? item.additionalBarcodes : [],
        secondaryBarcode: item.secondaryBarcode || undefined,
        primaryUnit: item.primaryUnit || undefined,
        primaryUnitQuantity: typeof item.primaryUnitQuantity === 'number' ? item.primaryUnitQuantity : 1,
        secondaryUnit: item.secondaryUnit || undefined,
        secondaryUnitQuantity: typeof item.secondaryUnitQuantity === 'number' ? item.secondaryUnitQuantity : undefined,
        secondaryUnitPrice: typeof item.secondaryUnitPrice === 'number' ? item.secondaryUnitPrice : undefined,
        secondaryUnits: Array.isArray((item as any).secondaryUnits)
            ? (item as any).secondaryUnits
                .map((entry: any) => ({
                    unit: typeof entry?.unit === 'string' ? entry.unit : undefined,
                    barcode: typeof entry?.barcode === 'string' ? entry.barcode : undefined,
                    quantity: typeof entry?.quantity === 'number' ? entry.quantity : undefined,
                    price: typeof entry?.price === 'number' ? entry.price : undefined,
                }))
                .filter((entry: any) => entry.unit || entry.barcode || typeof entry.quantity === 'number' || typeof entry.price === 'number')
            : ((item.secondaryUnit || item.secondaryBarcode || typeof item.secondaryUnitQuantity === 'number' || typeof item.secondaryUnitPrice === 'number')
                ? [{
                    unit: item.secondaryUnit || undefined,
                    barcode: item.secondaryBarcode || undefined,
                    quantity: typeof item.secondaryUnitQuantity === 'number' ? item.secondaryUnitQuantity : undefined,
                    price: typeof item.secondaryUnitPrice === 'number' ? item.secondaryUnitPrice : undefined,
                }]
                : []),
        isWeighed: typeof item.isWeighed === 'boolean' ? item.isWeighed : false,
        isSized: typeof (item as any).isSized === 'boolean' ? (item as any).isSized : false,
        isManufactured: typeof (item as any).isManufactured === 'boolean' ? (item as any).isManufactured : false,
        productionOutputQuantity: typeof (item as any).isManufactured === 'boolean' && (item as any).isManufactured
            ? (typeof (item as any).productionOutputQuantity === 'number' && (item as any).productionOutputQuantity > 0 ? (item as any).productionOutputQuantity : 1)
            : undefined,
        productionFormula: Array.isArray((item as any).productionFormula)
            ? (item as any).productionFormula
                .map((entry: any) => ({
                    materialId: String(entry?.materialId || '').trim(),
                    quantity: typeof entry?.quantity === 'number' ? entry.quantity : Number(entry?.quantity || 0),
                    unit: typeof entry?.unit === 'string' ? entry.unit : undefined,
                    notes: typeof entry?.notes === 'string' ? entry.notes : undefined,
                    wastePercent: typeof entry?.wastePercent === 'number' ? entry.wastePercent : undefined,
                }))
                .filter((entry: any) => entry.materialId && typeof entry.quantity === 'number' && entry.quantity > 0)
            : [],
        shelfLifeYears: typeof (item as any).shelfLifeYears === 'number' && (item as any).shelfLifeYears >= 0
            ? (item as any).shelfLifeYears
            : undefined,
        shelfLifeMonths: typeof (item as any).shelfLifeMonths === 'number' && (item as any).shelfLifeMonths >= 0
            ? (item as any).shelfLifeMonths
            : undefined,
        shelfLifeDays: typeof (item as any).shelfLifeDays === 'number' && (item as any).shelfLifeDays >= 0
            ? (item as any).shelfLifeDays
            : undefined,
        alternativeItemIds: Array.isArray((item as any).alternativeItemIds)
            ? Array.from(new Set<string>((item as any).alternativeItemIds.map((id: any) => String(id || '').trim()).filter((id: string) => Boolean(id))))
            : [],
        groupId: item.groupId || undefined,
        itemSymbolName: typeof item.itemSymbolName === 'string' ? item.itemSymbolName : undefined,
        globalReferenceNumber: typeof (item as any).globalReferenceNumber === 'string' ? (item as any).globalReferenceNumber : undefined,
        additionalDetails: typeof item.additionalDetails === 'string' ? item.additionalDetails : undefined,
        imageUrl: item.imageUrl || undefined,
        allowDescriptionEdit: typeof item.allowDescriptionEdit === 'boolean' ? item.allowDescriptionEdit : false,
        hasSerialNumber: typeof item.hasSerialNumber === 'boolean' ? item.hasSerialNumber : false,
        hasRfid: typeof item.hasRfid === 'boolean' ? item.hasRfid : false,
        usesDefaultTax: item.usesDefaultTax ?? true,
        taxRate: item.usesDefaultTax === false && typeof item.taxRate === 'number' ? item.taxRate : undefined,
        enableProfitMargin: typeof item.enableProfitMargin === 'boolean' ? item.enableProfitMargin : false,
        profitMargin: typeof item.profitMargin === 'number' ? item.profitMargin : undefined,
        usesProductShapes: item.usesProductShapes ?? false,
        productShapeType: typeof item.productShapeType === 'string' ? item.productShapeType : undefined,
        productShapes: Array.isArray(item.productShapes)
            ? item.productShapes
                .filter((shape) => shape && typeof shape.name === 'string' && shape.name.trim().length > 0)
                .map((shape, index) => ({
                    id: String(shape.id || `shape-${index + 1}`),
                    name: String(shape.name).trim(),
                    salePrice: typeof shape.salePrice === 'number' ? shape.salePrice : 0,
                    costPrice: typeof shape.costPrice === 'number' ? shape.costPrice : undefined,
                    type: typeof shape.type === 'string' ? shape.type : undefined,
                    isActive: shape.isActive ?? true,
                }))
            : [],
    }));

    const existingIds = new Set(normalized.map((entry) => entry.id));
    return normalized.map((entry) => ({
        ...entry,
        alternativeItemIds: (entry.alternativeItemIds || []).filter((altId: string) => altId !== entry.id && existingIds.has(altId)),
    }));
}

type FifoPurchaseLot = {
    materialId: string;
    materialName: string;
    purchaseOrderId: string;
    purchaseOrderNumber: string;
    purchaseDate: string;
    quantity: number;
    remainingQuantity: number;
    shelfLifeYears: number;
    shelfLifeMonths: number;
    shelfLifeDays: number;
};

function normalizeShelfLifePart(value: unknown): number {
    const numeric = Number(value ?? 0);
    if (!Number.isFinite(numeric) || numeric < 0) return 0;
    return Math.floor(numeric);
}

function parseSafeDate(value: string): Date | null {
    const dt = new Date(value);
    return Number.isFinite(dt.getTime()) ? dt : null;
}

function buildExpiryDate(baseDate: Date, years: number, months: number, days: number): Date {
    let next = baseDate;
    if (years > 0) next = addYears(next, years);
    if (months > 0) next = addMonths(next, months);
    if (days > 0) next = addDays(next, days);
    return next;
}

export function getMaterialExpiryFifoReport(nearExpiryDays: number = 30, includeLocations: boolean = true): MaterialExpiryFifoRow[] {
    const materials = getMaterials();
    const materialById = new Map(materials.map((material) => [material.id, material]));
    const thresholdDays = Number.isFinite(Number(nearExpiryDays))
        ? Math.max(0, Math.floor(Number(nearExpiryDays)))
        : 30;
    const locationRows = includeLocations ? getStockLocationsByMaterial(Array.from(materialById.keys())) : [];
    const locationSummaryByMaterial = new Map<string, string>();
    locationRows.forEach((entry) => {
        const summary = (entry.locations || [])
            .filter((loc) => Number(loc.quantity || 0) > 0)
            .map((loc) => `${loc.warehouseName} / ${loc.shelfName} (${Math.round(Number(loc.quantity || 0) * 10000) / 10000})`)
            .join('، ');
        if (summary) {
            locationSummaryByMaterial.set(entry.materialId, summary);
        }
    });

    const lotsByMaterial = new Map<string, FifoPurchaseLot[]>();
    const pushLot = (lot: FifoPurchaseLot) => {
        const list = lotsByMaterial.get(lot.materialId) || [];
        list.push(lot);
        lotsByMaterial.set(lot.materialId, list);
    };

    const purchaseOrders = getInvoicesPurchase().filter((order) => order.status !== 'cancelled');
    purchaseOrders.forEach((order) => {
        order.items.forEach((item) => {
            const materialId = String(item.materialId || '').trim();
            if (!materialId) return;
            const material = materialById.get(materialId);
            if (!material) return;

            const years = normalizeShelfLifePart((material as any).shelfLifeYears);
            const months = normalizeShelfLifePart((material as any).shelfLifeMonths);
            const days = normalizeShelfLifePart((material as any).shelfLifeDays);
            if (years + months + days <= 0) return;

            const quantity = Number(item.quantity || 0);
            if (!Number.isFinite(quantity) || quantity <= 0) return;

            pushLot({
                materialId,
                materialName: material.name,
                purchaseOrderId: order.id,
                purchaseOrderNumber: order.orderNumber,
                purchaseDate: order.date,
                quantity,
                remainingQuantity: quantity,
                shelfLifeYears: years,
                shelfLifeMonths: months,
                shelfLifeDays: days,
            });
        });
    });

    lotsByMaterial.forEach((lots) => {
        lots.sort((a, b) => {
            const dateDiff = new Date(a.purchaseDate).getTime() - new Date(b.purchaseDate).getTime();
            if (dateDiff !== 0) return dateDiff;
            return a.purchaseOrderNumber.localeCompare(b.purchaseOrderNumber);
        });
    });

    const outgoingByMaterial = new Map<string, number>();
    const addOutgoing = (materialId: string, quantity: number) => {
        if (!materialId || !Number.isFinite(quantity) || quantity <= 0) return;
        outgoingByMaterial.set(materialId, (outgoingByMaterial.get(materialId) || 0) + quantity);
    };

    const salesInvoices = getInvoices().filter((invoice) => invoice.status !== 'cancelled' && (invoice.type || 'sale') !== 'return');
    salesInvoices.forEach((invoice) => {
        invoice.items.forEach((item) => {
            addOutgoing(String(item.materialId || '').trim(), Number(item.quantity || 0));
        });
    });

    const purchaseReturns = getPurchaseReturns().filter((ret) => ret.status !== 'draft');
    purchaseReturns.forEach((ret) => {
        ret.items.forEach((item) => {
            addOutgoing(String(item.materialId || '').trim(), Number(item.quantity || 0));
        });
    });

    outgoingByMaterial.forEach((outgoingQty, materialId) => {
        const lots = lotsByMaterial.get(materialId);
        if (!lots || lots.length === 0) return;

        let remainingToConsume = outgoingQty;
        for (const lot of lots) {
            if (remainingToConsume <= 0) break;
            if (lot.remainingQuantity <= 0) continue;
            const consumed = Math.min(lot.remainingQuantity, remainingToConsume);
            lot.remainingQuantity -= consumed;
            remainingToConsume -= consumed;
        }
    });

    const today = new Date();
    today.setHours(0, 0, 0, 0);
    const msPerDay = 24 * 60 * 60 * 1000;
    const rows: MaterialExpiryFifoRow[] = [];

    lotsByMaterial.forEach((lots) => {
        lots.forEach((lot) => {
            const remainingQuantity = Number(lot.remainingQuantity || 0);
            if (!Number.isFinite(remainingQuantity) || remainingQuantity <= 0) return;

            const purchaseDate = parseSafeDate(lot.purchaseDate);
            if (!purchaseDate) return;
            const expiryDate = buildExpiryDate(purchaseDate, lot.shelfLifeYears, lot.shelfLifeMonths, lot.shelfLifeDays);
            const expiryStart = new Date(expiryDate);
            expiryStart.setHours(0, 0, 0, 0);
            const remainingDays = Math.floor((expiryStart.getTime() - today.getTime()) / msPerDay);

            const status: MaterialExpiryFifoRow['status'] = remainingDays < 0
                ? 'expired'
                : (remainingDays <= thresholdDays ? 'near-expiry' : 'valid');

            if (status === 'valid') return;

            rows.push({
                materialId: lot.materialId,
                materialName: lot.materialName,
                locationSummary: locationSummaryByMaterial.get(lot.materialId),
                purchaseOrderId: lot.purchaseOrderId,
                purchaseOrderNumber: lot.purchaseOrderNumber,
                purchaseDate: lot.purchaseDate,
                expiryDate: expiryDate.toISOString(),
                shelfLifeDays: (lot.shelfLifeYears * 365) + (lot.shelfLifeMonths * 30) + lot.shelfLifeDays,
                remainingQuantity: Math.round(remainingQuantity * 10000) / 10000,
                remainingDays,
                status,
            });
        });
    });

    return rows.sort((a, b) => {
        const statusRank = (value: MaterialExpiryFifoRow['status']) => {
            if (value === 'expired') return 0;
            if (value === 'near-expiry') return 1;
            return 2;
        };
        const rankDiff = statusRank(a.status) - statusRank(b.status);
        if (rankDiff !== 0) return rankDiff;
        const daysDiff = a.remainingDays - b.remainingDays;
        if (daysDiff !== 0) return daysDiff;
        return a.materialName.localeCompare(b.materialName);
    });
}

function resolveMaterialTaxRate(material: ProductionItem | undefined, itemGroupsById: Map<string, ItemGroup>, defaultLineTaxRate: number): number {
    if (!material) return 0;

    if (material.usesDefaultTax === false) {
        const customRate = Number(material.taxRate || 0);
        return Number.isFinite(customRate) && customRate > 0 ? customRate : 0;
    }

    const ownRate = Number(material.taxRate || 0);
    if (Number.isFinite(ownRate) && ownRate > 0) return ownRate;

    const groupId = String(material.groupId || '').trim();
    if (groupId) {
        const groupRate = Number(itemGroupsById.get(groupId)?.taxRate || 0);
        if (Number.isFinite(groupRate) && groupRate > 0) return groupRate;
    }

    return Number.isFinite(defaultLineTaxRate) && defaultLineTaxRate > 0 ? defaultLineTaxRate : 0;
}

function resolveTaxInclusivePurchasePrice(material: ProductionItem | undefined, itemGroupsById: Map<string, ItemGroup>, defaultLineTaxRate: number): number {
    const base = Number(material?.purchasePrice || 0);
    if (!Number.isFinite(base) || base <= 0) return 0;

    const taxRate = resolveMaterialTaxRate(material, itemGroupsById, defaultLineTaxRate);
    if (!Number.isFinite(taxRate) || taxRate <= 0) return base;

    return base * (1 + (taxRate / 100));
}

function resolveFormulaQuantityInPrimaryUnits(source: ProductionItem | undefined, formulaUnit: string | undefined, rawQuantity: number): number {
    if (!source || !Number.isFinite(rawQuantity) || rawQuantity <= 0) return 0;

    const quantity = Number(rawQuantity);
    const selectedUnit = String(formulaUnit || '').trim().toLowerCase();
    if (!selectedUnit) return quantity;

    const primaryUnit = String(source.primaryUnit || '').trim().toLowerCase();
    if (primaryUnit && selectedUnit === primaryUnit) return quantity;

    const primaryUnitQuantity = Number(source.primaryUnitQuantity || 1);
    const safePrimaryUnitQuantity = Number.isFinite(primaryUnitQuantity) && primaryUnitQuantity > 0 ? primaryUnitQuantity : 1;

    const secondaryCandidates = Array.isArray((source as any).secondaryUnits) && (source as any).secondaryUnits.length > 0
        ? (source as any).secondaryUnits
        : ((source.secondaryUnit || typeof source.secondaryUnitQuantity === 'number')
            ? [{ unit: source.secondaryUnit, quantity: source.secondaryUnitQuantity }]
            : []);

    const matchedSecondary = secondaryCandidates.find((entry: any) => String(entry?.unit || '').trim().toLowerCase() === selectedUnit);
    const secondaryRatio = Number(matchedSecondary?.quantity || 0);
    if (Number.isFinite(secondaryRatio) && secondaryRatio > 0) {
        return quantity * (safePrimaryUnitQuantity / secondaryRatio);
    }

    return quantity;
}

function computeManufacturedPurchasePrice(
    item: ProductionItem,
    materialsById: Map<string, ProductionItem>,
    itemGroupsById: Map<string, ItemGroup>,
    defaultLineTaxRate: number,
): number {
    if (!(item as any).isManufactured) {
        const fallback = Number(item.purchasePrice || 0);
        return Number.isFinite(fallback) && fallback >= 0 ? fallback : 0;
    }

    const outputQty = Number((item as any).productionOutputQuantity || 1);
    const safeOutputQty = Number.isFinite(outputQty) && outputQty > 0 ? outputQty : 1;
    const formula = Array.isArray((item as any).productionFormula) ? (item as any).productionFormula : [];

    let totalCost = 0;
    formula.forEach((entry: any) => {
        const materialId = String(entry?.materialId || '').trim();
        if (!materialId || materialId === item.id) return;

        const quantity = Number(entry?.quantity || 0);
        if (!Number.isFinite(quantity) || quantity <= 0) return;

        const wastePercent = Number(entry?.wastePercent || 0);
        const wasteMultiplier = 1 + ((Number.isFinite(wastePercent) && wastePercent > 0) ? (wastePercent / 100) : 0);

        const source = materialsById.get(materialId);
        const quantityInPrimaryUnits = resolveFormulaQuantityInPrimaryUnits(source, entry?.unit, quantity);
        if (!Number.isFinite(quantityInPrimaryUnits) || quantityInPrimaryUnits <= 0) return;

        const taxInclusiveUnitCost = resolveTaxInclusivePurchasePrice(source, itemGroupsById, defaultLineTaxRate);
        if (!Number.isFinite(taxInclusiveUnitCost) || taxInclusiveUnitCost <= 0) return;

        totalCost += taxInclusiveUnitCost * quantityInPrimaryUnits * wasteMultiplier;
    });

    const computed = totalCost / safeOutputQty;
    return Number.isFinite(computed) && computed > 0 ? Math.round(computed * 10000) / 10000 : 0;
}

    export function addMaterial(data: { itemNumber: string; name: string; itemCategory?: 'inventory' | 'service' | 'non_stock' | 'bundle'; reorderMinimumQty?: number; itemSymbolName?: string; globalReferenceNumber?: string; barcode?: string; secondaryBarcode?: string; additionalBarcodes?: string[]; primaryUnit?: string; primaryUnitQuantity?: number; secondaryUnit?: string; secondaryUnitQuantity?: number; secondaryUnitPrice?: number; secondaryUnits?: { unit?: string; barcode?: string; quantity?: number; price?: number; }[]; alternativeItemIds?: string[]; isWeighed?: boolean; isSized?: boolean; isManufactured?: boolean; productionOutputQuantity?: number; productionFormula?: { materialId: string; quantity: number; unit?: string; notes?: string; wastePercent?: number; }[]; shelfLifeYears?: number; shelfLifeMonths?: number; shelfLifeDays?: number; groupId?: string; additionalDetails?: string; imageUrl?: string; purchasePrice?: number; salePrice: number; categoryPrices?: Record<string, number>; allowDescriptionEdit?: boolean; hasSerialNumber?: boolean; hasRfid?: boolean; usesDefaultTax?: boolean; taxRate?: number; enableProfitMargin?: boolean; profitMargin?: number; usesProductShapes?: boolean; productShapeType?: string; productShapes?: { id?: string; name?: string; salePrice?: number; costPrice?: number; type?: string; isActive?: boolean; }[]; }): ProductionItem {
    return withDbLock(() => {
        const items = getMaterials();
                const normalizeCode = (value: any) => String(value || '').trim();
                const existingBarcodes = new Set<string>();
                items.forEach((entry) => {
                    const secondaryUnitCodes = Array.isArray((entry as any).secondaryUnits)
                        ? (entry as any).secondaryUnits.map((row: any) => row?.barcode)
                        : [];
                    [
                        entry.barcode,
                        entry.secondaryBarcode,
                        ...(entry.additionalBarcodes || []),
                        ...secondaryUnitCodes,
                    ]
                        .map(normalizeCode)
                        .filter(Boolean)
                        .forEach((code) => existingBarcodes.add(code));
                });

                const generateAutoBarcode = () => {
                    const now = new Date();
                    const pad = (num: number) => String(num).padStart(2, '0');
                    const base = `A${pad(now.getDate())}${pad(now.getMonth() + 1)}${now.getFullYear()}${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
                    let generated = base;
                    let counter = 1;
                    while (existingBarcodes.has(generated)) {
                        generated = `${base}${String(counter).padStart(2, '0')}`;
                        counter += 1;
                    }
                    return generated;
                };

            const normalizeItemNumber = (value: any) => String(value || '').trim();
            const usedItemNumbers = new Set<string>();
            let maxNumeric = 0;

            items.forEach((item) => {
                const candidates = [item.itemNumber, item.id]
                    .map(normalizeItemNumber)
                    .filter(Boolean);
                candidates.forEach((candidate) => {
                    usedItemNumbers.add(candidate);
                    const numeric = parseInt(candidate.replace(/\D+/g, ''), 10);
                    if (Number.isFinite(numeric)) {
                        maxNumeric = Math.max(maxNumeric, numeric);
                    }
                });
            });

            const requestedItemNumber = normalizeItemNumber(data.itemNumber);
            let resolvedItemNumber = requestedItemNumber;

            if (!resolvedItemNumber || usedItemNumbers.has(resolvedItemNumber)) {
                resolvedItemNumber = String(maxNumeric + 1);
                while (usedItemNumbers.has(resolvedItemNumber)) {
                    resolvedItemNumber = String(Number(resolvedItemNumber) + 1);
                }
            }

        const rawPrimaryBarcode = normalizeCode(data.barcode);
        const resolvedPrimaryBarcode = rawPrimaryBarcode || generateAutoBarcode();
        if (existingBarcodes.has(resolvedPrimaryBarcode)) {
            throw new Error('Barcode already exists');
        }
        existingBarcodes.add(resolvedPrimaryBarcode);

        const rawSecondaryBarcode = normalizeCode(data.secondaryBarcode);
        const resolvedSecondaryBarcode = rawSecondaryBarcode || undefined;
        if (resolvedSecondaryBarcode) {
            if (existingBarcodes.has(resolvedSecondaryBarcode)) {
                throw new Error('Secondary barcode already exists');
            }
            existingBarcodes.add(resolvedSecondaryBarcode);
        }

        const normalizedAdditionalBarcodes = Array.from(new Set((data.additionalBarcodes || [])
            .map((code) => normalizeCode(code))
            .filter(Boolean)));
        normalizedAdditionalBarcodes.forEach((code) => {
            if (existingBarcodes.has(code)) {
                throw new Error('Additional barcode already exists');
            }
            existingBarcodes.add(code);
        });

        const normalizedSecondaryUnits = Array.isArray(data.secondaryUnits)
            ? data.secondaryUnits
                .map((entry) => {
                    const normalizedBarcode = normalizeCode(entry?.barcode);
                    if (normalizedBarcode) {
                        if (existingBarcodes.has(normalizedBarcode)) {
                            throw new Error('Unit barcode already exists');
                        }
                        existingBarcodes.add(normalizedBarcode);
                    }

                    return {
                        unit: typeof entry?.unit === 'string' ? entry.unit : undefined,
                        barcode: normalizedBarcode || undefined,
                        quantity: typeof entry?.quantity === 'number' ? entry.quantity : undefined,
                        price: typeof entry?.price === 'number' ? entry.price : undefined,
                    };
                })
                .filter((entry) => entry.unit || entry.barcode || typeof entry.quantity === 'number' || typeof entry.price === 'number')
            : [];

        const selectedAlternatives = Array.from(new Set((data.alternativeItemIds || []).map((id) => String(id || '').trim()).filter(Boolean)));
        const normalizedItemCategory = ['inventory', 'service', 'non_stock', 'bundle'].includes(String((data as any).itemCategory || '').trim())
            ? (String((data as any).itemCategory || '').trim() as ProductionItem['itemCategory'])
            : 'inventory';
        const newItem: ProductionItem = {
                id: resolvedItemNumber,
                itemNumber: resolvedItemNumber,
            name: data.name,
            itemCategory: normalizedItemCategory,
            reorderMinimumQty: Number.isFinite(Number((data as any).reorderMinimumQty))
                ? Math.max(0, Number((data as any).reorderMinimumQty))
                : undefined,
            itemSymbolName: data.itemSymbolName,
            globalReferenceNumber: data.globalReferenceNumber,
            barcode: resolvedPrimaryBarcode,
            secondaryBarcode: resolvedSecondaryBarcode,
            additionalBarcodes: normalizedAdditionalBarcodes,
            primaryUnit: data.primaryUnit,
            primaryUnitQuantity: data.primaryUnitQuantity ?? 1,
            secondaryUnit: data.secondaryUnit,
            secondaryUnitQuantity: data.secondaryUnitQuantity,
            secondaryUnitPrice: data.secondaryUnitPrice,
            secondaryUnits: normalizedSecondaryUnits,
            isWeighed: data.isWeighed ?? false,
            isSized: data.isSized ?? false,
            isManufactured: (data as any).isManufactured ?? false,
            productionOutputQuantity: (data as any).isManufactured
                ? ((typeof (data as any).productionOutputQuantity === 'number' && (data as any).productionOutputQuantity > 0)
                    ? (data as any).productionOutputQuantity
                    : 1)
                : undefined,
            productionFormula: Array.isArray((data as any).productionFormula)
                ? (data as any).productionFormula
                    .map((entry: any) => ({
                        materialId: String(entry?.materialId || '').trim(),
                        quantity: typeof entry?.quantity === 'number' ? entry.quantity : Number(entry?.quantity || 0),
                        unit: typeof entry?.unit === 'string' ? entry.unit : undefined,
                        notes: typeof entry?.notes === 'string' ? entry.notes : undefined,
                        wastePercent: typeof entry?.wastePercent === 'number' ? entry.wastePercent : undefined,
                    }))
                    .filter((entry: any) => entry.materialId && typeof entry.quantity === 'number' && entry.quantity > 0)
                : [],
            shelfLifeYears: normalizeShelfLifePart((data as any).shelfLifeYears) || undefined,
            shelfLifeMonths: normalizeShelfLifePart((data as any).shelfLifeMonths) || undefined,
            shelfLifeDays: normalizeShelfLifePart((data as any).shelfLifeDays) || undefined,
            alternativeItemIds: selectedAlternatives.filter((altId) => altId !== resolvedItemNumber),
            groupId: data.groupId,
            additionalDetails: data.additionalDetails,
            imageUrl: data.imageUrl,
            purchasePrice: data.purchasePrice,
            salePrice: data.salePrice,
            categoryPrices: data.categoryPrices || {},
            allowDescriptionEdit: typeof data.allowDescriptionEdit === 'boolean' ? data.allowDescriptionEdit : false,
            hasSerialNumber: data.hasSerialNumber ?? false,
            hasRfid: data.hasRfid ?? false,
            usesDefaultTax: data.usesDefaultTax ?? true,
            enableProfitMargin: data.enableProfitMargin ?? false,
            profitMargin: typeof data.profitMargin === 'number' ? data.profitMargin : undefined,
            usesProductShapes: data.usesProductShapes ?? false,
            productShapeType: data.productShapeType,
            productShapes: Array.isArray(data.productShapes)
                ? data.productShapes
                    .filter((shape) => shape && typeof shape.name === 'string' && shape.name.trim().length > 0)
                    .map((shape, index) => ({
                        id: String(shape.id || `shape-${index + 1}`),
                        name: String(shape.name).trim(),
                        salePrice: typeof shape.salePrice === 'number' ? shape.salePrice : 0,
                        costPrice: typeof shape.costPrice === 'number' ? shape.costPrice : undefined,
                        type: typeof shape.type === 'string' ? shape.type : data.productShapeType,
                        isActive: shape.isActive ?? true,
                    }))
                : [],
        };
        if (data.usesDefaultTax === false && data.taxRate !== undefined) {
            newItem.taxRate = data.taxRate;
        }

        if ((newItem as any).isManufactured) {
            const settings = getSettings();
            const defaultLineTaxRate = Number(settings?.defaultLineTaxRate || 0);
            const itemGroups = getItemGroups();
            const itemGroupsById = new Map(itemGroups.map((group) => [group.id, group]));
            const materialsById = new Map(items.map((entry) => [String(entry.id || ''), entry]));
            materialsById.set(newItem.id, newItem);
            newItem.purchasePrice = computeManufacturedPurchasePrice(newItem, materialsById, itemGroupsById, defaultLineTaxRate);
        }

        const selectedSet = new Set(newItem.alternativeItemIds || []);
        const syncedExisting = items.map((entry) => {
            if (entry.id === newItem.id) return entry;
            const current = Array.isArray((entry as any).alternativeItemIds)
                ? (entry as any).alternativeItemIds.map((id: any) => String(id || '').trim()).filter(Boolean)
                : [];
            const nextSet = new Set(current);
            if (selectedSet.has(entry.id)) {
                nextSet.add(newItem.id);
            }
            return {
                ...entry,
                alternativeItemIds: Array.from(nextSet).filter((id): id is string => typeof id === 'string' && id !== entry.id),
            };
        });

        items.splice(0, items.length, ...syncedExisting);
        items.push(newItem);
        writeData('materials.json', items);
        return newItem;
    });
}

export function updateMaterial(id: string, data: { name?: string; itemCategory?: 'inventory' | 'service' | 'non_stock' | 'bundle'; reorderMinimumQty?: number; itemSymbolName?: string; globalReferenceNumber?: string; barcode?: string; secondaryBarcode?: string; additionalBarcodes?: string[]; primaryUnit?: string; primaryUnitQuantity?: number; secondaryUnit?: string; secondaryUnitQuantity?: number; secondaryUnitPrice?: number; secondaryUnits?: { unit?: string; barcode?: string; quantity?: number; price?: number; }[]; alternativeItemIds?: string[]; isWeighed?: boolean; isSized?: boolean; isManufactured?: boolean; productionOutputQuantity?: number; productionFormula?: { materialId: string; quantity: number; unit?: string; notes?: string; wastePercent?: number; }[]; shelfLifeYears?: number; shelfLifeMonths?: number; shelfLifeDays?: number; groupId?: string; additionalDetails?: string; imageUrl?: string; purchasePrice?: number; purchaseCurrencyCode?: string; salePrice?: number; itemNumber?: string; categoryPrices?: Record<string, number>; allowDescriptionEdit?: boolean; hasSerialNumber?: boolean; hasRfid?: boolean; usesDefaultTax?: boolean; taxRate?: number; enableProfitMargin?: boolean; profitMargin?: number; usesProductShapes?: boolean; productShapeType?: string; productShapes?: { id?: string; name?: string; salePrice?: number; costPrice?: number; type?: string; isActive?: boolean; }[]; }): boolean {
    return withDbLock(() => {
    const items = getMaterials();
    const itemIndex = items.findIndex(i => i.id === id);
    if (itemIndex !== -1) {
        const previousItem = items[itemIndex];
        const nextId = data.itemNumber ? data.itemNumber : previousItem.id;
        const rawAlternatives = data.alternativeItemIds ?? (previousItem.alternativeItemIds || []);
        const normalizedAlternatives = Array.from(new Set((rawAlternatives || []).map((altId) => String(altId || '').trim()).filter(Boolean)))
            .filter((altId) => altId !== nextId);

        const updated: ProductionItem = {
            ...previousItem,
            ...data,
            id: nextId,
            itemNumber: data.itemNumber ? data.itemNumber : previousItem.itemNumber,
            itemCategory: ['inventory', 'service', 'non_stock', 'bundle'].includes(String((data as any).itemCategory || '').trim())
                ? (String((data as any).itemCategory || '').trim() as ProductionItem['itemCategory'])
                : (['inventory', 'service', 'non_stock', 'bundle'].includes(String((previousItem as any).itemCategory || '').trim())
                    ? (String((previousItem as any).itemCategory || '').trim() as ProductionItem['itemCategory'])
                    : 'inventory'),
            reorderMinimumQty: data.hasOwnProperty('reorderMinimumQty')
                ? (Number.isFinite(Number((data as any).reorderMinimumQty))
                    ? Math.max(0, Number((data as any).reorderMinimumQty))
                    : undefined)
                : ((typeof (previousItem as any).reorderMinimumQty === 'number' && Number.isFinite((previousItem as any).reorderMinimumQty))
                    ? Math.max(0, Number((previousItem as any).reorderMinimumQty))
                    : undefined),
            categoryPrices: data.categoryPrices ?? previousItem.categoryPrices ?? {},
            additionalBarcodes: data.additionalBarcodes ?? previousItem.additionalBarcodes ?? [],
            secondaryBarcode: data.secondaryBarcode ?? previousItem.secondaryBarcode,
            primaryUnit: data.primaryUnit ?? previousItem.primaryUnit,
            primaryUnitQuantity: typeof data.primaryUnitQuantity === 'number' ? data.primaryUnitQuantity : previousItem.primaryUnitQuantity ?? 1,
            secondaryUnit: data.secondaryUnit ?? previousItem.secondaryUnit,
            secondaryUnitQuantity: typeof data.secondaryUnitQuantity === 'number' ? data.secondaryUnitQuantity : previousItem.secondaryUnitQuantity,
            secondaryUnitPrice: typeof data.secondaryUnitPrice === 'number' ? data.secondaryUnitPrice : previousItem.secondaryUnitPrice,
            secondaryUnits: Array.isArray(data.secondaryUnits)
                ? data.secondaryUnits
                    .map((entry) => ({
                        unit: typeof entry?.unit === 'string' ? entry.unit : undefined,
                        barcode: typeof entry?.barcode === 'string' ? entry.barcode : undefined,
                        quantity: typeof entry?.quantity === 'number' ? entry.quantity : undefined,
                        price: typeof entry?.price === 'number' ? entry.price : undefined,
                    }))
                    .filter((entry) => entry.unit || entry.barcode || typeof entry.quantity === 'number' || typeof entry.price === 'number')
                : (previousItem as any).secondaryUnits ?? [],
            isWeighed: typeof data.isWeighed === 'boolean' ? data.isWeighed : previousItem.isWeighed ?? false,
            isSized: typeof data.isSized === 'boolean' ? data.isSized : (previousItem as any).isSized ?? false,
            isManufactured: typeof (data as any).isManufactured === 'boolean' ? (data as any).isManufactured : (previousItem as any).isManufactured ?? false,
            productionOutputQuantity: (typeof (data as any).isManufactured === 'boolean'
                ? (data as any).isManufactured
                : ((previousItem as any).isManufactured ?? false))
                ? ((typeof (data as any).productionOutputQuantity === 'number' && (data as any).productionOutputQuantity > 0)
                    ? (data as any).productionOutputQuantity
                    : ((typeof (previousItem as any).productionOutputQuantity === 'number' && (previousItem as any).productionOutputQuantity > 0)
                        ? (previousItem as any).productionOutputQuantity
                        : 1))
                : undefined,
            productionFormula: Array.isArray((data as any).productionFormula)
                ? (data as any).productionFormula
                    .map((entry: any) => ({
                        materialId: String(entry?.materialId || '').trim(),
                        quantity: typeof entry?.quantity === 'number' ? entry.quantity : Number(entry?.quantity || 0),
                        unit: typeof entry?.unit === 'string' ? entry.unit : undefined,
                        notes: typeof entry?.notes === 'string' ? entry.notes : undefined,
                        wastePercent: typeof entry?.wastePercent === 'number' ? entry.wastePercent : undefined,
                    }))
                    .filter((entry: any) => entry.materialId && typeof entry.quantity === 'number' && entry.quantity > 0)
                : ((previousItem as any).productionFormula ?? []),
            shelfLifeYears: data.hasOwnProperty('shelfLifeYears')
                ? (normalizeShelfLifePart((data as any).shelfLifeYears) || undefined)
                : ((previousItem as any).shelfLifeYears ?? undefined),
            shelfLifeMonths: data.hasOwnProperty('shelfLifeMonths')
                ? (normalizeShelfLifePart((data as any).shelfLifeMonths) || undefined)
                : ((previousItem as any).shelfLifeMonths ?? undefined),
            shelfLifeDays: data.hasOwnProperty('shelfLifeDays')
                ? (normalizeShelfLifePart((data as any).shelfLifeDays) || undefined)
                : ((previousItem as any).shelfLifeDays ?? undefined),
            alternativeItemIds: normalizedAlternatives,
            groupId: data.groupId ?? previousItem.groupId,
            itemSymbolName: data.itemSymbolName ?? previousItem.itemSymbolName,
            globalReferenceNumber: data.globalReferenceNumber ?? (previousItem as any).globalReferenceNumber,
            additionalDetails: data.additionalDetails ?? previousItem.additionalDetails,
            imageUrl: data.imageUrl ?? previousItem.imageUrl,
            allowDescriptionEdit: typeof data.allowDescriptionEdit === 'boolean' ? data.allowDescriptionEdit : previousItem.allowDescriptionEdit ?? false,
            hasSerialNumber: typeof data.hasSerialNumber === 'boolean' ? data.hasSerialNumber : previousItem.hasSerialNumber ?? false,
            hasRfid: typeof data.hasRfid === 'boolean' ? data.hasRfid : previousItem.hasRfid ?? false,
            usesDefaultTax: data.usesDefaultTax !== undefined ? data.usesDefaultTax : (previousItem.usesDefaultTax ?? true),
            enableProfitMargin: typeof data.enableProfitMargin === 'boolean' ? data.enableProfitMargin : (previousItem.enableProfitMargin ?? false),
            profitMargin: typeof data.profitMargin === 'number' ? data.profitMargin : previousItem.profitMargin,
            usesProductShapes: typeof data.usesProductShapes === 'boolean' ? data.usesProductShapes : (previousItem.usesProductShapes ?? false),
            productShapeType: data.productShapeType ?? previousItem.productShapeType,
            productShapes: Array.isArray(data.productShapes)
                ? data.productShapes
                    .filter((shape) => shape && typeof shape.name === 'string' && shape.name.trim().length > 0)
                    .map((shape, index) => ({
                        id: String(shape.id || `shape-${index + 1}`),
                        name: String(shape.name).trim(),
                        salePrice: typeof shape.salePrice === 'number' ? shape.salePrice : 0,
                        costPrice: typeof shape.costPrice === 'number' ? shape.costPrice : undefined,
                        type: typeof shape.type === 'string' ? shape.type : data.productShapeType,
                        isActive: shape.isActive ?? true,
                    }))
                : (previousItem.productShapes ?? []),
        };
        if (data.hasOwnProperty('taxRate') && updated.usesDefaultTax === false && data.taxRate !== undefined) {
            updated.taxRate = data.taxRate;
        } else if (updated.usesDefaultTax === true && 'taxRate' in updated) {
            delete updated.taxRate;
        }

        if ((updated as any).isManufactured) {
            const settings = getSettings();
            const defaultLineTaxRate = Number(settings?.defaultLineTaxRate || 0);
            const itemGroups = getItemGroups();
            const itemGroupsById = new Map(itemGroups.map((group) => [group.id, group]));
            const materialsById = new Map(items.map((entry) => [String(entry.id || ''), entry]));
            materialsById.set(updated.id, updated);
            updated.purchasePrice = computeManufacturedPurchasePrice(updated, materialsById, itemGroupsById, defaultLineTaxRate);
        }
        // If itemNumber changes ensure uniqueness
        if (data.itemNumber) {
            const duplicate = items.some((itm, idx) => idx !== itemIndex && (itm.id === data.itemNumber || itm.itemNumber === data.itemNumber));
            if (duplicate) return false;
        }

        const normalizeCode = (value: any) => String(value || '').trim();
        const otherBarcodes = new Set<string>();
        items.forEach((entry, idx) => {
            if (idx === itemIndex) return;
            const secondaryUnitCodes = Array.isArray((entry as any).secondaryUnits)
                ? (entry as any).secondaryUnits.map((row: any) => row?.barcode)
                : [];

            [
                entry.barcode,
                entry.secondaryBarcode,
                ...(entry.additionalBarcodes || []),
                ...secondaryUnitCodes,
            ]
                .map(normalizeCode)
                .filter(Boolean)
                .forEach((code) => otherBarcodes.add(code));
        });

        const currentBarcodes = new Set<string>();
        const assertBarcode = (code: string, duplicateError: string) => {
            if (!code) return;
            if (currentBarcodes.has(code)) {
                throw new Error('Duplicate barcode value in the same item');
            }
            if (otherBarcodes.has(code)) {
                throw new Error(duplicateError);
            }
            currentBarcodes.add(code);
        };

        const normalizedPrimaryBarcode = normalizeCode(updated.barcode);
        assertBarcode(normalizedPrimaryBarcode, 'Barcode already exists');

        const normalizedSecondaryBarcode = normalizeCode(updated.secondaryBarcode);
        assertBarcode(normalizedSecondaryBarcode, 'Secondary barcode already exists');

        const normalizedAdditionalBarcodes = Array.from(new Set((updated.additionalBarcodes || [])
            .map((code) => normalizeCode(code))
            .filter(Boolean)));
        normalizedAdditionalBarcodes.forEach((code) => assertBarcode(code, 'Additional barcode already exists'));

        const normalizedSecondaryUnits = Array.isArray(updated.secondaryUnits)
            ? updated.secondaryUnits
                .map((entry) => {
                    const normalizedBarcode = normalizeCode(entry?.barcode);
                    assertBarcode(normalizedBarcode, 'Unit barcode already exists');

                    return {
                        unit: typeof entry?.unit === 'string' ? entry.unit : undefined,
                        barcode: normalizedBarcode || undefined,
                        quantity: typeof entry?.quantity === 'number' ? entry.quantity : undefined,
                        price: typeof entry?.price === 'number' ? entry.price : undefined,
                    };
                })
                .filter((entry) => entry.unit || entry.barcode || typeof entry.quantity === 'number' || typeof entry.price === 'number')
            : [];

        updated.barcode = normalizedPrimaryBarcode || undefined;
        updated.secondaryBarcode = normalizedSecondaryBarcode || undefined;
        updated.additionalBarcodes = normalizedAdditionalBarcodes;
        (updated as any).secondaryUnits = normalizedSecondaryUnits;

        const selectedSet = new Set(updated.alternativeItemIds || []);
        const previousId = previousItem.id;
        for (let idx = 0; idx < items.length; idx += 1) {
            if (idx === itemIndex) continue;
            const entry = items[idx];
            const entryAlternatives = new Set(
                Array.isArray((entry as any).alternativeItemIds)
                    ? (entry as any).alternativeItemIds.map((altId: any) => String(altId || '').trim()).filter(Boolean)
                    : []
            );

            if (previousId !== updated.id && entryAlternatives.has(previousId)) {
                entryAlternatives.delete(previousId);
                entryAlternatives.add(updated.id);
            }

            if (selectedSet.has(entry.id)) {
                entryAlternatives.add(updated.id);
            } else {
                entryAlternatives.delete(updated.id);
            }

            items[idx] = {
                ...entry,
                alternativeItemIds: Array.from(entryAlternatives).filter((altId): altId is string => typeof altId === 'string' && altId !== entry.id),
            };
        }

        items[itemIndex] = updated;
        writeData('materials.json', items);
        return true;
    }
    return false;
    });
}

export function deleteMaterial(id: string): boolean {
    let items = getMaterials();
    const initialLength = items.length;
    items = items
        .filter(i => i.id !== id)
        .map((item) => ({
            ...item,
            alternativeItemIds: Array.isArray((item as any).alternativeItemIds)
                ? (item as any).alternativeItemIds.map((altId: any) => String(altId || '').trim()).filter((altId: string) => altId && altId !== id)
                : [],
        }));
    if (items.length < initialLength) {
        writeData('materials.json', items);
        return true;
    }
    return false;
}

export function getExpenses() {
    const items = readData<ProductionItem[]>('expenses.json', []);
    return items.map((item, idx) => ({
        ...item,
        itemNumber: item.itemNumber || item.id || `exp-${idx + 1}`,
        id: item.itemNumber || item.id || `exp-${idx + 1}`,
        salePrice: typeof item.salePrice === 'number' ? item.salePrice : 0,
        categoryPrices: item.categoryPrices || {},
    }));
}

export function addExpense(data: string | Partial<ProductionItem>): ProductionItem {
    return withDbLock(() => {
        const items = getExpenses();
        const numericIds = items
            .map((item) => Number.parseInt(String(item.id || '').replace(/^exp-/, ''), 10))
            .filter((id) => Number.isFinite(id));
        const newId = `exp-${(numericIds.length > 0 ? Math.max(...numericIds) : 0) + 1}`;
        const payload = typeof data === 'string' ? { name: data } : (data || {});
        const newItem: ProductionItem = {
            id: newId,
            itemNumber: String(payload.itemNumber || newId),
            name: String(payload.name || '').trim() || 'Expense',
            salePrice: typeof payload.salePrice === 'number' ? payload.salePrice : 0,
            categoryPrices: payload.categoryPrices || {},
            realEstateProjectId: String(payload.realEstateProjectId || '').trim() || undefined,
            realEstateUnitId: String(payload.realEstateUnitId || '').trim() || undefined,
            realEstateCostType: (payload.realEstateCostType as any) || 'general',
            realEstateAllocationMethod: (payload.realEstateAllocationMethod as any) || 'area',
        };
        items.push(newItem);
        writeData('expenses.json', items);
        return newItem;
    });
}

export function updateExpense(id: string, data: string | Partial<ProductionItem>): boolean {
    const items = getExpenses();
    const itemIndex = items.findIndex(i => i.id === id);
    if (itemIndex !== -1) {
        const patch = typeof data === 'string' ? { name: data } : (data || {});
        items[itemIndex] = {
            ...items[itemIndex],
            ...patch,
            name: String(patch.name || items[itemIndex].name || '').trim() || items[itemIndex].name,
            realEstateProjectId: String(patch.realEstateProjectId || '').trim() || undefined,
            realEstateUnitId: String(patch.realEstateUnitId || '').trim() || undefined,
            realEstateCostType: (patch.realEstateCostType as any) || items[itemIndex].realEstateCostType || 'general',
            realEstateAllocationMethod: (patch.realEstateAllocationMethod as any) || items[itemIndex].realEstateAllocationMethod || 'area',
        };
        writeData('expenses.json', items);
        return true;
    }
    return false;
}

export function deleteExpense(id: string): boolean {
    let items = getExpenses();
    const initialLength = items.length;
    items = items.filter(i => i.id !== id);
    if (items.length < initialLength) {
        writeData('expenses.json', items);
        return true;
    }
    return false;
}

export function getProductShapeDefinitions() {
    const items = readData<ProductionItem[]>('product_shape_definitions.json', []);
    return items.map((item, idx) => ({
        ...item,
        itemNumber: item.itemNumber || item.id || `psh-${idx + 1}`,
        id: item.itemNumber || item.id || `psh-${idx + 1}`,
        salePrice: typeof item.salePrice === 'number' ? item.salePrice : 0,
        categoryPrices: item.categoryPrices || {},
    }));
}

export function addProductShapeDefinition(name: string): ProductionItem {
    return withDbLock(() => {
        const items = getProductShapeDefinitions();
        const trimmedName = String(name || '').trim();
        if (!trimmedName) throw new Error('Shape name is required');
        const duplicate = items.some((item) => item.name.trim().toLowerCase() === trimmedName.toLowerCase());
        if (duplicate) throw new Error('شكل/حجم بهذا الاسم موجود مسبقاً');

        const maxSeq = items.reduce((max, item) => {
            const m = String(item.itemNumber || '').match(/PSH-(\d+)/i);
            const n = m ? Number(m[1]) : 0;
            return Number.isFinite(n) ? Math.max(max, n) : max;
        }, 0);
        const shapeNumber = `PSH-${String(maxSeq + 1).padStart(4, '0')}`;

        const newItem: ProductionItem = { id: shapeNumber, itemNumber: shapeNumber, name: trimmedName, salePrice: 0, categoryPrices: {} };
        items.push(newItem);
        writeData('product_shape_definitions.json', items);
        return newItem;
    });
}

export function addProductShapeDefinitions(names: string[]): { added: ProductionItem[]; skipped: string[] } {
    return withDbLock(() => {
        const items = getProductShapeDefinitions();
        const existingNames = new Set(items.map((item) => item.name.trim().toLowerCase()));
        const maxSeq = items.reduce((max, item) => {
            const m = String(item.itemNumber || '').match(/PSH-(\d+)/i);
            const n = m ? Number(m[1]) : 0;
            return Number.isFinite(n) ? Math.max(max, n) : max;
        }, 0);
        const added: ProductionItem[] = [];
        const skipped: string[] = [];
        let nextSeq = maxSeq;
        for (const raw of names) {
            const trimmed = String(raw || '').trim();
            if (!trimmed) continue;
            if (existingNames.has(trimmed.toLowerCase())) { skipped.push(trimmed); continue; }
            nextSeq += 1;
            const shapeNumber = `PSH-${String(nextSeq).padStart(4, '0')}`;
            const newItem: ProductionItem = { id: shapeNumber, itemNumber: shapeNumber, name: trimmed, salePrice: 0, categoryPrices: {} };
            existingNames.add(trimmed.toLowerCase());
            added.push(newItem);
            items.push(newItem);
        }
        if (added.length > 0) writeData('product_shape_definitions.json', items);
        return { added, skipped };
    });
}

export function updateProductShapeDefinition(id: string, name: string): boolean {
    return withDbLock(() => {
        const items = getProductShapeDefinitions();
        const trimmedName = String(name || '').trim();
        const itemIndex = items.findIndex((item) => item.id === id);
        if (itemIndex === -1) return false;
        const duplicate = items.some((item, i) => i !== itemIndex && item.name.trim().toLowerCase() === trimmedName.toLowerCase());
        if (duplicate) throw new Error('شكل/حجم بهذا الاسم موجود مسبقاً');
        items[itemIndex] = { ...items[itemIndex], name: trimmedName };
        writeData('product_shape_definitions.json', items);
        return true;
    }) as boolean;
}

export function deleteProductShapeDefinition(id: string): boolean {
    let items = getProductShapeDefinitions();
    const initialLength = items.length;
    items = items.filter((item) => item.id !== id);
    if (items.length < initialLength) {
        writeData('product_shape_definitions.json', items);
        return true;
    }
    return false;
}

export function addCustomer(data: { name: string; category?: string; accountCode?: string; phone?: string; creditLimit?: number; address?: string; customerNumber?: string; salesRepId?: string; }): Customer {
    return withDbLock(() => {
        const items = getCustomers();
        const numericIds = items
            .map((customer) => Number.parseInt(String(customer.id || ''), 10))
            .filter((id) => Number.isFinite(id));
        const newId = String((numericIds.length > 0 ? Math.max(...numericIds) : 0) + 1);
        const number = data.customerNumber || newId;
        const category = data.category || 'general';

        if (category && category.trim()) {
            addCustomerCategory(category);
        }

        const newItem: Customer = {
            id: newId,
            customerNumber: number,
            name: data.name,
            balance: 0,
            category: category,
            accountCode: data.accountCode || '',
            phone: data.phone || '',
            creditLimit: typeof data.creditLimit === 'number' ? data.creditLimit : 0,
            address: data.address || '',
            salesRepId: data.salesRepId || '',
        };
        items.push(newItem);
        writeData('customers.json', items);
        return newItem;
    });
}

export function updateCustomer(id: string, name: string, category?: string, phone?: string, creditLimit?: number, address?: string, allowedDiscount?: number, salesRepId?: string, accountCode?: string): boolean {
    const items = getCustomers();
    const itemIndex = items.findIndex(i => i.id === id);
    if (itemIndex !== -1) {
        items[itemIndex].name = name;
        if (category) {
            items[itemIndex].category = category;
            // Ensure category exists in categories list
            if (category.trim()) {
                addCustomerCategory(category);
            }
        }
        if (typeof phone === 'string') items[itemIndex].phone = phone;
        if (typeof creditLimit === 'number') items[itemIndex].creditLimit = creditLimit;
        if (typeof address === 'string') items[itemIndex].address = address;
        if (typeof allowedDiscount === 'number') items[itemIndex].allowedDiscount = allowedDiscount;
        if (typeof salesRepId === 'string') items[itemIndex].salesRepId = salesRepId;
        if (typeof accountCode === 'string') items[itemIndex].accountCode = accountCode;
        writeData('customers.json', items);
        return true;
    }
    return false;
}

export function updateCustomerBalance(id: string, amountChange: number): boolean {
    const items = getCustomers();
    const itemIndex = items.findIndex(i => i.id === id);
    if (itemIndex !== -1) {
        items[itemIndex].balance += amountChange;
        writeData('customers.json', items);
        return true;
    }
    return false;
}

export function deleteCustomer(id: string): boolean {
    let items = getCustomers();
    const initialLength = items.length;
    items = items.filter(i => i.id !== id);
    if (items.length < initialLength) {
        writeData('customers.json', items);
        return true;
    }
    return false;
}

export function getProductionReports() {
    return readData<ProductionReport[]>('production_reports.json', []);
}

export function addProductionReport(report: Omit<ProductionReport, 'id'>): ProductionReport {
    const reports = getProductionReports();
    const newReport: ProductionReport = {
        ...report,
        id: format(new Date(report.date), 'yyyy-MM-dd')
    };

    const existingIndex = reports.findIndex(r => r.id === newReport.id);
    if (existingIndex !== -1) {
        reports[existingIndex] = newReport; // Overwrite if report for the same day exists
    } else {
        reports.push(newReport);
    }

    writeData('production_reports.json', reports.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()));
    return newReport;
}

export function getCustomerPricing() {
    const pricing = readData<any[]>('customer_pricing.json', []);
    // migrate legacy customerId-based pricing to category-based
    return pricing.map((p, idx) => ({
        ...p,
        category: p.category || p.customerId || 'general',
        prices: p.prices || [],
        id: p.id || `cp-${idx + 1}`,
    })) as CustomerPricing[];
}

export function updateCustomerPricing(data: CustomerPricing): boolean {
    const allPricing = getCustomerPricing();
    const customerIndex = allPricing.findIndex(p => p.category === data.category);
    if (customerIndex !== -1) {
        allPricing[customerIndex] = data;
    } else {
        allPricing.push(data);
    }
    writeData('customer_pricing.json', allPricing);
    return true;
}

const normalizeUnitCode = (value?: string) => String(value || '').trim();

export function getItemUnits(): ItemUnit[] {
    const units = readData<any[]>('item_units.json', []);
    return units
        .map((unit, index) => ({
            id: String(unit?.id || `unit-${index + 1}`),
            itemId: String(unit?.itemId || ''),
            serialNumber: normalizeUnitCode(unit?.serialNumber) || undefined,
            rfidCode: normalizeUnitCode(unit?.rfidCode) || undefined,
            status: (['in_stock', 'sold', 'returned', 'damaged'].includes(unit?.status) ? unit.status : 'in_stock') as ItemUnitStatus,
            warehouseId: normalizeUnitCode(unit?.warehouseId) || undefined,
            invoiceId: normalizeUnitCode(unit?.invoiceId) || undefined,
            createdAt: String(unit?.createdAt || new Date().toISOString()),
        }))
        .filter((unit) => unit.itemId && (unit.serialNumber || unit.rfidCode));
}

export function findItemUnitByCode(code: string, itemId?: string): ItemUnit | undefined {
    const normalized = normalizeUnitCode(code).toLowerCase();
    if (!normalized) return undefined;
    const units = getItemUnits();
    return units.find((unit) => {
        if (itemId && unit.itemId !== itemId) return false;
        return unit.serialNumber?.toLowerCase() === normalized || unit.rfidCode?.toLowerCase() === normalized;
    });
}

export function getAvailableItemUnitsByItemId(itemId: string): ItemUnit[] {
    return getItemUnits().filter((unit) => unit.itemId === itemId && unit.status !== 'sold' && unit.status !== 'damaged');
}

export function upsertItemUnitsBulk(payload: {
    itemId: string;
    warehouseId?: string;
    rows: Array<{ serialNumber?: string; rfidCode?: string }>;
}): {
    added: number;
    updated: number;
    rejected: Array<{ row: number; reason: string; serialNumber?: string; rfidCode?: string }>;
} {
    const units = getItemUnits();
    const normalizedItemId = String(payload.itemId || '').trim();
    if (!normalizedItemId) {
        return { added: 0, updated: 0, rejected: [{ row: 0, reason: 'MISSING_ITEM_ID' }] };
    }

    const bySerial = new Map<string, ItemUnit>();
    const byRfid = new Map<string, ItemUnit>();
    units.forEach((unit) => {
        if (unit.serialNumber) bySerial.set(unit.serialNumber.toLowerCase(), unit);
        if (unit.rfidCode) byRfid.set(unit.rfidCode.toLowerCase(), unit);
    });

    let added = 0;
    let updated = 0;
    const rejected: Array<{ row: number; reason: string; serialNumber?: string; rfidCode?: string }> = [];

    payload.rows.forEach((row, index) => {
        const serialNumber = normalizeUnitCode(row.serialNumber);
        const rfidCode = normalizeUnitCode(row.rfidCode);
        if (!serialNumber && !rfidCode) {
            rejected.push({ row: index + 1, reason: 'EMPTY_ROW' });
            return;
        }

        const serialKey = serialNumber.toLowerCase();
        const rfidKey = rfidCode.toLowerCase();
        const serialHit = serialNumber ? bySerial.get(serialKey) : undefined;
        const rfidHit = rfidCode ? byRfid.get(rfidKey) : undefined;

        if (serialHit && serialHit.itemId !== normalizedItemId) {
            rejected.push({ row: index + 1, reason: 'SERIAL_EXISTS_OTHER_ITEM', serialNumber, rfidCode });
            return;
        }
        if (rfidHit && rfidHit.itemId !== normalizedItemId) {
            rejected.push({ row: index + 1, reason: 'RFID_EXISTS_OTHER_ITEM', serialNumber, rfidCode });
            return;
        }

        const target = serialHit || rfidHit;
        if (target) {
            if (target.status === 'sold') {
                rejected.push({ row: index + 1, reason: 'UNIT_ALREADY_SOLD', serialNumber, rfidCode });
                return;
            }

            target.itemId = normalizedItemId;
            target.serialNumber = serialNumber || target.serialNumber;
            target.rfidCode = rfidCode || target.rfidCode;
            target.warehouseId = normalizeUnitCode(payload.warehouseId) || target.warehouseId;
            updated += 1;
            if (target.serialNumber) bySerial.set(target.serialNumber.toLowerCase(), target);
            if (target.rfidCode) byRfid.set(target.rfidCode.toLowerCase(), target);
            return;
        }

        const newUnit: ItemUnit = {
            id: `${nextPrefixedId('unit')}-${index}`,
            itemId: normalizedItemId,
            serialNumber: serialNumber || undefined,
            rfidCode: rfidCode || undefined,
            status: 'in_stock',
            warehouseId: normalizeUnitCode(payload.warehouseId) || undefined,
            invoiceId: undefined,
            createdAt: new Date().toISOString(),
        };
        units.unshift(newUnit);
        added += 1;
        if (newUnit.serialNumber) bySerial.set(newUnit.serialNumber.toLowerCase(), newUnit);
        if (newUnit.rfidCode) byRfid.set(newUnit.rfidCode.toLowerCase(), newUnit);
    });

    writeData('item_units.json', units);
    return { added, updated, rejected };
}

export function updateItemUnitsStatusByIds(unitIds: string[], status: ItemUnitStatus, invoiceId?: string): number {
    const idSet = new Set((unitIds || []).map((id) => String(id)));
    if (idSet.size === 0) return 0;

    const units = getItemUnits();
    let changed = 0;
    units.forEach((unit) => {
        if (!idSet.has(unit.id)) return;
        unit.status = status;
        unit.invoiceId = invoiceId || unit.invoiceId;
        changed += 1;
    });
    if (changed > 0) {
        writeData('item_units.json', units);
    }
    return changed;
}

export function getCoupons(): Coupon[] {
    return readData<Coupon[]>('coupons.json', []);
}

export function getCampaigns(): Campaign[] {
    return readData<Campaign[]>('campaigns.json', []);
}

export function getActiveCampaigns(atIso?: string): Campaign[] {
    const now = new Date(atIso || new Date().toISOString()).getTime();
    return getCampaigns()
        .filter((campaign) => {
            if ((campaign.status || 'inactive') !== 'active') return false;
            const start = new Date(String(campaign.startDate || '')).getTime();
            const end = new Date(String(campaign.endDate || '')).getTime();
            if (!Number.isNaN(start) && now < start) return false;
            if (!Number.isNaN(end) && now > end) return false;
            return true;
        })
        .sort((a, b) => Number(b.priority || 0) - Number(a.priority || 0));
}

export function addCampaign(payload: {
    name: string;
    type?: CampaignType;
    startDate: string;
    endDate: string;
    status?: CampaignStatus;
    description?: string;
    branchIds?: string[];
    customerTags?: string[];
    autoApplyInPos?: boolean;
    allowRepeats?: boolean;
    allowStacking?: boolean;
    priority?: number;
    maxApplicationsPerInvoice?: number;
    items: Array<{
        productId?: string;
        categoryId?: string;
        minQty: number;
        bundlePrice: number;
        regularUnitPrice?: number;
        maxApplications?: number;
    }>;
    createdBy?: string;
}): Campaign {
    return withDbLock(() => {
        const campaigns = getCampaigns();
        const now = new Date().toISOString();
        const campaignId = nextPrefixedId('campaign');
        const created: Campaign = {
            id: campaignId,
            name: String(payload.name || '').trim(),
            type: payload.type || 'multi_buy',
            startDate: String(payload.startDate || '').trim(),
            endDate: String(payload.endDate || '').trim(),
            status: payload.status || 'active',
            description: String(payload.description || '').trim() || undefined,
            branchIds: Array.isArray(payload.branchIds)
                ? payload.branchIds.map((value) => String(value || '').trim()).filter(Boolean)
                : undefined,
            customerTags: Array.isArray(payload.customerTags)
                ? payload.customerTags.map((value) => String(value || '').trim()).filter(Boolean)
                : undefined,
            autoApplyInPos: payload.autoApplyInPos !== false,
            allowRepeats: payload.allowRepeats !== false,
            allowStacking: payload.allowStacking === true,
            priority: Number.isFinite(Number(payload.priority)) ? Number(payload.priority) : undefined,
            maxApplicationsPerInvoice: Number.isFinite(Number(payload.maxApplicationsPerInvoice))
                ? Number(payload.maxApplicationsPerInvoice)
                : undefined,
            items: (payload.items || []).map((item) => ({
                id: nextPrefixedId('campaign-item'),
                campaignId,
                productId: String(item.productId || '').trim() || undefined,
                categoryId: String(item.categoryId || '').trim() || undefined,
                minQty: Number(item.minQty || 0),
                bundlePrice: Number(item.bundlePrice || 0),
                regularUnitPrice: Number.isFinite(Number(item.regularUnitPrice)) ? Number(item.regularUnitPrice) : undefined,
                maxApplications: Number.isFinite(Number(item.maxApplications)) ? Number(item.maxApplications) : undefined,
            })),
            createdAt: now,
            createdBy: String(payload.createdBy || '').trim() || undefined,
        };

        campaigns.unshift(created);
        writeData('campaigns.json', campaigns);
        return created;
    });
}

export function updateCampaign(campaignId: string, updates: Partial<Omit<Campaign, 'id' | 'createdAt' | 'createdBy'>>): Campaign | null {
    return withDbLock(() => {
        const campaigns = getCampaigns();
        const index = campaigns.findIndex((campaign) => String(campaign.id || '').trim() === String(campaignId || '').trim());
        if (index === -1) return null;

        const current = campaigns[index];
        const nextItems = Array.isArray(updates.items)
            ? updates.items.map((item) => ({
                ...item,
                id: String(item.id || '').trim() || nextPrefixedId('campaign-item'),
                campaignId: current.id,
                minQty: Number(item.minQty || 0),
                bundlePrice: Number(item.bundlePrice || 0),
                regularUnitPrice: Number.isFinite(Number(item.regularUnitPrice)) ? Number(item.regularUnitPrice) : undefined,
                maxApplications: Number.isFinite(Number(item.maxApplications)) ? Number(item.maxApplications) : undefined,
            }))
            : current.items;

        const next: Campaign = {
            ...current,
            ...updates,
            items: nextItems,
            updatedAt: new Date().toISOString(),
        };

        campaigns[index] = next;
        writeData('campaigns.json', campaigns);
        return next;
    });
}

export function deleteCampaign(campaignId: string): boolean {
    return withDbLock(() => {
        const campaigns = getCampaigns();
        const filtered = campaigns.filter((campaign) => String(campaign.id || '').trim() !== String(campaignId || '').trim());
        if (filtered.length === campaigns.length) return false;
        writeData('campaigns.json', filtered);
        return true;
    });
}

export function addCoupon(payload: {
    title: string;
    amount: number;
    couponCode?: string;
    currencyCode?: string;
    baseCurrencyCode?: string;
    exchangeRate?: number;
    rateDate?: string;
    originalAmountBase?: number;
    remainingAmountBase?: number;
    customerType?: 'cash' | 'customer';
    customerId?: string;
    customerName?: string;
    customerAccountCode?: string;
    paymentMethod?: 'cash' | 'check' | 'credit';
    settlementAccountCode?: string;
    settlementAccountName?: string;
    liabilityAccountCode?: string;
    liabilityAccountName?: string;
    expiryDate?: string;
    notes?: string;
    createdBy?: string;
}): Coupon {
    return withDbLock(() => {
        const coupons = getCoupons();
        const usedCodes = new Set(coupons.map((coupon) => String(coupon.couponCode || '').trim().toLowerCase()).filter(Boolean));
        const usedNumbers = new Set(coupons.map((coupon) => String(coupon.couponNumber || '').trim().toUpperCase()).filter(Boolean));

        const requestedCode = String(payload.couponCode || '').trim();
        let couponCode = requestedCode;
        if (!couponCode) {
            let generated = `CPN-${String(nextNamedSequence('coupon-code')).padStart(8, '0')}`;
            while (usedCodes.has(generated.toLowerCase())) {
                generated = `CPN-${String(nextNamedSequence('coupon-code')).padStart(8, '0')}`;
            }
            couponCode = generated;
        }
        if (usedCodes.has(couponCode.toLowerCase())) {
            throw new Error('COUPON_CODE_EXISTS');
        }

        let couponNumber = `CPN/${new Date().getFullYear()}/${String(nextNamedSequence('coupon-ref')).padStart(5, '0')}`;
        while (usedNumbers.has(couponNumber.toUpperCase())) {
            couponNumber = `CPN/${new Date().getFullYear()}/${String(nextNamedSequence('coupon-ref')).padStart(5, '0')}`;
        }

        const amount = Math.max(0, Number(payload.amount || 0));
        if (amount <= 0) {
            throw new Error('COUPON_AMOUNT_INVALID');
        }

        const now = new Date().toISOString();
        const created: Coupon = {
            id: nextPrefixedId('coupon'),
            couponNumber,
            couponCode,
            title: String(payload.title || '').trim() || couponCode,
            originalAmount: amount,
            remainingAmount: amount,
            originalAmountBase: Number.isFinite(Number(payload.originalAmountBase)) ? Number(payload.originalAmountBase) : undefined,
            remainingAmountBase: Number.isFinite(Number(payload.remainingAmountBase)) ? Number(payload.remainingAmountBase) : undefined,
            status: 'active',
            currencyCode: String(payload.currencyCode || '').trim() || undefined,
            baseCurrencyCode: String(payload.baseCurrencyCode || '').trim() || undefined,
            exchangeRate: Number.isFinite(Number(payload.exchangeRate)) && Number(payload.exchangeRate) > 0 ? Number(payload.exchangeRate) : undefined,
            rateDate: String(payload.rateDate || '').trim() || undefined,
            customerType: payload.customerType === 'customer' ? 'customer' : 'cash',
            customerId: String(payload.customerId || '').trim() || undefined,
            customerName: String(payload.customerName || '').trim() || undefined,
            customerAccountCode: String(payload.customerAccountCode || '').trim() || undefined,
            paymentMethod: payload.paymentMethod || 'cash',
            settlementAccountCode: String(payload.settlementAccountCode || '').trim() || undefined,
            settlementAccountName: String(payload.settlementAccountName || '').trim() || undefined,
            liabilityAccountCode: String(payload.liabilityAccountCode || '').trim() || undefined,
            liabilityAccountName: String(payload.liabilityAccountName || '').trim() || undefined,
            soldAt: now,
            expiryDate: String(payload.expiryDate || '').trim() || undefined,
            notes: String(payload.notes || '').trim() || undefined,
            createdBy: String(payload.createdBy || '').trim() || undefined,
            createdAt: now,
        };

        coupons.unshift(created);
        writeData('coupons.json', coupons);
        return created;
    });
}

export function updateCouponStatus(couponId: string, status: Coupon['status'], notes?: string): Coupon | null {
    return withDbLock(() => {
        const coupons = getCoupons();
        const index = coupons.findIndex((coupon) => String(coupon.id || '').trim() === String(couponId || '').trim());
        if (index === -1) return null;
        const current = coupons[index];
        const next: Coupon = {
            ...current,
            status,
            notes: String(notes || current.notes || '').trim() || current.notes,
            updatedAt: new Date().toISOString(),
        };
        coupons[index] = next;
        writeData('coupons.json', coupons);
        return next;
    });
}

export function applyCouponFxDifference(couponId: string, deltaBase: number, referenceId?: string): Coupon | null {
    return withDbLock(() => {
        const coupons = getCoupons();
        const index = coupons.findIndex((coupon) => String(coupon.id || '').trim() === String(couponId || '').trim());
        if (index === -1) return null;

        const current = coupons[index];
        const delta = Number(deltaBase || 0);
        if (!Number.isFinite(delta) || Math.abs(delta) <= 0.0000001) {
            return current;
        }

        const next: Coupon = {
            ...current,
            cumulativeFxDifferenceBase: Math.round(((Number(current.cumulativeFxDifferenceBase || 0) + delta) * 1_000_000)) / 1_000_000,
            lastFxSettlementAt: new Date().toISOString(),
            lastFxReferenceId: String(referenceId || '').trim() || current.lastFxReferenceId,
            updatedAt: new Date().toISOString(),
        };

        coupons[index] = next;
        writeData('coupons.json', coupons);
        return next;
    });
}

export function redeemCouponsBatch(payload: {
    lines: Array<{ couponCode: string; amount: number }>;
    referenceId?: string;
    redeemedBy?: string;
}): { success: boolean; error?: string; applied?: Array<{ couponId: string; couponCode: string; amount: number; amountBase?: number }> } {
    return withDbLock(() => {
        const normalizedLines = (payload.lines || [])
            .map((line) => ({
                couponCode: String(line.couponCode || '').trim(),
                amount: Math.max(0, Number(line.amount || 0)),
            }))
            .filter((line) => line.couponCode && line.amount > 0);

        if (normalizedLines.length === 0) {
            return { success: true, applied: [] };
        }

        const coupons = getCoupons();
        const nowMs = Date.now();
        const byCode = new Map(coupons.map((coupon) => [String(coupon.couponCode || '').trim().toLowerCase(), coupon]));

        for (const line of normalizedLines) {
            const coupon = byCode.get(line.couponCode.toLowerCase());
            if (!coupon) {
                return { success: false, error: `COUPON_NOT_FOUND:${line.couponCode}` };
            }
            if (coupon.status === 'cancelled' || coupon.status === 'expired') {
                return { success: false, error: `COUPON_NOT_ACTIVE:${line.couponCode}` };
            }
            if (coupon.expiryDate) {
                const expiryMs = new Date(coupon.expiryDate).getTime();
                if (!Number.isNaN(expiryMs) && expiryMs < nowMs) {
                    return { success: false, error: `COUPON_EXPIRED:${line.couponCode}` };
                }
            }
            if (Number(coupon.remainingAmount || 0) + 1e-9 < line.amount) {
                return { success: false, error: `COUPON_INSUFFICIENT:${line.couponCode}` };
            }
        }

        const applied: Array<{ couponId: string; couponCode: string; amount: number; amountBase?: number }> = [];
        normalizedLines.forEach((line) => {
            const coupon = byCode.get(line.couponCode.toLowerCase());
            if (!coupon) return;
            const couponRate = Number(coupon.exchangeRate || 0) > 0 ? Number(coupon.exchangeRate) : 1;
            const amountBase = line.amount * couponRate;
            const nextRemaining = Math.max(0, Number(coupon.remainingAmount || 0) - line.amount);
            const currentRemainingBase = Number.isFinite(Number(coupon.remainingAmountBase))
                ? Number(coupon.remainingAmountBase)
                : Number(coupon.remainingAmount || 0) * couponRate;
            const nextRemainingBase = Math.max(0, currentRemainingBase - amountBase);
            coupon.remainingAmount = Math.round(nextRemaining * 100) / 100;
            coupon.remainingAmountBase = Math.round(nextRemainingBase * 1_000_000) / 1_000_000;
            coupon.status = coupon.remainingAmount <= 0 ? 'used' : 'active';
            coupon.lastUsedAt = new Date().toISOString();
            coupon.lastUsedBy = String(payload.redeemedBy || '').trim() || undefined;
            coupon.lastReferenceId = String(payload.referenceId || '').trim() || undefined;
            coupon.updatedAt = coupon.lastUsedAt;
            applied.push({ couponId: coupon.id, couponCode: coupon.couponCode, amount: line.amount, amountBase });
        });

        writeData('coupons.json', coupons);
        return { success: true, applied };
    });
}

// Migrate old invoices to add new required fields
function migrateInvoices(invoices: any[]): SalesInvoice[] {
    return invoices.map(inv => {
        if (!inv.status) {
            // Add missing fields for old invoices
            return {
                ...inv,
                status: 'active' as const,
                createdBy: inv.createdBy || 'system',
                createdAt: inv.createdAt || inv.date || new Date().toISOString(),
                lockedBy: undefined,
                lockedAt: undefined,
                cancelledAt: undefined,
                cancelledBy: undefined,
                cancelledReason: undefined,
                salesRepId: typeof inv.salesRepId === 'string' ? inv.salesRepId : '',
            };
        }
        return {
            ...inv,
            salesRepId: typeof inv.salesRepId === 'string' ? inv.salesRepId : '',
        } as SalesInvoice;
    });
}

export function getInvoices() {
    const invoices = readDataCached<any[]>('invoices.json', [], 5_000);
    const migrated = migrateInvoices(invoices);
    
    // Write back if migration occurred
    if (invoices.length > 0 && !invoices[0].status) {
        writeData('invoices.json', migrated);
    }
    
    return migrated;
}

/**
 * Generate a unique invoice number in format: S/YYYY/XXXX
 * Example: S/2025/0001
 */
function generateInvoiceNumber(): string {
    const currentYear = new Date().getFullYear();
    const invoices = getInvoices();
    
    // Filter invoices from the current year
    const currentYearInvoices = invoices.filter(inv => {
        // Guard against undefined or invalid invoiceNumber
        if (!inv.invoiceNumber || typeof inv.invoiceNumber !== 'string') {
            return false;
        }
        const parts = inv.invoiceNumber.split('/');
        if (parts.length < 2) {
            return false;
        }
        const invoiceYear = parseInt(parts[1]);
        return invoiceYear === currentYear;
    });
    
    // Get the next sequence number
    const nextSequence = currentYearInvoices.length + 1;
    const sequenceStr = String(nextSequence).padStart(4, '0');
    
    return `S/${currentYear}/${sequenceStr}`;
}

export function addInvoice(invoice: Omit<SalesInvoice, 'id' | 'invoiceNumber' | 'status' | 'createdBy' | 'createdAt' | 'lockedBy' | 'lockedAt' | 'cancelledAt' | 'cancelledBy' | 'cancelledReason'> & { createdBy: string }): SalesInvoice {
    return withDbLock(() => {
        const invoices = getInvoices();
        const newId = nextPrefixedId('inv');
        const invoiceNumber = generateInvoiceNumber();
        const now = new Date().toISOString();

        const newInvoice: SalesInvoice = {
            ...invoice,
            id: newId,
            invoiceNumber,
            status: 'active',
            createdAt: now,
            lockedBy: undefined,
            lockedAt: undefined,
            cancelledAt: undefined,
            cancelledBy: undefined,
            cancelledReason: undefined
        };
        invoices.unshift(newInvoice);
        writeData('invoices.json', invoices);
        return newInvoice;
    });
}

export function getPayments(): CustomerPayment[] {
    const baseCurrency = getDefaultCurrency();
    const baseCode = baseCurrency?.code || 'JOD';
    const payments = readData<CustomerPayment[]>('payments.json', []);
    return payments.map((payment) => {
        const currencyCode = payment.currencyCode || baseCode;
        const exchangeRate = Number(payment.exchangeRate || (currencyCode === baseCode ? 1 : getExchangeRate(currencyCode, baseCode)));
        return {
            ...payment,
            status: payment.status === 'cancelled' ? 'cancelled' : 'active',
            baseCurrencyCode: payment.baseCurrencyCode || baseCode,
            currencyCode,
            exchangeRate,
            amountBase: Number(payment.amountBase ?? ((payment.amount || 0) * exchangeRate)),
            allocations: Array.isArray(payment.allocations) ? payment.allocations : [],
            totalFxDifferenceBase: Number(payment.totalFxDifferenceBase || 0),
        };
    });
}

export function addPayment(payment: Omit<CustomerPayment, 'id'>): CustomerPayment {
    return withDbLock(() => {
        const payments = getPayments();
        const newPayment: CustomerPayment = {
            ...payment,
            status: payment.status || 'active',
            id: nextPrefixedId('pay')
        };
        payments.unshift(newPayment);
        writeData('payments.json', payments);
        return newPayment;
    });
}

export function updatePayment(
    paymentId: string,
    updates: Partial<Omit<CustomerPayment, 'id' | 'customerId'>>
): { success: boolean; payment?: CustomerPayment; error?: string } {
    return withDbLock(() => {
        const payments = getPayments();
        const idx = payments.findIndex((entry) => entry.id === paymentId);
        if (idx === -1) {
            return { success: false, error: 'Payment voucher not found.' };
        }

        const existing = payments[idx];
        if (existing.status === 'cancelled') {
            return { success: false, error: 'Cancelled payment voucher cannot be modified.' };
        }

        const updated: CustomerPayment = {
            ...existing,
            ...updates,
            id: existing.id,
            customerId: existing.customerId,
        };

        payments[idx] = updated;
        writeData('payments.json', payments);
        return { success: true, payment: updated };
    });
}

export function cancelPayment(
    paymentId: string,
    cancelledBy: string,
    reason?: string
): { success: boolean; payment?: CustomerPayment; error?: string } {
    return withDbLock(() => {
        const payments = getPayments();
        const idx = payments.findIndex((entry) => entry.id === paymentId);
        if (idx === -1) {
            return { success: false, error: 'Payment voucher not found.' };
        }

        const existing = payments[idx];
        if (existing.status === 'cancelled') {
            return { success: false, error: 'Payment voucher is already cancelled.' };
        }

        const cancelledPayment: CustomerPayment = {
            ...existing,
            status: 'cancelled',
            cancelledAt: new Date().toISOString(),
            cancelledBy,
            cancelledReason: reason,
        };

        payments[idx] = cancelledPayment;
        writeData('payments.json', payments);
        return { success: true, payment: cancelledPayment };
    });
}

export function getSupplierPayments(): SupplierPayment[] {
    const baseCurrency = getDefaultCurrency();
    const baseCode = baseCurrency?.code || 'JOD';
    const payments = readData<SupplierPayment[]>('supplier_payments.json', []);

    return payments.map((p): SupplierPayment => {
        const paymentCurrency = p.currencyCode || baseCode;
        const rate = p.exchangeRate || (paymentCurrency === baseCode ? 1 : getExchangeRate(paymentCurrency, baseCode));
        const amountBase = p.amountBase ?? (p.amount ?? 0) * rate;

        const totalFxDifferenceBase = (p.allocations || []).reduce((sum, row) => sum + Number(row.fxDifferenceBase || 0), 0);

        return {
            ...p,
            status: p.status === 'cancelled' ? 'cancelled' : 'active',
            postingStatus: p.postingStatus || 'posted',
            currencyCode: paymentCurrency,
            baseCurrencyCode: p.baseCurrencyCode || baseCode,
            exchangeRate: rate,
            amountBase,
            allocations: p.allocations || [],
            totalFxDifferenceBase,
        };
    });
}

export function addSupplierPayment(payment: Omit<SupplierPayment, 'id'>): SupplierPayment {
    return withDbLock(() => {
        const payments = getSupplierPayments();
        const newPayment: SupplierPayment = {
            ...payment,
            status: 'active',
            postingStatus: payment.postingStatus || 'posted',
            currencyCode: payment.currencyCode || getDefaultCurrency()?.code || 'JOD',
            baseCurrencyCode: payment.baseCurrencyCode || getDefaultCurrency()?.code || 'JOD',
            exchangeRate: payment.exchangeRate || 1,
            amountBase: payment.amountBase ?? payment.amount * (payment.exchangeRate || 1),
            id: nextPrefixedId('spay')
        };
        payments.unshift(newPayment);
        writeData('supplier_payments.json', payments);
        return newPayment;
    });
}

export function updateSupplierPaymentMetadata(
    paymentId: string,
    updates: { reference?: string; notes?: string; rateDate?: string }
): boolean {
    return withDbLock(() => {
        const payments = getSupplierPayments();
        const idx = payments.findIndex((entry) => entry.id === paymentId);
        if (idx === -1) return false;

        payments[idx] = {
            ...payments[idx],
            reference: updates.reference,
            notes: updates.notes,
            rateDate: updates.rateDate,
        };

        writeData('supplier_payments.json', payments);
        return true;
    });
}

export function updateSupplierPaymentFull(
    paymentId: string,
    updates: {
        amount: number;
        currencyCode: string;
        exchangeRate: number;
        rateDate?: string;
        isAdvance?: boolean;
        postingStatus?: 'draft' | 'saved' | 'posted';
        reference?: string;
        notes?: string;
        allocations?: { purchaseOrderId: string; amount: number }[];
    }
): { success: boolean; payment?: SupplierPayment; error?: string } {
    return withDbLock(() => {
        const epsilon = 0.01;
        const nowIso = new Date().toISOString();
        const payments = getSupplierPayments();
        const paymentIndex = payments.findIndex((entry) => entry.id === paymentId);
        if (paymentIndex === -1) {
            return { success: false, error: 'Payment voucher not found.' };
        }

        const existingPayment = payments[paymentIndex];
            const nextPostingStatus = updates.postingStatus || existingPayment.postingStatus || 'posted';
            const oldIsApplied = (existingPayment.postingStatus || 'posted') !== 'draft';
            const nextIsApplied = nextPostingStatus !== 'draft';
        if (existingPayment.status === 'cancelled') {
            return { success: false, error: 'Cancelled payment voucher cannot be modified.' };
        }
        const supplierId = existingPayment.supplierId;
        const suppliers = getSuppliers();
        const supplierIndex = suppliers.findIndex((entry) => entry.id === supplierId);
        if (supplierIndex === -1) {
            return { success: false, error: 'Supplier not found.' };
        }

        const baseCurrency = getDefaultCurrency();
        const baseCurrencyCode = baseCurrency?.code || 'JOD';
        const paymentCurrency = String(updates.currencyCode || existingPayment.currencyCode || baseCurrencyCode).trim() || baseCurrencyCode;
        const paymentExchangeRate = paymentCurrency === baseCurrencyCode
            ? 1
            : (Number.isFinite(Number(updates.exchangeRate)) && Number(updates.exchangeRate) > 0
                ? Number(updates.exchangeRate)
                : getExchangeRate(paymentCurrency, baseCurrencyCode));
        const paymentAmount = Number(updates.amount || 0);
        if (!Number.isFinite(paymentAmount) || paymentAmount <= 0) {
            return { success: false, error: 'Amount must be positive.' };
        }
        const paymentAmountBase = paymentAmount * paymentExchangeRate;

        const orders = getInvoicesPurchase();
        const ordersById = new Map(orders.map((order, index) => [order.id, index]));

        const oldAllocations = Array.isArray(existingPayment.allocations) ? existingPayment.allocations : [];
        let oldLiabilityBase = 0;

        if (oldIsApplied) {
        oldAllocations.forEach((allocation) => {
            const invoiceRate = Number(allocation.invoiceExchangeRate || 1);
            const allocationBaseAtInvoiceRate = allocation.amount / (Number.isFinite(invoiceRate) && invoiceRate > 0 ? invoiceRate : 1);
            oldLiabilityBase += allocationBaseAtInvoiceRate;

            const orderIdx = ordersById.get(allocation.purchaseOrderId);
            if (orderIdx === undefined) return;
            const order = orders[orderIdx];

            const nextAmountPaid = Math.max(0, Number(order.amountPaid || 0) - allocation.amount);
            const nextAmountDue = Math.max(0, Number(order.amountDue || 0) + allocation.amount);
            const nextAmountPaidBase = Math.max(0, Number(order.amountPaidBase || 0) - allocationBaseAtInvoiceRate);
            const currentAmountDueBase = Number(order.amountDueBase ?? ((order.amountDue || 0) / ((order.exchangeRate || 1) || 1)));
            const nextAmountDueBase = Math.max(0, currentAmountDueBase + allocationBaseAtInvoiceRate);

            orders[orderIdx] = {
                ...order,
                amountPaid: nextAmountPaid,
                amountDue: nextAmountDue,
                amountPaidBase: nextAmountPaidBase,
                amountDueBase: nextAmountDueBase,
            };
        });
        }

        if (oldIsApplied && oldAllocations.length === 0) {
            oldLiabilityBase = Number(existingPayment.amountBase ?? ((existingPayment.amount || 0) * (existingPayment.exchangeRate || 1)));
        }

        if (oldIsApplied) {
            suppliers[supplierIndex] = {
                ...suppliers[supplierIndex],
                balance: Number(suppliers[supplierIndex].balance || 0) + oldLiabilityBase,
            };
        }

        const isAdvance = updates.isAdvance === true;
        const requestedAllocations = (Array.isArray(updates.allocations) ? updates.allocations : [])
            .map((entry) => ({
                purchaseOrderId: String(entry.purchaseOrderId || '').trim(),
                amount: Number(entry.amount || 0),
            }))
            .filter((entry) => entry.purchaseOrderId && Number.isFinite(entry.amount) && entry.amount > 0);

        const allocationRecords: NonNullable<SupplierPayment['allocations']> = [];
        let totalAllocatedInPaymentCurrency = 0;
        let liabilityBase = 0;

        for (let i = 0; i < requestedAllocations.length; i += 1) {
            const allocation = requestedAllocations[i];
            const orderIdx = ordersById.get(allocation.purchaseOrderId);
            if (orderIdx === undefined) {
                return { success: false, error: 'Purchase order not found for allocation.' };
            }

            const order = orders[orderIdx];
            if (order.status === 'cancelled') {
                return { success: false, error: 'Cannot allocate to a cancelled purchase order.' };
            }
            if (order.supplierId !== supplierId) {
                return { success: false, error: 'Cannot allocate to an invoice from a different supplier.' };
            }

            const invoiceCurrency = order.currencyCode || baseCurrencyCode;
            const capturedInvoiceRate = Number(order.exchangeRate || 0);
            const derivedDueRate = Number(order.amountDueBase || 0) > 0 ? Number(order.amountDue || 0) / Number(order.amountDueBase || 1) : 0;
            const derivedGrandRate = Number(order.grandTotalBase || 0) > 0 ? Number(order.grandTotal || 0) / Number(order.grandTotalBase || 1) : 0;
            const invoiceRate = Number(
                Number.isFinite(capturedInvoiceRate) && capturedInvoiceRate > 0
                    ? capturedInvoiceRate
                    : Number.isFinite(derivedDueRate) && derivedDueRate > 0
                        ? derivedDueRate
                        : Number.isFinite(derivedGrandRate) && derivedGrandRate > 0
                            ? derivedGrandRate
                            : (invoiceCurrency === baseCurrencyCode ? 1 : getExchangeRate(invoiceCurrency, baseCurrencyCode))
            );

            const currentFromThisPayment = oldAllocations
                .filter((entry) => entry.purchaseOrderId === allocation.purchaseOrderId)
                .reduce((sum, entry) => sum + Number(entry.amount || 0), 0);
            const remainingDue = Number(order.amountDue || 0) + (oldIsApplied ? currentFromThisPayment : 0);
            if (allocation.amount - remainingDue > epsilon) {
                return { success: false, error: 'Allocation exceeds remaining invoice balance.' };
            }

            const allocatedInPaymentCurrency = invoiceCurrency === paymentCurrency
                ? allocation.amount
                : convertCurrency(allocation.amount, invoiceCurrency, paymentCurrency);
            const baseAtInvoiceRate = allocation.amount / (Number.isFinite(invoiceRate) && invoiceRate > 0 ? invoiceRate : 1);
            const baseAtPaymentRate = allocatedInPaymentCurrency * paymentExchangeRate;

            allocationRecords.push({
                id: `${allocation.purchaseOrderId}-${Date.now()}-${i}`,
                purchaseOrderId: allocation.purchaseOrderId,
                purchaseOrderNumber: order.orderNumber,
                invoiceCurrencyCode: invoiceCurrency,
                invoiceExchangeRate: invoiceRate,
                invoiceRateDate: order.rateDate,
                amount: allocation.amount,
                amountBase: baseAtPaymentRate,
                fxDifferenceBase: baseAtPaymentRate - baseAtInvoiceRate,
            });

            totalAllocatedInPaymentCurrency += allocatedInPaymentCurrency;
            liabilityBase += baseAtInvoiceRate;

            if (nextIsApplied) {
                const nextAmountPaid = Number(order.amountPaid || 0) + allocation.amount;
                const nextAmountDue = Math.max(0, Number(order.amountDue || 0) - allocation.amount);
                const nextAmountPaidBase = Number(order.amountPaidBase || 0) + baseAtInvoiceRate;
                const currentAmountDueBase = Number(order.amountDueBase ?? ((order.amountDue || 0) / (Number.isFinite(invoiceRate) && invoiceRate > 0 ? invoiceRate : 1)));
                const nextAmountDueBase = Math.max(0, currentAmountDueBase - baseAtInvoiceRate);

                orders[orderIdx] = {
                    ...order,
                    amountPaid: nextAmountPaid,
                    amountDue: nextAmountDue,
                    amountPaidBase: nextAmountPaidBase,
                    amountDueBase: nextAmountDueBase,
                };
            }
        }

        if (paymentCurrency !== baseCurrencyCode && !isAdvance) {
            if (allocationRecords.length === 0) {
                return { success: false, error: 'Allocation is required for foreign currency payments unless marked as advance.' };
            }
            // Allow partial allocation for foreign currency; don't require exact match
            // The allocation just needs to be valid (not exceed invoice amounts)
            // Real validation is done at database level with constraint checks
        }

        const liabilityImpactBase = allocationRecords.length > 0 ? liabilityBase : paymentAmountBase;
        if (nextIsApplied) {
            suppliers[supplierIndex] = {
                ...suppliers[supplierIndex],
                balance: Number(suppliers[supplierIndex].balance || 0) - liabilityImpactBase,
            };
        }

        const totalFxDifferenceBase = allocationRecords.reduce((sum, row) => sum + Number(row.fxDifferenceBase || 0), 0);

        const updatedPayment: SupplierPayment = {
            ...existingPayment,
            status: 'active',
            amount: paymentAmount,
            amountBase: paymentAmountBase,
            currencyCode: paymentCurrency,
            baseCurrencyCode,
            exchangeRate: paymentExchangeRate,
            rateDate: updates.rateDate || existingPayment.rateDate || nowIso,
            isAdvance,
            postingStatus: nextPostingStatus,
            allocations: allocationRecords,
            totalFxDifferenceBase,
            reference: updates.reference,
            notes: updates.notes,
        };

        payments[paymentIndex] = updatedPayment;

        writeData('purchase_orders.json', orders);
        writeData('suppliers.json', suppliers);
        writeData('supplier_payments.json', payments);

        return { success: true, payment: updatedPayment };
    });
}

export function cancelSupplierPayment(
    paymentId: string,
    params: { cancelledBy: string; reason?: string }
): { success: boolean; payment?: SupplierPayment; error?: string } {
    return withDbLock(() => {
        const payments = getSupplierPayments();
        const paymentIndex = payments.findIndex((entry) => entry.id === paymentId);
        if (paymentIndex === -1) {
            return { success: false, error: 'Payment voucher not found.' };
        }

        const payment = payments[paymentIndex];
        if (payment.status === 'cancelled') {
            return { success: false, error: 'Payment voucher is already cancelled.' };
        }

        const suppliers = getSuppliers();
        const supplierIndex = suppliers.findIndex((entry) => entry.id === payment.supplierId);
        if (supplierIndex === -1) {
            return { success: false, error: 'Supplier not found.' };
        }

        const orders = getInvoicesPurchase();
        const ordersById = new Map(orders.map((order, index) => [order.id, index]));
        const allocations = Array.isArray(payment.allocations) ? payment.allocations : [];
        const isApplied = (payment.postingStatus || 'posted') !== 'draft';

        let liabilityBaseImpact = 0;
        if (isApplied) {
        allocations.forEach((allocation) => {
            const invoiceRate = Number(allocation.invoiceExchangeRate || 1);
            const baseAtInvoiceRate = Number(allocation.amount || 0) / (Number.isFinite(invoiceRate) && invoiceRate > 0 ? invoiceRate : 1);
            liabilityBaseImpact += baseAtInvoiceRate;

            const orderIdx = ordersById.get(allocation.purchaseOrderId);
            if (orderIdx === undefined) return;
            const order = orders[orderIdx];

            const nextAmountPaid = Math.max(0, Number(order.amountPaid || 0) - Number(allocation.amount || 0));
            const nextAmountDue = Math.max(0, Number(order.amountDue || 0) + Number(allocation.amount || 0));
            const nextAmountPaidBase = Math.max(0, Number(order.amountPaidBase || 0) - baseAtInvoiceRate);
            const currentAmountDueBase = Number(order.amountDueBase ?? ((order.amountDue || 0) / (Number(order.exchangeRate || 1) || 1)));
            const nextAmountDueBase = Math.max(0, currentAmountDueBase + baseAtInvoiceRate);

            orders[orderIdx] = {
                ...order,
                amountPaid: nextAmountPaid,
                amountDue: nextAmountDue,
                amountPaidBase: nextAmountPaidBase,
                amountDueBase: nextAmountDueBase,
            };
        });
        }

        if (isApplied && allocations.length === 0) {
            liabilityBaseImpact = Number(payment.amountBase ?? ((payment.amount || 0) * Number(payment.exchangeRate || 1)));
        }

        if (isApplied) {
            suppliers[supplierIndex] = {
                ...suppliers[supplierIndex],
                balance: Number(suppliers[supplierIndex].balance || 0) + liabilityBaseImpact,
            };
        }

        const nowIso = new Date().toISOString();
        const cancelledPayment: SupplierPayment = {
            ...payment,
            status: 'cancelled',
            cancelledAt: nowIso,
            cancelledBy: params.cancelledBy,
            cancelledReason: params.reason,
        };
        payments[paymentIndex] = cancelledPayment;

        const journalEntries = getJournalEntries();
        const filteredEntries = journalEntries.filter((entry) => !(entry.referenceType === 'supplier_payment' && entry.referenceId === paymentId));

        writeData('purchase_orders.json', orders);
        writeData('suppliers.json', suppliers);
        writeData('supplier_payments.json', payments);
        if (filteredEntries.length !== journalEntries.length) {
            writeData('journal_entries.json', filteredEntries);
        }

        return { success: true, payment: cancelledPayment };
    });
}

export function lockInvoice(invoiceId: string, userId: string): boolean {
    const invoices = getInvoices();
    const index = invoices.findIndex(inv => inv.id === invoiceId);
    if (index === -1 || invoices[index].status !== 'active') {
        return false;
    }
    
    invoices[index].lockedBy = userId;
    invoices[index].lockedAt = new Date().toISOString();
    
    writeData('invoices.json', invoices);
    return true;
}

export function unlockInvoice(invoiceId: string): boolean {
    const invoices = getInvoices();
    const index = invoices.findIndex(inv => inv.id === invoiceId);
    if (index === -1) {
        return false;
    }
    
    invoices[index].lockedBy = undefined;
    invoices[index].lockedAt = undefined;
    
    writeData('invoices.json', invoices);
    return true;
}

export function updateInvoice(invoiceId: string, updates: Partial<Omit<SalesInvoice, 'id' | 'invoiceNumber' | 'status' | 'createdBy' | 'createdAt' | 'cancelledAt' | 'cancelledBy' | 'cancelledReason'>>, userId: string): { success: boolean; error?: string } {
    const invoices = getInvoices();
    const index = invoices.findIndex(inv => inv.id === invoiceId);
    if (index === -1) {
        return { success: false, error: 'Invoice not found' };
    }
    
    const invoice = invoices[index];
    if (invoice.status !== 'active') {
        return { success: false, error: 'Cannot update cancelled invoice' };
    }
    
    // Check if locked by another user
    if (invoice.lockedBy && invoice.lockedBy !== userId) {
        return { success: false, error: 'Invoice is locked by another user' };
    }
    
    invoices[index] = {
        ...invoices[index],
        ...updates
    };
    
    writeData('invoices.json', invoices);
    return { success: true };
}

export function cancelInvoice(invoiceId: string, userId: string, reason?: string): boolean {
    const invoices = getInvoices();
    const index = invoices.findIndex(inv => inv.id === invoiceId);
    if (index === -1 || invoices[index].status !== 'active') {
        return false;
    }
    
    invoices[index].status = 'cancelled';
    invoices[index].cancelledBy = userId;
    invoices[index].cancelledAt = new Date().toISOString();
    invoices[index].cancelledReason = reason;
    invoices[index].lockedBy = undefined;
    invoices[index].lockedAt = undefined;
    
    writeData('invoices.json', invoices);
    return true;
}

export function getEmployeeById(id: string) {
  const employees = getEmployees();
  return employees.find((e) => e.id === id);
}

export function getEmployeeByUsername(username: string) {
  const employees = getEmployees();
  return employees.find((e) => e.username === username);
}

export function addEmployee(employee: Omit<Employee, 'id' | 'attendance' | 'financialRecords' | 'leaveRequests'> & { role?: Employee['role'] }) {
    return withDbLock(() => {
        const employees = getEmployees();
        const existingIds = new Set(employees.map((entry) => String(entry.id || '').trim()));
        let newId = nextPrefixedId('emp');
        while (existingIds.has(newId)) {
            newId = nextPrefixedId('emp');
        }

        const newEmployee: Employee = {
            ...employee,
            id: newId,
            attendance: [],
            financialRecords: [],
            role: employee.role || 'employee',
            leaveRequests: [],
        };
        employees.push(newEmployee);
        writeData('employees.json', employees);
        return newEmployee;
    });
}

type UpdateEmployeeData = Partial<Omit<Employee, 'id' | 'username' | 'attendance' | 'financialRecords' | 'leaveRequests'>>;

export function updateEmployee(employeeId: string, data: UpdateEmployeeData) {
    const employees = getEmployees();
    const employeeIndex = employees.findIndex(e => e.id === employeeId);
    if(employeeIndex !== -1) {
        const employee = employees[employeeIndex];
        employees[employeeIndex] = { ...employee, ...data };
        if(data.password === '') {
            employees[employeeIndex].password = employee.password;
        }
        if (data.status === 'temporary') {
            employees[employeeIndex].salary = 0;
            employees[employeeIndex].salaryType = 'daily';
        }
        writeData('employees.json', employees);
        return true;
    }
    return false;
}

export function deleteEmployee(employeeId: string): boolean {
  let employees = getEmployees();
  const initialLength = employees.length;
  employees = employees.filter(emp => emp.id !== employeeId);
  
  if (employees.length < initialLength) {
    writeData('employees.json', employees);
    return true;
  }
  return false;
}

export function recordAttendance(employeeId: string, checkInTime: string) {
  const employees = getEmployees();
  const employee = employees.find(e => e.id === employeeId);
  if (employee) {
    const today = new Date(checkInTime);
    const dateString = today.toISOString().split('T')[0];
    const alreadyCheckedIn = employee.attendance.some(a => a.date === dateString);
    if (!alreadyCheckedIn) {
      employee.attendance.push({
        date: dateString,
        checkIn: checkInTime,
        checkOut: null,
      });
      writeData('employees.json', employees);
      return true;
    }
  }
  return false;
}

export function recordCheckOut(employeeId: string, checkOutTime: string) {
    const employees = getEmployees();
    const employee = employees.find(e => e.id === employeeId);
    if (employee) {
        const today = new Date(checkOutTime);
        const dateString = today.toISOString().split('T')[0];
        const attendanceRecord = employee.attendance.find(a => a.date === dateString && a.checkOut === null);
        if (attendanceRecord) {
            attendanceRecord.checkOut = checkOutTime;
            writeData('employees.json', employees);
            return true;
        }
    }
    return false;
}

export function updatePassword(employeeId: string, newPassword: string): boolean {
    const employees = getEmployees();
    const employee = employees.find(e => e.id === employeeId);
    if (employee) {
        employee.password = newPassword;
        writeData('employees.json', employees);
        return true;
    }
    return false;
}

export function dbUpdateFinancialRecord(employeeId: string, data: Omit<FinancialRecord, 'id'>): boolean {
    return withDbLock(() => {
        const employees = getEmployees();
        const employee = employees.find(e => e.id === employeeId);
        if (!employee) return false;

        const recordType = data.totalAmount > 0 ? 'ded' : 'bon';
        const newRecord: FinancialRecord = {
            id: nextPrefixedId(`rec-${recordType}`),
            ...data,
        };
        employee.financialRecords.push(newRecord);
        writeData('employees.json', employees);
        return true;
    });
}

export function deleteFinancialRecord(employeeId: string, recordId: string): boolean {
    const employees = getEmployees();
    const employee = employees.find(e => e.id === employeeId);
    if (!employee) return false;

    const recordIndex = employee.financialRecords.findIndex(r => r.id === recordId);
    if (recordIndex === -1) return false;

    employee.financialRecords.splice(recordIndex, 1);
    writeData('employees.json', employees);
    return true;
}

type LeaveRequestData = 
    | { requestType: 'leave', dateRange: { from: Date, to: Date }, reason: string }
    | { requestType: 'advance', from: Date, amount: number, reason: string }
    | { requestType: 'shift-swap', shiftDate: Date, toEmployeeId: string, reason: string };

export function addLeaveRequest(
  employeeId: string,
  requestData: LeaveRequestData
): boolean {
    return withDbLock(() => {
        const employees = getEmployees();
        const employee = employees.find(e => e.id === employeeId);
        if (employee) {
            const requestId = nextPrefixedId('req');
            let newRequest: Omit<LeaveRequest, 'employeeId' | 'employeeName' | 'toEmployeeName'>;

            switch(requestData.requestType) {
                    case 'leave':
                            newRequest = {
                                    id: requestId, status: 'pending', requestType: 'leave',
                                    from: requestData.dateRange.from,
                                    to: requestData.dateRange.to,
                                    reason: requestData.reason,
                            };
                            break;
                    case 'advance':
                            newRequest = {
                                    id: requestId, status: 'pending', requestType: 'advance',
                                    from: new Date(),
                                    amount: requestData.amount,
                                    reason: requestData.reason,
                            };
                            break;
                    case 'shift-swap':
                            newRequest = {
                                    id: requestId, status: 'pending', requestType: 'shift-swap',
                                    shiftDate: requestData.shiftDate,
                                    toEmployeeId: requestData.toEmployeeId,
                                    reason: requestData.reason,
                            };
                            break;
            }

            employee.leaveRequests.push(newRequest);
            writeData('employees.json', employees);
            return true;
        }
        return false;
    });
}

export function getAllLeaveRequests(): LeaveRequest[] {
    const employees = getEmployees();
    const employeesById = new Map(employees.map(e => [e.id, e]));

    const allRequests: LeaveRequest[] = [];
    employees.forEach(employee => {
        if (employee.role === 'employee') {
            employee.leaveRequests.forEach(req => {
                const toEmployee = req.toEmployeeId ? employeesById.get(req.toEmployeeId) : null;
                allRequests.push({
                    ...req,
                    employeeId: employee.id,
                    employeeName: employee.name,
                    toEmployeeName: toEmployee?.name ?? 'N/A'
                });
            });
        }
    });
    // Sort by date descending.
    const sortDate = (req: LeaveRequest) => req.from || req.shiftDate || new Date(0);
    return allRequests.sort((a, b) => sortDate(b).getTime() - sortDate(a).getTime());
}

export function getRequestsForEmployee(employeeId: string): LeaveRequest[] {
    const employees = getEmployees();
    const incomingRequests: LeaveRequest[] = [];
    employees.forEach(emp => {
        emp.leaveRequests.forEach(req => {
            if (req.toEmployeeId === employeeId && req.status === 'pending') {
                incomingRequests.push({
                    ...req,
                    employeeId: emp.id,
                    employeeName: emp.name,
                });
            }
        });
    });
    return incomingRequests;
}

export function updateLeaveRequestStatus(employeeId: string, requestId: string, status: 'approved' | 'rejected'): boolean {
    const employees = getEmployees();
    const employee = employees.find(e => e.id === employeeId);
    const request = employee?.leaveRequests.find(r => r.id === requestId);
    if (request) {
        request.status = status;
        writeData('employees.json', employees);
        return true;
    }
    return false;
}

export function updateShiftSwapStatus(requestId: string, approverId: string, status: 'approved' | 'rejected'): boolean {
    const employees = getEmployees();
    let requester: Employee | undefined;
    let request: Omit<LeaveRequest, 'employeeId' | 'employeeName' | 'toEmployeeName'> | undefined;

    // Find the employee who made the request and the request itself
    for (const emp of employees) {
        const foundReq = emp.leaveRequests.find(r => r.id === requestId);
        if (foundReq) {
            requester = emp;
            request = foundReq;
            break;
        }
    }
    
    // Find the employee who is being asked to cover
    const coverer = employees.find(e => e.id === approverId);

    // Ensure we found all parties and the request is valid
    if (request && requester && coverer && request.requestType === 'shift-swap' && request.toEmployeeId === approverId) {
        // Update the request status in our in-memory object
        request.status = status;

        if (status === 'approved' && request.shiftDate) {
            // Special financial logic if a temporary employee is covering
            if (coverer.status === 'temporary') {
                let dailyRate = 0;
                if (requester.salaryType === 'monthly') {
                    // Calculate daily rate from monthly salary
                    dailyRate = requester.salary / getDaysInMonth(request.shiftDate);
                } else { // daily
                    dailyRate = requester.salary;
                }
                dailyRate = parseFloat(dailyRate.toFixed(2));

                // Add a deduction record to the permanent employee (requester)
                const deductionRecord: FinancialRecord = {
                    id: nextPrefixedId('rec-ded'),
                    description: `Covered by ${coverer.name} on ${format(request.shiftDate, 'MMM d')}`,
                    totalAmount: dailyRate,
                    installments: [{ date: request.shiftDate, amount: dailyRate }]
                };
                requester.financialRecords.push(deductionRecord);

                // Add a bonus record to the temporary employee (coverer)
                const bonusRecord: FinancialRecord = {
                    id: nextPrefixedId('rec-bon'),
                    description: `Covering for ${requester.name} on ${format(request.shiftDate, 'MMM d')}`,
                    totalAmount: -dailyRate,
                    installments: [{ date: request.shiftDate, amount: -dailyRate }]
                };
                coverer.financialRecords.push(bonusRecord);

                // Update the shift schedule for that day
                const shiftOverrides = getShiftOverrides();
                const dateString = format(request.shiftDate, 'yyyy-MM-dd');
                if (!shiftOverrides[dateString]) {
                    shiftOverrides[dateString] = {};
                }
                const requesterOriginalShift = getShiftForEmployee(requester, request.shiftDate);
                shiftOverrides[dateString][coverer.id] = requesterOriginalShift;
                shiftOverrides[dateString][requester.id] = 'none';
                writeData('shiftOverrides.json', shiftOverrides);

            } else {
                // If it's a swap between two permanent employees, just swap their shifts
                dbSwapShifts(request.shiftDate, requester.id, approverId);
            }
        }
        
        // Write the final, modified employees array to the disk once.
        // This includes the request status change and any new financial records.
        writeData('employees.json', employees);
        return true;
    }
    
    // If request or parties not found, return false
    return false;
}

export function getSetting(key: string): string | undefined {
  const settings = getSettings();
  return settings[key];
}

export function updateSetting(key: string, value: string): void {
  const settings = getSettings();
  settings[key] = value;
  writeData('settings.json', settings);
}

export function getDashboardAnalytics(month: number, year: number) {
    const employees = getEmployees();
    let totalNetSalary = 0;
    let totalDeductions = 0;
    let totalBonuses = 0;

    employees.forEach(employee => {
        totalNetSalary += getNetSalary(employee, month, year);
        totalDeductions += getMonthlyDeductions(employee, month, year).total;
        totalBonuses += getMonthlyBonuses(employee, month, year).total;
    });

    return {
        totalNetSalary,
        totalDeductions,
        totalBonuses,
    };
}

export function getShiftForEmployee(employee: Employee, date: Date): 'first' | 'second' | 'none' {
  const shiftOverrides = getShiftOverrides();
  return getShiftForEmployeeHelper(employee, date, shiftOverrides);
}

export function dbSwapShifts(date: Date, employee1Id: string, employee2Id: string) {
    const employees = getEmployees();
    const employee1 = employees.find(e => e.id === employee1Id);
    const employee2 = employees.find(e => e.id === employee2Id);

    if (!employee1 || !employee2) return false;
    
    const shiftOverrides = getShiftOverrides();
    const dateString = format(date, 'yyyy-MM-dd');

    const shift1 = getShiftForEmployee(employee1, date);
    const shift2 = getShiftForEmployee(employee2, date);
    
    if (!shiftOverrides[dateString]) {
        shiftOverrides[dateString] = {};
    }

    shiftOverrides[dateString][employee1.id] = shift2;
    shiftOverrides[dateString][employee2.id] = shift1;
    
    writeData('shiftOverrides.json', shiftOverrides);
    return true;
}

export function getEmployeeStatement(employee: Employee): StatementRow[] {
    return getEmployeeStatementHelper(employee);
}

export function dbEditAdvanceSchedule(employeeId: string, data: { recordId: string, installments: { month: string, year: string, amount: number }[] }): boolean {
    const employees = getEmployees();
    const employee = employees.find(e => e.id === employeeId);
    if (!employee) return false;

    const record = employee.financialRecords.find(r => r.id === data.recordId);
    if (!record || record.description !== 'Salary Advance Repayment') {
        return false;
    }
    
    const totalInstallmentAmount = data.installments.reduce((sum, inst) => sum + inst.amount, 0);
    if (Math.abs(record.totalAmount - totalInstallmentAmount) > 0.01) {
        return false; // Total must match
    }

    record.installments = data.installments.map(inst => ({
        date: new Date(parseInt(inst.year), parseInt(inst.month)),
        amount: inst.amount
    }));

    writeData('employees.json', employees);
    return true;
}

// ============== Accounting Functions ==============

/**
 * Get all chart of accounts
 */
export function getChartOfAccounts(): ChartOfAccount[] {
  return readData<ChartOfAccount[]>('chart_of_accounts.json', []);
}

/**
 * Get an account by ID
 */
export function getAccountById(accountId: string): ChartOfAccount | undefined {
  const accounts = getChartOfAccounts();
  return accounts.find(acc => acc.id === accountId);
}

/**
 * Get all journal entries
 */
export function getJournalEntries(): JournalEntry[] {
  return readData<JournalEntry[]>('journal_entries.json', []);
}

// Exchange rate sources (for currency forms)
export function getExchangeRateSources(): string[] {
    const sources = readData<string[]>('exchange_rate_sources.json', []);
    if (!sources || sources.length === 0) {
        const defaults = ['https://www.xe.com'];
        writeData('exchange_rate_sources.json', defaults);
        return defaults;
    }
    return sources;
}

export function addExchangeRateSource(source: string): void {
    const value = (source || '').trim();
    if (!value) return;
    const sources = getExchangeRateSources();
    if (sources.includes(value)) return;
    sources.push(value);
    writeData('exchange_rate_sources.json', sources);
}
/**
 * Generate a unique sequential number for journal entries
 * Format: YYYYMMDDHHmmssMMM (e.g., 20260222111310)
 * - YYYY = year
 * - MM = month
 * - DD = day
 * - HH = hour (24-hour format)
 * - mm = minute
 * - ss = second
 * - MMM = milliseconds (3 digits)
 */
export function generateUniqueSequentialNumber(): string {
  const now = new Date();
  const year = now.getFullYear();
  const month = String(now.getMonth() + 1).padStart(2, '0');
  const day = String(now.getDate()).padStart(2, '0');
  const hours = String(now.getHours()).padStart(2, '0');
  const minutes = String(now.getMinutes()).padStart(2, '0');
  const seconds = String(now.getSeconds()).padStart(2, '0');
  const milliseconds = String(now.getMilliseconds()).padStart(3, '0');
  
  return `${year}${month}${day}${hours}${minutes}${seconds}${milliseconds}`;
}

/**
 * Add a new journal entry with custom ID (used for linking related entries)
 */
export function addJournalEntryWithCustomId(
  entry: Omit<JournalEntry, 'id' | 'createdAt'>,
  customId: string
): JournalEntry {
    return withDbLock(() => {
        const entries = getJournalEntries();
        let resolvedId = customId;
        if (entries.some((existingEntry) => existingEntry.id === resolvedId)) {
            resolvedId = `${customId}-${nextSequenceValue('JE-CUSTOM')}`;
        }
        const newEntry: JournalEntry = {
            ...entry,
            id: resolvedId,
            createdAt: new Date().toISOString(),
        };

        const totalDebits = newEntry.lines.reduce((sum, line) => sum + line.debit, 0);
        const totalCredits = newEntry.lines.reduce((sum, line) => sum + line.credit, 0);

        if (Math.abs(totalDebits - totalCredits) > 0.01) {
            throw new Error(`Journal entry is not balanced. Debits: ${totalDebits}, Credits: ${totalCredits}`);
        }

        entries.push(newEntry);
        writeData('journal_entries.json', entries);

        return newEntry;
    });
}

export function upsertJournalEntryWithCustomId(
  entry: Omit<JournalEntry, 'id' | 'createdAt'>,
  customId: string
): JournalEntry {
    return withDbLock(() => {
        const entries = getJournalEntries();
        const newEntry: JournalEntry = {
            ...entry,
            id: customId,
            createdAt: new Date().toISOString(),
        };

        const totalDebits = newEntry.lines.reduce((sum, line) => sum + line.debit, 0);
        const totalCredits = newEntry.lines.reduce((sum, line) => sum + line.credit, 0);

        if (Math.abs(totalDebits - totalCredits) > 0.01) {
            throw new Error(`Journal entry is not balanced. Debits: ${totalDebits}, Credits: ${totalCredits}`);
        }

        const existingIndex = entries.findIndex((existingEntry) => existingEntry.id === customId);
        if (existingIndex >= 0) {
            entries[existingIndex] = newEntry;
        } else {
            entries.push(newEntry);
        }

        writeData('journal_entries.json', entries);
        return newEntry;
    });
}

/**
 * Add a new journal entry
 */
export function addJournalEntry(entry: Omit<JournalEntry, 'id' | 'createdAt'>): JournalEntry {
    return withDbLock(() => {
        const entries = getJournalEntries();
        const newEntry: JournalEntry = {
            ...entry,
            id: nextPrefixedId('JE'),
            createdAt: new Date().toISOString(),
        };

        const totalDebits = newEntry.lines.reduce((sum, line) => sum + line.debit, 0);
        const totalCredits = newEntry.lines.reduce((sum, line) => sum + line.credit, 0);

        if (Math.abs(totalDebits - totalCredits) > 0.01) {
            throw new Error(`Journal entry is not balanced. Debits: ${totalDebits}, Credits: ${totalCredits}`);
        }

        entries.push(newEntry);
        writeData('journal_entries.json', entries);

        return newEntry;
    });
}

type EnsureAccountParams = {
    preferredCode: string;
    nameAr: string;
    nameEn: string;
    parentCode: string;
    nature: 'debit' | 'credit';
    type?: 'account' | 'header';
};

function getAccountByCode(code: string): ChartOfAccount | undefined {
    const accounts = getChartOfAccounts();
    return accounts.find((acc) => acc.code === code);
}

function ensureAccountExists(params: EnsureAccountParams): ChartOfAccount {
    const { preferredCode, nameAr, nameEn, parentCode, nature, type = 'account' } = params;
    const accounts = getChartOfAccounts();

    const byName = accounts.find((acc) => acc.nameAr === nameAr || acc.nameEn === nameEn);
    if (byName) return byName;

    const byCode = accounts.find((acc) => acc.code === preferredCode);
    if (byCode && byCode.nameAr === nameAr) return byCode;

    const usedCodes = new Set(accounts.map((acc) => acc.code));
    let code = preferredCode;
    while (usedCodes.has(code)) {
        const numeric = Number(code);
        const next = Number.isFinite(numeric) ? String(numeric + 1) : `${code}-1`;
        if (usedCodes.has(next) && next === code) break;
        code = next;
    }

    let parent = accounts.find((acc) => acc.code === parentCode);
    if (!parent) {
        parent = {
            id: parentCode,
            code: parentCode,
            nameAr: parentCode,
            nameEn: parentCode,
            type: 'header',
            parentId: null,
        } as ChartOfAccount;
        accounts.push(parent);
    }

    const newAccount: ChartOfAccount = {
        id: code,
        code,
        nameAr,
        nameEn,
        type,
        parentId: parent.id,
        nature,
    };

    accounts.push(newAccount);
    writeData('chart_of_accounts.json', accounts);
    return newAccount;
}

function createInventoryShortageJournalEntry(params: {
    doc: InternalTransferDocument;
    totalValue: number;
    createdBy: string;
    resolutionType: 'supplier' | 'employee' | 'wastage';
    note?: string;
}): JournalEntry {
    const { doc, totalValue, createdBy, resolutionType, note } = params;
    const supplierLabel = doc.supplierName ? ` (${doc.supplierName})` : '';

    const inventoryAccount = ensureAccountExists({
        preferredCode: '1130',
        nameAr: 'المخزون',
        nameEn: 'Inventory',
        parentCode: '1100',
        nature: 'debit',
    });

    const supplierAccount = ensureAccountExists({
        preferredCode: '2110',
        nameAr: 'الموردون',
        nameEn: 'Suppliers',
        parentCode: '2100',
        nature: 'credit',
    });

    const employeeAccount = ensureAccountExists({
        preferredCode: '1230',
        nameAr: 'عهد الموظفين',
        nameEn: 'Employee Advances',
        parentCode: '1100',
        nature: 'debit',
    });

    const wastageAccount = ensureAccountExists({
        preferredCode: '5110',
        nameAr: 'مصروف فروقات مخزون',
        nameEn: 'Inventory Shrinkage Expense',
        parentCode: '6000',
        nature: 'debit',
    });

    const debitAccount =
        resolutionType === 'supplier'
            ? supplierAccount
            : resolutionType === 'employee'
            ? employeeAccount
            : wastageAccount;

    const lines: JournalEntryLine[] = [
        {
            accountId: debitAccount.id,
            accountCode: debitAccount.code,
            accountName: debitAccount.nameAr,
            debit: totalValue,
            credit: 0,
            description: `معالجة نقص الارسالية ${doc.transferNumber}${supplierLabel}`,
        },
        {
            accountId: inventoryAccount.id,
            accountCode: inventoryAccount.code,
            accountName: inventoryAccount.nameAr,
            debit: 0,
            credit: totalValue,
            description: `إخراج مخزون لنقص الارسالية ${doc.transferNumber}${supplierLabel}`,
        },
    ];

    return addJournalEntry({
        date: new Date().toISOString(),
        referenceType: 'manual',
        referenceId: doc.id,
        description: note || `معالجة نقص الارسالية ${doc.transferNumber}${supplierLabel}`,
        createdBy,
        lines,
    });
}

/**
 * Create journal entry for sales invoice
 */
export function createSalesInvoiceJournalEntry(
    invoiceId: string,
    customerId: string,
    customerName: string,
    grandTotal: number,
    discountAmount: number,
    bonusAmount: number,
    amountPaid: number,
    createdBy: string,
    isReturn: boolean = false,
    entryIdWithSuffix?: string,  // Optional: for linking related entries (e.g., "JE-20260222111310-1")
    paymentBreakdown?: Array<{ method: 'cash' | 'visa' | 'receivables'; amount: number }>
): JournalEntry {
  const lines: JournalEntryLine[] = [];

        const total = Math.abs(grandTotal);
        const discount = Math.min(Math.max(Number(discountAmount || 0), 0), total);
        const bonus = Math.min(Math.max(Number(bonusAmount || 0), 0), total + discount);
        const grossTotal = total + discount + bonus;
        const paid = Math.abs(amountPaid);
  
    if (!isReturn) {
        const hasPaymentBreakdown = Array.isArray(paymentBreakdown) && paymentBreakdown.length > 0;

        // تحديد حساب الصندوق الخاص بالكاشير
        let cashierCashAccountCode = '1110';
        if (typeof global !== 'undefined' && global.currentEmployee && global.currentEmployee.employeeCashAccountCode) {
            cashierCashAccountCode = global.currentEmployee.employeeCashAccountCode;
        }

        if (hasPaymentBreakdown) {
            const cashAmount = paymentBreakdown
                .filter((payment) => payment.method === 'cash')
                .reduce((sum, payment) => sum + Math.max(0, Number(payment.amount || 0)), 0);
            const bankAmount = paymentBreakdown
                .filter((payment) => payment.method === 'visa')
                .reduce((sum, payment) => sum + Math.max(0, Number(payment.amount || 0)), 0);
            const receivablesAmount = paymentBreakdown
                .filter((payment) => payment.method === 'receivables')
                .reduce((sum, payment) => sum + Math.max(0, Number(payment.amount || 0)), 0);

            if (cashAmount > 0) {
                lines.push({
                    accountId: cashierCashAccountCode,
                    accountCode: cashierCashAccountCode,
                    accountName: 'الصندوق',
                    debit: cashAmount,
                    credit: 0,
                    description: `تحصيل نقدي من فاتورة ${invoiceId} - ${customerName}`,
                });
            }

            if (bankAmount > 0) {
                lines.push({
                    accountId: '1120',
                    accountCode: '1120',
                    accountName: 'البنك',
                    debit: bankAmount,
                    credit: 0,
                    description: `تحصيل بنكي من فاتورة ${invoiceId} - ${customerName}`,
                });
            }

            if (receivablesAmount > 0) {
                lines.push({
                    accountId: '1150',
                    accountCode: '1150',
                    accountName: 'العملاء',
                    debit: receivablesAmount,
                    credit: 0,
                    description: `ذمم فاتورة ${invoiceId} - ${customerName}`,
                });
            }

            const methodDebitTotal = cashAmount + bankAmount + receivablesAmount;
            if (Math.abs(methodDebitTotal - total) > 0.01) {
                const adjustment = total - methodDebitTotal;
                if (adjustment > 0) {
                    lines.push({
                        accountId: '1150',
                        accountCode: '1150',
                        accountName: 'العملاء',
                        debit: adjustment,
                        credit: 0,
                        description: `تسوية ذمم لفاتورة ${invoiceId} - ${customerName}`,
                    });
                }
            }
        } else {
            lines.push({
                accountId: '1150',
                accountCode: '1150',
                accountName: 'العملاء',
                debit: total,
                credit: 0,
                description: `فاتورة مبيعات رقم ${invoiceId} - ${customerName}`,
            });
        }

                if (discount > 0) {
                    lines.push({
                        accountId: '4112',
                        accountCode: '4112',
                        accountName: 'خصم مسموح به',
                        debit: discount,
                        credit: 0,
                        description: `خصم مسموح به لفاتورة ${invoiceId} - ${customerName}`,
                    });
                }

                if (bonus > 0) {
                    lines.push({
                        accountId: '4113',
                        accountCode: '4113',
                        accountName: 'خصم مسموح به (بونص)',
                        debit: bonus,
                        credit: 0,
                        description: `بونص لفاتورة ${invoiceId} - ${customerName}`,
                    });
                }
    
        // 2. Credit Sales Revenue (إيرادات المبيعات) for total amount
        lines.push({
            accountId: '4110',
            accountCode: '4110',
            accountName: 'إيرادات المبيعات',
            debit: 0,
            credit: grossTotal,
            description: `فاتورة مبيعات رقم ${invoiceId} - ${customerName}`,
        });
    } else {
        // Return: reverse sales
        lines.push({
            accountId: '4111',
            accountCode: '4111',
            accountName: 'مردودات المبيعات',
            debit: total,
            credit: 0,
            description: `مردود مبيعات رقم ${invoiceId} - ${customerName}`,
        });

        lines.push({
            accountId: '1150',
            accountCode: '1150',
            accountName: 'العملاء',
            debit: 0,
            credit: total,
            description: `مردود مبيعات رقم ${invoiceId} - ${customerName}`,
        });
    }
  
    // 3. Legacy immediate payment entry (only when payment breakdown is not provided)
        if (paid > 0 && (!paymentBreakdown || paymentBreakdown.length === 0)) {
        if (!isReturn) {
            // Debit Cash
            lines.push({
                accountId: '1110',
                accountCode: '1110',
                accountName: 'الصندوق',
                debit: paid,
                credit: 0,
                description: `تحصيل نقدي من فاتورة ${invoiceId} - ${customerName}`,
            });
      
            // Credit Accounts Receivable
            lines.push({
                accountId: '1150',
                accountCode: '1150',
                accountName: 'العملاء',
                debit: 0,
                credit: paid,
                description: `تحصيل نقدي من فاتورة ${invoiceId} - ${customerName}`,
            });
        } else {
            // Return refund: credit cash, debit AR
            lines.push({
                accountId: '1110',
                accountCode: '1110',
                accountName: 'الصندوق',
                debit: 0,
                credit: paid,
                description: `رد نقدي لمردود ${invoiceId} - ${customerName}`,
            });
            lines.push({
                accountId: '1150',
                accountCode: '1150',
                accountName: 'العملاء',
                debit: paid,
                credit: 0,
                description: `رد نقدي لمردود ${invoiceId} - ${customerName}`,
            });
        }
    }
  
  const entryData = {
    date: new Date().toISOString(),
    description: isReturn ? `قيد مردود مبيعات رقم ${invoiceId} - ${customerName}` : `قيد فاتورة مبيعات رقم ${invoiceId} - ${customerName}`,
    referenceType: 'sales_invoice' as const,
    referenceId: invoiceId,
    lines,
    createdBy,
  };

  if (entryIdWithSuffix) {
    return addJournalEntryWithCustomId(entryData, entryIdWithSuffix);
  }
  return addJournalEntry(entryData);
}

/**
 * Create journal entry for customer payment (سند قبض)
 */
export function createCustomerPaymentJournalEntry(
  paymentId: string,
  customerId: string,
  customerName: string,
  amount: number,
    createdBy: string,
    options?: {
        paymentMethod?: 'cash' | 'check' | 'bank_transfer' | 'credit';
        bankId?: string;
    }
): JournalEntry {
    const accounts = getChartOfAccounts();
    const cashAccount = accounts.find((acc) => acc.code === '1110');
    const defaultBankAccount = accounts.find((acc) => acc.code === '1120');
    const bank = options?.bankId ? getBankById(options.bankId) : null;
    const linkedBankAccount = (bank?.accountCode && accounts.find((acc) => acc.code === bank.accountCode)) || null;
    const receivingAccount = (options?.paymentMethod === 'check' || options?.paymentMethod === 'bank_transfer')
        ? (linkedBankAccount || defaultBankAccount || cashAccount)
        : (cashAccount || defaultBankAccount);

  const lines: JournalEntryLine[] = [
    {
                        accountId: receivingAccount?.id || '1110',
                        accountCode: receivingAccount?.code || '1110',
                        accountName: receivingAccount?.nameAr || 'الصندوق',
      debit: amount,
      credit: 0,
      description: `تحصيل من العميل ${customerName}`,
    },
    {
            accountId: '1150',
            accountCode: '1150',
            accountName: 'العملاء',
      debit: 0,
      credit: amount,
      description: `تحصيل من العميل ${customerName}`,
    },
  ];
  
  // Return entry object instead of immediately saving
  // Caller will decide where to save (JSON or PostgreSQL)
  return {
    id: nextPrefixedId('JE'),
    date: new Date().toISOString(),
    description: `قيد سند قبض رقم ${paymentId} - ${customerName}`,
    referenceType: 'payment',
    referenceId: paymentId,
    lines,
    createdBy,
    createdAt: new Date().toISOString(),
  };
}

/**
 * Create journal entry for supplier payment (سند صرف)
 */
export function createSupplierPaymentJournalEntry(
    paymentId: string,
    supplierId: string,
    supplierName: string,
    liabilityBaseAmount: number,
    cashBaseAmount: number,
    fxDifferenceBase: number,
    createdBy: string,
    options?: {
        paymentMethod?: 'cash' | 'check' | 'bank_transfer' | 'credit';
        bankId?: string;
        paymentCurrencyCode?: string;
        paymentExchangeRate?: number;
        supplierAccountCode?: string;
        resolvedSupplierAccount?: { id: string; code: string; nameAr: string; [key: string]: any } | null;
    }
): JournalEntry {
    const round2 = (value: number) => Math.round((Number(value || 0) + Number.EPSILON) * 100) / 100;
    const accounts = getChartOfAccounts();
    const suppliers = getSuppliers();
    const supplier = suppliers.find((s) => s.id === supplierId);
    // If caller already resolved the supplier account (e.g. from Postgres), use it directly
    const preResolvedAccount = options?.resolvedSupplierAccount ?? null;
    const explicitSupplierAccount = preResolvedAccount
        ? preResolvedAccount
        : options?.supplierAccountCode
            ? accounts.find((acc) => acc.code === options.supplierAccountCode)
            : null;
    const supplierAccount = explicitSupplierAccount
        || (supplier?.accountCode && accounts.find((acc) => acc.code === supplier.accountCode))
        || accounts.find((acc) => acc.code === '2110');
    const cashAccount = accounts.find((acc) => acc.code === '1110');
    const defaultBankAccount = accounts.find((acc) => acc.code === '1120');
    const bank = options?.bankId ? getBankById(options.bankId) : null;
    const linkedBankAccount = (bank?.accountCode && accounts.find((acc) => acc.code === bank.accountCode)) || null;
    const settlementAccount = (options?.paymentMethod === 'check' || options?.paymentMethod === 'bank_transfer')
        ? (linkedBankAccount || defaultBankAccount || cashAccount)
        : (cashAccount || defaultBankAccount);

    const lines: JournalEntryLine[] = [
        {
            accountId: supplierAccount?.id || '2110',
            accountCode: supplierAccount?.code || '2110',
            accountName: supplierAccount?.nameAr || 'الموردون',
            debit: round2(liabilityBaseAmount),
            credit: 0,
            description: `دفع للمورد ${supplierName}`,
        },
        {
            accountId: settlementAccount?.id || '1110',
            accountCode: settlementAccount?.code || '1110',
            accountName: settlementAccount?.nameAr || 'الصندوق',
            debit: 0,
            credit: round2(cashBaseAmount),
            description: `دفع للمورد ${supplierName}`,
        },
    ];

    // If payment base exceeds allocated liability + FX difference,
    // treat the remainder as supplier prepayment on the supplier sub-account.
    const allocatedSettlementBase = liabilityBaseAmount + fxDifferenceBase;
    const unallocatedPrepaymentBase = Math.max(0, cashBaseAmount - allocatedSettlementBase);
    if (unallocatedPrepaymentBase > 0.0001) {
        lines.push({
            accountId: supplierAccount?.id || '2110',
            accountCode: supplierAccount?.code || '2110',
            accountName: supplierAccount?.nameAr || 'الموردون',
            debit: round2(unallocatedPrepaymentBase),
            credit: 0,
            description: `دفعة مقدمة للمورد ${supplierName}`,
        });
    }

    // Balance strictly in base currency; add FX line only for the rounded imbalance.
    const totalDebitBeforeFx = lines.reduce((sum, line) => sum + Number(line.debit || 0), 0);
    const totalCreditBeforeFx = lines.reduce((sum, line) => sum + Number(line.credit || 0), 0);
    const baseImbalance = round2(totalDebitBeforeFx - totalCreditBeforeFx);
    if (Math.abs(baseImbalance) >= 0.01) {
        lines.push({
            accountId: '5350',
            accountCode: '5350',
            accountName: 'فروقات عملة',
            debit: baseImbalance < 0 ? Math.abs(baseImbalance) : 0,
            credit: baseImbalance > 0 ? baseImbalance : 0,
            description: 'فروقات صرف الدفع',
        });
    }

    // Return entry object instead of immediately saving
    // Caller will decide where to save (JSON or PostgreSQL)
    return {
        id: nextPrefixedId('JE'),
        date: new Date().toISOString(),
        description: `قيد سند صرف رقم ${paymentId} - ${supplierName}`,
        referenceType: 'supplier_payment',
        referenceId: paymentId,
        lines,
        createdBy,
        createdAt: new Date().toISOString(),
        currencyCode: options?.paymentCurrencyCode,
        fxRate: options?.paymentExchangeRate,
    };
}

/**
 * Get account balance
 */
export function getAccountBalance(accountId: string): number {
  const account = getAccountById(accountId);
  if (!account || account.type === 'header') {
    return 0;
  }
  
  const entries = getJournalEntries();
  let balance = 0;
  
  entries.forEach(entry => {
    entry.lines.forEach(line => {
      if (line.accountId === accountId) {
        if (account.nature === 'debit') {
          balance += line.debit - line.credit;
        } else {
          balance += line.credit - line.debit;
        }
      }
    });
  });
  
  return balance;
}

// ===== SUPPLIERS & PURCHASES =====

function allocateSupplierAccountCode(parentCode: string): string {
    const seq = nextSequenceValue('sup-acc');
    return `${parentCode}-${String(seq).padStart(3, '0')}`;
}

function ensureSupplierAccount(supplier: Supplier, parentCode = '2110'): { supplier: Supplier; updated: boolean } {
    // If already linked, keep as-is
    if (supplier.accountId && supplier.accountCode) return { supplier, updated: false };

    // Ensure parent account exists
    const parent = ensureAccountExists({
        preferredCode: parentCode,
        nameAr: 'الموردون',
        nameEn: 'Suppliers',
        parentCode: '2100',
        nature: 'credit',
        type: 'account',
    });

    // Allocate unique sub-account code and create account
    const accountCode = allocateSupplierAccountCode(parent.code);
    const account = ensureAccountExists({
        preferredCode: accountCode,
        nameAr: supplier.name,
        nameEn: supplier.name,
        parentCode: parent.code,
        nature: 'credit',
        type: 'account',
    });

    return {
        supplier: { ...supplier, accountId: account.id, accountCode: account.code },
        updated: true,
    };
}

export function getSuppliers(): Supplier[] {
    const suppliers = readDataCached<Supplier[]>('suppliers.json', [], 60_000);
    let changed = false;

    const normalized = suppliers.map((s, idx) => {
        const base: Supplier = {
            ...s,
            id: s.id || String(idx + 1),
            supplierNumber: s.supplierNumber || s.id || String(idx + 1),
            phone: s.phone || '',
            address: s.address || '',
            paymentTerms: typeof s.paymentTerms === 'number' ? s.paymentTerms : 30,
            taxNumber: s.taxNumber || '',
            accountId: s.accountId,
            accountCode: s.accountCode,
            inactive: s.inactive === true,
        };

        const { supplier: withAccount, updated } = ensureSupplierAccount(base);
        if (updated) changed = true;
        return withAccount;
    });

    if (changed) {
        writeData('suppliers.json', normalized);
    }

    return normalized;
}

export function addSupplier(data: { name: string; phone?: string; address?: string; paymentTerms?: number; taxNumber?: string; supplierNumber?: string; accountCode?: string; }): Supplier {
    return withDbLock(() => {
        const items = getSuppliers();
        const numericIds = items
            .map((supplier) => Number.parseInt(String(supplier.id || ''), 10))
            .filter((id) => Number.isFinite(id));
        const newId = String((numericIds.length > 0 ? Math.max(...numericIds) : 0) + 1);
        const number = data.supplierNumber || newId;

        const newItem: Supplier = {
            id: newId,
            supplierNumber: number,
            name: data.name,
            balance: 0,
            phone: data.phone || '',
            address: data.address || '',
            paymentTerms: typeof data.paymentTerms === 'number' ? data.paymentTerms : 30,
            taxNumber: data.taxNumber || '',
            accountCode: data.accountCode || '',
        };
        items.push(newItem);
        writeData('suppliers.json', items);
        return newItem;
    });
}

export function updateSupplier(id: string, name: string, phone?: string, address?: string, paymentTerms?: number, taxNumber?: string, accountCode?: string): boolean {
    const items = getSuppliers();
    const itemIndex = items.findIndex(s => s.id === id);
    if (itemIndex !== -1) {
        items[itemIndex].name = name;
        if (typeof phone === 'string') items[itemIndex].phone = phone;
        if (typeof address === 'string') items[itemIndex].address = address;
        if (typeof paymentTerms === 'number') items[itemIndex].paymentTerms = paymentTerms;
        if (typeof taxNumber === 'string') items[itemIndex].taxNumber = taxNumber;
        if (typeof accountCode === 'string') items[itemIndex].accountCode = accountCode;
        writeData('suppliers.json', items);
        return true;
    }
    return false;
}

export function updateSupplierBalance(id: string, amountChange: number): boolean {
    const items = getSuppliers();
    const itemIndex = items.findIndex(s => s.id === id);
    if (itemIndex !== -1) {
        items[itemIndex].balance += amountChange;
        writeData('suppliers.json', items);
        return true;
    }
    return false;
}

export function deleteSupplier(id: string): boolean {
    const items = getSuppliers();
    const supplier = items.find((s) => s.id === id);
    if (!supplier) return false;

    const hasBalance = Math.abs(supplier.balance || 0) > 0.0001;
    const entries = getJournalEntries();
    const hasJournals = entries.some((e) =>
        e.lines.some((line) => line.accountId === supplier.accountId || line.accountCode === supplier.accountCode)
    );

    if (hasBalance || hasJournals) {
        // Mark as inactive instead of deletion
        const updated = items.map((s) => (s.id === id ? { ...s, inactive: true } : s));
        writeData('suppliers.json', updated);
        return false;
    }

    const remaining = items.filter((s) => s.id !== id);
    writeData('suppliers.json', remaining);
    return true;
}

export function getInvoicesPurchase() {
    return readDataCached<PurchaseOrder[]>('purchase_orders.json', [], 5_000);
}

export type PaginationParams = {
    page?: number;
    pageSize?: number;
};

export type PaginatedResult<T> = {
    items: T[];
    page: number;
    pageSize: number;
    totalItems: number;
    totalPages: number;
    hasNext: boolean;
    hasPrev: boolean;
};

export type PurchaseOrderHistoryRow = {
    id: string;
    orderNumber: string;
    supplierId: string;
    supplierName: string;
    date: string;
    itemCount: number;
    currencyCode: string;
    baseCurrencyCode: string;
    exchangeRate: number;
    grandTotal: number;
    grandTotalBase?: number;
    status: string;
    postingStatus?: string;
};

export type PurchaseOrderHistoryQueryParams = PaginationParams & {
    search?: string;
};

export type PurchaseOrderHistoryQueryResult = PaginatedResult<PurchaseOrderHistoryRow> & {
    stats: {
        totalOrders: number;
        activeOrders: number;
        totalAmount: number;
    };
};

export type SalesInvoiceHistoryQueryParams = PaginationParams & {
    search?: string;
};

type PurchaseOrderHistoryIndexes = {
    byOrderNumber: Map<string, PurchaseOrderHistoryRow[]>;
    bySupplierName: Map<string, PurchaseOrderHistoryRow[]>;
    byDatePrefix: Map<string, PurchaseOrderHistoryRow[]>;
};

type SalesInvoiceIndexes = {
    byInvoiceNumber: Map<string, SalesInvoice[]>;
    byCustomerName: Map<string, SalesInvoice[]>;
    byDatePrefix: Map<string, SalesInvoice[]>;
};

const DEFAULT_PAGE_SIZE = 25;
const MAX_PAGE_SIZE = 200;

function normalizeSearchToken(value: string): string {
    return String(value || '').trim().toLowerCase();
}

function paginateArray<T>(items: T[], params?: PaginationParams): PaginatedResult<T> {
    const safePageSize = Math.min(MAX_PAGE_SIZE, Math.max(1, Number(params?.pageSize || DEFAULT_PAGE_SIZE)));
    const totalItems = items.length;
    const totalPages = Math.max(1, Math.ceil(totalItems / safePageSize));
    const safePage = Math.min(totalPages, Math.max(1, Number(params?.page || 1)));
    const start = (safePage - 1) * safePageSize;
    const pagedItems = items.slice(start, start + safePageSize);

    return {
        items: pagedItems,
        page: safePage,
        pageSize: safePageSize,
        totalItems,
        totalPages,
        hasNext: safePage < totalPages,
        hasPrev: safePage > 1,
    };
}

function buildPurchaseOrderHistoryIndexes(rows: PurchaseOrderHistoryRow[]): PurchaseOrderHistoryIndexes {
    const byOrderNumber = new Map<string, PurchaseOrderHistoryRow[]>();
    const bySupplierName = new Map<string, PurchaseOrderHistoryRow[]>();
    const byDatePrefix = new Map<string, PurchaseOrderHistoryRow[]>();

    rows.forEach((row) => {
        const orderNumberKey = normalizeSearchToken(row.orderNumber);
        if (orderNumberKey) {
            byOrderNumber.set(orderNumberKey, [...(byOrderNumber.get(orderNumberKey) || []), row]);
        }

        const supplierNameKey = normalizeSearchToken(row.supplierName);
        if (supplierNameKey) {
            bySupplierName.set(supplierNameKey, [...(bySupplierName.get(supplierNameKey) || []), row]);
        }

        const dateKey = normalizeSearchToken(String(row.date || '').slice(0, 10));
        if (dateKey) {
            byDatePrefix.set(dateKey, [...(byDatePrefix.get(dateKey) || []), row]);
        }
    });

    return { byOrderNumber, bySupplierName, byDatePrefix };
}

function buildSalesInvoiceIndexes(invoices: SalesInvoice[]): SalesInvoiceIndexes {
    const byInvoiceNumber = new Map<string, SalesInvoice[]>();
    const byCustomerName = new Map<string, SalesInvoice[]>();
    const byDatePrefix = new Map<string, SalesInvoice[]>();

    invoices.forEach((invoice) => {
        const invoiceNumberKey = normalizeSearchToken(String(invoice.invoiceNumber || ''));
        if (invoiceNumberKey) {
            byInvoiceNumber.set(invoiceNumberKey, [...(byInvoiceNumber.get(invoiceNumberKey) || []), invoice]);
        }

        const customerNameKey = normalizeSearchToken(String(invoice.customerName || ''));
        if (customerNameKey) {
            byCustomerName.set(customerNameKey, [...(byCustomerName.get(customerNameKey) || []), invoice]);
        }

        const dateKey = normalizeSearchToken(String(invoice.date || '').slice(0, 10));
        if (dateKey) {
            byDatePrefix.set(dateKey, [...(byDatePrefix.get(dateKey) || []), invoice]);
        }
    });

    return { byInvoiceNumber, byCustomerName, byDatePrefix };
}

export function queryPurchaseOrderHistory(params: PurchaseOrderHistoryQueryParams = {}): PurchaseOrderHistoryQueryResult {
    const orders = getInvoicesPurchase();
    const suppliersById = new Map(getSuppliers().map((supplier) => [supplier.id, supplier.name]));

    const allRows: PurchaseOrderHistoryRow[] = orders.map((order) => ({
        id: order.id,
        orderNumber: String(order.orderNumber || ''),
        supplierId: String(order.supplierId || ''),
        supplierName: suppliersById.get(String(order.supplierId || '')) || order.supplierName || 'Unknown Supplier',
        date: String(order.date || ''),
        itemCount: Array.isArray(order.items) ? order.items.length : 0,
        currencyCode: String(order.currencyCode || ''),
        baseCurrencyCode: String(order.baseCurrencyCode || ''),
        exchangeRate: Number(order.exchangeRate || 1),
        grandTotal: Number(order.grandTotal || 0),
        grandTotalBase: Number.isFinite(Number(order.grandTotalBase)) ? Number(order.grandTotalBase) : undefined,
        status: String(order.status || 'active'),
        postingStatus: order.postingStatus,
    }));

    allRows.sort((left, right) => {
        const dateDiff = new Date(right.date || 0).getTime() - new Date(left.date || 0).getTime();
        if (dateDiff !== 0) return dateDiff;
        return right.orderNumber.localeCompare(left.orderNumber);
    });

    const indexes = buildPurchaseOrderHistoryIndexes(allRows);

    const searchToken = normalizeSearchToken(params.search || '');
    const indexedMatches = searchToken
        ? [
            ...(indexes.byOrderNumber.get(searchToken) || []),
            ...(indexes.bySupplierName.get(searchToken) || []),
            ...(indexes.byDatePrefix.get(searchToken) || []),
        ]
        : [];

    const filteredRows = !searchToken
        ? allRows
        : indexedMatches.length > 0
            ? Array.from(new Map(indexedMatches.map((row) => [row.id, row])).values())
            : allRows.filter((row) => {
                const haystack = [
                    row.orderNumber,
                    row.supplierName,
                    row.date,
                    row.currencyCode,
                    row.baseCurrencyCode,
                ].map(normalizeSearchToken).join(' ');
                return haystack.includes(searchToken);
            });

    const paged = paginateArray(filteredRows, params);
    const stats = {
        totalOrders: allRows.length,
        activeOrders: allRows.filter((row) => row.status === 'active').length,
        totalAmount: allRows.reduce((sum, row) => sum + Number(row.grandTotal || 0), 0),
    };

    return {
        ...paged,
        stats,
    };
}

export function querySalesInvoices(params: SalesInvoiceHistoryQueryParams = {}): PaginatedResult<SalesInvoice> {
    const allInvoices = getInvoices();
    const sorted = [...allInvoices].sort((left, right) => {
        const dateDiff = new Date(right.date || 0).getTime() - new Date(left.date || 0).getTime();
        if (dateDiff !== 0) return dateDiff;
        return String(right.invoiceNumber || '').localeCompare(String(left.invoiceNumber || ''));
    });

    const indexes = buildSalesInvoiceIndexes(sorted);

    const searchToken = normalizeSearchToken(params.search || '');
    const indexedMatches = searchToken
        ? [
            ...(indexes.byInvoiceNumber.get(searchToken) || []),
            ...(indexes.byCustomerName.get(searchToken) || []),
            ...(indexes.byDatePrefix.get(searchToken) || []),
        ]
        : [];

    const filtered = !searchToken
        ? sorted
        : indexedMatches.length > 0
            ? Array.from(new Map(indexedMatches.map((invoice) => [invoice.id, invoice])).values())
            : sorted.filter((invoice) => {
                const haystack = [
                    invoice.invoiceNumber,
                    invoice.customerName,
                    String(invoice.grandTotal || ''),
                    String(invoice.amountDue || ''),
                    invoice.date,
                ].map(normalizeSearchToken).join(' ');
                return haystack.includes(searchToken);
            });

    return paginateArray(filtered, params);
}

function generatePurchaseOrderNumber(): string {
    const currentYear = new Date().getFullYear();
    const orders = getInvoicesPurchase();
    
    // Filter orders from the current year
    const currentYearOrders = orders.filter(ord => {
        if (!ord.orderNumber || typeof ord.orderNumber !== 'string') {
            return false;
        }
        const parts = ord.orderNumber.split('/');
        if (parts.length < 2) {
            return false;
        }
        const orderYear = parseInt(parts[1]);
        return orderYear === currentYear;
    });
    
    // Get the next sequence number
    const nextSequence = currentYearOrders.length + 1;
    const sequenceStr = String(nextSequence).padStart(4, '0');
    
    return `P/${currentYear}/${sequenceStr}`;
}

export function addPurchaseOrder(
    order: Omit<PurchaseOrder, 'orderNumber' | 'status' | 'createdBy' | 'createdAt' | 'lockedById' | 'lockedByName' | 'lockedAt' | 'cancelledAt' | 'cancelledBy' | 'cancelledReason'> & { id?: string; createdBy: string },
    opts?: { id?: string }
): PurchaseOrder {
    return withDbLock(() => {
        const orders = getInvoicesPurchase();
        const newId = opts?.id || order.id || nextPrefixedId('po');
        const orderNumber = generatePurchaseOrderNumber();
        const now = new Date().toISOString();

        const newOrder: PurchaseOrder = {
            ...order,
            id: newId,
            orderNumber,
            status: 'active',
            createdAt: now,
            lockedById: undefined,
            lockedByName: undefined,
            lockedAt: undefined,
            cancelledAt: undefined,
            cancelledBy: undefined,
            cancelledReason: undefined
        };
        orders.unshift(newOrder);
        writeData('purchase_orders.json', orders);
        return newOrder;
    });
}

export function getPurchaseOrderById(id: string): PurchaseOrder | null {
    const orders = getInvoicesPurchase();
    return orders.find(order => order.id === id) || null;
}

export function updatePurchaseOrder(id: string, updates: Partial<PurchaseOrder>): boolean {
    const orders = getInvoicesPurchase();
    const index = orders.findIndex(order => order.id === id);
    if (index === -1) {
        return false;
    }
    orders[index] = { ...orders[index], ...updates };
    writeData('purchase_orders.json', orders);
    return true;
}

export function lockPurchaseOrder(orderId: string, userId: string, userName: string): { success: boolean; lockedBy?: string } {
    const orders = getInvoicesPurchase();
    const index = orders.findIndex((ord) => ord.id === orderId);
    if (index === -1 || orders[index].status === 'cancelled') {
        return { success: false, lockedBy: 'UNKNOWN' };
    }

    const order = orders[index];
    const lockStillActive = !!order.lockedById && (() => {
        const lockedAtValue = String(order.lockedAt || '').trim();
        if (!lockedAtValue) return true;
        const lockedAtTime = Date.parse(lockedAtValue);
        if (!Number.isFinite(lockedAtTime)) return true;
        return Date.now() - lockedAtTime < 30 * 60 * 1000;
    })();

    if (lockStillActive && order.lockedById !== userId) {
        return { success: false, lockedBy: order.lockedByName || order.lockedById };
    }

    orders[index].lockedById = userId;
    orders[index].lockedByName = userName;
    orders[index].lockedAt = new Date().toISOString();
    writeData('purchase_orders.json', orders);
    return { success: true };
}

export function unlockPurchaseOrder(orderId: string, userId: string): boolean {
    const orders = getInvoicesPurchase();
    const index = orders.findIndex((ord) => ord.id === orderId);
    if (index === -1) {
        return false;
    }

    const order = orders[index];
    if (order.lockedById && order.lockedById !== userId) {
        return false;
    }

    orders[index].lockedById = undefined;
    orders[index].lockedByName = undefined;
    orders[index].lockedAt = undefined;
    writeData('purchase_orders.json', orders);
    return true;
}

export function cancelPurchaseOrder(orderId: string, userId: string, reason?: string): boolean {
    const orders = getInvoicesPurchase();
    const index = orders.findIndex(ord => ord.id === orderId);
    if (index === -1 || orders[index].status === 'cancelled') {
        return false;
    }
    
    orders[index].status = 'cancelled';
    orders[index].cancelledAt = new Date().toISOString();
    orders[index].cancelledBy = userId;
    orders[index].cancelledReason = reason;
    
    writeData('purchase_orders.json', orders);
    return true;
}

export function receivePurchaseOrder(orderId: string, userId: string): boolean {
    const orders = getInvoicesPurchase();
    const index = orders.findIndex(ord => ord.id === orderId);
    if (index === -1 || orders[index].status !== 'active') {
        return false;
    }
    
    orders[index].status = 'received';
    orders[index].receivedAt = new Date().toISOString();
    orders[index].receivedBy = userId;
    
    writeData('purchase_orders.json', orders);
    return true;
}

export function getPurchaseReturns() {
    return readData<PurchaseReturn[]>('purchase_returns.json', []);
}

function generatePurchaseReturnNumber(): string {
    const currentYear = new Date().getFullYear();
    const returns = getPurchaseReturns();

    const currentYearReturns = returns.filter(ret => {
        if (!ret.returnNumber || typeof ret.returnNumber !== 'string') {
            return false;
        }
        const parts = ret.returnNumber.split('/');
        if (parts.length < 2) {
            return false;
        }
        const returnYear = parseInt(parts[1]);
        return returnYear === currentYear;
    });

    const nextSequence = currentYearReturns.length + 1;
    const sequenceStr = String(nextSequence).padStart(4, '0');

    return `PR/${currentYear}/${sequenceStr}`;
}

export function addPurchaseReturn(data: Omit<PurchaseReturn, 'id' | 'returnNumber' | 'status' | 'createdAt' | 'createdBy'> & { createdBy: string }): PurchaseReturn {
    return withDbLock(() => {
        const returns = getPurchaseReturns();
        const now = new Date().toISOString();
        const newReturn: PurchaseReturn = {
            ...data,
            id: nextPrefixedId('prn'),
            returnNumber: generatePurchaseReturnNumber(),
            status: 'draft',
            createdAt: now
        };
        returns.unshift(newReturn);
        writeData('purchase_returns.json', returns);
        return newReturn;
    });
}

export function getPurchaseReturnById(id: string): PurchaseReturn | null {
    const returns = getPurchaseReturns();
    return returns.find(ret => ret.id === id) || null;
}

export function getPurchaseReturnsByOrder(purchaseOrderId: string): PurchaseReturn[] {
    return getPurchaseReturns().filter(ret => ret.purchaseOrderId === purchaseOrderId);
}

export function updatePurchaseReturn(id: string, updates: Partial<PurchaseReturn>): boolean {
    return withDbLock(() => {
        const returns = getPurchaseReturns();
        const index = returns.findIndex(ret => ret.id === id);
        if (index === -1) {
            return false;
        }
        returns[index] = { ...returns[index], ...updates };
        writeData('purchase_returns.json', returns);
        return true;
    });
}
// ===== Check Management Functions =====

// Incoming Checks (from customer payments)
export function getIncomingChecks(): IncomingCheck[] {
    return readData<IncomingCheck[]>('checks_received.json', []);
}

export function addIncomingCheck(check: Omit<IncomingCheck, 'id' | 'createdAt' | 'updatedAt'>): IncomingCheck {
    return withDbLock(() => {
        const checks = getIncomingChecks();
        const newCheck: IncomingCheck = {
            ...check,
            id: String(Math.max(0, ...checks.map(c => Number.parseInt(String(c.id || ''), 10) || 0)) + 1),
            createdAt: new Date().toISOString(),
            updatedAt: new Date().toISOString(),
        };
        checks.push(newCheck);
        writeData('checks_received.json', checks);
        return newCheck;
    });
}

export function removePendingIncomingChecksBySourceTag(sourceTag: string): number {
    return withDbLock(() => {
        const normalizedTag = String(sourceTag || '').trim();
        if (!normalizedTag) return 0;

        const checks = getIncomingChecks();
        const beforeCount = checks.length;
        const remaining = checks.filter((check) => {
            const notes = String(check.notes || '');
            const isTagged = notes.includes(normalizedTag);
            const isPending = String(check.status || '') === 'pending';
            const isNotEndorsed = !check.endorsementStatus || check.endorsementStatus === 'none';
            return !(isTagged && isPending && isNotEndorsed);
        });

        if (remaining.length !== beforeCount) {
            writeData('checks_received.json', remaining);
        }

        return beforeCount - remaining.length;
    });
}

export function markPendingIncomingChecksBySourceTagAsReturned(sourceTag: string, reason?: string): number {
    return withDbLock(() => {
        const normalizedTag = String(sourceTag || '').trim();
        if (!normalizedTag) return 0;

        const checks = getIncomingChecks();
        const returnedDate = new Date().toISOString();
        let updatedCount = 0;

        checks.forEach((check) => {
            const notes = String(check.notes || '');
            const isTagged = notes.includes(normalizedTag);
            const isPending = String(check.status || '') === 'pending';
            const isNotEndorsed = !check.endorsementStatus || check.endorsementStatus === 'none';
            if (!(isTagged && isPending && isNotEndorsed)) return;

            check.status = 'returned';
            check.returnedDate = returnedDate;
            check.updatedAt = returnedDate;
            if (reason && !check.rejectedReason) {
                check.rejectedReason = reason;
            }
            check.notes = notes ? `${notes}\n[RETURNED] ${reason || 'Invoice cancelled'}` : `[RETURNED] ${reason || 'Invoice cancelled'}`;
            updatedCount += 1;
        });

        if (updatedCount > 0) {
            writeData('checks_received.json', checks);
        }

        return updatedCount;
    });
}

export function updateIncomingCheckStatus(checkId: string, status: 'pending' | 'cleared' | 'rejected' | 'returned', metadata?: { rejectedReason?: string; clearedDate?: string; returnedDate?: string }): boolean {
    return withDbLock(() => {
        const checks = getIncomingChecks();
        const check = checks.find(c => c.id === checkId);
        if (!check) return false;

        check.status = status;
        check.updatedAt = new Date().toISOString();

        if (status === 'cleared' && metadata?.clearedDate) {
            check.clearedDate = metadata.clearedDate;
        }
        if (status === 'rejected' && metadata?.rejectedReason) {
            check.rejectedReason = metadata.rejectedReason;
        }
        if (status === 'returned' && metadata?.returnedDate) {
            check.returnedDate = metadata.returnedDate;
        }

        writeData('checks_received.json', checks);
        return true;
    });
}

export function getCheckReturnEvents(): CheckReturnEvent[] {
    return readData<CheckReturnEvent[]>('checks_return_events.json', []);
}

export function returnEndorsedIncomingCheck(params: {
    checkId: string;
    reason: string;
    returnedDate?: string;
    bankFeeAmount?: number;
    chargeFeeToCustomer?: boolean;
    notes?: string;
    createdBy?: string;
}): { check: IncomingCheck; event: CheckReturnEvent; journalEntryId: string } {
    const {
        checkId,
        reason,
        returnedDate,
        bankFeeAmount = 0,
        chargeFeeToCustomer = false,
        notes,
        createdBy = 'system',
    } = params;

    const normalizedReason = String(reason || '').trim();
    if (!normalizedReason) {
        throw new Error('CHECK_RETURN_REASON_REQUIRED');
    }

    return withDbLock(() => {
        const checks = getIncomingChecks();
        const check = checks.find((entry) => entry.id === checkId);
        if (!check) {
            throw new Error('CHECK_NOT_FOUND');
        }
        if (check.status === 'returned') {
            throw new Error('CHECK_ALREADY_RETURNED');
        }
        if (check.endorsementStatus !== 'endorsed-to-party' || check.endorsedToType !== 'supplier' || !check.endorsedToId) {
            throw new Error('CHECK_NOT_ENDORSED_TO_SUPPLIER');
        }

        const suppliers = getSuppliers();
        const supplier = suppliers.find((entry) => entry.id === check.endorsedToId);
        if (!supplier) {
            throw new Error('SUPPLIER_NOT_FOUND');
        }

        const customers = getCustomers();
        const customer = customers.find((entry) => entry.id === check.customerId);
        if (!customer) {
            throw new Error('CUSTOMER_NOT_FOUND');
        }

        const nowIso = new Date().toISOString();
        const effectiveDateIso = returnedDate ? new Date(returnedDate).toISOString() : nowIso;
        const baseCurrencyCode = getDefaultCurrency()?.code || 'JOD';
        const checkCurrency = String(check.currency || baseCurrencyCode).trim();
        const checkAmountBase = checkCurrency === baseCurrencyCode
            ? Number(check.amount || 0)
            : (Number(check.convertedAmount || 0) > 0
                ? Number(check.convertedAmount || 0)
                : convertCurrency(Number(check.amount || 0), checkCurrency, baseCurrencyCode));

        const normalizedBankFee = Math.max(0, Number(bankFeeAmount || 0));
        const customerLoad = checkAmountBase + (chargeFeeToCustomer ? normalizedBankFee : 0);

        const supplierUpdated = updateSupplierBalance(supplier.id, checkAmountBase);
        const customerUpdated = updateCustomerBalance(customer.id, customerLoad);
        if (!supplierUpdated || !customerUpdated) {
            throw new Error('CHECK_RETURN_BALANCE_UPDATE_FAILED');
        }

        const journalLines: JournalEntryLine[] = [
            {
                accountId: '1150',
                accountCode: '1150',
                accountName: 'ذمم العملاء',
                debit: checkAmountBase,
                credit: 0,
                description: `إعادة تحميل العميل بقيمة الشيك المرتجع ${check.checkNumber}`,
            },
            {
                accountId: supplier.accountId || '2110',
                accountCode: supplier.accountCode || '2110',
                accountName: `ذمم موردين - ${supplier.name}`,
                debit: 0,
                credit: checkAmountBase,
                description: `إعادة فتح ذمة المورد بسبب ارتجاع الشيك ${check.checkNumber}`,
            },
        ];

        if (normalizedBankFee > 0) {
            journalLines.push({
                accountId: chargeFeeToCustomer ? '1150' : '5360',
                accountCode: chargeFeeToCustomer ? '1150' : '5360',
                accountName: chargeFeeToCustomer ? 'ذمم العملاء' : 'مصروفات بنكية',
                debit: normalizedBankFee,
                credit: 0,
                description: chargeFeeToCustomer
                    ? `تحميل رسوم الشيك المرتجع على العميل ${check.checkNumber}`
                    : `رسوم بنكية للشيك المرتجع ${check.checkNumber}`,
            });
            journalLines.push({
                accountId: chargeFeeToCustomer ? '4390' : '1110',
                accountCode: chargeFeeToCustomer ? '4390' : '1110',
                accountName: chargeFeeToCustomer ? 'إيراد رسوم الشيكات المرتجعة' : 'الصندوق',
                debit: 0,
                credit: normalizedBankFee,
                description: chargeFeeToCustomer
                    ? `إثبات إيراد رسوم الشيك المرتجع ${check.checkNumber}`
                    : `سداد رسوم بنكية للشيك المرتجع ${check.checkNumber}`,
            });
        }

        const entry = addJournalEntry({
            date: effectiveDateIso,
            referenceType: 'payment',
            referenceId: `CHK-RETURN-${check.id}`,
            description: `ارتجاع شيك وارد رقم ${check.checkNumber}`,
            lines: journalLines,
            createdBy,
        });

        check.status = 'returned';
        check.returnedDate = effectiveDateIso;
        check.rejectedReason = normalizedReason;
        check.notes = [check.notes, notes].filter(Boolean).join(' | ').trim() || undefined;
        check.updatedAt = nowIso;
        writeData('checks_received.json', checks);

        const events = getCheckReturnEvents();
        const event: CheckReturnEvent = {
            id: nextPrefixedId('chkret'),
            checkId: check.id,
            checkNumber: check.checkNumber,
            customerId: customer.id,
            customerName: customer.name,
            supplierId: supplier.id,
            supplierName: supplier.name,
            reason: normalizedReason,
            bankFeeAmount: normalizedBankFee,
            chargeFeeToCustomer,
            amountBase: checkAmountBase,
            currency: checkCurrency,
            amountOriginal: Number(check.amount || 0),
            journalEntryId: entry.id,
            notes: notes || undefined,
            createdAt: nowIso,
            createdBy,
        };
        events.push(event);
        writeData('checks_return_events.json', events);

        return { check: { ...check }, event, journalEntryId: entry.id };
    });
}

export function getIncomingChecksByCustomer(customerId: string): IncomingCheck[] {
    const checks = getIncomingChecks();
    return checks.filter(c => c.customerId === customerId);
}

// Issued Checks (to suppliers)
export function getIssuedChecks(): IssuedCheck[] {
    return readData<IssuedCheck[]>('checks_issued.json', []);
}

export function addIssuedCheck(check: Omit<IssuedCheck, 'id' | 'createdAt' | 'updatedAt'>): IssuedCheck {
    return withDbLock(() => {
        const checks = getIssuedChecks();
        const newCheck: IssuedCheck = {
            ...check,
            id: String(Math.max(0, ...checks.map(c => Number.parseInt(String(c.id || ''), 10) || 0)) + 1),
            createdAt: new Date().toISOString(),
            updatedAt: new Date().toISOString(),
        };
        checks.push(newCheck);
        writeData('checks_issued.json', checks);
        return newCheck;
    });
}

export function updateIssuedCheckStatus(checkId: string, status: 'pending' | 'cleared' | 'rejected' | 'returned', metadata?: { rejectedReason?: string; clearedDate?: string; returnedDate?: string }): boolean {
    const checks = getIssuedChecks();
    const check = checks.find(c => c.id === checkId);
    if (!check) return false;
    
    check.status = status;
    check.updatedAt = new Date().toISOString();
    
    if (status === 'cleared' && metadata?.clearedDate) {
        check.clearedDate = metadata.clearedDate;
    }
    if (status === 'rejected' && metadata?.rejectedReason) {
        check.rejectedReason = metadata.rejectedReason;
    }
    if (status === 'returned' && metadata?.returnedDate) {
        check.returnedDate = metadata.returnedDate;
    }
    
    writeData('checks_issued.json', checks);
    return true;
}

export function getIssuedChecksBySupplier(supplierId: string): IssuedCheck[] {
    const checks = getIssuedChecks();
    return checks.filter(c => c.supplierId === supplierId);
}

// --- Bank Management ---
export function getBanks(): Bank[] {
    const banks = readData<Bank[]>('banks.json', []);
    const accounts = getChartOfAccounts();
    const bankParent = accounts.find((acc) => acc.code === '1120');
    let changed = false;

    const normalized = banks.map((entry) => {
        const base: Bank = {
            ...entry,
            accountId: entry.accountId,
            accountCode: entry.accountCode,
        };

        // Backfill account link from chart of accounts when the bank code already matches a bank sub-account.
        if ((!base.accountId || !base.accountCode) && bankParent) {
            const matchedAccount = accounts.find(
                (acc) => acc.type === 'account' && acc.parentId === bankParent.id && acc.code === base.code
            );
            if (matchedAccount) {
                base.accountId = matchedAccount.id;
                base.accountCode = matchedAccount.code;
                changed = true;
            }
        }

        const { bank, updated } = ensureBankAccount(base);
        if (updated) changed = true;
        return bank;
    });

    // Also expose banks created directly in chart-of-accounts under account 1120.
    if (bankParent) {
        const bankSubAccounts = accounts.filter(
            (acc) => acc.type === 'account' && acc.parentId === bankParent.id && acc.code !== bankParent.code
        );
        for (const account of bankSubAccounts) {
            const alreadyLinked = normalized.some(
                (bank) => bank.accountCode === account.code || bank.accountId === account.id
            );
            if (alreadyLinked) continue;

            normalized.push({
                id: nextPrefixedId('bank'),
                code: account.code,
                name: account.nameAr || account.nameEn || `Bank ${account.code}`,
                nameEn: account.nameEn || account.nameAr || `Bank ${account.code}`,
                fullName: account.nameAr || account.nameEn || `Bank ${account.code}`,
                bankNumber: account.code,
                accountId: account.id,
                accountCode: account.code,
                createdAt: new Date().toISOString(),
                createdBy: 'system',
            });
            changed = true;
        }
    }

    if (changed) {
        writeData('banks.json', normalized);
    }

    return normalized;
}

// === Purchase Credit Notes (إشعارات الدائن) ===

export function getPurchaseCreditNotes(): PurchaseCreditNote[] {
    return readData<PurchaseCreditNote[]>('purchase_credit_notes.json', []);
}

export function getPurchaseCreditNoteById(id: string): PurchaseCreditNote | null {
    const creditNotes = getPurchaseCreditNotes();
    return creditNotes.find(cn => cn.id === id) || null;
}

export function getPurchaseCreditNotesBySupplier(supplierId: string): PurchaseCreditNote[] {
    const creditNotes = getPurchaseCreditNotes();
    return creditNotes.filter(cn => cn.supplierId === supplierId && cn.status !== 'cancelled');
}

export function getPurchaseCreditNotesByInvoice(invoiceId: string): PurchaseCreditNote[] {
    const creditNotes = getPurchaseCreditNotes();
    return creditNotes.filter(cn => cn.invoiceId === invoiceId && cn.status !== 'cancelled');
}

export function addPurchaseCreditNote(
    data: Omit<
        PurchaseCreditNote,
        'id' | 'creditNoteNumber' | 'status' | 'createdAt' | 'createdBy' | 'postingStatus' | 'journalEntryId' | 'journalEntryIdForImpactOnBalance'
    > & { createdBy: string }
): PurchaseCreditNote {
    return withDbLock(() => {
        const creditNotes = getPurchaseCreditNotes();
        const nextSeqNum = nextSequenceValue('purchase_credit_note_seq');
        const creditNoteNumber = `CN/${format(new Date(), 'yyyy')}/${String(nextSeqNum).padStart(4, '0')}`;

        const normalizedScope = data.creditScope ?? (data.items?.length ? 'items' : 'invoice-total');
        const hasLinkedInvoice = Boolean(data.invoiceId && data.invoiceId !== '__supplier_balance__');

        // Validate positive amount
        if (Number(data.grandTotal || 0) <= 0) {
            throw new Error('CREDIT_NOTE_AMOUNT_INVALID');
        }

        if (hasLinkedInvoice) {
            // Validate that credit note total doesn't exceed linked invoice total
            const invoiceId = String(data.invoiceId);
            const invoice = getPurchaseOrderById(invoiceId);
            if (!invoice) {
                throw new Error(`Invoice ${invoiceId} not found`);
            }

            const existingCreditNotes = creditNotes.filter(
                cn => cn.invoiceId === invoiceId && cn.status !== 'cancelled'
            );
            const existingTotal = existingCreditNotes.reduce((sum, cn) => sum + Number(cn.grandTotal || 0), 0);

            if (existingTotal + Number(data.grandTotal || 0) > Number(invoice.grandTotal || 0)) {
                throw new Error('CREDIT_NOTE_EXCEEDS_INVOICE');
            }
        }

        const newCreditNote: PurchaseCreditNote = {
            ...data,
            creditScope: normalizedScope,
            id: nextPrefixedId('cn'),
            creditNoteNumber,
            status: 'draft',
            postingStatus: 'draft',
            createdAt: new Date().toISOString(),
        };

        creditNotes.push(newCreditNote);
        writeData('purchase_credit_notes.json', creditNotes);

        return newCreditNote;
    });
}

export function postPurchaseCreditNote(creditNoteId: string, createdBy: string): PurchaseCreditNote {
    return withDbLock(() => {
        const creditNotes = getPurchaseCreditNotes();
        const creditNote = creditNotes.find(cn => cn.id === creditNoteId);

        if (!creditNote) {
            throw new Error('CREDIT_NOTE_NOT_FOUND');
        }

        if (creditNote.status === 'posted') {
            throw new Error('CREDIT_NOTE_ALREADY_POSTED');
        }

        const hasLinkedInvoice = Boolean(creditNote.invoiceId && creditNote.invoiceId !== '__supplier_balance__');
        if (hasLinkedInvoice) {
            const invoice = getPurchaseOrderById(String(creditNote.invoiceId));
            if (!invoice) {
                throw new Error('INVOICE_NOT_FOUND');
            }
        }

        // Get supplier
        const supplier = getSuppliers().find((s) => s.id === creditNote.supplierId);
        if (!supplier) {
            throw new Error('SUPPLIER_NOT_FOUND');
        }

        // Create journal entries
        const entries: JournalEntryLine[] = [];
        const baseAmount = creditNote.grandTotalBase || creditNote.grandTotal;
        const taxAmount = creditNote.taxAmount || 0;
        const baseSubTotal = creditNote.subTotal - (creditNote.totalDiscount || 0);

        // Debit: Accounts Payable (Suppliers) - يتم تقليل ذمة المورد
        entries.push({
            accountId: '2100', // Accounts Payable
            accountCode: '2100',
            accountName: 'Accounts Payable',
            debit: baseAmount,
            credit: 0,
            description: `Credit Note: ${creditNote.creditNoteNumber}`,
        });

        // Credit: Purchase Returns (if it's a return)
        if (creditNote.reason === 'return') {
            entries.push({
                accountId: '5150', // Purchase Returns
                accountCode: '5150',
                accountName: 'Purchase Returns',
                debit: 0,
                credit: baseSubTotal,
                description: `Credit Note Return: ${creditNote.creditNoteNumber}`,
            });

            // If there's tax, credit input VAT
            if (taxAmount > 0) {
                entries.push({
                    accountId: '1160', // Input VAT
                    accountCode: '1160',
                    accountName: 'Input VAT',
                    debit: 0,
                    credit: taxAmount,
                    description: `Credit Note Tax: ${creditNote.creditNoteNumber}`,
                });
            }
        } else if (creditNote.reason === 'price-adjustment' || creditNote.reason === 'invoice-error') {
            // Credit: Purchase Discounts
            entries.push({
                accountId: '5140', // Purchase Discounts
                accountCode: '5140',
                accountName: 'Purchase Discounts',
                debit: 0,
                credit: baseSubTotal,
                description: `Credit Note Adjustment: ${creditNote.creditNoteNumber}`,
            });

            if (taxAmount > 0) {
                entries.push({
                    accountId: '1160', // Input VAT
                    accountCode: '1160',
                    accountName: 'Input VAT',
                    debit: 0,
                    credit: taxAmount,
                    description: `Credit Note Tax: ${creditNote.creditNoteNumber}`,
                });
            }
        } else {
            // Other reasons
            entries.push({
                accountId: '5150', // Purchase Returns
                accountCode: '5150',
                accountName: 'Purchase Returns',
                debit: 0,
                credit: baseSubTotal,
                description: `Credit Note: ${creditNote.creditNoteNumber}`,
            });

            if (taxAmount > 0) {
                entries.push({
                    accountId: '1160', // Input VAT
                    accountCode: '1160',
                    accountName: 'Input VAT',
                    debit: 0,
                    credit: taxAmount,
                    description: `Credit Note Tax: ${creditNote.creditNoteNumber}`,
                });
            }
        }

        const journalEntry: JournalEntry = {
            id: nextPrefixedId('je'),
            date: creditNote.date,
            referenceType: 'purchase_return',
            referenceId: creditNote.creditNoteNumber,
            description: `Purchase Credit Note: ${creditNote.creditNoteNumber} - ${supplier.name}`,
            lines: entries,
            createdBy,
            createdAt: new Date().toISOString(),
        };

        const addedEntry = addJournalEntry(journalEntry);

        // Update supplier balance
        updateSupplierBalance(creditNote.supplierId, -baseAmount);

        // Update credit note status
        creditNote.status = 'posted';
        creditNote.postingStatus = 'posted';
        creditNote.journalEntryId = addedEntry.id;

        writeData('purchase_credit_notes.json', creditNotes);

        return creditNote;
    });
}

export function cancelPurchaseCreditNote(
    creditNoteId: string,
    reason: string,
    cancelledBy: string
): PurchaseCreditNote {
    return withDbLock(() => {
        const creditNotes = getPurchaseCreditNotes();
        const creditNote = creditNotes.find(cn => cn.id === creditNoteId);

        if (!creditNote) {
            throw new Error('CREDIT_NOTE_NOT_FOUND');
        }

        if (creditNote.status === 'cancelled') {
            throw new Error('CREDIT_NOTE_ALREADY_CANCELLED');
        }

        // If credit note was posted, reverse the journal entry
        if (creditNote.status === 'posted' && creditNote.journalEntryId) {
            const entries = getJournalEntries();
            const entry = entries.find(e => e.id === creditNote.journalEntryId);
            if (entry) {
                // Reverse journal entry logic would go here
                // For now, we'll add a reversing entry
                const reversingLines = entry.lines.map((line: JournalEntryLine) => ({
                    ...line,
                    debit: line.credit,
                    credit: line.debit,
                }));

                const reversingEntry: JournalEntry = {
                    id: nextPrefixedId('je'),
                    date: new Date().toISOString(),
                    referenceType: 'purchase_return_cancel',
                    referenceId: `REVERSE-${creditNote.creditNoteNumber}`,
                    description: `Reversal of Credit Note: ${creditNote.creditNoteNumber}`,
                    lines: reversingLines,
                    createdBy: cancelledBy,
                    createdAt: new Date().toISOString(),
                };

                addJournalEntry(reversingEntry);
                creditNote.journalEntryId = reversingEntry.id;
            }
        }

        creditNote.status = 'cancelled';
        creditNote.cancelledAt = new Date().toISOString();
        creditNote.cancelledBy = cancelledBy;
        creditNote.cancelledReason = reason;

        writeData('purchase_credit_notes.json', creditNotes);

        return creditNote;
    });
}

function allocateBankAccountCode(parentCode: string): string {
    const seq = nextSequenceValue('bank-acc');
    return `${parentCode}-${String(seq).padStart(3, '0')}`;
}

function normalizeBankCode(raw: string | undefined): string {
    return String(raw || '').trim().toUpperCase();
}

function normalizeBankNumber(raw: string | undefined): string {
    return String(raw || '').trim();
}

function nextBankNumber(existingBanks: Bank[]): string {
    const usedNumbers = new Set(
        existingBanks
            .map((bank) => normalizeBankNumber(bank.bankNumber))
            .filter(Boolean)
    );

    let maxNumeric = 0;
    for (const value of usedNumbers) {
        if (/^\d+$/.test(value)) {
            maxNumeric = Math.max(maxNumeric, Number.parseInt(value, 10));
        }
    }

    let candidate = maxNumeric + 1;
    while (usedNumbers.has(String(candidate)) || usedNumbers.has(String(candidate).padStart(3, '0'))) {
        candidate += 1;
    }

    return String(candidate).padStart(3, '0');
}

function ensureBankAccount(bank: Bank, parentCode = '1120'): { bank: Bank; updated: boolean } {
    if (bank.accountId && bank.accountCode) return { bank, updated: false };

    const parent = ensureAccountExists({
        preferredCode: parentCode,
        nameAr: 'البنك',
        nameEn: 'Bank',
        parentCode: '1100',
        nature: 'debit',
        type: 'account',
    });

    const displayName = String(bank.fullName || bank.name || bank.code || 'Bank').trim();
    const preferredCode = String(bank.accountCode || '').trim() || allocateBankAccountCode(parent.code);
    const account = ensureAccountExists({
        preferredCode,
        nameAr: `بنك - ${displayName}`,
        nameEn: `Bank - ${displayName}`,
        parentCode: parent.code,
        nature: 'debit',
        type: 'account',
    });

    return {
        bank: {
            ...bank,
            accountId: account.id,
            accountCode: account.code,
        },
        updated: true,
    };
}

export function addBank(bank: Omit<Bank, 'id' | 'createdAt' | 'createdBy'>): Bank {
    return withDbLock(() => {
        const banks = getBanks();
        const generatedBankNumber = nextBankNumber(banks);
        const normalizedName = String(bank.name || bank.fullName || '').trim();
        const normalizedNameEn = String(bank.nameEn || bank.fullName || normalizedName).trim();
        const normalizedCode = normalizeBankCode(bank.code || `BANK${generatedBankNumber}`);

        if (!normalizedCode) {
            throw new Error('BANK_CODE_REQUIRED');
        }

        const hasCodeConflict = banks.some((existing) => normalizeBankCode(existing.code) === normalizedCode);
        if (hasCodeConflict) {
            throw new Error('BANK_CODE_EXISTS');
        }

        const newBank: Bank = {
            ...bank,
            name: normalizedName,
            nameEn: normalizedNameEn,
            code: normalizedCode,
            accountCode: String(bank.accountCode || '').trim() || undefined,
            accountId: String(bank.accountId || '').trim() || undefined,
            bankNumber: generatedBankNumber,
            fullName: String(bank.fullName || normalizedName || '').trim() || undefined,
            iban: String(bank.iban || '').trim() || undefined,
            currencyCode: String(bank.currencyCode || '').trim() || undefined,
            id: nextPrefixedId('bank'),
            createdAt: new Date().toISOString(),
            createdBy: 'system',
        };
        const withAccount = ensureBankAccount(newBank).bank;
        banks.push(withAccount);
        writeData('banks.json', banks);
        return withAccount;
    });
}

export function updateBank(id: string, updates: Partial<Bank>): Bank | null {
    return withDbLock(() => {
        const banks = getBanks();
        const bankIndex = banks.findIndex(b => b.id === id);
        if (bankIndex === -1) return null;

        const merged = { ...banks[bankIndex], ...updates };
        const normalizedName = String(merged.name || merged.fullName || '').trim();
        const normalizedNameEn = String(merged.nameEn || merged.fullName || normalizedName).trim();
        const normalizedCode = normalizeBankCode(merged.code);

        if (!normalizedCode) {
            throw new Error('BANK_CODE_REQUIRED');
        }

        const hasCodeConflict = banks.some((existing, idx) => idx !== bankIndex && normalizeBankCode(existing.code) === normalizedCode);
        if (hasCodeConflict) {
            throw new Error('BANK_CODE_EXISTS');
        }

        // Bank number is system-managed and immutable after creation.
        const lockedBankNumber = normalizeBankNumber(banks[bankIndex].bankNumber) || nextBankNumber(banks);

        banks[bankIndex] = {
            ...merged,
            name: normalizedName,
            nameEn: normalizedNameEn,
            code: normalizedCode,
            accountCode: String(merged.accountCode || '').trim() || undefined,
            accountId: String(merged.accountId || '').trim() || undefined,
            bankNumber: lockedBankNumber,
            fullName: String(merged.fullName || normalizedName || '').trim() || undefined,
            iban: String(merged.iban || '').trim() || undefined,
            currencyCode: String(merged.currencyCode || '').trim() || undefined,
        };
        banks[bankIndex] = ensureBankAccount(banks[bankIndex]).bank;
        writeData('banks.json', banks);
        return banks[bankIndex];
    });
}

export function deleteBank(id: string): boolean {
    const banks = getBanks();
    const filteredBanks = banks.filter(b => b.id !== id);
    if (filteredBanks.length === banks.length) return false;
    
    writeData('banks.json', filteredBanks);
    return true;
}

export function getBankById(id: string): Bank | null {
    const banks = getBanks();
    return banks.find(b => b.id === id) || null;
}

// --- General Banks (for incoming checks and customer references) ---
import type { GeneralBank } from './types';

export function getGeneralBanks(): GeneralBank[] {
    return readData<GeneralBank[]>('general_banks.json', []);
}

export function getGeneralBankById(id: string): GeneralBank | null {
    const banks = getGeneralBanks();
    return banks.find(b => b.id === id) || null;
}

export function getGeneralBanksByCountry(country: string): GeneralBank[] {
    const banks = getGeneralBanks();
    return banks.filter(b => b.country === country && (b.isActive !== false));
}

export function addGeneralBank(
    bank: Omit<GeneralBank, 'id' | 'createdAt' | 'createdBy' | 'updatedAt'>
): GeneralBank {
    return withDbLock(() => {
        const banks = getGeneralBanks();

        // Validate required fields
        const normalizedName = String(bank.name || '').trim();
        const normalizedNameEn = String(bank.nameEn || normalizedName).trim();
        const normalizedCode = String(bank.code || '').trim().toUpperCase();

        if (!normalizedCode) {
            throw new Error('GENERAL_BANK_CODE_REQUIRED');
        }

        // Check for duplicate code
        const hasCodeConflict = banks.some(
            (existing) =>
                String(existing.code || '').trim().toUpperCase() === normalizedCode
        );
        if (hasCodeConflict) {
            throw new Error('GENERAL_BANK_CODE_EXISTS');
        }

        const newBank: GeneralBank = {
            ...bank,
            id: nextPrefixedId('gbank'),
            code: normalizedCode,
            name: normalizedName,
            nameEn: normalizedNameEn,
            accountId: String(bank.accountId || '').trim() || undefined,
            accountCode: String(bank.accountCode || '').trim() || undefined,
            swiftCode: String(bank.swiftCode || '').trim() || undefined,
            branchCode: String(bank.branchCode || '').trim() || undefined,
            branch: String(bank.branch || '').trim() || undefined,
            country: String(bank.country || '').trim() || undefined,
            city: String(bank.city || '').trim() || undefined,
            address: String(bank.address || '').trim() || undefined,
            phone: String(bank.phone || '').trim() || undefined,
            email: String(bank.email || '').trim() || undefined,
            ibanPattern: String(bank.ibanPattern || '').trim() || undefined,
            isActive: bank.isActive !== false,
            notes: String(bank.notes || '').trim() || undefined,
            createdAt: new Date().toISOString(),
            createdBy: 'system',
        };

        banks.push(newBank);
        writeData('general_banks.json', banks);
        return newBank;
    });
}

export function updateGeneralBank(
    id: string,
    updates: Partial<Omit<GeneralBank, 'id' | 'createdAt' | 'createdBy'>>
): GeneralBank | null {
    return withDbLock(() => {
        const banks = getGeneralBanks();
        const bankIndex = banks.findIndex((b) => b.id === id);
        if (bankIndex === -1) return null;

        const merged = { ...banks[bankIndex], ...updates };
        const normalizedCode = String(merged.code || '').trim().toUpperCase();

        // Check for duplicate code (excluding self)
        const hasCodeConflict = banks.some(
            (existing, idx) =>
                idx !== bankIndex &&
                String(existing.code || '').trim().toUpperCase() === normalizedCode
        );
        if (hasCodeConflict) {
            throw new Error('GENERAL_BANK_CODE_EXISTS');
        }

        banks[bankIndex] = {
            ...merged,
            code: normalizedCode,
            name: String(merged.name || '').trim(),
            nameEn: String(merged.nameEn || merged.name || '').trim(),
            accountId: String(merged.accountId || '').trim() || undefined,
            accountCode: String(merged.accountCode || '').trim() || undefined,
            swiftCode: String(merged.swiftCode || '').trim() || undefined,
            branchCode: String(merged.branchCode || '').trim() || undefined,
            branch: String(merged.branch || '').trim() || undefined,
            country: String(merged.country || '').trim() || undefined,
            city: String(merged.city || '').trim() || undefined,
            address: String(merged.address || '').trim() || undefined,
            phone: String(merged.phone || '').trim() || undefined,
            email: String(merged.email || '').trim() || undefined,
            ibanPattern: String(merged.ibanPattern || '').trim() || undefined,
            isActive: merged.isActive !== false,
            notes: String(merged.notes || '').trim() || undefined,
            updatedAt: new Date().toISOString(),
        };

        writeData('general_banks.json', banks);
        return banks[bankIndex];
    });
}

export function deleteGeneralBank(id: string): boolean {
    const banks = getGeneralBanks();
    const filteredBanks = banks.filter((b) => b.id !== id);
    if (filteredBanks.length === banks.length) return false;

    writeData('general_banks.json', filteredBanks);
    return true;
}

// --- Currency Management ---
export function getCurrencies(): Currency[] {
    const currencies = readData<Currency[]>('currencies.json', []);
    let maxRef = 0;
    const assignedRefs = new Set<string>();

    currencies.forEach((currency) => {
        const ref = String(currency.currencyNumber || '').trim().toUpperCase();
        const match = /^CUR-(\d+)$/.exec(ref);
        if (match) {
            maxRef = Math.max(maxRef, Number(match[1] || 0));
        }
    });

    let hasChanges = false;
    const normalized = currencies.map((currency) => {
        const normalizedCode = String(currency.code || '').trim().toUpperCase();
        const normalizedRef = String(currency.currencyNumber || '').trim().toUpperCase();
        const hasValidRef = /^CUR-(\d+)$/.test(normalizedRef) && !assignedRefs.has(normalizedRef);

        let nextRef = normalizedRef;
        if (!hasValidRef) {
            do {
                maxRef += 1;
                nextRef = `CUR-${String(maxRef).padStart(4, '0')}`;
            } while (assignedRefs.has(nextRef));
            hasChanges = true;
        }

        if (nextRef !== normalizedRef || normalizedCode !== String(currency.code || '')) {
            hasChanges = true;
        }

        assignedRefs.add(nextRef);
        return {
            ...currency,
            code: normalizedCode,
            currencyNumber: nextRef,
        };
    });

    if (hasChanges) {
        writeData('currencies.json', normalized);
    }

    return normalized;
}

export function getDefaultCurrency(): Currency | null {
    const currencies = getCurrencies();
    return currencies.find(c => c.isDefault) || null;
}

export function addCurrency(currency: Omit<Currency, 'id' | 'createdAt' | 'createdBy'>): Currency {
    return withDbLock(() => {
        const currencies = getCurrencies();
        const normalizedCode = String(currency.code || '').trim().toUpperCase();
        if (!normalizedCode) {
            throw new Error('CURRENCY_CODE_REQUIRED');
        }

        const codeExists = currencies.some((entry) => String(entry.code || '').trim().toUpperCase() === normalizedCode);
        if (codeExists) {
            throw new Error('CURRENCY_CODE_EXISTS');
        }

        const existingRefs = new Set(
            currencies
                .map((entry) => String(entry.currencyNumber || '').trim().toUpperCase())
                .filter((value) => value.length > 0)
        );

        let nextRef = `CUR-${String(nextNamedSequence('currency-ref')).padStart(4, '0')}`;
        while (existingRefs.has(nextRef)) {
            nextRef = `CUR-${String(nextNamedSequence('currency-ref')).padStart(4, '0')}`;
        }

        if (currency.isDefault) {
            currencies.forEach(c => c.isDefault = false);
        }

        const newCurrency: Currency = {
            ...currency,
            code: normalizedCode,
            currencyNumber: nextRef,
            id: nextPrefixedId('cur'),
            createdAt: new Date().toISOString(),
            lastUpdatedAt: new Date().toISOString(),
            createdBy: 'system',
        };
        currencies.push(newCurrency);
        writeData('currencies.json', currencies);

        if (newCurrency.source) {
            addExchangeRateSource(newCurrency.source);
        }

        return newCurrency;
    });
}

export function updateCurrency(code: string, updates: Partial<Currency>): Currency | null {
    const currencies = getCurrencies();
    const currencyIndex = currencies.findIndex(c => c.code === code);
    if (currencyIndex === -1) return null;
    
    // If setting as default, unset other defaults
    if (updates.isDefault) {
        currencies.forEach(c => c.isDefault = false);
    }
    
    currencies[currencyIndex] = {
        ...currencies[currencyIndex],
        ...updates,
        lastUpdatedAt: new Date().toISOString(),
    };
    writeData('currencies.json', currencies);

    const sourceVal = updates.source;
    if (sourceVal) {
        addExchangeRateSource(sourceVal);
    }

    return currencies[currencyIndex];
}

export function deleteCurrency(code: string): boolean {
    const currencies = getCurrencies();
    
    // Don't delete default currency
    const currency = currencies.find(c => c.code === code);
    if (currency?.isDefault) return false;
    
    const filteredCurrencies = currencies.filter(c => c.code !== code);
    if (filteredCurrencies.length === currencies.length) return false;
    
    writeData('currencies.json', filteredCurrencies);
    return true;
}

export function getCurrencyByCode(code: string): Currency | null {
    const currencies = getCurrencies();
    return currencies.find(c => c.code === code) || null;
}

// --- Daily Exchange Rates (historical) ---
const dailyRateFile = 'daily_exchange_rates.json';

function normalizeRateDate(date: string | Date): string {
    const d = typeof date === 'string' ? new Date(date) : date;
    // Store as yyyy-MM-dd for deduplication
    return format(d, 'yyyy-MM-dd');
}

export function getDailyExchangeRates(): DailyExchangeRate[] {
    return readData<DailyExchangeRate[]>(dailyRateFile, []);
}

export function findDailyExchangeRate(baseCurrency: string, currency: string, date: string | Date): DailyExchangeRate | undefined {
    const targetDate = normalizeRateDate(date);
    const rates = getDailyExchangeRates();
    return rates.find((r) => r.baseCurrency === baseCurrency && r.currency === currency && normalizeRateDate(r.date) === targetDate);
}

export function upsertDailyExchangeRate(params: { baseCurrency: string; currency: string; rate: number; date: string | Date; source?: string; isAuto?: boolean; }): DailyExchangeRate {
    const { baseCurrency, currency, rate, date, source, isAuto } = params;
    const targetDate = normalizeRateDate(date);
    const now = new Date().toISOString();

    return withDbLock(() => {
        const rates = getDailyExchangeRates();
        const existingIndex = rates.findIndex((r) => r.baseCurrency === baseCurrency && r.currency === currency && normalizeRateDate(r.date) === targetDate);
        const entry: DailyExchangeRate = {
            id: existingIndex >= 0 ? rates[existingIndex].id : nextPrefixedId('fx'),
            baseCurrency,
            currency,
            rate,
            date: targetDate,
            source,
            isAuto,
            createdAt: existingIndex >= 0 ? rates[existingIndex].createdAt : now,
        };

        if (existingIndex >= 0) {
            rates[existingIndex] = entry;
        } else {
            rates.push(entry);
        }

        writeData(dailyRateFile, rates);
        return entry;
    });
}

// --- Currency Conversion ---
export function convertCurrency(amount: number, fromCurrency: string, toCurrency: string): number {
    const fromCurr = getCurrencyByCode(fromCurrency);
    const toCurr = getCurrencyByCode(toCurrency);
    
    if (!fromCurr || !toCurr) {
        console.warn(`Currency conversion failed: ${fromCurrency} or ${toCurrency} not found`);
        return amount;
    }
    
    // Convert to base currency first, then to target currency
    const amountInBase = amount / fromCurr.exchangeRate;
    const convertedAmount = amountInBase * toCurr.exchangeRate;
    
    return Math.round(convertedAmount * 100) / 100;
}

export function getExchangeRate(fromCurrency: string, toCurrency: string): number {
    const fromCurr = getCurrencyByCode(fromCurrency);
    const toCurr = getCurrencyByCode(toCurrency);
    
    if (!fromCurr || !toCurr) return 1;
    
    return Math.round((toCurr.exchangeRate / fromCurr.exchangeRate) * 10000) / 10000;
}

// ===== Check Endorsement Functions =====
export function endorseIncomingCheck(
    checkId: string,
    endorsementType: 'transferred-to-collection' | 'endorsed-to-party',
    endorsedToId?: string,
    endorsedToName?: string,
    endorsedToType?: 'customer' | 'supplier',
    notes?: string
): boolean {
    return withDbLock(() => {
        const checks = getIncomingChecks();
        const check = checks.find(c => c.id === checkId);
        if (!check) return false;

        check.endorsementStatus = endorsementType;
        if (endorsementType === 'endorsed-to-party') {
            check.endorsedToId = endorsedToId;
            check.endorsedToName = endorsedToName;
            check.endorsedToType = endorsedToType;
        }
        check.endorsedAt = new Date().toISOString();
        if (notes) {
            check.notes = notes;
        }
        check.updatedAt = new Date().toISOString();

        writeData('checks_received.json', checks);
        return true;
    });
}

/**
 * Create journal entries for multi-method payments (cash, visa, receivables)
 * This creates separate journal entries based on payment method breakdown
 */
export function createMultiMethodPaymentJournalEntry(
  paymentId: string,
  customerId: string,
  customerName: string,
  paymentBreakdown: Array<{ method: 'cash' | 'visa' | 'receivables'; amount: number; customerName?: string }>,
  createdBy: string,
  baseEntryId?: string  // Optional: for linking to sales invoice entry (e.g., "JE-20260222111310" -> createsu "JE-20260222111310-2")
): void {
  if (!paymentBreakdown || paymentBreakdown.length === 0) {
    return;
  }

    // Invoice mixed-payment mode now records payment methods inside the sales invoice JE itself.
    // This function remains for standalone payment vouchers only.

  // Create a single journal entry with all payment lines
  const lines: JournalEntryLine[] = [];
  let totalPaymentAmount = 0;

  // Process each payment method
  for (const payment of paymentBreakdown) {
    if (payment.amount <= 0) continue;
    
    const amount = payment.amount;

    switch (payment.method) {
      case 'cash':
                totalPaymentAmount += amount;
        // Cash payment: Debit Cash (1110), Credit Accounts Receivable (1150)
        // This records that we received cash from the customer
        if (!lines.find(l => l.accountCode === '1110' && l.debit > 0)) {
          lines.push({
            accountId: '1110',
            accountCode: '1110',
            accountName: 'الصندوق',
            debit: amount,
            credit: 0,
            description: `دفع نقدي من ${customerName}`,
          });
        } else {
          // Update existing cash line
          const cashLine = lines.find(l => l.accountCode === '1110' && l.debit > 0);
          if (cashLine) cashLine.debit += amount;
        }
        break;

      case 'visa':
                totalPaymentAmount += amount;
        // Visa payment: Debit Bank Account (1120), Credit Accounts Receivable (1150)
        // This records that payment was deposited to bank account
        if (!lines.find(l => l.accountCode === '1120' && l.debit > 0)) {
          lines.push({
            accountId: '1120',
            accountCode: '1120',
            accountName: 'البنك',
            debit: amount,
            credit: 0,
            description: `دفع بطاقة فيزا من ${customerName}`,
          });
        } else {
          // Update existing bank line
          const bankLine = lines.find(l => l.accountCode === '1120' && l.debit > 0);
          if (bankLine) bankLine.debit += amount;
        }
        break;

      case 'receivables':
        // Receivables (ذمم): No immediate cash/bank entry
        // This is just tracked as a note that the customer owes this amount
        // No debit/credit entry needed here - the customer is already debited in the sales invoice
        break;

      default:
        break;
    }
  }

  // Add the credit to Accounts Receivable (العملاء) for the total payment
  if (totalPaymentAmount > 0) {
    lines.push({
      accountId: '1150',
      accountCode: '1150',
      accountName: 'العملاء',
      debit: 0,
      credit: totalPaymentAmount,
      description: `تسديد من ${customerName}`,
    });
  }

  // Add the journal entry if we have lines
  if (lines.length > 0) {
    try {
      const entryData = {
        date: new Date().toISOString(),
        description: `سند قبض من ${customerName} - ${paymentId}`,
        referenceType: 'payment' as const,
        referenceId: paymentId,
        lines,
        createdBy,
      };

      if (baseEntryId) {
        // Link this payment entry to the sales invoice entry with sequence number
        addJournalEntryWithCustomId(entryData, `${baseEntryId}-2`);
      } else {
        addJournalEntry(entryData);
      }
    } catch (error) {
      console.error('Failed to create multi-method payment journal entry:', error);
    }
  }
}
