import { AppError } from "../../core/AppError";
import { LifecycleEventType, OrderItemStatus, Prisma } from "@prisma/client";
import { tracked, trackedTransaction } from "../../core/tracked";
import {
    resolveRouteById,
    findNextTransport,
    resolveDeliveryOutcome,
    stepUpdateData,
} from "../logisticsRoute/logisticsNavigation";
import {
    buildDefaultProductionWorkflowDto,
    buildProductionWorkflowSnapshot,
    getAcknowledgedProductionStepState,
    hasProductionStepChanged,
    resolveProductionStepStateForStatus,
    toProductionWorkflowDto,
} from "../station/production-workflow";
import { buildOrderItemProductionDisplayData } from "../station/station-stock.service";
import { posPushDispatchService, type OrderItemReadyPushCandidate } from "../push/push-dispatch.service";

/* =========================
   Allowed transitions
========================= */

const allowedTransitions: Record<
    OrderItemStatus,
    OrderItemStatus[]
> = {
    PENDING: [OrderItemStatus.PREPARING, OrderItemStatus.CANCELLED],
    PREPARING: [OrderItemStatus.READY, OrderItemStatus.PENDING, OrderItemStatus.CANCELLED],
    READY: [OrderItemStatus.PICKED_UP, OrderItemStatus.DELIVERED, OrderItemStatus.PREPARING, OrderItemStatus.CANCELLED],
    PICKED_UP: [OrderItemStatus.DELIVERED],
    DELIVERED: [],
    CANCELLED: [OrderItemStatus.PENDING]
};

/* =========================
   Lifecycle event type resolver
========================= */

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;
}

async function createLifecycleEvent(
    tx: any,
    orderItemId: string,
    fromStatus: OrderItemStatus,
    toStatus: OrderItemStatus,
    actorId?: string | null,
    actorName?: string | null,
    stationId?: string | null,
    options?: {
        fromProductionStepIndex?: number | null
        toProductionStepIndex?: number | null
        fromProductionStepCode?: string | null
        toProductionStepCode?: string | null
    }
) {
    await tx.orderItemLifecycleEvent.create({
        data: {
            orderItemId,
            eventType: resolveLifecycleEventType(fromStatus, toStatus),
            fromStatus,
            toStatus,
            fromProductionStepIndex: options?.fromProductionStepIndex ?? null,
            toProductionStepIndex: options?.toProductionStepIndex ?? null,
            fromProductionStepCode: options?.fromProductionStepCode ?? null,
            toProductionStepCode: options?.toProductionStepCode ?? null,
            actorId: actorId ?? null,
            actorName: actorName ?? null,
            stationId: stationId ?? null,
        }
    });
}

async function resolveProductionWorkflowForStation(tx: any, stationId: string | null | undefined) {
    if (!stationId) return null
    const station = await tx.station.findUnique({
        where: { id: stationId },
        include: { productionWorkflowSteps: { orderBy: { position: "asc" } } },
    })

    const workflow = toProductionWorkflowDto(station)
    if (workflow) {
        return workflow.enabled === false
            ? {
                ...buildDefaultProductionWorkflowDto(),
                enabled: false,
                revision: workflow.revision ?? 1,
            }
            : workflow
    }

    return station?.mainType === "PRODUCTION" ? buildDefaultProductionWorkflowDto() : null
}

function resolveProductionStationId(existing: any, overrides?: { currentStepStationId?: string | null }) {
    return overrides?.currentStepStationId ?? existing.currentStepStationId ?? existing.product?.stationId ?? null
}

async function resolveProductionStateForStatusChange(
    tx: any,
    existing: any,
    nextStatus: OrderItemStatus,
    overrides?: { currentStepStationId?: string | null }
) {
    const stationId = resolveProductionStationId(existing, overrides)
    const workflow = await resolveProductionWorkflowForStation(tx, stationId)
    if (!workflow) return { workflow: null, state: null, stationId }

    if (nextStatus === OrderItemStatus.PREPARING && workflow.steps.length < 3) {
        throw new AppError("This production workflow has no intermediate PREPARING step", 400)
    }

    const state = resolveProductionStepStateForStatus(workflow, nextStatus, {
        previousStatus: existing.status as OrderItemStatus,
        previousStepIndex: existing.productionStepIndex ?? null,
    })

    return { workflow, state, stationId }
}

function buildProductionStateUpdate(workflow: ReturnType<typeof buildDefaultProductionWorkflowDto> | null, state: { index: number | null; code: string | null; label: string | null; workflowRevision: number | null } | null) {
    if (!workflow || !state) return {}
    return {
        productionStepIndex: state.index,
        productionStepCode: state.code,
        productionStepLabel: state.label,
        productionWorkflowRevision: state.workflowRevision,
        productionWorkflowSnapshot: buildProductionWorkflowSnapshot(workflow),
    }
}

async function resolveActorName(tx: any, actorUserId?: string | null) {
    if (!actorUserId) return null
    const actor = await tx.shopUser.findUnique({
        where: { id: actorUserId },
        select: { displayName: true, username: true, email: true },
    })
    return actor?.displayName ?? actor?.username ?? actor?.email ?? null
}

function shouldClearProductionOverrideForStatus(status: OrderItemStatus) {
    return status === OrderItemStatus.READY
        || status === OrderItemStatus.PICKED_UP
        || status === OrderItemStatus.DELIVERED
        || status === OrderItemStatus.CANCELLED
}

function buildProductionDisplay(item: {
    status?: string | null
    configurationSnapshot?: unknown
    productionOverrideSnapshot?: unknown
}) {
    return buildOrderItemProductionDisplayData({
        status: item.status,
        configurationSnapshot: item.configurationSnapshot ?? null,
        productionOverrideSnapshot: item.productionOverrideSnapshot ?? null,
    })
}

function attachProductionDisplay<T extends {
    status?: string | null
    configurationSnapshot?: unknown
    productionOverrideSnapshot?: unknown
}>(item: T) {
    return {
        ...item,
        ...buildProductionDisplay(item),
    }
}

function applyProductionDisplayToRealtimePayload<T extends Record<string, any>>(
    payload: T,
    item: {
        status?: string | null
        configurationSnapshot?: unknown
        productionOverrideSnapshot?: unknown
    }
) {
    return {
        ...payload,
        ...buildProductionDisplay(item),
    }
}

function resolveCurrentProductionStepIndex(
    workflow: ReturnType<typeof buildDefaultProductionWorkflowDto>,
    status: OrderItemStatus,
    currentIndex: number | null | undefined,
) {
    if (typeof currentIndex === "number" && Number.isFinite(currentIndex)) {
        return Math.max(0, Math.min(currentIndex, workflow.steps.length - 1))
    }

    if (status === OrderItemStatus.PENDING) return 0
    if (status === OrderItemStatus.READY) return workflow.steps.length - 1
    if (status === OrderItemStatus.PREPARING) return Math.min(1, workflow.steps.length - 1)
    return null
}

