import { prisma } from "@/core/prisma";
import { AppError } from "@/core/AppError";
import { OrderStatus, OrderItemStatus, LifecycleEventType } from "@prisma/client";
import { trackedTransaction } from "@/core/tracked";
import { promoCodeService } from "@/modules/promoCode/promoCode.service";
import {
    resolveRouteForProduct,
    findFirstProductionStep,
    stepUpdateData,
    type ResolvedRoute,
} from "@/modules/logisticsRoute/logisticsNavigation";
import type { ProductConfigurationSnapshotInput } from "ttm-shared";
import {
    buildDefaultProductionWorkflowDto,
    buildProductionWorkflowSnapshot,
    getStartProductionStepState,
    toProductionWorkflowDto,
} from "@/modules/station/production-workflow";

/* ── Helper : résoudre le type d'événement lifecycle ── */
function resolveLifecycleEventType(
    from: OrderItemStatus,
    to: OrderItemStatus
): LifecycleEventType {
    if (to === OrderItemStatus.PREPARING && from === OrderItemStatus.PENDING) return LifecycleEventType.ACKNOWLEDGED;
    if (to === OrderItemStatus.READY)     return LifecycleEventType.READY;
    if (to === OrderItemStatus.PICKED_UP) return LifecycleEventType.PICKED_UP;
    if (to === OrderItemStatus.DELIVERED) return LifecycleEventType.DELIVERED;
    if (to === OrderItemStatus.CANCELLED) return LifecycleEventType.CANCELLED;
    return LifecycleEventType.REVERTED;
}

/* ── Helper : créer un événement lifecycle ── */
async function createLifecycleEvent(
    tx: any,
    orderItemId: string,
    fromStatus: OrderItemStatus,
    toStatus: OrderItemStatus,
    actorId?: string | null,
    actorName?: string | null,
    stationId?: string | null
) {
    await tx.orderItemLifecycleEvent.create({
        data: {
            orderItemId,
            eventType: resolveLifecycleEventType(fromStatus, toStatus),
            fromStatus,
            toStatus,
            actorId: actorId ?? null,
            actorName: actorName ?? null,
            stationId: stationId ?? null,
        }
    });
}

async function resolveProductionWorkflowForStation(
    tx: any,
    stationId: string | null | undefined,
    cache?: Map<string, ReturnType<typeof buildDefaultProductionWorkflowDto>>,
) {
    if (!stationId) return null

    const cached = cache?.get(stationId)
    if (cached) return cached

    const station = await tx.station.findUnique({
        where: { id: stationId },
        include: { productionWorkflowSteps: { orderBy: { position: "asc" } } },
    })

    const workflowDto = toProductionWorkflowDto(station)
    const workflow = workflowDto
        ? (workflowDto.enabled === false
            ? {
                ...buildDefaultProductionWorkflowDto(),
                enabled: false,
                revision: workflowDto.revision ?? 1,
            }
            : workflowDto)
        : (station?.mainType === "PRODUCTION" ? buildDefaultProductionWorkflowDto() : null)
    if (workflow && cache) {
        cache.set(stationId, workflow)
    }
    return workflow
}

async function resolveInitialProductionData(
    tx: any,
    stationId: string | null | undefined,
    cache?: Map<string, ReturnType<typeof buildDefaultProductionWorkflowDto>>,
) {
    const workflow = await resolveProductionWorkflowForStation(tx, stationId, cache)
    if (!workflow) return {}

    const step = getStartProductionStepState(workflow)
    return {
        productionStepIndex: step.index,
        productionStepCode: step.code,
        productionStepLabel: step.label,
        productionWorkflowRevision: step.workflowRevision,
        productionWorkflowSnapshot: buildProductionWorkflowSnapshot(workflow),
    }
}

function cloneProductionState(sourceItem: any) {
    return {
        productionStepIndex: sourceItem.productionStepIndex ?? null,
        productionStepCode: sourceItem.productionStepCode ?? null,
        productionStepLabel: sourceItem.productionStepLabel ?? null,
        productionWorkflowRevision: sourceItem.productionWorkflowRevision ?? null,
        productionWorkflowSnapshot: sourceItem.productionWorkflowSnapshot ?? null,
    }
}

type IncomingOrderItemCustomization = {
    ingredientId: string
    action: "ADDED" | "REMOVED"
    quantity?: number
}

type IncomingOrderItemInput = {
    productId: string
    quantity: number
    recipeId?: string
    configurationLabel?: string
    configurationSnapshot?: ProductConfigurationSnapshotInput
    customizations?: IncomingOrderItemCustomization[]
}

type ProductRecipeSelection = {
    id: string
    name: string
    recipeIngredients: Array<{
        ingredientId: string
        role: string
        maxQuantity: number | null
        extraPrice: number
        ingredient: { id: string; name: string }
    }>
}

type ResolvedOrderItemCustomization = {
    ingredientId: string
    ingredientName: string
    action: string
    quantity: number
    extraPrice: number
}

type OrderDepositLineInput = {
    depositDefinitionId: string
    quantity: number
}

function resolveEffectiveProductCustomizationMode(product: {
    customizationMode?: string | null
    recipeId?: string | null
    configuratorId?: string | null
}) {
    if (product.customizationMode === "CONFIGURATOR" || product.configuratorId) {
        return "CONFIGURATOR"
    }

    if (product.customizationMode === "RECIPE" || product.recipeId) {
        return "RECIPE"
    }

    return "NONE"
}

function normalizeConfigurationLabel(value?: string | null) {
    const normalized = value?.trim()
    return normalized ? normalized : null
}

function computeConfigurationUnitPriceDelta(snapshot?: ProductConfigurationSnapshotInput | null) {
    if (!snapshot) return 0
    if (Number.isInteger(snapshot.totalUnitPriceDelta)) {
        return snapshot.totalUnitPriceDelta
    }

    return (snapshot.steps ?? []).reduce((sum, step) => (
        sum + (step.entries ?? []).reduce((stepSum, entry) => stepSum + (entry.unitPriceDelta ?? 0), 0)
    ), 0)
}

function buildConfigurationSignature(
    snapshot?: ProductConfigurationSnapshotInput | null,
    configurationLabel?: string | null,
) {
    if (!snapshot && !configurationLabel) return ""
    return `${JSON.stringify(snapshot ?? null)}|${configurationLabel ?? ""}`
}

function buildResolvedCustomizationSignature(customizations: ResolvedOrderItemCustomization[]) {
    return [...customizations]
        .map((customization) => `${customization.ingredientId}:${customization.action}:${customization.quantity}:${customization.extraPrice}`)
        .sort()
        .join("|")
}

function buildStoredCustomizationSignature(customizations: Array<{
    ingredientId: string
    action: string
    quantity: number
    extraPrice: number
}>) {
    return [...customizations]
        .map((customization) => `${customization.ingredientId}:${customization.action}:${customization.quantity}:${customization.extraPrice}`)
        .sort()
        .join("|")
}

