const crypto=require("crypto"),db=require("../../models"),pricing=require("../pricing/pricing.service"),money=require("../pricing/money"),inventory=require("../inventory/inventory.service");const fault=(m,c,s=400)=>Object.assign(new Error(m),{code:c,status:s}); async function context(userId){return db.BusinessCustomer.findOne({where:{user_id:userId}});} async function activeCart(userId,{transaction,lock}={}){let cart=await db.Cart.findOne({where:{user_id:userId,status:"ACTIVE"},transaction,lock:lock&&transaction.LOCK.UPDATE});if(!cart)cart=await db.Cart.create({id:crypto.randomUUID(),user_id:userId,status:"ACTIVE",currency:"LKR"},{transaction});return cart;} async function validateVariant(variantId){const variant=await db.ProductVariant.findOne({where:{id:variantId,status:"ACTIVE"},include:[{model:db.Product,as:"product",where:{status:"ACTIVE",visibility:"PUBLIC"}},{model:db.ProductOptionValue,as:"optionValues"}]});if(!variant)throw fault("Product variant is unavailable","PRODUCT_UNAVAILABLE",409);return variant;} async function availability(variantId,quantity){const balances=await db.InventoryBalance.findAll({where:{variant_id:variantId}}),available=balances.reduce((n,x)=>n+Number(x.on_hand)-Number(x.reserved),0),threshold=balances.reduce((n,x)=>n+Number(x.low_stock_threshold),0);return {status:available=quantity};} async function project(userId){const cart=await activeCart(userId),items=await db.CartItem.findAll({where:{cart_id:cart.id},order:[["createdAt","ASC"]]}),businessCustomer=await context(userId);let subtotal="0.00",merchandiseTotal="0.00";const projected=[];for(const item of items){let variant,state,quote;try{variant=await validateVariant(item.variant_id);quote=await pricing.quoteVariant({variantId:item.variant_id,businessCustomer,quantity:item.quantity,couponCode:cart.coupon_code});state=await availability(item.variant_id,item.quantity);}catch(e){projected.push({id:item.id,variantId:item.variant_id,quantity:item.quantity,status:e.code||"PRODUCT_UNAVAILABLE"});continue;}const lineSubtotal=money.multiply(quote.baseEligiblePrice,item.quantity),lineTotal=money.multiply(quote.effectiveUnitPrice,item.quantity),lineDiscount=money.subtractFloor(lineSubtotal,lineTotal);subtotal=money.add(subtotal,lineSubtotal);merchandiseTotal=money.add(merchandiseTotal,lineTotal);projected.push({id:item.id,variantId:item.variant_id,quantity:item.quantity,sku:variant.sku,status:state.status,unitPrice:quote.effectiveUnitPrice,lineSubtotal,discount:lineDiscount,lineTotal,pricing:quote});}return {id:cart.id,status:cart.status,version:cart.version,couponCode:cart.coupon_code,items:projected,subtotal,discountTotal:money.subtractFloor(subtotal,merchandiseTotal),merchandiseTotal,currency:cart.currency};} async function add(userId,variantId,quantity){await validateVariant(variantId);const businessCustomer=await context(userId);await pricing.quoteVariant({variantId,businessCustomer,quantity});const state=await availability(variantId,quantity);if(!state.available)throw fault("Requested quantity is unavailable",state.status,409);return db.sequelize.transaction(async transaction=>{const cart=await activeCart(userId,{transaction,lock:true});let item=await db.CartItem.findOne({where:{cart_id:cart.id,variant_id:variantId},transaction,lock:transaction.LOCK.UPDATE});const next=(item?item.quantity:0)+quantity;if(next>Number(process.env.CART_MAX_ITEM_QUANTITY||100))throw fault("Cart quantity limit exceeded","CART_QUANTITY_LIMIT");if(item)await item.update({quantity:next},{transaction});else item=await db.CartItem.create({id:crypto.randomUUID(),cart_id:cart.id,variant_id:variantId,quantity},{transaction});await cart.increment("version",{transaction});return item;});} async function mutate(userId,itemId,quantity){return db.sequelize.transaction(async transaction=>{const cart=await activeCart(userId,{transaction,lock:true}),item=await db.CartItem.findOne({where:{id:itemId,cart_id:cart.id},transaction,lock:transaction.LOCK.UPDATE});if(!item)throw fault("Cart item not found","NOT_FOUND",404);if(quantity===0)await item.destroy({transaction});else{await validateVariant(item.variant_id);await item.update({quantity},{transaction});}await cart.increment("version",{transaction});return item;});} module.exports={activeCart,validateVariant,availability,project,add,mutate,context};