function createStatusTransitionAllowanceResolver(tx: any) {
    const workflowByStationId = new Map<string, Awaited<ReturnType<typeof resolveProductionWorkflowForStation>>>()

    async function getWorkflowForItem(existing: any) {
        const stationId = resolveProductionStationId(existing)
        if (!stationId) return null
        if (workflowByStationId.has(stationId)) {
            return workflowByStationId.get(stationId) ?? null
        }

        const workflow = await resolveProductionWorkflowForStation(tx, stationId)
        workflowByStationId.set(stationId, workflow)
        return workflow
    }

    return async (existing: any, newStatus: OrderItemStatus) => {
        const currentStatus = existing.status as OrderItemStatus
        if (allowedTransitions[currentStatus]?.includes(newStatus)) {
            return true
        }

        if (currentStatus === OrderItemStatus.READY && newStatus === OrderItemStatus.PENDING) {
            const workflow = await getWorkflowForItem(existing)
            return Boolean(workflow && workflow.steps.length === 2)
        }

        return false
    }
}

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

function resolveCustomizationRecipe(product: any, requestedRecipeId?: string | null): ResolvedCustomizationRecipe | null {
    const effectiveCustomizationMode = product?.customizationMode === "CONFIGURATOR" || product?.configuratorId
        ? "CONFIGURATOR"
        : product?.customizationMode === "RECIPE" || product?.recipeId
            ? "RECIPE"
            : "NONE"

    if (effectiveCustomizationMode !== "RECIPE") {
        return null
    }

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

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

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 withOrderItemRealtimeScope<T extends Record<string, any>>(
    payload: T,
    options: {
        order?: { stationId?: string | null; cashSessionId?: string | null; createdById?: string | null } | null
        productStationId?: string | null
        logisticsRouteId?: string | null
        currentStepPosition?: number | null
        currentStepStationId?: string | null
        productionStepIndex?: number | null
        productionStepCode?: string | null
        productionStepLabel?: string | null
        productionWorkflowRevision?: number | null
    }
) {
    return {
        ...withOrderRealtimeScope(payload, options.order),
        productStationId: options.productStationId ?? null,
        logisticsRouteId: options.logisticsRouteId ?? null,
        currentStepPosition: options.currentStepPosition ?? null,
        currentStepStationId: options.currentStepStationId ?? null,
        productionStepIndex: options.productionStepIndex ?? null,
        productionStepCode: options.productionStepCode ?? null,
        productionStepLabel: options.productionStepLabel ?? null,
        productionWorkflowRevision: options.productionWorkflowRevision ?? null,
    }
}

function formatPushOrderShortId(input: { orderId: string; sessionOrderNumber?: number | null }) {
    if (typeof input.sessionOrderNumber === "number" && Number.isFinite(input.sessionOrderNumber)) {
        return `#${input.sessionOrderNumber}`
    }

    return `#${input.orderId.slice(0, 8)}`
}

function createReadyPushCandidateBuilder(tx: any) {
    const stationNameById = new Map<string, string | null>()

    return async function buildOrderItemReadyPushCandidate(
        shopId: string,
        item: {
            id: string
            orderId: string
            name?: string | null
            currentStepStationId?: string | null
            product?: { stationId?: string | null } | null
            order?: {
                createdById?: string | null
                sessionOrderNumber?: number | null
                stationId?: string | null
            } | null
        }
    ): Promise<OrderItemReadyPushCandidate> {
        const readyStationId = item.currentStepStationId ?? item.product?.stationId ?? null

        let readyStationName: string | null = null
        if (readyStationId) {
            if (!stationNameById.has(readyStationId)) {
                const station = await tx.station.findUnique({
                    where: { id: readyStationId },
                    select: { name: true },
                })
                stationNameById.set(readyStationId, station?.name ?? null)
            }

            readyStationName = stationNameById.get(readyStationId) ?? null
        }

        return {
            shopId,
            orderId: item.orderId,
            orderItemId: item.id,
            targetShopUserId: item.order?.createdById ?? null,
            productLabel: item.name?.trim() || "Produit",
            orderShortId: formatPushOrderShortId({
                orderId: item.orderId,
                sessionOrderNumber: item.order?.sessionOrderNumber ?? null,
            }),
            readyStationId,
            readyStationName,
            cashStationId: item.order?.stationId ?? null,
        }
    }
}

async function dispatchReadyPushCandidates(candidates: OrderItemReadyPushCandidate[]) {
    const uniqueCandidates = [...new Map(candidates.map((candidate) => [candidate.orderItemId, candidate])).values()]

    for (const candidate of uniqueCandidates) {
        try {
            await posPushDispatchService.dispatchOrderItemReady(candidate)
        } catch (error) {
            console.error("[push] order item ready dispatch failed", {
                shopId: candidate.shopId,
                orderId: candidate.orderId,
                orderItemId: candidate.orderItemId,
                targetShopUserId: candidate.targetShopUserId,
                error,
            })
        }
    }
}


export const orderItemService = {

    /* =========================
       Generic status update
    ========================= */

    async updateStatus(
        shopId: string,
        orderItemId: string,
        newStatus: OrderItemStatus,
        senderSocketId?: string,
        actorUserId?: string
    ) {
        const pushCandidates: OrderItemReadyPushCandidate[] = []

        const result = await trackedTransaction(shopId, "OrderItem", async (t, tx: any) => {
            const buildReadyPushCandidate = createReadyPushCandidateBuilder(tx)
            const isStatusTransitionAllowed = createStatusTransitionAllowanceResolver(tx)

            const existing = await tx.orderItem.findFirst({
                where: {
                    id: orderItemId,
                    order: { shopId }
                },
                include: {
                    product: true,
                    order: true,
                    takenBy: true,
                    pickedUpBy: true
                }
            });

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

            if (!(await isStatusTransitionAllowed(existing, newStatus))) {
                throw new AppError(
                    `Invalid status transition from ${existing.status} to ${newStatus}`,
                    400
                );
            }

            // ── Logique de navigation route logistique ──
            const extraData: Record<string, any> = {}
            let effectiveNewStatus = newStatus;
            let currentPos = existing.currentStepPosition ?? 0;
            let route = null;
            let transportStep = null;
            const previousProductionStep = {
                index: existing.productionStepIndex ?? null,
                code: existing.productionStepCode ?? null,
                label: existing.productionStepLabel ?? null,
                workflowRevision: existing.productionWorkflowRevision ?? null,
            }

            if (existing.logisticsRouteId) {
                route = await resolveRouteById(existing.logisticsRouteId, { allowInactive: true });
                transportStep = await validateTransportAction(
                    tx,
                    route,
                    currentPos,
                    existing.status as OrderItemStatus,
                    newStatus,
                    actorUserId
                );

                if (route) {
                    if (newStatus === OrderItemStatus.PICKED_UP) {
                        if (actorUserId) {
                            extraData.pickedUpById = actorUserId;
                            const actor = await tx.shopUser.findUnique({
                                where: { id: actorUserId },
                                select: { displayName: true, username: true }
                            });
                            extraData.transportedById = actorUserId;
                            extraData.transportedByName = actor?.displayName ?? actor?.username ?? null;
                        }
                        if (transportStep) {
                            Object.assign(extraData, stepUpdateData(route, transportStep as any));
                        }
                    } else if (newStatus === OrderItemStatus.DELIVERED) {
                        const deliveryPosition = existing.status === OrderItemStatus.READY
                            ? (transportStep?.position ?? currentPos)
                            : currentPos;
                        const outcome = resolveDeliveryOutcome(route, deliveryPosition);
                        if (outcome.action === "next_production") {
                            effectiveNewStatus = OrderItemStatus.PENDING;
                            Object.assign(extraData, stepUpdateData(route, outcome.step));
                            extraData.pickedUpById = null;
                            extraData.transportedById = null;
                            extraData.transportedByName = null;
                            extraData.takenById = null;
                        } else {
                            extraData.currentStepStationId = outcome.destinationStationId;
                        }
                    }
                }
            } else {
                // Pas de route logistique → ancien comportement
                if (newStatus === OrderItemStatus.PICKED_UP && actorUserId) {
                    extraData.pickedUpById = actorUserId;
                    const actor = await tx.shopUser.findUnique({
                        where: { id: actorUserId },
                        select: { displayName: true, username: true }
                    });
                    extraData.transportedById = actorUserId;
                    extraData.transportedByName = actor?.displayName ?? actor?.username ?? null;
                }
            }

            const { workflow, state, stationId: productionStationId } = await resolveProductionStateForStatusChange(
                tx,
                existing,
                effectiveNewStatus,
                { currentStepStationId: extraData.currentStepStationId ?? null }
            )
            Object.assign(extraData, buildProductionStateUpdate(workflow, state))
            if (shouldClearProductionOverrideForStatus(effectiveNewStatus)) {
                extraData.productionOverrideSnapshot = Prisma.DbNull
            }

            const item = await tx.orderItem.update({
                where: { id: orderItemId },
                data: { status: effectiveNewStatus, revision: { increment: 1 }, ...extraData },
                include: {
                    product: true,
                    order: true,
                    takenBy: true,
                    pickedUpBy: true
                }
            });

            // Résoudre le nom de l'acteur courant
            let actorName: string | null = null
            if (actorUserId) {
                const actor = await tx.shopUser.findUnique({ where: { id: actorUserId }, select: { displayName: true, username: true, email: true } })
                actorName = actor?.displayName ?? actor?.username ?? actor?.email ?? null
            }

            // Créer le lifecycle event
            await createLifecycleEvent(
                tx,
                orderItemId,
                existing.status as OrderItemStatus,
                effectiveNewStatus,
                actorUserId ?? null,
                actorName,
                productionStationId ?? item.product.stationId ?? null,
                {
                    fromProductionStepIndex: previousProductionStep.index,
                    toProductionStepIndex: item.productionStepIndex ?? null,
                    fromProductionStepCode: previousProductionStep.code,
                    toProductionStepCode: item.productionStepCode ?? null,
                }
            )

            const nextProductionStep = {
                index: item.productionStepIndex ?? null,
                code: item.productionStepCode ?? null,
                label: item.productionStepLabel ?? null,
                workflowRevision: item.productionWorkflowRevision ?? null,
            }

            if (hasProductionStepChanged(previousProductionStep, nextProductionStep)) {
                await t.emit(
                    "order_item.production_step_changed",
                    withOrderItemRealtimeScope(applyProductionDisplayToRealtimePayload({
                        orderItemId,
                        orderId: item.orderId,
                        previousStatus: existing.status,
                        newStatus: effectiveNewStatus,
                        previousStepIndex: previousProductionStep.index,
                        newStepIndex: nextProductionStep.index,
                        previousStepCode: previousProductionStep.code,
                        newStepCode: nextProductionStep.code,
                        previousStepLabel: previousProductionStep.label,
                        newStepLabel: nextProductionStep.label,
                    }, item), {
                        order: item.order,
                        productStationId: item.product.stationId ?? null,
                        logisticsRouteId: item.logisticsRouteId ?? null,
                        currentStepPosition: item.currentStepPosition ?? null,
                        currentStepStationId: item.currentStepStationId ?? null,
                        productionStepIndex: item.productionStepIndex ?? null,
                        productionStepCode: item.productionStepCode ?? null,
                        productionStepLabel: item.productionStepLabel ?? null,
                        productionWorkflowRevision: item.productionWorkflowRevision ?? null,
                    }),
                    orderItemId,
                    item.revision
                )
            }

            await t.emit(
                "order_item.status_changed",
                withOrderItemRealtimeScope(applyProductionDisplayToRealtimePayload({
                    orderItemId,
                    orderId: item.orderId,
                    newStatus: effectiveNewStatus,
                    previousStatus: existing.status,
                    requestedStatus: newStatus,
                    takenByName: item.takenBy?.displayName ?? item.takenBy?.username ?? null,
                    pickedUpByName: item.pickedUpBy?.displayName ?? item.pickedUpBy?.username ?? null,
                    transportedByName: item.transportedByName ?? null,
                }, item), {
                    order: item.order,
                    productStationId: item.product.stationId ?? null,
                    logisticsRouteId: item.logisticsRouteId ?? null,
                    currentStepPosition: item.currentStepPosition ?? null,
                    currentStepStationId: item.currentStepStationId ?? null,
                    productionStepIndex: item.productionStepIndex ?? null,
                    productionStepCode: item.productionStepCode ?? null,
                    productionStepLabel: item.productionStepLabel ?? null,
                    productionWorkflowRevision: item.productionWorkflowRevision ?? null,
                }),
                orderItemId,
                item.revision
            );

            // ── Recalcul du total quand un item est annulé ──
            if (effectiveNewStatus === OrderItemStatus.CANCELLED) {
                const activeItems = await tx.orderItem.findMany({
                    where: { orderId: item.orderId, status: { not: "CANCELLED" } }
                });

                const newTotal = activeItems.reduce(
                    (sum: number, i: any) => sum + i.unitPrice * i.quantity,
                    0
                );

                const tOrder = tracked({
                    shopId,
                    entity: "Order",
                    senderSocketId,
                    tx
                });

                await tOrder.update(
                    "order",
                    { id: item.orderId },
                    { total: newTotal },
                    {
                        include: {
                            orderItems: { include: { product: true } },
                            payments: true
                        }
                    },
                    "order.updated",
                    withOrderRealtimeScope({
                        orderId: item.orderId,
                        reason: "item_cancelled",
                        cancelledItemId: orderItemId,
                        previousTotal: item.order.total,
                        newTotal,
                        productionStationIds: [item.currentStepStationId ?? item.product.stationId ?? null].filter(Boolean)
                    }, item.order)
                );
            }

            if (effectiveNewStatus === OrderItemStatus.READY) {
                pushCandidates.push(await buildReadyPushCandidate(shopId, item))
            }

            return attachProductionDisplay(item);
        }, { actorUserId, senderSocketId });

        await dispatchReadyPushCandidates(pushCandidates)

        return result
    },

    /* =========================
       ACK (PENDING → PREPARING)
    ========================= */

    async acknowledgeItem(
        shopId: string,
        orderItemId: string,
        userId: string,
        senderSocketId?: string
    ) {
        const pushCandidates: OrderItemReadyPushCandidate[] = []

        const result = await trackedTransaction(shopId, "OrderItem", async (t, tx: any) => {
            const buildReadyPushCandidate = createReadyPushCandidateBuilder(tx)

            const existing = await tx.orderItem.findFirst({
                where: {
                    id: orderItemId,
                    order: { shopId }
                },
                include: {
                    product: true,
                    order: true,
                    takenBy: true
                }
            });

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

            if (existing.status !== OrderItemStatus.PENDING) {
                throw new AppError("Item already taken", 400);
            }

            const previousProductionStep = {
                index: existing.productionStepIndex ?? null,
                code: existing.productionStepCode ?? null,
                label: existing.productionStepLabel ?? null,
                workflowRevision: existing.productionWorkflowRevision ?? null,
            }
            const workflow = await resolveProductionWorkflowForStation(
                tx,
                existing.currentStepStationId ?? existing.product.stationId ?? null,
            )
            const acknowledgedState = workflow ? getAcknowledgedProductionStepState(workflow) : null
            const nextStatus = acknowledgedState && acknowledgedState.index === workflow!.steps.length - 1
                ? OrderItemStatus.READY
                : OrderItemStatus.PREPARING

            const item = await tx.orderItem.update({
                where: { id: orderItemId },
                data: {
                    status: nextStatus,
                    takenById: userId,
                    revision: { increment: 1 },
                    ...(shouldClearProductionOverrideForStatus(nextStatus)
                        ? { productionOverrideSnapshot: Prisma.DbNull }
                        : {}),
                    ...buildProductionStateUpdate(workflow, acknowledgedState),
                },
                include: {
                    product: true,
                    order: true,
                    takenBy: true
                }
            });

            // Créer le lifecycle event ACKNOWLEDGED
            const actorName = item.takenBy?.displayName ?? item.takenBy?.email ?? null
            await createLifecycleEvent(
                tx,
                orderItemId,
                OrderItemStatus.PENDING,
                nextStatus,
                userId,
                actorName,
                item.currentStepStationId ?? item.product.stationId ?? null,
                {
                    fromProductionStepIndex: previousProductionStep.index,
                    toProductionStepIndex: item.productionStepIndex ?? null,
                    fromProductionStepCode: previousProductionStep.code,
                    toProductionStepCode: item.productionStepCode ?? null,
                }
            )

            const nextProductionStep = {
                index: item.productionStepIndex ?? null,
                code: item.productionStepCode ?? null,
                label: item.productionStepLabel ?? null,
                workflowRevision: item.productionWorkflowRevision ?? null,
            }

            if (hasProductionStepChanged(previousProductionStep, nextProductionStep)) {
                await t.emit(
                    "order_item.production_step_changed",
                    withOrderItemRealtimeScope(applyProductionDisplayToRealtimePayload({
                        orderItemId,
                        orderId: item.orderId,
                        previousStatus: OrderItemStatus.PENDING,
                        newStatus: nextStatus,
                        previousStepIndex: previousProductionStep.index,
                        newStepIndex: nextProductionStep.index,
                        previousStepCode: previousProductionStep.code,
                        newStepCode: nextProductionStep.code,
                        previousStepLabel: previousProductionStep.label,
                        newStepLabel: nextProductionStep.label,
                        takenByName: actorName,
                    }, item), {
                        order: item.order,
                        productStationId: item.product.stationId ?? null,
                        logisticsRouteId: item.logisticsRouteId ?? null,
                        currentStepPosition: item.currentStepPosition ?? null,
                        currentStepStationId: item.currentStepStationId ?? null,
                        productionStepIndex: item.productionStepIndex ?? null,
                        productionStepCode: item.productionStepCode ?? null,
                        productionStepLabel: item.productionStepLabel ?? null,
                        productionWorkflowRevision: item.productionWorkflowRevision ?? null,
                    }),
                    orderItemId,
                    item.revision
                )
            }

            await t.emit(
                "order_item.status_changed",
                withOrderItemRealtimeScope(applyProductionDisplayToRealtimePayload({
                    orderItemId,
                    orderId: item.orderId,
                    newStatus: nextStatus,
                    previousStatus: OrderItemStatus.PENDING,
                    takenByName: item.takenBy?.displayName ?? item.takenBy?.email ?? null
                }, item), {
                    order: item.order,
                    productStationId: item.product.stationId ?? null,
                    logisticsRouteId: item.logisticsRouteId ?? null,
                    currentStepPosition: item.currentStepPosition ?? null,
                    currentStepStationId: item.currentStepStationId ?? null,
                    productionStepIndex: item.productionStepIndex ?? null,
                    productionStepCode: item.productionStepCode ?? null,
                    productionStepLabel: item.productionStepLabel ?? null,
                    productionWorkflowRevision: item.productionWorkflowRevision ?? null,
                }),
                orderItemId,
                item.revision
            );

            if (nextStatus === OrderItemStatus.READY) {
                pushCandidates.push(await buildReadyPushCandidate(shopId, item))
            }

            return attachProductionDisplay(item);
        }, { actorUserId: userId, senderSocketId });

        await dispatchReadyPushCandidates(pushCandidates)

        return result
    },

    async batchAcknowledgeItems(
        shopId: string,
        itemIds: string[],
        userId: string,
        senderSocketId?: string
    ) {
        const uniqueItemIds = [...new Set(itemIds)]
        if (uniqueItemIds.length === 0) return []

        const pushCandidates: OrderItemReadyPushCandidate[] = []

        const result = await trackedTransaction(shopId, "OrderItem", async (t, tx: any) => {
            const buildReadyPushCandidate = createReadyPushCandidateBuilder(tx)
            const items = await tx.orderItem.findMany({
                where: {
                    id: { in: uniqueItemIds },
                    order: { shopId }
                },
                include: {
                    product: true,
                    order: true,
                    takenBy: true
                }
            })

            if (items.length !== uniqueItemIds.length) {
                throw new AppError("One or more order items were not found", 404)
            }

            const invalidItem = items.find((item: any) => item.status !== OrderItemStatus.PENDING)
            if (invalidItem) {
                throw new AppError(`Invalid status transition from ${invalidItem.status} to PREPARING`, 400)
            }

            const results: any[] = []

            for (const existing of items) {
                const previousProductionStep = {
                    index: existing.productionStepIndex ?? null,
                    code: existing.productionStepCode ?? null,
                    label: existing.productionStepLabel ?? null,
                    workflowRevision: existing.productionWorkflowRevision ?? null,
                }
                const workflow = await resolveProductionWorkflowForStation(
                    tx,
                    existing.currentStepStationId ?? existing.product.stationId ?? null,
                )
                const acknowledgedState = workflow ? getAcknowledgedProductionStepState(workflow) : null
                const nextStatus = acknowledgedState && workflow && acknowledgedState.index === workflow.steps.length - 1
                    ? OrderItemStatus.READY
                    : OrderItemStatus.PREPARING

                const item = await tx.orderItem.update({
                    where: { id: existing.id },
                    data: {
                        status: nextStatus,
                        takenById: userId,
                        revision: { increment: 1 },
                        ...(shouldClearProductionOverrideForStatus(nextStatus)
                            ? { productionOverrideSnapshot: Prisma.DbNull }
                            : {}),
                        ...buildProductionStateUpdate(workflow, acknowledgedState),
                    },
                    include: {
                        product: true,
                        order: true,
                        takenBy: true
                    }
                })

                const actorName = item.takenBy?.displayName ?? item.takenBy?.email ?? null
                await createLifecycleEvent(
                    tx,
                    item.id,
                    OrderItemStatus.PENDING,
                    nextStatus,
                    userId,
                    actorName,
                    item.currentStepStationId ?? item.product.stationId ?? null,
                    {
                        fromProductionStepIndex: previousProductionStep.index,
                        toProductionStepIndex: item.productionStepIndex ?? null,
                        fromProductionStepCode: previousProductionStep.code,
                        toProductionStepCode: item.productionStepCode ?? null,
                    }
                )

                const nextProductionStep = {
                    index: item.productionStepIndex ?? null,
                    code: item.productionStepCode ?? null,
                    label: item.productionStepLabel ?? null,
                    workflowRevision: item.productionWorkflowRevision ?? null,
                }

                if (hasProductionStepChanged(previousProductionStep, nextProductionStep)) {
                    await t.emit(
                        "order_item.production_step_changed",
                        withOrderItemRealtimeScope(applyProductionDisplayToRealtimePayload({
                            orderItemId: item.id,
                            orderId: item.orderId,
                            previousStatus: OrderItemStatus.PENDING,
                            newStatus: nextStatus,
                            previousStepIndex: previousProductionStep.index,
                            newStepIndex: nextProductionStep.index,
                            previousStepCode: previousProductionStep.code,
                            newStepCode: nextProductionStep.code,
                            previousStepLabel: previousProductionStep.label,
                            newStepLabel: nextProductionStep.label,
                            takenByName: item.takenBy?.displayName ?? item.takenBy?.email ?? null,
                        }, item), {
                            order: item.order,
                            productStationId: item.product.stationId ?? null,
                            logisticsRouteId: item.logisticsRouteId ?? null,
                            currentStepPosition: item.currentStepPosition ?? null,
                            currentStepStationId: item.currentStepStationId ?? null,
                            productionStepIndex: item.productionStepIndex ?? null,
                            productionStepCode: item.productionStepCode ?? null,
                            productionStepLabel: item.productionStepLabel ?? null,
                            productionWorkflowRevision: item.productionWorkflowRevision ?? null,
                        }),
                        item.id,
                        item.revision
                    )
                }

                await t.emit(
                    "order_item.status_changed",
                    withOrderItemRealtimeScope(applyProductionDisplayToRealtimePayload({
                        orderItemId: item.id,
                        orderId: item.orderId,
                        newStatus: nextStatus,
                        previousStatus: OrderItemStatus.PENDING,
                        takenByName: item.takenBy?.displayName ?? item.takenBy?.email ?? null
                    }, item), {
                        order: item.order,
                        productStationId: item.product.stationId ?? null,
                        logisticsRouteId: item.logisticsRouteId ?? null,
                        currentStepPosition: item.currentStepPosition ?? null,
                        currentStepStationId: item.currentStepStationId ?? null,
                        productionStepIndex: item.productionStepIndex ?? null,
                        productionStepCode: item.productionStepCode ?? null,
                        productionStepLabel: item.productionStepLabel ?? null,
                        productionWorkflowRevision: item.productionWorkflowRevision ?? null,
                    }),
                    item.id,
                    item.revision
                )

                if (nextStatus === OrderItemStatus.READY) {
                    pushCandidates.push(await buildReadyPushCandidate(shopId, item))
                }

                results.push(attachProductionDisplay(item))
            }

            return results
        }, { actorUserId: userId, senderSocketId })

        await dispatchReadyPushCandidates(pushCandidates)

        return result
    },

    /* =========================
       SET CUSTOMIZATIONS
       Replace all customizations on a PENDING order item
    ========================= */

    async setCustomizations(
        shopId: string,
        orderItemId: string,
        customizations: { ingredientId: string; action: "ADDED" | "REMOVED"; quantity?: number }[],
        recipeId?: string,
        senderSocketId?: string
    ) {
        return trackedTransaction(shopId, "OrderItem", async (t, tx: any) => {
            const existing = await tx.orderItem.findFirst({
                where: { id: orderItemId, order: { shopId } },
                include: {
                    product: {
                        include: {
                            recipe: { include: { recipeIngredients: { include: { ingredient: true } } } },
                        }
                    },
                    order: true
                }
            });

            if (!existing) throw new AppError("Order item not found", 404);
            if (existing.status !== OrderItemStatus.PENDING) {
                throw new AppError("Cannot customize an item that is already being prepared", 400);
            }
            const selectedRecipe = resolveCustomizationRecipe(existing.product, recipeId ?? existing.recipeId ?? null);
            if ((recipeId ?? null) && !selectedRecipe) {
                throw new AppError(`Recipe ${recipeId} is not linked to this product`, 400);
            }
            if (!selectedRecipe) {
                throw new AppError("This product has no recipe to customize", 400);
            }

            // Validate ingredients belong to the recipe (for REMOVED) or are SUPPLEMENT in recipe (for ADDED)
            const recipeIngredients = selectedRecipe.recipeIngredients;
            const recipeIngredientMap = new Map(recipeIngredients.map((ri: any) => [ri.ingredientId, ri]));

            for (const c of customizations) {
                const ri = recipeIngredientMap.get(c.ingredientId);
                if (!ri) throw new AppError(`Ingredient ${c.ingredientId} is not part of this product's recipe`, 400);
                if (c.action === "ADDED" && (ri as any).role !== "SUPPLEMENT") {
                    throw new AppError(`Only supplements can be added (ingredient ${c.ingredientId} has role ${(ri as any).role})`, 400);
                }
                if (c.action === "REMOVED" && (ri as any).role === "BASE") {
                    throw new AppError(`Base ingredients cannot be removed (ingredient ${c.ingredientId})`, 400);
                }
                // Check maxQuantity for supplements
                if (c.action === "ADDED" && (ri as any).maxQuantity !== null) {
                    const qty = c.quantity ?? 1;
                    if (qty > (ri as any).maxQuantity) {
                        throw new AppError(`Supplement ${c.ingredientId} max quantity is ${(ri as any).maxQuantity}`, 400);
                    }
                }
            }

            // Compute extra price delta
            const extraPriceTotal = customizations.reduce((sum, c) => {
                const ri: any = recipeIngredientMap.get(c.ingredientId);
                if (c.action === "ADDED") {
                    return sum + ((ri?.extraPrice ?? 0) * (c.quantity ?? 1));
                }
                return sum;
            }, 0);

            // If quantity > 1, split one unit into its own order item before applying customizations.
            // This prevents customized unit from being merged with remaining plain units.
            let targetOrderItemId = orderItemId;

            if (existing.quantity > 1) {
                const previousCustomizations = await tx.orderItemCustomization.findMany({
                    where: { orderItemId }
                });

                await tx.orderItem.update({
                    where: { id: orderItemId },
                    data: {
                        quantity: { decrement: 1 },
                        revision: { increment: 1 }
                    }
                });

                const splitItem = await tx.orderItem.create({
                    data: {
                        name: existing.name,
                        quantity: 1,
                        unitPrice: existing.unitPrice,
                        status: existing.status,
                        revision: 1,
                        orderId: existing.orderId,
                        productId: existing.productId,
                        recipeId: existing.recipeId ?? null,
                        recipeName: existing.recipeName ?? null,
                        configurationLabel: existing.configurationLabel ?? null,
                        configurationSnapshot: existing.configurationSnapshot ?? null,
                        takenById: existing.takenById ?? null,
                        pickedUpById: existing.pickedUpById ?? null,
                        logisticsRouteId: existing.logisticsRouteId ?? null,
                        currentStepPosition: existing.currentStepPosition ?? null,
                        currentStepStationId: existing.currentStepStationId ?? null,
                        transportedById: existing.transportedById ?? null,
                        transportedByName: existing.transportedByName ?? null,
                    }
                });

                targetOrderItemId = splitItem.id;

                if (previousCustomizations.length > 0) {
                    await tx.orderItemCustomization.createMany({
                        data: previousCustomizations.map((c: any) => ({
                            orderItemId: targetOrderItemId,
                            ingredientId: c.ingredientId,
                            ingredientName: c.ingredientName,
                            action: c.action,
                            quantity: c.quantity,
                            extraPrice: c.extraPrice,
                        }))
                    });
                }
            }

            // Replace customizations on target item
            await tx.orderItemCustomization.deleteMany({ where: { orderItemId: targetOrderItemId } });

            if (customizations.length > 0) {
                await tx.orderItemCustomization.createMany({
                    data: customizations.map(c => {
                        const ri: any = recipeIngredientMap.get(c.ingredientId);
                        return {
                            orderItemId: targetOrderItemId,
                            ingredientId: c.ingredientId,
                            ingredientName: ri.ingredient.name,
                            action: c.action,
                            quantity: c.quantity ?? 1,
                            extraPrice: c.action === "ADDED" ? (ri.extraPrice ?? 0) : 0,
                        };
                    }),
                });
            }

            // Update unitPrice on target item only (idempotent from base product price)
            const newUnitPrice = existing.product.price + extraPriceTotal;
            const updatedItem = await tx.orderItem.update({
                where: { id: targetOrderItemId },
                data: {
                    unitPrice: newUnitPrice,
                    recipeId: selectedRecipe.id,
                    recipeName: selectedRecipe.name,
                    revision: { increment: 1 }
                },
                include: {
                    product: true,
                    order: true,
                    customizations: { include: { ingredient: true } }
                }
            });

            // Recalculate order total
            const allItems = await tx.orderItem.findMany({
                where: { orderId: existing.orderId, status: { not: "CANCELLED" } }
            });
            const newOrderTotal = allItems.reduce((sum: number, i: any) => sum + i.unitPrice * i.quantity, 0);
            await tx.order.update({
                where: { id: existing.orderId },
                data: { total: newOrderTotal }
            });

            await t.emit(
                "order_item.customized",
                {
                    orderItemId: targetOrderItemId,
                    orderId: existing.orderId,
                    customizationCount: customizations.length,
                    extraPrice: extraPriceTotal,
                    splitFromOrderItemId: targetOrderItemId !== orderItemId ? orderItemId : null,
                },
                targetOrderItemId,
                updatedItem.revision
            );

            return updatedItem;
        }, { senderSocketId });
    },

    /* =========================
       BATCH STATUS UPDATE
       Updates multiple items to the same status in one transaction
    ========================= */

    async batchUpdateStatus(
        shopId: string,
        itemIds: string[],
        newStatus: OrderItemStatus,
        senderSocketId?: string,
        actorUserId?: string
    ) {
        const uniqueItemIds = [...new Set(itemIds)]
        if (uniqueItemIds.length === 0) return [];

        const pushCandidates: OrderItemReadyPushCandidate[] = []

        const result = await trackedTransaction(shopId, "OrderItem", async (t, tx: any) => {
            const buildReadyPushCandidate = createReadyPushCandidateBuilder(tx)
            const isStatusTransitionAllowed = createStatusTransitionAllowanceResolver(tx)
            const items = await tx.orderItem.findMany({
                where: {
                    id: { in: uniqueItemIds },
                    order: { shopId }
                },
                include: { product: true, order: true, takenBy: true, pickedUpBy: true }
            });

            if (items.length !== uniqueItemIds.length) {
                throw new AppError("One or more order items were not found", 404);
            }

            if (newStatus === OrderItemStatus.PREPARING && items.some((item: any) => item.status === OrderItemStatus.PENDING)) {
                throw new AppError("Use acknowledge endpoints for PENDING to PREPARING transitions", 400);
            }

            const invalidItem = await (async () => {
                for (const existing of items) {
                    if (!(await isStatusTransitionAllowed(existing, newStatus))) {
                        return existing
                    }
                }

                return null
            })()
            if (invalidItem) {
                throw new AppError(
                    `Invalid status transition from ${invalidItem.status} to ${newStatus}`,
                    400
                );
            }

            // Résoudre le nom de l'acteur une seule fois pour le batch
            let actorName: string | null = null
            if (actorUserId) {
                const actor = await tx.shopUser.findUnique({ where: { id: actorUserId }, select: { displayName: true, username: true, email: true } })
                actorName = actor?.displayName ?? actor?.username ?? actor?.email ?? null
            }

            const results: any[] = [];

            for (const existing of items) {
                const extraData: Record<string, any> = {}
                let effectiveNewStatus = newStatus;
                const currentPos = existing.currentStepPosition ?? 0;
                let route = null;
                let transportStep = null;
                const previousProductionStep = {
                    index: existing.productionStepIndex ?? null,
                    code: existing.productionStepCode ?? null,
                    label: existing.productionStepLabel ?? null,
                    workflowRevision: existing.productionWorkflowRevision ?? null,
                }

                if (existing.logisticsRouteId) {
                    route = await resolveRouteById(existing.logisticsRouteId, { allowInactive: true });
                    transportStep = await validateTransportAction(
                        tx,
                        route,
                        currentPos,
                        existing.status as OrderItemStatus,
                        newStatus,
                        actorUserId
                    );

                    if (route) {
                        if (newStatus === OrderItemStatus.PICKED_UP) {
                            if (actorUserId) {
                                extraData.pickedUpById = actorUserId;
                                extraData.transportedById = actorUserId;
                                extraData.transportedByName = actorName;
                            }
                            if (transportStep) {
                                Object.assign(extraData, stepUpdateData(route, transportStep as any));
                            }
                        } else if (newStatus === OrderItemStatus.DELIVERED) {
                            const deliveryPosition = existing.status === OrderItemStatus.READY
                                ? (transportStep?.position ?? currentPos)
                                : currentPos;
                            const outcome = resolveDeliveryOutcome(route, deliveryPosition);
                            if (outcome.action === "next_production") {
                                effectiveNewStatus = OrderItemStatus.PENDING;
                                Object.assign(extraData, stepUpdateData(route, outcome.step));
                                extraData.pickedUpById = null;
                                extraData.transportedById = null;
                                extraData.transportedByName = null;
                                extraData.takenById = null;
                            } else {
                                extraData.currentStepStationId = outcome.destinationStationId;
                            }
                        }
                    }
                } else {
                    if (newStatus === OrderItemStatus.PICKED_UP && actorUserId) {
                        extraData.pickedUpById = actorUserId;
                        extraData.transportedById = actorUserId;
                        extraData.transportedByName = actorName;
                    }
                }

                const { workflow, state, stationId: productionStationId } = await resolveProductionStateForStatusChange(
                    tx,
                    existing,
                    effectiveNewStatus,
                    { currentStepStationId: extraData.currentStepStationId ?? null }
                )
                Object.assign(extraData, buildProductionStateUpdate(workflow, state))
                if (shouldClearProductionOverrideForStatus(effectiveNewStatus)) {
                    extraData.productionOverrideSnapshot = Prisma.DbNull
                }

                const item = await tx.orderItem.update({
                    where: { id: existing.id },
                    data: { status: effectiveNewStatus, revision: { increment: 1 }, ...extraData },
                    include: { product: true, order: true, takenBy: true, pickedUpBy: true }
                });

                // Créer le lifecycle event
                await createLifecycleEvent(
                    tx,
                    existing.id,
                    existing.status as OrderItemStatus,
                    effectiveNewStatus,
                    actorUserId ?? null,
                    actorName,
                    productionStationId ?? item.product.stationId ?? null,
                    {
                        fromProductionStepIndex: previousProductionStep.index,
                        toProductionStepIndex: item.productionStepIndex ?? null,
                        fromProductionStepCode: previousProductionStep.code,
                        toProductionStepCode: item.productionStepCode ?? null,
                    }
                )

                const nextProductionStep = {
                    index: item.productionStepIndex ?? null,
                    code: item.productionStepCode ?? null,
                    label: item.productionStepLabel ?? null,
                    workflowRevision: item.productionWorkflowRevision ?? null,
                }

                if (hasProductionStepChanged(previousProductionStep, nextProductionStep)) {
                    await t.emit(
                        "order_item.production_step_changed",
                        withOrderItemRealtimeScope(applyProductionDisplayToRealtimePayload({
                            orderItemId: item.id,
                            orderId: item.orderId,
                            previousStatus: existing.status,
                            newStatus: effectiveNewStatus,
                            previousStepIndex: previousProductionStep.index,
                            newStepIndex: nextProductionStep.index,
                            previousStepCode: previousProductionStep.code,
                            newStepCode: nextProductionStep.code,
                            previousStepLabel: previousProductionStep.label,
                            newStepLabel: nextProductionStep.label,
                        }, item), {
                            order: item.order,
                            productStationId: item.product.stationId ?? null,
                            logisticsRouteId: item.logisticsRouteId ?? null,
                            currentStepPosition: item.currentStepPosition ?? null,
                            currentStepStationId: item.currentStepStationId ?? null,
                            productionStepIndex: item.productionStepIndex ?? null,
                            productionStepCode: item.productionStepCode ?? null,
                            productionStepLabel: item.productionStepLabel ?? null,
                            productionWorkflowRevision: item.productionWorkflowRevision ?? null,
                        }),
                        item.id,
                        item.revision
                    )
                }

                await t.emit(
                    "order_item.status_changed",
                    withOrderItemRealtimeScope(applyProductionDisplayToRealtimePayload({
                        orderItemId: item.id,
                        orderId: item.orderId,
                        newStatus: effectiveNewStatus,
                        previousStatus: existing.status,
                        requestedStatus: newStatus,
                        takenByName: item.takenBy?.displayName ?? item.takenBy?.username ?? null,
                        pickedUpByName: item.pickedUpBy?.displayName ?? item.pickedUpBy?.username ?? null,
                        transportedByName: item.transportedByName ?? null,
                    }, item), {
                        order: item.order,
                        productStationId: item.product.stationId ?? null,
                        logisticsRouteId: item.logisticsRouteId ?? null,
                        currentStepPosition: item.currentStepPosition ?? null,
                        currentStepStationId: item.currentStepStationId ?? null,
                        productionStepIndex: item.productionStepIndex ?? null,
                        productionStepCode: item.productionStepCode ?? null,
                        productionStepLabel: item.productionStepLabel ?? null,
                        productionWorkflowRevision: item.productionWorkflowRevision ?? null,
                    }),
                    item.id,
                    item.revision
                );

                if (effectiveNewStatus === OrderItemStatus.READY) {
                    pushCandidates.push(await buildReadyPushCandidate(shopId, item))
                }

                // Recalcul du total si annulation
                if (effectiveNewStatus === OrderItemStatus.CANCELLED) {
                    const activeItems = await tx.orderItem.findMany({
                        where: { orderId: item.orderId, status: { not: "CANCELLED" } }
                    });

                    const newTotal = activeItems.reduce(
                        (sum: number, i: any) => sum + i.unitPrice * i.quantity,
                        0
                    );

                    const tOrder = tracked({
                        shopId,
                        entity: "Order",
                        senderSocketId,
                        tx
                    });

                    await tOrder.update(
                        "order",
                        { id: item.orderId },
                        { total: newTotal },
                        {
                            include: {
                                orderItems: { include: { product: true } },
                                payments: true
                            }
                        },
                        "order.updated",
                        withOrderRealtimeScope({
                            orderId: item.orderId,
                            reason: "item_cancelled",
                            cancelledItemId: item.id,
                            previousTotal: item.order.total,
                            newTotal,
                            productionStationIds: [item.currentStepStationId ?? item.product.stationId ?? null].filter(Boolean)
                        }, item.order)
                    );
                }

                results.push(attachProductionDisplay(item));
            }

            return results;
        }, { senderSocketId });

        await dispatchReadyPushCandidates(pushCandidates)

        return result
    },

    async batchMarkReadyItems(
        shopId: string,
        itemIds: string[],
        senderSocketId?: string,
        actorUserId?: string
    ) {
        return this.batchUpdateStatus(
            shopId,
            itemIds,
            OrderItemStatus.READY,
            senderSocketId,
            actorUserId
        )
    },

    async batchMovePreparingItemsStep(
        shopId: string,
        itemIds: string[],
        direction: "next" | "previous",
        senderSocketId?: string,
        actorUserId?: string,
    ) {
        const uniqueItemIds = [...new Set(itemIds)]
        if (uniqueItemIds.length === 0) return []

        return trackedTransaction(shopId, "OrderItem", async (t, tx: any) => {
            const items = await tx.orderItem.findMany({
                where: {
                    id: { in: uniqueItemIds },
                    order: { shopId },
                },
                include: {
                    product: true,
                    order: true,
                    takenBy: true,
                    pickedUpBy: true,
                },
            })

            if (items.length !== uniqueItemIds.length) {
                throw new AppError("One or more order items were not found", 404)
            }

            const invalidStatusItem = items.find((item: any) => item.status !== OrderItemStatus.PREPARING)
            if (invalidStatusItem) {
                throw new AppError("Only PREPARING items can move between intermediate production steps", 400)
            }

            const actorName = await resolveActorName(tx, actorUserId)
            const results: any[] = []

            for (const existing of items) {
                const productionStationId = existing.currentStepStationId ?? existing.product.stationId ?? null
                const workflow = await resolveProductionWorkflowForStation(tx, productionStationId)

                if (!workflow) {
                    throw new AppError("No production workflow configured for this item", 400)
                }

                if (workflow.steps.length < 4) {
                    throw new AppError("This production workflow has no extra intermediate step", 400)
                }

                const lastIndex = workflow.steps.length - 1
                const currentIndex = resolveCurrentProductionStepIndex(
                    workflow,
                    existing.status as OrderItemStatus,
                    existing.productionStepIndex ?? null,
                )

                if (currentIndex == null) {
                    throw new AppError("Unable to resolve current production step", 400)
                }

                if (direction === "next" && currentIndex >= lastIndex - 1) {
                    throw new AppError("Use READY transition for the final production step", 400)
                }

                if (direction === "previous" && currentIndex <= 1) {
                    throw new AppError("Use PENDING transition for the first production step", 400)
                }

                const targetIndex = direction === "next" ? currentIndex + 1 : currentIndex - 1
                const targetStep = workflow.steps[targetIndex]

                if (!targetStep || targetStep.systemRole !== "INTERMEDIATE") {
                    throw new AppError("Target production step must be an intermediate step", 400)
                }

                const previousProductionStep = {
                    index: existing.productionStepIndex ?? null,
                    code: existing.productionStepCode ?? null,
                    label: existing.productionStepLabel ?? null,
                    workflowRevision: existing.productionWorkflowRevision ?? null,
                }

                const item = await tx.orderItem.update({
                    where: { id: existing.id },
                    data: {
                        status: OrderItemStatus.PREPARING,
                        revision: { increment: 1 },
                        productionStepIndex: targetIndex,
                        productionStepCode: targetStep.code,
                        productionStepLabel: targetStep.label,
                        productionWorkflowRevision: workflow.revision ?? 1,
                        productionWorkflowSnapshot: buildProductionWorkflowSnapshot(workflow),
                    },
                    include: {
                        product: true,
                        order: true,
                        takenBy: true,
                        pickedUpBy: true,
                    },
                })

                await tx.orderItemLifecycleEvent.create({
                    data: {
                        orderItemId: existing.id,
                        eventType: LifecycleEventType.PRODUCTION_STEP_CHANGED,
                        fromStatus: existing.status,
                        toStatus: OrderItemStatus.PREPARING,
                        fromProductionStepIndex: previousProductionStep.index,
                        toProductionStepIndex: item.productionStepIndex ?? null,
                        fromProductionStepCode: previousProductionStep.code,
                        toProductionStepCode: item.productionStepCode ?? null,
                        actorId: actorUserId ?? null,
                        actorName,
                        stationId: productionStationId,
                    },
                })

                const nextProductionStep = {
                    index: item.productionStepIndex ?? null,
                    code: item.productionStepCode ?? null,
                    label: item.productionStepLabel ?? null,
                    workflowRevision: item.productionWorkflowRevision ?? null,
                }

                if (hasProductionStepChanged(previousProductionStep, nextProductionStep)) {
                    await t.emit(
                        "order_item.production_step_changed",
                        withOrderItemRealtimeScope(applyProductionDisplayToRealtimePayload({
                            orderItemId: item.id,
                            orderId: item.orderId,
                            previousStatus: existing.status,
                            newStatus: OrderItemStatus.PREPARING,
                            previousStepIndex: previousProductionStep.index,
                            newStepIndex: nextProductionStep.index,
                            previousStepCode: previousProductionStep.code,
                            newStepCode: nextProductionStep.code,
                            previousStepLabel: previousProductionStep.label,
                            newStepLabel: nextProductionStep.label,
                            takenByName: item.takenBy?.displayName ?? item.takenBy?.username ?? actorName,
                        }, item), {
                            order: item.order,
                            productStationId: item.product.stationId ?? null,
                            logisticsRouteId: item.logisticsRouteId ?? null,
                            currentStepPosition: item.currentStepPosition ?? null,
                            currentStepStationId: item.currentStepStationId ?? null,
                            productionStepIndex: item.productionStepIndex ?? null,
                            productionStepCode: item.productionStepCode ?? null,
                            productionStepLabel: item.productionStepLabel ?? null,
                            productionWorkflowRevision: item.productionWorkflowRevision ?? null,
                        }),
                        item.id,
                        item.revision,
                    )
                }

                results.push(item)
            }

            return results
        }, { senderSocketId, actorUserId })
    }
};

/* =========================
   Transport validation
========================= */

function resolveRelevantTransportStep(route: { steps: Array<{ position: number; stepType: string; handlerMode: string | null; assignedRunnerId?: string | null }> }, currentPosition: number) {
    const currentStep = route.steps.find((s) => s.position === currentPosition) ?? null;
    if (currentStep?.stepType === "TRANSPORT") return currentStep;
    return findNextTransport(route as any, currentPosition);
}

async function validateTransportAction(
    tx: any,
    route: { steps: Array<{ position: number; stepType: string; handlerMode: string | null; assignedRunnerId?: string | null }> } | null,
    currentPosition: number,
    fromStatus: OrderItemStatus,
    newStatus: OrderItemStatus,
    actorUserId?: string
) {
    if (!route) return null;

    const transportStep = resolveRelevantTransportStep(route, currentPosition);
    const handlerMode = transportStep?.handlerMode ?? null;

    if (newStatus === OrderItemStatus.PICKED_UP) {
        if (handlerMode !== "RUNNER") {
            throw new AppError("PICKED_UP n'est autorisé que pour une étape de transport gérée par un runner", 400);
        }
        if (!actorUserId) {
            throw new AppError("Unauthorized", 401);
        }

        const actor = await tx.shopUser.findUnique({
            where: { id: actorUserId },
            select: { role: true, active: true }
        });

        if (!actor?.active || actor.role !== "RUNNER") {
            throw new AppError("Seul un runner actif peut récupérer cet item", 403);
        }
        if (transportStep?.assignedRunnerId && transportStep.assignedRunnerId !== actorUserId) {
            throw new AppError("Cet item est assigné à un autre runner", 403);
        }
    }

    if (newStatus === OrderItemStatus.DELIVERED && fromStatus === OrderItemStatus.READY && handlerMode === "RUNNER") {
        throw new AppError("Une étape RUNNER doit être récupérée avant d'être livrée", 400);
    }

    return transportStep;
}