function buildOrderShortId(
    orderId: string,
    sessionOrderNumber: number | null | undefined,
    stationName: string | null | undefined
): string {
    if (sessionOrderNumber == null) return orderId.slice(0, 8);
    if (!stationName) return `#${sessionOrderNumber}`;

    const digitMatch = stationName.match(/\d+/);
    if (digitMatch) {
        return `${stationName.charAt(0).toUpperCase()}${digitMatch[0]}-#${sessionOrderNumber}`;
    }

    const fillers = new Set(["de", "du", "des", "la", "le", "les", "l", "d", "a", "au", "aux"]);
    const words = stationName.split(/[\s\-_']+/).filter((word) => word.length > 0);
    const meaningful = words.filter((word) => !fillers.has(word.toLowerCase()));
    const lastWord = meaningful.length > 0 ? meaningful[meaningful.length - 1] : words[words.length - 1] ?? "";
    const tag = lastWord.slice(0, 2);
    const formatted = tag.charAt(0).toUpperCase() + (tag.charAt(1)?.toLowerCase() ?? "");

    return `${formatted}-#${sessionOrderNumber}`;
}

function toIsoString(value: Date | string | null | undefined): string | null {
    if (!value) return null;
    if (value instanceof Date) return value.toISOString();
    const parsed = new Date(value);
    return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString();
}

function resolveDisplayName(actor: { displayName?: string | null; username?: string | null; email?: string | null } | null | undefined): string | null {
    return actor?.displayName ?? actor?.username ?? actor?.email ?? null;
}

function buildCancelledRealtimeItemSnapshot(
    item: any,
    order: { id: string; sessionOrderNumber?: number | null; stationId?: string | null; station?: { name?: string | null } | null } | null | undefined
) {
    const sourceStationName = order?.station?.name ?? null;
    return {
        name: item.name ?? "Produit",
        productName: item.name ?? "Produit",
        productId: item.productId ?? "",
        cancelledQuantity: item.quantity ?? 1,
        unitPrice: item.unitPrice ?? 0,
        recipeId: item.recipeId ?? null,
        recipeName: item.recipeName ?? null,
        configurationLabel: item.configurationLabel ?? null,
        configurationSnapshot: item.configurationSnapshot ?? null,
        orderShortId: buildOrderShortId(item.orderId ?? order?.id ?? "", order?.sessionOrderNumber, sourceStationName),
        sourceStationId: order?.stationId ?? null,
        sourceStationName,
        createdAt: toIsoString(item.createdAt) ?? new Date().toISOString(),
        takenByName: resolveDisplayName(item.takenBy),
        pickedUpByName: resolveDisplayName(item.pickedUpBy),
        customizations: (item.customizations ?? []).map((customization: any) => ({
            id: customization.id,
            ingredientId: customization.ingredientId ?? null,
            ingredientName: customization.ingredientName,
            action: customization.action,
            quantity: customization.quantity,
            extraPrice: customization.extraPrice,
        })),
    };
}

function resolveSelectedRecipe(product: any, requestedRecipeId?: string | null): ProductRecipeSelection | null {
    if (resolveEffectiveProductCustomizationMode(product) !== "RECIPE") {
        return null
    }

    const primaryRecipe = product.recipe
        ? {
            id: product.recipeId,
            name: product.recipe.name,
            recipeIngredients: product.recipe.recipeIngredients ?? [],
        } satisfies ProductRecipeSelection
        : null

    const effectiveRecipeId = requestedRecipeId ?? product.recipeId ?? null
    if (!effectiveRecipeId) return null

    if (primaryRecipe?.id === effectiveRecipeId) return primaryRecipe
    return null
}

async function productsAffectProduction(
    shopId: string,
    productIds: string[],
    tx: any
): Promise<boolean> {
    const ids = [...new Set(productIds)].filter(Boolean)
    if (ids.length === 0) return false

    const [products, shop] = await Promise.all([
        tx.product.findMany({
            where: { id: { in: ids }, shopId },
            select: { id: true, stationId: true, logisticsRouteId: true }
        }),
        tx.shop.findUnique({
            where: { id: shopId },
            select: { defaultLogisticsRouteId: true }
        })
    ])

    return products.some((p: any) => !!p.stationId || !!p.logisticsRouteId || !!shop?.defaultLogisticsRouteId)
}

function withOrderRealtimeScope<T extends Record<string, any>>(
    payload: T,
    order: { stationId?: string | null; cashSessionId?: string | null; createdById?: string | null } | null | undefined
) {
    return {
        ...payload,
        stationId: order?.stationId ?? null,
        cashSessionId: order?.cashSessionId ?? null,
        createdById: order?.createdById ?? null,
    }
}

function collectOrderProductionStationIds(
    order: {
        orderItems?: Array<{
            currentStepStationId?: string | null
            product?: { stationId?: string | null } | null
        }>
    } | null | undefined
) {
    return [...new Set(
        (order?.orderItems ?? [])
            .map((item) => item.currentStepStationId ?? item.product?.stationId ?? null)
            .filter((value): value is string => typeof value === "string")
    )]
}

function withOrderItemRealtimeScope<T extends Record<string, any>>(
    payload: T,
    options: {
        order?: { stationId?: string | null; cashSessionId?: string | null } | null
        productStationId?: string | null
        logisticsRouteId?: string | null
        currentStepPosition?: number | null
        currentStepStationId?: string | null
    }
) {
    return {
        ...withOrderRealtimeScope(payload, options.order),
        productStationId: options.productStationId ?? null,
        logisticsRouteId: options.logisticsRouteId ?? null,
        currentStepPosition: options.currentStepPosition ?? null,
        currentStepStationId: options.currentStepStationId ?? null,
    }
}

function productMapFromPreviousItems(previousItems: any[], productId: string) {
    const match = previousItems.find((entry: any) => entry.productId === productId)
    return match?.product?.stationId ?? null
}

function resolveOrderActorName(user: { displayName?: string | null; username?: string | null; email?: string | null } | null | undefined) {
    return user?.displayName ?? user?.username ?? user?.email ?? null
}

function resolveOrderItemProductionStationId(item: {
    currentStepStationId?: string | null
    product?: { stationId?: string | null } | null
}) {
    return item.currentStepStationId ?? item.product?.stationId ?? null
}

/** Include standard pour charger un order complet (items, payments, attempts) */
const ORDER_DETAIL_INCLUDE = {
    createdBy: {
        select: {
            displayName: true,
            username: true,
            email: true,
        },
    },
    depositLines: true,
    orderItems: {
        include: {
            product: { include: { station: true } },
            takenBy: true,
            pickedUpBy: true,
            customizations: true,
            lifecycleEvents: {
                orderBy: { occurredAt: "asc" as const }
            },
        }
    },
    payments: true,
    paymentAttempt: {
        orderBy: { createdAt: "desc" as const },
        take: 5
    },
    promoCodeUsages: {
        include: {
            promoCode: { select: { code: true, label: true, discountType: true } }
        }
    }
};

async function computeOrderPricing(tx: any, orderId: string) {
    const [order, orderItems, depositLines] = await Promise.all([
        tx.order.findUnique({
            where: { id: orderId },
            select: { discountTotal: true },
        }),
        tx.orderItem.findMany({
            where: { orderId, status: { not: "CANCELLED" } },
            select: { quantity: true, unitPrice: true },
        }),
        tx.orderDepositLine.findMany({
            where: { orderId },
            select: { totalAmount: true },
        }),
    ])

    const productSubtotal = orderItems.reduce(
        (sum: number, item: { quantity: number; unitPrice: number }) => sum + item.unitPrice * item.quantity,
        0,
    )
    const depositTotal = depositLines.reduce(
        (sum: number, line: { totalAmount: number }) => sum + line.totalAmount,
        0,
    )
    const discountTotal = order?.discountTotal ?? 0
    const total = Math.max(productSubtotal - discountTotal, 0) + depositTotal

    return {
        productSubtotal,
        depositTotal,
        discountTotal,
        total,
    }
}

/** Sérialise un order Prisma (avec includes) vers le format DTO frontend */
export function serializeOrder(order: any) {
    // Dernier paymentAttempt (le plus récent)
    const attempts = order.paymentAttempt ?? []
    const lastAttempt = attempts.length > 0
        ? attempts.sort((a: any, b: any) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())[0]
        : null

    return {
        ...order,
        depositTotal: order.depositTotal ?? 0,
        discountTotal: order.discountTotal ?? 0,
        createdById: order.createdById ?? null,
        createdByName: resolveOrderActorName(order.createdBy),
        cashSessionId: order.cashSessionId ?? null,
        depositLines: (order.depositLines ?? []).map((line: any) => ({
            id: line.id,
            depositDefinitionId: line.depositDefinitionId,
            label: line.labelSnapshot,
            unitAmount: line.unitAmountSnapshot,
            quantity: line.quantity,
            totalAmount: line.totalAmount,
        })),
        orderItems: (order.orderItems ?? []).map((item: any) => ({
            id: item.id,
            name: item.name,
            productId: item.productId,
            quantity: item.quantity,
            unitPrice: item.unitPrice,
            status: item.status,
            orderId: item.orderId,
            revision: item.revision,
            stationId: item.product?.stationId ?? null,
            stationName: item.product?.station?.name ?? null,
            createdAt: item.createdAt,
            takenByName: item.takenBy?.displayName ?? item.takenBy?.email ?? null,
            pickedUpByName: (() => {
                const lastPickedUp = (item.lifecycleEvents ?? []).filter((e: any) => e.eventType === "PICKED_UP").slice(-1)[0]
                return lastPickedUp?.actorName
                    ?? item.pickedUpBy?.displayName
                    ?? item.pickedUpBy?.username
                    ?? null
            })(),
            recipeId: item.recipeId ?? null,
            recipeName: item.recipeName ?? null,
            configurationLabel: item.configurationLabel ?? null,
            configurationSnapshot: item.configurationSnapshot ?? null,
            logisticsRouteId: item.logisticsRouteId ?? null,
            currentStepPosition: item.currentStepPosition ?? null,
            currentStepStationId: item.currentStepStationId ?? null,
            transportedByName: item.transportedByName ?? null,
            customizations: (item.customizations ?? []).map((c: any) => ({
                id: c.id,
                ingredientId: c.ingredientId,
                ingredientName: c.ingredientName,
                action: c.action,
                quantity: c.quantity,
                extraPrice: c.extraPrice,
            })),
            lifecycleEvents: (item.lifecycleEvents ?? []).map((e: any) => ({
                id: e.id,
                eventType: e.eventType,
                fromStatus: e.fromStatus,
                toStatus: e.toStatus,
                fromProductionStepIndex: e.fromProductionStepIndex ?? null,
                toProductionStepIndex: e.toProductionStepIndex ?? null,
                fromProductionStepCode: e.fromProductionStepCode ?? null,
                toProductionStepCode: e.toProductionStepCode ?? null,
                actorId: e.actorId,
                actorName: e.actorName,
                stationId: e.stationId,
                occurredAt: typeof e.occurredAt === 'string' ? e.occurredAt : e.occurredAt.toISOString(),
            })),
            productionStep: {
                index: item.productionStepIndex ?? null,
                code: item.productionStepCode ?? null,
                label: item.productionStepLabel ?? null,
                workflowRevision: item.productionWorkflowRevision ?? null,
            },
            productionWorkflowSnapshot: item.productionWorkflowSnapshot ?? null,
        })),
        appliedPromoCodes: (order.promoCodeUsages ?? []).map((u: any) => ({
            id: u.id,
            promoCodeId: u.promoCodeId,
            promoCode: u.promoCode?.code ?? "",
            promoLabel: u.promoCode?.label ?? null,
            discountType: u.promoCode?.discountType ?? "FIXED_AMOUNT",
            discountApplied: u.discountApplied,
            createdAt: u.createdAt,
        })),
        payments: (order.payments ?? []).map((payment: any) => ({
            id: payment.id,
            amount: payment.amount,
            method: payment.method,
            executionMethod: payment.executionMethod ?? null,
            provider: payment.provider ?? null,
            reference: payment.reference ?? null,
            createdAt: typeof payment.createdAt === "string" ? payment.createdAt : payment.createdAt.toISOString(),
        })),
        lastPaymentAttempt: lastAttempt ? {
            id: lastAttempt.id,
            status: lastAttempt.status,
            method: lastAttempt.method,
            amount: lastAttempt.amount,
            failureReason: lastAttempt.failureReason ?? null,
            createdAt: lastAttempt.createdAt,
        } : null,
        // Ne pas exposer le tableau brut
        createdBy: undefined,
        paymentAttempt: undefined,
        promoCodeUsages: undefined,
    };
}

type BatchDecrementItemInput = {
    orderItemId: string
    quantity: number
}

function getOrderItemProductionStationIds(items: any[]) {
    return [...new Set(
        items
            .map((item) => item.currentStepStationId ?? item.product?.stationId ?? null)
            .filter((value): value is string => typeof value === "string")
    )]
}

async function cloneOrderItemCustomizations(tx: any, sourceItem: any, orderItemId: string) {
    if ((sourceItem.customizations?.length ?? 0) === 0) return

    await tx.orderItemCustomization.createMany({
        data: sourceItem.customizations.map((customization: any) => ({
            orderItemId,
            ingredientId: customization.ingredientId,
            ingredientName: customization.ingredientName,
            action: customization.action,
            quantity: customization.quantity,
            extraPrice: customization.extraPrice,
        })),
    })
}

function cloneOrderItemConfiguration(sourceItem: any) {
    return {
        configurationLabel: sourceItem.configurationLabel ?? null,
        configurationSnapshot: sourceItem.configurationSnapshot ?? null,
    }
}

async function applyOrderItemDecrement(
    tx: any,
    t: any,
    order: any,
    targetItem: any,
    quantityToRemove: number,
    isActiveOrder: boolean,
) {
    if (quantityToRemove <= 0) {
        throw new AppError("Decrement quantity must be positive", 400)
    }

    if (!["PENDING", "PREPARING", "READY"].includes(targetItem.status)) {
        throw new AppError("No decrementable item found", 400)
    }

    if (quantityToRemove > targetItem.quantity) {
        throw new AppError("Cannot decrement more quantity than available for this item", 400)
    }

    const productionStationIds = getOrderItemProductionStationIds([targetItem])

    if (targetItem.status === "PENDING") {
        if (targetItem.quantity > quantityToRemove) {
            await tx.orderItem.update({
                where: { id: targetItem.id },
                data: { quantity: targetItem.quantity - quantityToRemove, revision: { increment: 1 } }
            })
        } else {
            await tx.orderItem.delete({ where: { id: targetItem.id } })
        }

        return {
            affectedProduction: false,
            productionStationIds,
            removedQuantity: quantityToRemove,
            resolvedProductId: targetItem.productId,
        }
    }

    if (!isActiveOrder) {
        throw new AppError("No decrementable item found", 400)
    }

    if (targetItem.quantity > quantityToRemove) {
        await tx.orderItem.update({
            where: { id: targetItem.id },
            data: { quantity: targetItem.quantity - quantityToRemove, revision: { increment: 1 } }
        })

        const cancelledLine = await tx.orderItem.create({
            data: {
                orderId: order.id,
                productId: targetItem.productId,
                name: targetItem.name,
                unitPrice: targetItem.unitPrice,
                quantity: quantityToRemove,
                status: "CANCELLED",
                revision: 1,
                recipeId: targetItem.recipeId ?? null,
                recipeName: targetItem.recipeName ?? null,
                ...cloneOrderItemConfiguration(targetItem),
                takenById: targetItem.takenById ?? null,
                pickedUpById: targetItem.pickedUpById ?? null,
                logisticsRouteId: targetItem.logisticsRouteId ?? null,
                currentStepPosition: targetItem.currentStepPosition ?? null,
                currentStepStationId: targetItem.currentStepStationId ?? null,
                transportedById: targetItem.transportedById ?? null,
                transportedByName: targetItem.transportedByName ?? null,
                ...cloneProductionState(targetItem),
            }
        })

        await cloneOrderItemCustomizations(tx, targetItem, cancelledLine.id)

        await createLifecycleEvent(
            tx,
            cancelledLine.id,
            targetItem.status as OrderItemStatus,
            OrderItemStatus.CANCELLED,
            null,
            null,
            targetItem.product?.stationId ?? null
        )

        await t.emit(
            "order_item.status_changed",
            withOrderItemRealtimeScope({
                orderItemId: cancelledLine.id,
                orderId: order.id,
                newStatus: "CANCELLED",
                previousStatus: targetItem.status,
                cancelledByModification: true,
                ...buildCancelledRealtimeItemSnapshot(cancelledLine, order)
            }, {
                order,
                productStationId: targetItem.product?.stationId ?? null,
                logisticsRouteId: cancelledLine.logisticsRouteId ?? null,
                currentStepPosition: cancelledLine.currentStepPosition ?? null,
                currentStepStationId: cancelledLine.currentStepStationId ?? null,
            }),
            cancelledLine.id,
            1
        )

        return {
            affectedProduction: true,
            productionStationIds,
            removedQuantity: quantityToRemove,
            resolvedProductId: targetItem.productId,
        }
    }

    await tx.orderItem.update({
        where: { id: targetItem.id },
        data: { status: "CANCELLED", revision: { increment: 1 } }
    })

    await createLifecycleEvent(
        tx,
        targetItem.id,
        targetItem.status as OrderItemStatus,
        OrderItemStatus.CANCELLED,
        null,
        null,
        targetItem.product?.stationId ?? null
    )

    await t.emit(
        "order_item.status_changed",
        withOrderItemRealtimeScope({
            orderItemId: targetItem.id,
            orderId: order.id,
            newStatus: "CANCELLED",
            previousStatus: targetItem.status,
            cancelledByModification: true,
            ...buildCancelledRealtimeItemSnapshot(targetItem, order)
        }, {
            order,
            productStationId: targetItem.product?.stationId ?? null,
            logisticsRouteId: targetItem.logisticsRouteId ?? null,
            currentStepPosition: targetItem.currentStepPosition ?? null,
            currentStepStationId: targetItem.currentStepStationId ?? null,
        }),
        targetItem.id,
        targetItem.revision + 1
    )

    return {
        affectedProduction: true,
        productionStationIds,
        removedQuantity: quantityToRemove,
        resolvedProductId: targetItem.productId,
    }
}

export const orderService = {
    async createDraft(shopId: string, userId: string, senderSocketId?: string, stationId?: string) {
        return trackedTransaction(shopId, "Order", async (t, tx: any) => {
            // Session de caisse : chercher par station d'abord, puis fallback par shop
            let session = null;
            if (stationId) {
                session = await tx.cashSession.findFirst({
                    where: { shopId, stationId, status: "OPEN" }
                });

                if (!session) {
                    throw new AppError("No open cash session for this station", 400);
                }
            }
            if (!session) {
                session = await tx.cashSession.findFirst({
                    where: { shopId, status: "OPEN" }
                });
            }

            let sessionOrderNumber: number | null = null;

            if (session) {
                // Incrément atomique du compteur de commande
                const updated = await tx.cashSession.update({
                    where: { id: session.id },
                    data: { nextOrderNumber: { increment: 1 } }
                });
                sessionOrderNumber = updated.nextOrderNumber - 1; // Valeur avant incrément
            }

            const order = await t.create("order", {
                shopId,
                cashSessionId: session?.id ?? null,
                sessionOrderNumber,
                createdById: userId,
                stationId: stationId ?? null,
                total: 0,
                status: OrderStatus.DRAFT
            }, undefined,
                "order.created",
                {
                    cashSessionId: session?.id ?? null,
                    sessionOrderNumber,
                    createdById: userId,
                    stationId: stationId ?? null,
                    total: 0,
                    status: OrderStatus.DRAFT,
                    withoutCashSession: !session
                }
            );

            return order;
        }, { actorUserId: userId, senderSocketId });
    },

    findAll(shopId: string, filters?: { cashSessionId?: string | null; stationId?: string | null }) {
        return prisma.order.findMany({
            where: {
                shopId,
                ...(filters?.cashSessionId ? { cashSessionId: filters.cashSessionId } : {}),
                ...(filters?.stationId ? { stationId: filters.stationId } : {}),
            },
            orderBy: { createdAt: "desc" },
            include: ORDER_DETAIL_INCLUDE
        }).then(orders => orders.map(serializeOrder));
    },

    async findById(shopId: string, id: string, filters?: { cashSessionId?: string | null; stationId?: string | null }) {
        const order = await prisma.order.findFirst({
            where: {
                id,
                shopId,
                ...(filters?.cashSessionId ? { cashSessionId: filters.cashSessionId } : {}),
                ...(filters?.stationId ? { stationId: filters.stationId } : {}),
            },
            include: ORDER_DETAIL_INCLUDE
        });

        if (!order) return null;

        return serializeOrder(order);
    },

    async addItems(
        shopId: string,
        orderId: string,
        items: IncomingOrderItemInput[],
        userId?: string,
        senderSocketId?: string
    ) {
        return trackedTransaction(shopId, "Order", async (t, tx: any) => {

            let order = await tx.order.findFirst({
                where: { id: orderId, shopId }
            });

            if (!order) {
                throw new AppError("Order not found", 404);
            }

            if (order.status === OrderStatus.PAID) {
                throw new AppError("Cannot modify a paid order", 400);
            }

            const productIds = items.map(i => i.productId);

            const products = await tx.product.findMany({
                where: {
                    id: { in: productIds },
                    shopId,
                    active: true
                },
                include: {
                    recipe: {
                        include: {
                            recipeIngredients: { include: { ingredient: true } }
                        }
                    }
                },
            });

            const productMap = new Map<string, any>(products.map((p: any) => [p.id, p]));

            // Pré-résoudre les routes logistiques pour chaque produit distinct
            const routeCache = new Map<string, ResolvedRoute | null>();
            const workflowCache = new Map<string, ReturnType<typeof buildDefaultProductionWorkflowDto>>()
            for (const pid of productIds) {
                if (!routeCache.has(pid)) {
                    routeCache.set(pid, await resolveRouteForProduct(pid, shopId));
                }
            }

            for (const item of items) {
                const rawProduct = productMap.get(item.productId);

                if (!rawProduct) {
                    throw new AppError(`Product ${item.productId} not found`, 404);
                }

                const product = rawProduct as {
                    id: string
                    name: string
                    price: number
                    stationId?: string | null
                    customizationMode?: string | null
                    configuratorId?: string | null
                    recipeId?: string | null
                    recipe?: { id: string; name: string; recipeIngredients?: Array<any> } | null
                };
                const hasCustomizations = item.customizations && item.customizations.length > 0;
                const configurationLabel = normalizeConfigurationLabel(item.configurationLabel)
                const configurationSnapshot = item.configurationSnapshot ?? null
                const configurationUnitPriceDelta = computeConfigurationUnitPriceDelta(configurationSnapshot)
                const effectiveCustomizationMode = resolveEffectiveProductCustomizationMode(product)
                const selectedRecipe = resolveSelectedRecipe(product, item.recipeId ?? null);

                if ((item.recipeId ?? null) && !selectedRecipe) {
                    throw new AppError(`Recipe ${item.recipeId} is not linked to product ${item.productId}`, 400);
                }

                if (configurationSnapshot && effectiveCustomizationMode !== "CONFIGURATOR") {
                    throw new AppError("This product does not accept configurator snapshots", 400)
                }

                if ((configurationLabel ?? null) && effectiveCustomizationMode !== "CONFIGURATOR") {
                    throw new AppError("This product does not accept configurator labels", 400)
                }

                // Calculer le surcoût des ingrédients ajoutés
                let extraTotal = 0;
                let resolvedCustomizations: ResolvedOrderItemCustomization[] = [];

                if (hasCustomizations) {
                    if (!selectedRecipe) {
                        throw new AppError("This product has no recipe to customize", 400);
                    }

                    const recipeIngredientMap = new Map(
                        selectedRecipe.recipeIngredients.map((ri) => [ri.ingredientId, ri])
                    );

                    for (const cust of item.customizations!) {
                        const recipeIngredient = recipeIngredientMap.get(cust.ingredientId);
                        if (!recipeIngredient) {
                            throw new AppError(`Ingredient ${cust.ingredientId} is not part of recipe ${selectedRecipe.name}`, 400);
                        }
                        if (cust.action === "ADDED" && recipeIngredient.role !== "SUPPLEMENT") {
                            throw new AppError(`Only supplements can be added (ingredient ${cust.ingredientId} has role ${recipeIngredient.role})`, 400);
                        }
                        if (cust.action === "REMOVED" && recipeIngredient.role === "BASE") {
                            throw new AppError(`Base ingredients cannot be removed (ingredient ${cust.ingredientId})`, 400);
                        }
                        const qty = cust.quantity ?? 1;
                        if (cust.action === "ADDED" && recipeIngredient.maxQuantity !== null && qty > recipeIngredient.maxQuantity) {
                            throw new AppError(`Supplement ${cust.ingredientId} max quantity is ${recipeIngredient.maxQuantity}`, 400);
                        }
                        const custExtraPrice = cust.action === "ADDED" ? recipeIngredient.extraPrice * qty : 0;
                        extraTotal += custExtraPrice;
                        resolvedCustomizations.push({
                            ingredientId: recipeIngredient.ingredient.id,
                            ingredientName: recipeIngredient.ingredient.name,
                            action: cust.action,
                            quantity: qty,
                            extraPrice: custExtraPrice,
                        });
                    }
                }

                const finalUnitPrice = product.price + extraTotal + configurationUnitPriceDelta;

                const mergeCandidates = await tx.orderItem.findMany({
                    where: {
                        orderId,
                        productId: product.id,
                        recipeId: selectedRecipe?.id ?? null,
                        unitPrice: finalUnitPrice,
                        status: "PENDING",
                        takenById: null,
                    },
                    select: {
                        id: true,
                        quantity: true,
                        logisticsRouteId: true,
                        configurationLabel: true,
                        configurationSnapshot: true,
                        customizations: {
                            select: {
                                ingredientId: true,
                                action: true,
                                quantity: true,
                                extraPrice: true,
                            }
                        }
                    }
                });

                const resolvedCustomizationSignature = buildResolvedCustomizationSignature(resolvedCustomizations)
                const configurationSignature = buildConfigurationSignature(configurationSnapshot, configurationLabel)
                const existingMergeTarget = mergeCandidates.find((candidate: any) => {
                    const storedCustomizationSignature = buildStoredCustomizationSignature(candidate.customizations ?? [])
                    const storedConfigurationSignature = buildConfigurationSignature(
                        candidate.configurationSnapshot ?? null,
                        candidate.configurationLabel ?? null,
                    )
                    return storedCustomizationSignature === resolvedCustomizationSignature
                        && storedConfigurationSignature === configurationSignature
                })

                if (existingMergeTarget) {
                    const mergeRoute = routeCache.get(product.id) ?? null;
                    const mergeFirstStep = mergeRoute ? findFirstProductionStep(mergeRoute) : null;
                    const mergeRouteData = mergeRoute && mergeFirstStep && !existingMergeTarget.logisticsRouteId
                        ? stepUpdateData(mergeRoute, mergeFirstStep)
                        : {};

                    await tx.orderItem.update({
                        where: { id: existingMergeTarget.id },
                        data: {
                            quantity: existingMergeTarget.quantity + item.quantity,
                            revision: { increment: 1 },
                            ...mergeRouteData,
                        }
                    });
                    continue;
                }

                // 👉 NOUVELLE LIGNE SNAPSHOT (avec ou sans customizations)
                // Résoudre la route logistique → positionner l'item à la 1ère étape PRODUCTION
                const route = routeCache.get(product.id) ?? null;
                const firstProdStep = route ? findFirstProductionStep(route) : null;
                const routeData = route && firstProdStep
                    ? stepUpdateData(route, firstProdStep)
                    : {};
                const productionData = await resolveInitialProductionData(
                    tx,
                    firstProdStep?.stationId ?? product.stationId ?? null,
                    workflowCache,
                )

                const orderItem = await tx.orderItem.create({
                    data: {
                        orderId,
                        productId: product.id,
                        name: product.name,
                        unitPrice: finalUnitPrice,
                        quantity: item.quantity,
                        status: "PENDING",
                        revision: 1,
                        recipeId: selectedRecipe?.id ?? null,
                        recipeName: selectedRecipe?.name ?? null,
                        configurationLabel,
                        configurationSnapshot,
                        ...routeData,
                        ...productionData,
                    }
                });

                // Créer les customizations
                if (resolvedCustomizations.length > 0) {
                    await tx.orderItemCustomization.createMany({
                        data: resolvedCustomizations.map(c => ({
                            orderItemId: orderItem.id,
                            ingredientId: c.ingredientId,
                            ingredientName: c.ingredientName,
                            action: c.action,
                            quantity: c.quantity,
                            extraPrice: c.extraPrice,
                        }))
                    });
                }
            }

            const { total, depositTotal } = await computeOrderPricing(tx, orderId)

            const hasProductionItems = await productsAffectProduction(
                shopId,
                items.map(i => i.productId),
                tx
            )
            const productionStationIds = [...new Set(
                items
                    .map((entry) => {
                        const route = routeCache.get(entry.productId) ?? null
                        return (route ? findFirstProductionStep(route)?.stationId : null) ?? productMap.get(entry.productId)?.stationId ?? null
                    })
                    .filter((value): value is string => typeof value === "string")
            )]
            const isActiveOrder = ["PREPARING", "READY", "PENDING_PAYMENT", "PARTIALLY_PAID"].includes(order.status);

            // 🎯 Update + revision via tracked
            order = await t.update("order", { id: orderId }, { total, depositTotal },
                { include: ORDER_DETAIL_INCLUDE },
                "order.updated",
                withOrderRealtimeScope({
                    orderId,
                    itemsAdded: items.length,
                    newTotal: total,
                    depositTotal,
                    affectsProduction: isActiveOrder && hasProductionItems,
                    productionStationIds
                }, order)
            );

            return serializeOrder(order);
        }, { actorUserId: userId, senderSocketId });
    },

    /**
     * Décrémente de 1 la quantité d'un produit dans la commande.
     * Cible en priorité les items PENDING (non en production).
     * Si la dernière unité PENDING est retirée, elle est supprimée.
     * Si seuls des items en production existent, annule le plus récent.
     */
    async decrementItem(
        shopId: string,
        orderId: string,
        target: { productId?: string; orderItemId?: string },
        senderSocketId?: string
    ) {
        return trackedTransaction(shopId, "Order", async (t, tx: any) => {

            const order = await tx.order.findFirst({
                where: { id: orderId, shopId },
                include: {
                    station: {
                        select: { name: true }
                    }
                }
            });

            if (!order) throw new AppError("Order not found", 404);
            if (order.status === "PAID") throw new AppError("Cannot modify a paid order", 400);

            const isActiveOrder = ["PREPARING", "READY", "PENDING_PAYMENT", "PARTIALLY_PAID"].includes(order.status);
            let resolvedProductId = target.productId ?? null

            let activeItems: any[] = []

            if (target.orderItemId) {
                const selectedItem = await tx.orderItem.findFirst({
                    where: {
                        id: target.orderItemId,
                        orderId,
                        order: { shopId },
                        status: { notIn: ["CANCELLED", "DELIVERED"] }
                    },
                    include: {
                        product: true,
                        customizations: true,
                        takenBy: {
                            select: { displayName: true, username: true, email: true }
                        },
                        pickedUpBy: {
                            select: { displayName: true, username: true, email: true }
                        },
                    },
                });

                if (!selectedItem) {
                    throw new AppError("No active item found for this order item", 404);
                }

                resolvedProductId = selectedItem.productId
                activeItems = [selectedItem]
            } else if (target.productId) {
                activeItems = await tx.orderItem.findMany({
                    where: {
                        orderId,
                        productId: target.productId,
                        status: { notIn: ["CANCELLED", "DELIVERED"] }
                    },
                    include: {
                        product: true,
                        customizations: true,
                        takenBy: {
                            select: { displayName: true, username: true, email: true }
                        },
                        pickedUpBy: {
                            select: { displayName: true, username: true, email: true }
                        },
                    },
                    orderBy: { createdAt: "desc" }
                });
                resolvedProductId = target.productId
            }

            if (activeItems.length === 0) {
                throw new AppError("No active item found for this product", 404);
            }

            // Priorité : décrémenter les PENDING d'abord
            const pendingItems = activeItems.filter((i: any) => i.status === "PENDING");
            const inProductionItems = activeItems.filter((i: any) => ["PREPARING", "READY"].includes(i.status));

            if (pendingItems.length > 0) {
                const target = pendingItems[0]; // le plus récent PENDING
                if (target.quantity > 1) {
                    await tx.orderItem.update({
                        where: { id: target.id },
                        data: { quantity: target.quantity - 1, revision: { increment: 1 } }
                    });
                } else {
                    // Dernière unité — supprimer
                    await tx.orderItem.delete({ where: { id: target.id } });
                }
            } else if (inProductionItems.length > 0 && isActiveOrder) {
                // Pas de PENDING, annuler un item en production (le plus récent)
                const target = inProductionItems[0];
                if (target.quantity > 1) {
                    // Scinder : réduire de 1 et créer une ligne CANCELLED pour 1
                    await tx.orderItem.update({
                        where: { id: target.id },
                        data: { quantity: target.quantity - 1, revision: { increment: 1 } }
                    });
                    const cancelledLine = await tx.orderItem.create({
                        data: {
                            orderId,
                            productId: target.productId,
                            name: target.name,
                            unitPrice: target.unitPrice,
                            quantity: 1,
                            status: "CANCELLED",
                            revision: 1,
                            recipeId: target.recipeId ?? null,
                            recipeName: target.recipeName ?? null,
                            ...cloneOrderItemConfiguration(target),
                            takenById: target.takenById ?? null,
                            pickedUpById: target.pickedUpById ?? null,
                            logisticsRouteId: target.logisticsRouteId ?? null,
                            currentStepPosition: target.currentStepPosition ?? null,
                            currentStepStationId: target.currentStepStationId ?? null,
                            transportedById: target.transportedById ?? null,
                            transportedByName: target.transportedByName ?? null,
                            ...cloneProductionState(target),
                        }
                    });

                    if ((target.customizations?.length ?? 0) > 0) {
                        await tx.orderItemCustomization.createMany({
                            data: target.customizations.map((customization: any) => ({
                                orderItemId: cancelledLine.id,
                                ingredientId: customization.ingredientId,
                                ingredientName: customization.ingredientName,
                                action: customization.action,
                                quantity: customization.quantity,
                                extraPrice: customization.extraPrice,
                            })),
                        });
                    }

                    await createLifecycleEvent(
                        tx, cancelledLine.id,
                        target.status as OrderItemStatus, OrderItemStatus.CANCELLED,
                        null, null, target.product?.stationId ?? null
                    )
                    await t.emit(
                        "order_item.status_changed",
                        withOrderItemRealtimeScope({
                            orderItemId: cancelledLine.id,
                            orderId,
                            newStatus: "CANCELLED",
                            previousStatus: target.status,
                            cancelledByModification: true,
                            ...buildCancelledRealtimeItemSnapshot(cancelledLine, order)
                        }, {
                            order,
                            productStationId: target.product?.stationId ?? null,
                            logisticsRouteId: cancelledLine.logisticsRouteId ?? null,
                            currentStepPosition: cancelledLine.currentStepPosition ?? null,
                            currentStepStationId: cancelledLine.currentStepStationId ?? null,
                        }),
                        cancelledLine.id,
                        1
                    );
                } else {
                    // Quantité 1 : annuler la ligne entière
                    await tx.orderItem.update({
                        where: { id: target.id },
                        data: { status: "CANCELLED", revision: { increment: 1 } }
                    });
                    await createLifecycleEvent(
                        tx, target.id,
                        target.status as OrderItemStatus, OrderItemStatus.CANCELLED,
                        null, null, target.product?.stationId ?? null
                    )
                    await t.emit(
                        "order_item.status_changed",
                        withOrderItemRealtimeScope({
                            orderItemId: target.id,
                            orderId,
                            newStatus: "CANCELLED",
                            previousStatus: target.status,
                            cancelledByModification: true,
                            ...buildCancelledRealtimeItemSnapshot(target, order)
                        }, {
                            order,
                            productStationId: target.product?.stationId ?? null,
                            logisticsRouteId: target.logisticsRouteId ?? null,
                            currentStepPosition: target.currentStepPosition ?? null,
                            currentStepStationId: target.currentStepStationId ?? null,
                        }),
                        target.id,
                        target.revision + 1
                    );
                }
            } else {
                throw new AppError("No decrementable item found", 400);
            }

            // Revalider les promos (retire celles devenues invalides, recalcule discountTotal + total)
            await promoCodeService.revalidateOrderPromos(tx, orderId, shopId);
            const { total, depositTotal } = await computeOrderPricing(tx, orderId)

            const hasProductionItem = resolvedProductId
                ? await productsAffectProduction(shopId, [resolvedProductId], tx)
                : false
            const productionStationIds = [...new Set(
                activeItems
                    .map((item: any) => item.currentStepStationId ?? item.product?.stationId ?? null)
                    .filter((value: unknown): value is string => typeof value === "string")
            )]

            const updated = await t.update("order", { id: orderId }, { total, depositTotal },
                { include: ORDER_DETAIL_INCLUDE },
                "order.updated",
                withOrderRealtimeScope({
                    orderId,
                    reason: "item_decremented",
                    productId: resolvedProductId,
                    newTotal: total,
                    depositTotal,
                    affectsProduction: isActiveOrder && hasProductionItem,
                    productionStationIds
                }, order)
            );

            return serializeOrder(updated);
        }, { senderSocketId });
    },

    async decrementItemsBatch(
        shopId: string,
        orderId: string,
        targets: BatchDecrementItemInput[],
        senderSocketId?: string,
    ) {
        return trackedTransaction(shopId, "Order", async (t, tx: any) => {
            const order = await tx.order.findFirst({
                where: { id: orderId, shopId },
                include: {
                    station: {
                        select: { name: true }
                    }
                }
            })

            if (!order) throw new AppError("Order not found", 404)
            if (order.status === "PAID") throw new AppError("Cannot modify a paid order", 400)

            const quantitiesByOrderItemId = new Map<string, number>()
            for (const target of targets) {
                quantitiesByOrderItemId.set(
                    target.orderItemId,
                    (quantitiesByOrderItemId.get(target.orderItemId) ?? 0) + target.quantity,
                )
            }

            const normalizedTargets = [...quantitiesByOrderItemId.entries()]
                .map(([orderItemId, quantity]) => ({ orderItemId, quantity }))
                .filter((target) => target.quantity > 0)

            if (normalizedTargets.length === 0) {
                const unchanged = await tx.order.findUnique({
                    where: { id: orderId },
                    include: ORDER_DETAIL_INCLUDE,
                })

                return serializeOrder(unchanged)
            }

            const targetItems = await tx.orderItem.findMany({
                where: {
                    id: { in: normalizedTargets.map((target) => target.orderItemId) },
                    orderId,
                    order: { shopId },
                    status: { notIn: ["CANCELLED", "DELIVERED"] }
                },
                include: {
                    product: true,
                    customizations: true,
                    takenBy: {
                        select: { displayName: true, username: true, email: true }
                    },
                    pickedUpBy: {
                        select: { displayName: true, username: true, email: true }
                    },
                },
            })

            if (targetItems.length !== normalizedTargets.length) {
                throw new AppError("One or more order items are missing or already inactive", 404)
            }

            const itemById = new Map(targetItems.map((item: any) => [item.id, item]))
            const isActiveOrder = ["PREPARING", "READY", "PENDING_PAYMENT", "PARTIALLY_PAID"].includes(order.status)
            let hadProductionCancellation = false
            let totalRemovedQuantity = 0
            const removedProductIds = new Set<string>()
            const productionStationIds = new Set<string>()

            for (const target of normalizedTargets) {
                const item = itemById.get(target.orderItemId)
                if (!item) {
                    throw new AppError("No active item found for this order item", 404)
                }

                const result = await applyOrderItemDecrement(tx, t, order, item, target.quantity, isActiveOrder)
                totalRemovedQuantity += result.removedQuantity
                if (result.resolvedProductId) {
                    removedProductIds.add(result.resolvedProductId)
                }
                if (result.affectedProduction) {
                    hadProductionCancellation = true
                    for (const stationId of result.productionStationIds) {
                        productionStationIds.add(stationId)
                    }
                }
            }

            await promoCodeService.revalidateOrderPromos(tx, orderId, shopId)
            const { total, depositTotal } = await computeOrderPricing(tx, orderId)

            const updated = await t.update("order", { id: orderId }, { total, depositTotal },
                { include: ORDER_DETAIL_INCLUDE },
                "order.updated",
                withOrderRealtimeScope({
                    orderId,
                    reason: "items_decremented_batch",
                    decrementedItemCount: normalizedTargets.length,
                    totalRemovedQuantity,
                    productIds: [...removedProductIds],
                    newTotal: total,
                    depositTotal,
                    affectsProduction: isActiveOrder && hadProductionCancellation,
                    productionStationIds: [...productionStationIds],
                }, order)
            )

            return serializeOrder(updated)
        }, { senderSocketId })
    },

    async replaceItems(shopId: string, orderId: string, items: { productId: string, quantity: number }[], userId?: string, senderSocketId?: string) {
        return trackedTransaction(shopId, "Order", async (t, tx: any) => {

            const order = await tx.order.findFirst({
                where: { id: orderId, shopId },
                include: {
                    station: {
                        select: { name: true }
                    }
                }
            })

            if (!order) throw new AppError("Order not found", 404)

            if (order.status === "PAID") {
                throw new AppError("Order locked", 400)
            }

            const isActiveOrder = ["PREPARING", "READY", "PENDING_PAYMENT", "PARTIALLY_PAID"].includes(order.status)

            // ── 1. Récupérer les items existants ──
            const previousItems = await tx.orderItem.findMany({
                where: { orderId },
                include: {
                    product: true,
                    customizations: true,
                    takenBy: {
                        select: { displayName: true, username: true, email: true }
                    },
                    pickedUpBy: {
                        select: { displayName: true, username: true, email: true }
                    }
                }
            })

            // Index des nouveaux items par productId → quantity
            const newItemsMap = new Map<string, number>()
            for (const item of items) {
                newItemsMap.set(item.productId, (newItemsMap.get(item.productId) ?? 0) + item.quantity)
            }

            // Pré-résoudre les routes logistiques pour chaque produit de la commande
            const routeCache = new Map<string, ResolvedRoute | null>();
            const workflowCache = new Map<string, ReturnType<typeof buildDefaultProductionWorkflowDto>>()
            for (const pid of newItemsMap.keys()) {
                if (!routeCache.has(pid)) {
                    routeCache.set(pid, await resolveRouteForProduct(pid, shopId));
                }
            }

            // Index des anciens items ACTIFS par productId
            // Les items CANCELLED/DELIVERED sont figés et ne participent pas au diff
            const oldItemsByProduct = new Map<string, any[]>()
            for (const old of previousItems) {
                if (["CANCELLED", "DELIVERED"].includes(old.status)) continue
                const list = oldItemsByProduct.get(old.productId) ?? []
                list.push(old)
                oldItemsByProduct.set(old.productId, list)
            }

            const handledProductIds = new Set<string>()
            let hadProductionCancellation = false
            const productionStationIds = [...new Set([
                ...items
                    .map((entry) => {
                        const route = routeCache.get(entry.productId) ?? null
                        return (route ? findFirstProductionStep(route)?.stationId : null) ?? productMapFromPreviousItems(previousItems, entry.productId) ?? null
                    }),
                ...previousItems
                    .map((entry: any) => entry.currentStepStationId ?? entry.product?.stationId ?? null),
            ].filter((value): value is string => typeof value === "string"))]

            // ── 2. Traiter les anciens items ──
            for (const [productId, oldLines] of oldItemsByProduct.entries()) {
                const newQty = newItemsMap.get(productId)

                if (newQty === undefined || newQty <= 0) {
                    // Produit retiré de la commande
                    for (const old of oldLines) {
                        const wasActive = !["CANCELLED", "DELIVERED"].includes(old.status)
                        const wasInProduction = ["PREPARING", "READY"].includes(old.status)

                        if (wasInProduction && isActiveOrder) {
                            // Passer en CANCELLED + émettre event pour le KDS
                            await tx.orderItem.update({
                                where: { id: old.id },
                                data: { status: "CANCELLED", revision: { increment: 1 } }
                            })
                            await t.emit(
                                "order_item.status_changed",
                                withOrderItemRealtimeScope({
                                    orderItemId: old.id,
                                    orderId,
                                    newStatus: "CANCELLED",
                                    previousStatus: old.status,
                                    cancelledByModification: true,
                                    ...buildCancelledRealtimeItemSnapshot(old, order)
                                }, {
                                    order,
                                    productStationId: old.product?.stationId ?? null,
                                    logisticsRouteId: old.logisticsRouteId ?? null,
                                    currentStepPosition: old.currentStepPosition ?? null,
                                    currentStepStationId: old.currentStepStationId ?? null,
                                }),
                                old.id,
                                old.revision + 1
                            )
                            hadProductionCancellation = true
                        } else if (wasActive) {
                            // Item PENDING → supprimer directement
                            await tx.orderItem.delete({ where: { id: old.id } })
                        }
                        // CANCELLED/DELIVERED → on ne touche pas
                    }
                } else {
                    // Produit toujours dans la commande — gérer la quantité intelligemment
                    // Les items en production (PREPARING/READY) sont intouchables
                    const inProductionLines = oldLines.filter((l: any) => ["PREPARING", "READY"].includes(l.status))
                    const pendingLines = oldLines.filter((l: any) => l.status === "PENDING")
                    const inProductionQty = inProductionLines.reduce((sum: number, l: any) => sum + l.quantity, 0)

                    const neededPendingQty = newQty - inProductionQty

                    if (neededPendingQty <= 0) {
                        // Assez (ou trop) d'items déjà en production — supprimer tous les PENDING
                        for (const p of pendingLines) {
                            await tx.orderItem.delete({ where: { id: p.id } })
                        }
                        // Si on en a trop en production, annuler l'excédent (les plus récents)
                        let excess = inProductionQty - newQty
                        for (let i = inProductionLines.length - 1; i >= 0 && excess > 0; i--) {
                            const line = inProductionLines[i]
                            if (line.quantity <= excess) {
                                // Annuler la ligne entière
                                await tx.orderItem.update({
                                    where: { id: line.id },
                                    data: { status: "CANCELLED", revision: { increment: 1 } }
                                })
                                await t.emit(
                                    "order_item.status_changed",
                                    withOrderItemRealtimeScope({
                                        orderItemId: line.id,
                                        orderId,
                                        newStatus: "CANCELLED",
                                        previousStatus: line.status,
                                        cancelledByModification: true,
                                        ...buildCancelledRealtimeItemSnapshot(line, order)
                                    }, {
                                        order,
                                        productStationId: line.product?.stationId ?? null,
                                        logisticsRouteId: line.logisticsRouteId ?? null,
                                        currentStepPosition: line.currentStepPosition ?? null,
                                        currentStepStationId: line.currentStepStationId ?? null,
                                    }),
                                    line.id,
                                    line.revision + 1
                                )
                                hadProductionCancellation = true
                                excess -= line.quantity
                            } else {
                                // Réduction partielle : scinder la ligne
                                // 1) Réduire la ligne existante à la quantité conservée
                                const keptQty = line.quantity - excess

                                await tx.orderItem.update({
                                    where: { id: line.id },
                                    data: { quantity: keptQty, revision: { increment: 1 } }
                                })

                                // 2) Créer une nouvelle ligne CANCELLED pour l'excédent
                                const cancelledLine = await tx.orderItem.create({
                                    data: {
                                        orderId,
                                        productId: line.productId,
                                        name: line.name,
                                        unitPrice: line.unitPrice,
                                        quantity: excess,
                                        status: "CANCELLED",
                                        revision: 1,
                                        recipeId: line.recipeId ?? null,
                                        recipeName: line.recipeName ?? null,
                                        ...cloneOrderItemConfiguration(line),
                                        takenById: line.takenById ?? null,
                                        pickedUpById: line.pickedUpById ?? null,
                                        logisticsRouteId: line.logisticsRouteId ?? null,
                                        currentStepPosition: line.currentStepPosition ?? null,
                                        currentStepStationId: line.currentStepStationId ?? null,
                                        transportedById: line.transportedById ?? null,
                                        transportedByName: line.transportedByName ?? null,
                                        ...cloneProductionState(line),
                                    }
                                })

                                // 3) Émettre les events
                                // — La ligne existante a changé de quantité
                                await t.emit(
                                    "order_item.quantity_changed",
                                    {
                                        orderItemId: line.id,
                                        orderId,
                                        previousQuantity: line.quantity,
                                        newQuantity: keptQty,
                                        productStationId: line.product?.stationId ?? null,
                                        reducedByModification: true
                                    },
                                    line.id,
                                    line.revision + 1
                                )

                                // — La nouvelle ligne annulée pour la cuisine
                                await t.emit(
                                    "order_item.status_changed",
                                    withOrderItemRealtimeScope({
                                        orderItemId: cancelledLine.id,
                                        orderId,
                                        newStatus: "CANCELLED",
                                        previousStatus: line.status,
                                        cancelledByModification: true,
                                        ...buildCancelledRealtimeItemSnapshot(cancelledLine, order)
                                    }, {
                                        order,
                                        productStationId: line.product?.stationId ?? null,
                                        logisticsRouteId: cancelledLine.logisticsRouteId ?? null,
                                        currentStepPosition: cancelledLine.currentStepPosition ?? null,
                                        currentStepStationId: cancelledLine.currentStepStationId ?? null,
                                    }),
                                    cancelledLine.id,
                                    1
                                )

                                hadProductionCancellation = true
                                excess = 0
                            }
                        }
                    } else if (pendingLines.length > 0) {
                        // Ajuster le premier PENDING à la quantité nécessaire, supprimer les extras
                        const [keptPending, ...extraPending] = pendingLines

                        // Aussi injecter la route logistique si pas encore définie
                        const pendingRoute = routeCache.get(productId) ?? null;
                        const pendingFirstStep = pendingRoute ? findFirstProductionStep(pendingRoute) : null;
                        const pendingRouteData = pendingRoute && pendingFirstStep && !keptPending.logisticsRouteId
                            ? stepUpdateData(pendingRoute, pendingFirstStep)
                            : {};

                        if (keptPending.quantity !== neededPendingQty || Object.keys(pendingRouteData).length > 0) {
                            await tx.orderItem.update({
                                where: { id: keptPending.id },
                                data: {
                                    quantity: neededPendingQty,
                                    revision: { increment: 1 },
                                    ...pendingRouteData,
                                }
                            })
                        }
                        for (const extra of extraPending) {
                            await tx.orderItem.delete({ where: { id: extra.id } })
                        }
                    } else {
                        // Pas de ligne PENDING existante → créer une nouvelle ligne PENDING
                        const product = await tx.product.findFirst({
                            where: { id: productId, shopId }
                        })
                        if (product) {
                            const route = routeCache.get(productId) ?? null;
                            const firstStep = route ? findFirstProductionStep(route) : null;
                            const routeData = route && firstStep ? stepUpdateData(route, firstStep) : {};
                            const productionData = await resolveInitialProductionData(
                                tx,
                                firstStep?.stationId ?? product.stationId ?? null,
                                workflowCache,
                            )

                            await tx.orderItem.create({
                                data: {
                                    orderId,
                                    productId: product.id,
                                    name: product.name,
                                    unitPrice: product.price,
                                    quantity: neededPendingQty,
                                    status: "PENDING",
                                    revision: 1,
                                    ...routeData,
                                    ...productionData,
                                }
                            })
                        }
                    }

                    handledProductIds.add(productId)
                }
            }

            // ── 3. Créer les nouveaux items (produits pas encore dans la commande) ──
            for (const item of items) {
                if (handledProductIds.has(item.productId)) continue
                if (oldItemsByProduct.has(item.productId)) continue // déjà traité

                const product = await tx.product.findFirst({
                    where: { id: item.productId, shopId }
                })

                if (!product) {
                    throw new AppError("Product not found", 404)
                }

                const route = routeCache.get(item.productId) ?? null;
                const firstStep = route ? findFirstProductionStep(route) : null;
                const routeData = route && firstStep ? stepUpdateData(route, firstStep) : {};
                const productionData = await resolveInitialProductionData(
                    tx,
                    firstStep?.stationId ?? product.stationId ?? null,
                    workflowCache,
                )

                await tx.orderItem.create({
                    data: {
                        orderId,
                        productId: product.id,
                        name: product.name,
                        unitPrice: product.price,
                        quantity: item.quantity,
                        status: "PENDING",
                        revision: 1,
                        ...routeData,
                        ...productionData,
                    }
                })
            }

            // Revalider les promos (retire celles devenues invalides)
            await promoCodeService.revalidateOrderPromos(tx, orderId, shopId)
            const { total, depositTotal } = await computeOrderPricing(tx, orderId)

            const hasProductionItems = await productsAffectProduction(
                shopId,
                items.map(i => i.productId),
                tx
            )

            const updatedOrder = await t.update("order", { id: orderId }, { total, depositTotal },
                { include: ORDER_DETAIL_INCLUDE },
                "order.items_replaced",
                withOrderRealtimeScope({
                    orderId,
                    itemCount: items.length,
                    newTotal: total,
                    depositTotal,
                    affectsProduction: isActiveOrder && (hadProductionCancellation || hasProductionItems),
                    productionStationIds
                }, order)
            )

            return serializeOrder(updatedOrder)
        }, { actorUserId: userId, senderSocketId })
    },

    async updateStatus(
        shopId: string,
        orderId: string,
        status: OrderStatus,
        userId?: string,
        senderSocketId?: string
    ) {
        return trackedTransaction(shopId, "Order", async (t, tx: any) => {

            const order = await tx.order.findFirst({
                where: { id: orderId, shopId },
                include: {
                    station: {
                        select: { name: true }
                    }
                }
            });

            if (!order) {
                throw new AppError("Order not found", 404);
            }

            if (
                order.status === OrderStatus.PREPARING &&
                status === OrderStatus.DRAFT
            ) {
                throw new AppError("Cannot go back to draft", 400);
            }

            const isCancellingPaidOrder = order.status === OrderStatus.PAID && status === OrderStatus.CANCELLED;

            if (order.status === OrderStatus.PAID && !isCancellingPaidOrder) {
                throw new AppError("Order already paid", 400);
            }

            if (isCancellingPaidOrder) {
                const nonCancelledItems = await tx.orderItem.findMany({
                    where: {
                        orderId,
                        status: { not: OrderItemStatus.CANCELLED }
                    },
                    select: { status: true }
                });

                const hasCancelableItems = nonCancelledItems.some((item: { status: OrderItemStatus }) => item.status !== OrderItemStatus.DELIVERED);

                if (!hasCancelableItems) {
                    throw new AppError("Cannot cancel a fully delivered paid order", 400);
                }
            }

            // Vérifier que tous les items production sont prêts avant de passer la commande à READY
            if (status === OrderStatus.READY && order.status === OrderStatus.PREPARING) {
                const unfinishedProductionItems = await tx.orderItem.findMany({
                    where: {
                        orderId,
                        status: { notIn: ["READY", "PICKED_UP", "DELIVERED", "CANCELLED"] },
                        product: { stationId: { not: null } }
                    }
                });

                if (unfinishedProductionItems.length > 0) {
                    throw new AppError(
                        `${unfinishedProductionItems.length} produit(s) de production ne sont pas encore prêts`,
                        400
                    );
                }
            }

            const productionStationIds = [...new Set(
                (await tx.orderItem.findMany({
                    where: {
                        orderId,
                        status: { notIn: ["CANCELLED", "DELIVERED"] }
                    },
                    select: {
                        currentStepStationId: true,
                        product: {
                            select: {
                                stationId: true,
                            }
                        }
                    }
                }))
                    .map((item: any) => item.currentStepStationId ?? item.product?.stationId ?? null)
                    .filter((value: unknown): value is string => typeof value === "string")
            )]

            const updatedOrder = await t.update("order", { id: orderId }, { status },
                { include: ORDER_DETAIL_INCLUDE },
                "order.status_changed",
                withOrderRealtimeScope({
                    orderId,
                    previousStatus: order.status,
                    newStatus: status,
                    productionStationIds
                }, order)
            );

            // ── Cascade CANCELLED vers les OrderItems actifs ──
            if (status === OrderStatus.CANCELLED) {
                const activeItems = await tx.orderItem.findMany({
                    where: {
                        orderId,
                        status: { notIn: ["DELIVERED", "CANCELLED"] }
                    },
                    include: {
                        product: true,
                        customizations: true,
                        takenBy: {
                            select: { displayName: true, username: true, email: true }
                        },
                        pickedUpBy: {
                            select: { displayName: true, username: true, email: true }
                        }
                    }
                });

                if (activeItems.length > 0) {
                    // Bulk update en DB
                    await tx.orderItem.updateMany({
                        where: {
                            orderId,
                            status: { notIn: ["DELIVERED", "CANCELLED"] }
                        },
                        data: { status: "CANCELLED" }
                    });

                    // Émettre un event par item pour que le KDS se mette à jour
                    // + créer les lifecycle events
                    // Résoudre le nom de l'acteur
                    let cancelActorName: string | null = null
                    if (userId) {
                        const actor = await tx.shopUser.findUnique({ where: { id: userId }, select: { displayName: true, username: true, email: true } })
                        cancelActorName = actor?.displayName ?? actor?.username ?? actor?.email ?? null
                    }

                    for (const item of activeItems) {
                        await createLifecycleEvent(
                            tx, item.id,
                            item.status as OrderItemStatus, OrderItemStatus.CANCELLED,
                            userId ?? null, cancelActorName, item.product?.stationId ?? null
                        )
                        await t.emit(
                            "order_item.status_changed",
                            withOrderItemRealtimeScope({
                                orderItemId: item.id,
                                orderId,
                                newStatus: "CANCELLED",
                                previousStatus: item.status,
                                cancelledByOrder: true,
                                ...buildCancelledRealtimeItemSnapshot(item, order)
                            }, {
                                order,
                                productStationId: item.product?.stationId ?? null,
                                logisticsRouteId: item.logisticsRouteId ?? null,
                                currentStepPosition: item.currentStepPosition ?? null,
                                currentStepStationId: item.currentStepStationId ?? null,
                            }),
                            item.id,
                            item.revision + 1
                        );
                    }
                }
            }

            return updatedOrder;
        }, { actorUserId: userId, senderSocketId });
    },

    async restoreCancelledOrder(
        shopId: string,
        orderId: string,
        userId?: string,
        senderSocketId?: string
    ) {
        return trackedTransaction(shopId, "Order", async (t, tx: any) => {
            const order = await tx.order.findFirst({
                where: { id: orderId, shopId },
                include: {
                    payments: true,
                    orderItems: {
                        include: {
                            product: true,
                            lifecycleEvents: {
                                orderBy: { occurredAt: "asc" as const }
                            }
                        }
                    }
                }
            });

            if (!order) {
                throw new AppError("Order not found", 404);
            }

            if (order.status !== OrderStatus.CANCELLED) {
                throw new AppError("Only cancelled orders can be restored", 400);
            }

            const statusEvents = await tx.shopEvent.findMany({
                where: {
                    shopId,
                    entity: "Order",
                    entityId: orderId,
                    type: "order.status_changed"
                },
                orderBy: { createdAt: "desc" },
                take: 20
            });

            const cancellationEvent = statusEvents.find((event: any) => event.payload?.newStatus === OrderStatus.CANCELLED);
            const restoredStatus = cancellationEvent?.payload?.previousStatus as OrderStatus | undefined;

            if (!restoredStatus || !Object.values(OrderStatus).includes(restoredStatus) || restoredStatus === OrderStatus.CANCELLED) {
                throw new AppError("Unable to determine the previous status for this cancelled order", 400);
            }

            let restoreActorName: string | null = null;
            if (userId) {
                const actor = await tx.shopUser.findUnique({
                    where: { id: userId },
                    select: { displayName: true, username: true, email: true }
                });
                restoreActorName = actor?.displayName ?? actor?.username ?? actor?.email ?? null;
            }

            const cancelledAt = cancellationEvent.createdAt instanceof Date
                ? cancellationEvent.createdAt.getTime()
                : new Date(cancellationEvent.createdAt).getTime();

            await t.update(
                "order",
                { id: orderId },
                { status: restoredStatus },
                undefined,
                "order.status_changed",
                withOrderRealtimeScope({
                    orderId,
                    previousStatus: order.status,
                    newStatus: restoredStatus,
                    restoredFromCancellation: true,
                    productionStationIds: [...new Set(
                        order.orderItems
                            .map((item: any) => item.currentStepStationId ?? item.product?.stationId ?? null)
                            .filter((value: unknown): value is string => typeof value === "string")
                    )]
                }, order)
            );

            for (const item of order.orderItems) {
                if (item.status !== OrderItemStatus.CANCELLED) continue;

                const matchingCancelEvent = [...(item.lifecycleEvents ?? [])]
                    .reverse()
                    .find((event: any) => {
                        if (event.toStatus !== OrderItemStatus.CANCELLED) return false;
                        const occurredAt = event.occurredAt instanceof Date
                            ? event.occurredAt.getTime()
                            : new Date(event.occurredAt).getTime();
                        return occurredAt >= cancelledAt;
                    });

                if (!matchingCancelEvent) continue;

                const restoredItemStatus = matchingCancelEvent.fromStatus as OrderItemStatus;
                if (!Object.values(OrderItemStatus).includes(restoredItemStatus) || restoredItemStatus === OrderItemStatus.CANCELLED) {
                    continue;
                }

                await tx.orderItem.update({
                    where: { id: item.id },
                    data: { status: restoredItemStatus, revision: { increment: 1 } }
                });

                await createLifecycleEvent(
                    tx,
                    item.id,
                    OrderItemStatus.CANCELLED,
                    restoredItemStatus,
                    userId ?? null,
                    restoreActorName,
                    item.product?.stationId ?? null
                );

                await t.emit(
                    "order_item.status_changed",
                    withOrderItemRealtimeScope({
                        orderItemId: item.id,
                        orderId,
                        newStatus: restoredItemStatus,
                        previousStatus: OrderItemStatus.CANCELLED,
                        restoredFromCancellation: true
                    }, {
                        order,
                        productStationId: item.product?.stationId ?? null,
                        logisticsRouteId: item.logisticsRouteId ?? null,
                        currentStepPosition: item.currentStepPosition ?? null,
                        currentStepStationId: item.currentStepStationId ?? null,
                    }),
                    item.id,
                    item.revision + 1
                );
            }

            const result = await tx.order.findUnique({
                where: { id: orderId },
                include: ORDER_DETAIL_INCLUDE
            });

            return serializeOrder(result);
        }, { actorUserId: userId, senderSocketId });
    },

    async incrementProduct(
        shopId: string,
        orderId: string,
        productId: string,
        delta: number,
        userId?: string,
        senderSocketId?: string
    ) {
        return trackedTransaction(shopId, "Order", async (t, tx: any) => {

            const order = await tx.order.findFirst({
                where: { id: orderId, shopId }
            });

            if (!order) {
                throw new AppError("Order not found", 404);
            }

            if (order.status !== OrderStatus.DRAFT) {
                throw new AppError("Cannot modify this order", 400);
            }

            const product = await tx.product.findFirst({
                where: { id: productId, shopId, active: true }
            });

            if (!product) {
                throw new AppError("Product not found", 404);
            }

            // Résoudre la route logistique pour ce produit
            const route = await resolveRouteForProduct(productId, shopId);
            const firstStep = route ? findFirstProductionStep(route) : null;
            const routeData = route && firstStep ? stepUpdateData(route, firstStep) : {};
            const workflowCache = new Map<string, ReturnType<typeof buildDefaultProductionWorkflowDto>>()

            // 🔥 Cherche ligne mergeable
            const existing = await tx.orderItem.findFirst({
                where: {
                    orderId,
                    productId,
                    unitPrice: product.price,
                    status: "PENDING",
                    takenById: null
                },
                select: { id: true, quantity: true, logisticsRouteId: true }
            });

            if (existing) {
                const newQty = existing.quantity + delta;

                if (newQty <= 0) {
                    await tx.orderItem.delete({
                        where: { id: existing.id }
                    });
                } else {
                    const mergeRouteData = !existing.logisticsRouteId && Object.keys(routeData).length > 0
                        ? routeData : {};
                    await tx.orderItem.update({
                        where: { id: existing.id },
                        data: { quantity: newQty, revision: { increment: 1 }, ...mergeRouteData }
                    });
                }
            } else {
                if (delta > 0) {
                    const productionData = await resolveInitialProductionData(
                        tx,
                        firstStep?.stationId ?? product.stationId ?? null,
                        workflowCache,
                    )
                    await tx.orderItem.create({
                        data: {
                            orderId,
                            productId,
                            name: product.name,
                            unitPrice: product.price,
                            quantity: delta,
                            status: "PENDING",
                            revision: 1,
                            ...routeData,
                            ...productionData,
                        }
                    });
                }
            }

            const { total, depositTotal } = await computeOrderPricing(tx, orderId)

            const updatedOrder = await t.update("order", { id: orderId }, { total, depositTotal },
                { include: ORDER_DETAIL_INCLUDE },
                "order.product_incremented",
                { orderId, productId, delta, newTotal: total, depositTotal }
            );

            return updatedOrder;
        }, { actorUserId: userId, senderSocketId });
    },

    async recalculateTotal(shopId: string, orderId: string) {
        const { total, depositTotal } = await computeOrderPricing(prisma, orderId)

        await prisma.order.update({
            where: { id: orderId },
            data: { total, depositTotal }
        });

        return total;
    },

    async replaceDepositLines(
        shopId: string,
        orderId: string,
        lines: OrderDepositLineInput[],
        userId?: string,
        senderSocketId?: string,
    ) {
        return trackedTransaction(shopId, "Order", async (t, tx: any) => {
            const order = await tx.order.findFirst({
                where: { id: orderId, shopId },
                include: {
                    station: {
                        select: { name: true }
                    }
                }
            })

            if (!order) {
                throw new AppError("Order not found", 404)
            }

            if (order.status === OrderStatus.PAID) {
                throw new AppError("Cannot modify a paid order", 400)
            }

            if (order.status === OrderStatus.CANCELLED) {
                throw new AppError("Cannot modify a cancelled order", 400)
            }

            const normalizedMap = new Map<string, number>()
            for (const line of lines) {
                normalizedMap.set(line.depositDefinitionId, (normalizedMap.get(line.depositDefinitionId) ?? 0) + line.quantity)
            }

            const normalizedLines = [...normalizedMap.entries()]
                .map(([depositDefinitionId, quantity]) => ({
                    depositDefinitionId,
                    quantity,
                }))
                .filter((line) => line.quantity !== 0)

            const definitions = normalizedLines.length > 0
                ? await tx.depositDefinition.findMany({
                    where: {
                        shopId,
                        active: true,
                        id: { in: normalizedLines.map((line) => line.depositDefinitionId) },
                    },
                    select: {
                        id: true,
                        label: true,
                        amount: true,
                    }
                })
                : []

            if (definitions.length !== normalizedLines.length) {
                throw new AppError("One or more deposit definitions are invalid or inactive", 400)
            }

            const definitionMap = new Map(definitions.map((definition: { id: string; label: string; amount: number }) => [definition.id, definition]))

            await tx.orderDepositLine.deleteMany({ where: { orderId } })

            if (normalizedLines.length > 0) {
                await tx.orderDepositLine.createMany({
                    data: normalizedLines.map((line) => {
                        const definition = definitionMap.get(line.depositDefinitionId) as { id: string; label: string; amount: number } | undefined
                        if (!definition) {
                            throw new AppError("Deposit definition not found", 400)
                        }

                        return {
                            orderId,
                            depositDefinitionId: definition.id,
                            labelSnapshot: definition.label,
                            unitAmountSnapshot: definition.amount,
                            quantity: line.quantity,
                            totalAmount: definition.amount * line.quantity,
                        }
                    })
                })
            }

            const { total, depositTotal } = await computeOrderPricing(tx, orderId)

            const updatedOrder = await t.update("order", { id: orderId }, { total, depositTotal },
                { include: ORDER_DETAIL_INCLUDE },
                "order.updated",
                withOrderRealtimeScope({
                    orderId,
                    reason: "deposit_lines_replaced",
                    depositLineCount: normalizedLines.length,
                    depositTotal,
                    newTotal: total,
                }, order)
            )

            return serializeOrder(updatedOrder)
        }, { actorUserId: userId, senderSocketId })
    },

    async softDelete(shopId: string, id: string) {
        return prisma.$transaction(async (tx) => {
            // 1) Vérifie que la commande existe bien
            const order = await tx.order.findFirst({
                where: { id, shopId },
                select: { id: true }
            });

            if (!order) {
                throw new AppError("Order not found", 404);
            }

            // 2) Vérifie qu'il n'y a AUCUN paiement lié
            // (adapte le model: Payment / PaymentAttempt selon ton schéma)
            const paymentsCount = await tx.payment.count({
                where: { orderId: id }
            });

            const attemptsCount = await tx.paymentAttempt.count({
                where: { orderId: id }
            });

            if (paymentsCount > 0 || attemptsCount > 0) {
                throw new AppError("Cannot delete: payments linked to this order", 400);
            }

            // 3) Supprime (deleteMany OK, mais delete est mieux si id unique)
            await tx.order.delete({
                where: { id }
            });

            return { id };
        });
    },

    async getPendings(shopId: string) {
        return prisma.order.findMany({
            where: { shopId: shopId, status: OrderStatus.DRAFT },
            orderBy: { createdAt: "asc" },
            include: {
                orderItems: {
                    include: { product: true }
                },
                payments: true,
                paymentAttempt: true
            }
        });
    },

    /**
     * Finalise une commande : passe tous les items non-annulés en DELIVERED.
     * Utilisable uniquement sur des commandes PAID.
     */
    async finalizeOrder(shopId: string, orderId: string, userId?: string, senderSocketId?: string) {
        return trackedTransaction(shopId, "Order", async (t, tx: any) => {
            const order = await tx.order.findFirst({
                where: { id: orderId, shopId },
                include: {
                    orderItems: { include: { product: true } }
                }
            });

            if (!order) throw new AppError("Order not found", 404);
            if (order.status !== "PAID") throw new AppError("Order must be PAID to finalize", 400);

            const itemsToDeliver = order.orderItems.filter(
                (i: any) => !["CANCELLED", "DELIVERED"].includes(i.status)
            );

            // Passer chaque item en DELIVERED + créer lifecycle events
            let finalizeActorName: string | null = null
            if (userId) {
                const actor = await tx.shopUser.findUnique({ where: { id: userId }, select: { displayName: true, username: true, email: true } })
                finalizeActorName = actor?.displayName ?? actor?.username ?? actor?.email ?? null
            }

            for (const item of itemsToDeliver) {
                await tx.orderItem.update({
                    where: { id: item.id },
                    data: { status: "DELIVERED", revision: { increment: 1 } }
                });
                await createLifecycleEvent(
                    tx, item.id,
                    item.status as OrderItemStatus, OrderItemStatus.DELIVERED,
                    userId ?? null, finalizeActorName, item.product?.stationId ?? null
                )
            }

            // Émettre un événement par item
            for (const item of itemsToDeliver) {
                await t.emit(
                    "order_item.status_changed",
                    withOrderItemRealtimeScope({
                        orderItemId: item.id,
                        orderId,
                        newStatus: "DELIVERED",
                        previousStatus: item.status,
                        forcedByUser: true
                    }, {
                        order,
                        productStationId: item.product?.stationId ?? null,
                        logisticsRouteId: item.logisticsRouteId ?? null,
                        currentStepPosition: item.currentStepPosition ?? null,
                        currentStepStationId: item.currentStepStationId ?? null,
                    }),
                    item.id,
                    item.revision + 1
                );
            }

            // Re-fetch complet
            const result = await tx.order.findUnique({
                where: { id: orderId },
                include: ORDER_DETAIL_INCLUDE
            });
            return serializeOrder(result);
        }, { actorUserId: userId, senderSocketId });
    },

    /**
     * Annule la finalisation : remet les items DELIVERED liés à la production en READY.
     * Les items purement hors production restent DELIVERED.
     */
    async unfinalizeOrder(shopId: string, orderId: string, userId?: string, senderSocketId?: string) {
        return trackedTransaction(shopId, "Order", async (t, tx: any) => {
            const order = await tx.order.findFirst({
                where: { id: orderId, shopId },
                include: {
                    orderItems: { include: { product: { include: { station: true } } } }
                }
            });

            if (!order) throw new AppError("Order not found", 404);
            if (order.status !== "PAID") throw new AppError("Order must be PAID to unfinalize", 400);

            const itemsToRevert = order.orderItems.filter(
                (i: any) => i.status === "DELIVERED" && !!(resolveOrderItemProductionStationId(i) ?? i.logisticsRouteId)
            );

            if (itemsToRevert.length === 0) {
                throw new AppError("This finalized order has no production item to reopen", 400);
            }

            let unfinalizeActorName: string | null = null
            if (userId) {
                const actor = await tx.shopUser.findUnique({ where: { id: userId }, select: { displayName: true, username: true, email: true } })
                unfinalizeActorName = actor?.displayName ?? actor?.username ?? actor?.email ?? null
            }

            for (const item of itemsToRevert) {
                const productionStationId = resolveOrderItemProductionStationId(item)
                await tx.orderItem.update({
                    where: { id: item.id },
                    data: { status: "READY", revision: { increment: 1 } }
                });
                await createLifecycleEvent(
                    tx, item.id,
                    OrderItemStatus.DELIVERED, OrderItemStatus.READY,
                    userId ?? null, unfinalizeActorName, productionStationId
                )
            }

            for (const item of itemsToRevert) {
                const productionStationId = resolveOrderItemProductionStationId(item)
                await t.emit(
                    "order_item.status_changed",
                    withOrderItemRealtimeScope({
                        orderItemId: item.id,
                        orderId,
                        newStatus: "READY",
                        previousStatus: "DELIVERED",
                        unfinalizedByUser: true
                    }, {
                        order,
                        productStationId: productionStationId,
                        logisticsRouteId: item.logisticsRouteId ?? null,
                        currentStepPosition: item.currentStepPosition ?? null,
                        currentStepStationId: item.currentStepStationId ?? null,
                    }),
                    item.id,
                    item.revision + 1
                );
            }

            const result = await tx.order.findUnique({
                where: { id: orderId },
                include: ORDER_DETAIL_INCLUDE
            });
            return serializeOrder(result);
        }, { actorUserId: userId, senderSocketId });
    },

    /**
     * Valide une commande dont le net est nul ou négatif.
     * Crée un Payment VOUCHER de montant 0 et passe la commande en PAID.
     */
    async completeZeroAmountOrder(shopId: string, orderId: string, userId?: string, senderSocketId?: string) {
        return trackedTransaction(shopId, "Order", async (t, tx: any) => {
            const order = await tx.order.findFirst({
                where: { id: orderId, shopId },
                include: {
                    payments: true,
                    orderItems: {
                        select: {
                            currentStepStationId: true,
                            product: {
                                select: {
                                    stationId: true,
                                }
                            }
                        }
                    }
                }
            });

            if (!order) throw new AppError("Order not found", 404);
            if (order.status === "PAID") throw new AppError("Order is already paid", 400);
            if (order.status === "CANCELLED") throw new AppError("Cannot pay a cancelled order", 400);
            if (order.total > 0) throw new AppError("Order total is positive, use normal payment flow", 400);

            // Créer un PaymentAttempt VOUCHER MANUAL pour traçabilité
            const attempt = await tx.paymentAttempt.create({
                data: {
                    orderId,
                    shopId,
                    stationId: order.stationId ?? null,
                    amount: 0,
                    currency: "EUR",
                    method: "VOUCHER",
                    executionMethod: "MANUAL",
                    status: "PAID",
                    provider: null,
                    configSnapshot: {
                        source: "promo_code",
                        method: "VOUCHER",
                        executionMethod: "MANUAL",
                        zeroAmountOrder: true,
                        nonPositiveNetOrder: order.total < 0,
                    }
                }
            });

            // Créer le Payment
            await tx.payment.create({
                data: {
                    orderId,
                    shopId,
                    stationId: order.stationId ?? null,
                    paymentAttemptId: attempt.id,
                    amount: 0,
                    method: "VOUCHER",
                    executionMethod: "MANUAL",
                    configSnapshot: {
                        source: "promo_code",
                        zeroAmountOrder: true,
                        nonPositiveNetOrder: order.total < 0,
                    }
                }
            });

            // Passer en PAID
            await t.update(
                "order",
                { id: orderId },
                { status: "PAID", paidAt: new Date() },
                undefined,
                "order.status_changed",
                withOrderRealtimeScope({
                    orderId,
                    previousStatus: order.status,
                    newStatus: "PAID",
                    zeroAmountOrder: true,
                    nonPositiveNetOrder: order.total < 0,
                    productionStationIds: collectOrderProductionStationIds(order),
                }, order)
            );

            const result = await tx.order.findUnique({
                where: { id: orderId },
                include: ORDER_DETAIL_INCLUDE
            });
            return serializeOrder(result);
        }, { actorUserId: userId, senderSocketId });
    }
};

