<?php

namespace App\Services\Manage;

use App\Enums\CuttingStatus;
use App\Enums\Role;
use App\Models\Cutting;
use App\Models\CuttingMaterial;
use App\Models\CuttingResult;
use App\Models\CuttingResultPrice;
use App\Models\Product;
use App\Models\ProductVariant;
use App\Models\RawMaterial;
use App\Models\RawMaterialPrice;
use App\Models\User;
use App\Services\System\PushNotificationService;
use App\Support\Media\MediaPresenter;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;

class CuttingService
{
    public function __construct(
        private readonly PushNotificationService $pushNotificationService,
    ) {}

    public function paginateForIndex(array $tableQuery, User $user): LengthAwarePaginator
    {
        $query = Cutting::query()
            ->with([
                'createdBy.profile',
                'rejection.rejectedBy.profile',
                'materials.rawMaterialPrice.rawMaterial:id,name,unit',
                'results.productVariant.product:id,name',
                'results.productVariant:id,product_id,name',
            ])
            ->when($tableQuery['search'] !== '', function (Builder $query) use ($tableQuery): void {
                $search = $tableQuery['search'];
                $query->where(function (Builder $query) use ($search): void {
                    $query->where('description', 'like', "%{$search}%")
                        ->orWhereHas('materials.rawMaterialPrice', function (Builder $query) use ($search): void {
                            $query->where('variant', 'like', "%{$search}%")
                                ->orWhereHas('rawMaterial', fn (Builder $query) => $query->where('name', 'like', "%{$search}%"));
                        })
                        ->orWhereHas('results.productVariant', function (Builder $query) use ($search): void {
                            $query->where('name', 'like', "%{$search}%")
                                ->orWhereHas('product', fn (Builder $query) => $query->where('name', 'like', "%{$search}%"));
                        });
                });
            });

        $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);

        return $query
            ->paginate(25)
            ->withQueryString()
            ->through(function (Cutting $cutting) use ($user) {
                $cutting->setAttribute('available_actions', $this->filterActionsForUser($cutting, $user));
                $cutting->setAttribute('is_editable', $cutting->status->isEditable());
                $this->appendCostPreview($cutting);
                $cutting->materials->each(fn (CuttingMaterial $m) => $this->breakMaterialCircularReference($m));

                return $cutting;
            });
    }

    public function getInProgressCuttings(User $user): Collection
    {
        return Cutting::query()
            ->with([
                'createdBy.profile',
                'rejection.rejectedBy.profile',
                'materials.rawMaterialPrice.rawMaterial:id,name,unit',
                'results.productVariant.product:id,name',
                'results.productVariant:id,product_id,name',
            ])
            ->inProgress()
            ->latest()
            ->get()
            ->each(function (Cutting $cutting) use ($user): void {
                $cutting->setAttribute('available_actions', $this->filterActionsForUser($cutting, $user));
                $cutting->setAttribute('is_editable', $cutting->status->isEditable());
                $this->appendCostPreview($cutting);
                $cutting->materials->each(fn (CuttingMaterial $m) => $this->breakMaterialCircularReference($m));
            });
    }

    public function getCompletedCuttings(User $user): Collection
    {
        return Cutting::query()
            ->with([
                'createdBy.profile',
                'rejection.rejectedBy.profile',
                'materials.rawMaterialPrice.rawMaterial:id,name,unit',
                'results.productVariant.product:id,name',
                'results.productVariant:id,product_id,name',
            ])
            ->completed()
            ->latest()
            ->get()
            ->each(function (Cutting $cutting) use ($user): void {
                $cutting->setAttribute('available_actions', $this->filterActionsForUser($cutting, $user));
                $cutting->setAttribute('is_editable', $cutting->status->isEditable());
                $this->appendCostPreview($cutting);
                $cutting->materials->each(fn (CuttingMaterial $m) => $this->breakMaterialCircularReference($m));
            });
    }

    public function rawMaterialCatalog(?Cutting $cutting = null, ?User $user = null): Collection
    {
        $selectedPriceIds = $cutting
            ? $cutting->materials()->pluck('raw_material_price_id')->all()
            : ($user ? $this->draftMaterialsQuery($user)->pluck('raw_material_price_id')->all() : []);

        return RawMaterial::query()
            ->with([
                'prices' => fn ($query) => $query
                    ->orderBy('created_at')
                    ->with('media'),
            ])
            ->where(function (Builder $query) use ($selectedPriceIds): void {
                $query->active();

                if ($selectedPriceIds !== []) {
                    $query->orWhereHas(
                        'prices',
                        fn (Builder $query) => $query->whereIn('id', $selectedPriceIds),
                    );
                }
            })
            ->orderBy('name')
            ->get()
            ->each(function (RawMaterial $rawMaterial): void {
                $rawMaterial->prices->each(function (RawMaterialPrice $price) use ($rawMaterial): void {
                    $price->setAttribute('images', MediaPresenter::collection($price, 'images'));
                    $price->setAttribute('unit_abbreviation', $rawMaterial->unit->abbreviation());
                    $price->unsetRelation('rawMaterial');
                });
            });
    }

    public function productCatalog(?Cutting $cutting = null, ?User $user = null): Collection
    {
        $selectedVariantIds = $cutting
            ? $cutting->results()->pluck('product_variant_id')->all()
            : ($user ? $this->draftResultsQuery($user)->pluck('product_variant_id')->all() : []);

        return Product::query()
            ->with([
                'variants' => fn ($query) => $query
                    ->with('media')
                    ->orderBy('created_at'),
            ])
            ->where(function (Builder $query) use ($selectedVariantIds): void {
                $query->active();

                if ($selectedVariantIds !== []) {
                    $query->orWhereHas(
                        'variants',
                        fn (Builder $query) => $query->whereIn('id', $selectedVariantIds),
                    );
                }
            })
            ->orderBy('name')
            ->get()
            ->each(function (Product $product): void {
                $product->variants->each(function (ProductVariant $variant): void {
                    $variant->setAttribute(
                        'images',
                        MediaPresenter::collection($variant, 'images'),
                    );
                });
            });
    }

    public function findForEdit(Cutting $cutting): Cutting
    {
        $cutting->load([
            'materials.rawMaterialPrice.rawMaterial:id,name,unit',
            'materials.rawMaterialPrice.media',
            'results.productVariant.product:id,name',
            'results.productVariant.media',
            'rejection.rejectedBy.profile',
        ]);

        $cutting->materials->each(function (CuttingMaterial $material): void {
            $price = $material->rawMaterialPrice;

            if ($price) {
                $price->setAttribute('images', MediaPresenter::collection($price, 'images'));
            }

            $this->breakMaterialCircularReference($material);
        });

        $cutting->results->each(function (CuttingResult $result): void {
            $variant = $result->productVariant;

            if ($variant) {
                $variant->setAttribute(
                    'images',
                    MediaPresenter::collection($variant, 'images'),
                );
            }
        });

        return $cutting;
    }

    public function draftMaterialsForUser(User $user): array
    {
        return $this->draftMaterialsQuery($user)
            ->with([
                'rawMaterialPrice.rawMaterial:id,name,unit',
                'rawMaterialPrice.media',
            ])
            ->get()
            ->map(function (CuttingMaterial $item) {
                $result = $this->presentDraftMaterial($item);
                $this->breakMaterialCircularReference($item);

                return $result;
            })
            ->values()
            ->all();
    }

    public function draftResultsForUser(User $user): array
    {
        return $this->draftResultsQuery($user)
            ->with([
                'productVariant.product:id,name',
                'productVariant.media',
            ])
            ->get()
            ->map(fn (CuttingResult $item) => $this->presentDraftResult($item))
            ->values()
            ->all();
    }

    public function syncDraftMaterial(array $validated, User $user): array
    {
        $price = RawMaterialPrice::query()
            ->with('rawMaterial')
            ->findOrFail($validated['raw_material_price_id']);

        $materialUsage = round((float) $validated['material_usage'], 2);

        if ($materialUsage <= 0) {
            throw ValidationException::withMessages([
                'material_usage' => 'Pemakaian bahan harus lebih dari 0.',
            ]);
        }

        $item = CuttingMaterial::query()->updateOrCreate(
            [
                'user_id' => $user->id,
                'raw_material_price_id' => $price->id,
                'cutting_id' => null,
            ],
            [
                'material_usage' => $materialUsage,
            ],
        );

        $item->load([
            'rawMaterialPrice.rawMaterial:id,name,unit',
            'rawMaterialPrice.media',
        ]);

        $result = $this->presentDraftMaterial($item);
        $this->breakMaterialCircularReference($item);

        return $result;
    }

    public function syncDraftResult(array $validated, User $user): array
    {
        $variant = ProductVariant::query()
            ->with('product:id,name')
            ->findOrFail($validated['product_variant_id']);

        $cuttingResult = (int) $validated['cutting_result'];
        $sample = (int) $validated['sample'];
        $originalOutsideSample = (int) ($validated['original_outside_sample'] ?? 0);

        if ($cuttingResult < 1) {
            throw ValidationException::withMessages([
                'cutting_result' => 'Hasil cutting minimal 1 pcs.',
            ]);
        }

        if ($sample < 0) {
            throw ValidationException::withMessages([
                'sample' => 'sample tidak boleh negatif.',
            ]);
        }

        if ($originalOutsideSample < 0) {
            throw ValidationException::withMessages([
                'original_outside_sample' => 'Hasil cutting diluar sample tidak boleh negatif.',
            ]);
        }

        if (($sample + $originalOutsideSample) !== $cuttingResult) {
            throw ValidationException::withMessages([
                'cutting_result' => 'Hasil cutting harus sama dengan sample ditambah hasil cutting diluar sample.',
            ]);
        }

        $item = CuttingResult::query()->updateOrCreate(
            [
                'user_id' => $user->id,
                'product_variant_id' => $variant->id,
                'cutting_id' => null,
            ],
            [
                'cutting_result' => $cuttingResult,
                'sample' => $sample,
                'original_outside_sample' => $originalOutsideSample,
            ],
        );

        $item->load([
            'productVariant.product:id,name',
            'productVariant.media',
        ]);

        return $this->presentDraftResult($item);
    }

    public function removeDraftMaterial(User $user, RawMaterialPrice $rawMaterialPrice): void
    {
        CuttingMaterial::query()
            ->whereNull('cutting_id')
            ->where('user_id', $user->id)
            ->where('raw_material_price_id', $rawMaterialPrice->id)
            ->delete();
    }

    public function removeDraftResult(User $user, ProductVariant $productVariant): void
    {
        CuttingResult::query()
            ->whereNull('cutting_id')
            ->where('user_id', $user->id)
            ->where('product_variant_id', $productVariant->id)
            ->delete();
    }

    public function create(array $validated, User $user): Cutting
    {
        try {
            $cutting = DB::transaction(function () use ($validated, $user): Cutting {

                $draftMaterials = $this->draftMaterialsQuery($user)
                    ->with('rawMaterialPrice.rawMaterial')
                    ->lockForUpdate()
                    ->get();

                $draftResults = $this->draftResultsQuery($user)
                    ->lockForUpdate()
                    ->get();

                if ($draftMaterials->isEmpty()) {
                    throw ValidationException::withMessages([
                        'materials' => 'Tambahkan minimal satu bahan baku.',
                    ]);
                }

                if ($draftResults->isEmpty()) {
                    throw ValidationException::withMessages([
                        'results' => 'Tambahkan minimal satu hasil produk.',
                    ]);
                }

                $cutting = Cutting::create([
                    'status' => CuttingStatus::IN_PROGRESS,
                    'description' => $validated['description'] ?? null,
                    'sewing_cost' => (int) ($validated['sewing_cost'] ?? 0),
                    'other_cost' => (int) ($validated['other_cost'] ?? 0),
                    'created_by_id' => $user->id,
                ]);

                foreach ($draftMaterials as $material) {
                    $material->cutting_id = $cutting->id;
                    $material->user_id = null;
                    $material->save();
                }

                foreach ($draftResults as $result) {
                    $result->cutting_id = $cutting->id;
                    $result->user_id = null;
                    $result->save();
                }

                $cutting->load('materials.rawMaterialPrice.rawMaterial');
                $this->deductMaterialStock($cutting);

                return $cutting;
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal membuat proses cutting: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

            throw ValidationException::withMessages([
                'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
            ]);
        }

        $this->pushNotificationService->sendToRoles(
            '✂️ Proses Cutting Baru',
            "Proses cutting dengan deskripsi ({$cutting->description}) telah dimulai oleh {$user->profile?->full_name}.",
            ['owner', 'developer'],
            route('admin.manage.cuttings.index'),
        );

        return $cutting;
    }

    public function update(Cutting $cutting, array $validated): void
    {
        if (! $cutting->status->isEditable()) {
            throw ValidationException::withMessages([
                'status' => 'Proses cutting tidak dapat diubah.',
            ]);
        }

        try {
            DB::transaction(function () use ($cutting, $validated): void {
                $cutting->load(['materials.rawMaterialPrice.rawMaterial', 'results']);

                if ($cutting->status === CuttingStatus::IN_PROGRESS) {
                    $this->reverseTotalMaterialStock($cutting);
                } elseif ($cutting->status === CuttingStatus::REJECTED) {
                    $this->reverseMaterialStock($cutting);
                }

                $cutting->materials()->delete();
                $cutting->results()->delete();

                $materials = $this->buildMaterials($validated['materials']);
                $results = $this->buildResults($validated['results']);

                $cutting->description = $validated['description'] ?? null;
                $cutting->sewing_cost = (int) ($validated['sewing_cost'] ?? 0);
                $cutting->other_cost = (int) ($validated['other_cost'] ?? 0);
                if ($cutting->status === CuttingStatus::REJECTED) {
                    $cutting->status = CuttingStatus::IN_PROGRESS;
                    $cutting->rejection()?->delete();
                }
                $cutting->save();

                foreach ($materials as $materialData) {
                    $cutting->materials()->create($materialData);
                }

                foreach ($results as $resultData) {
                    $cutting->results()->create($resultData);
                }

                if ($cutting->status === CuttingStatus::IN_PROGRESS) {
                    $cutting->load('materials.rawMaterialPrice.rawMaterial');
                    $this->deductMaterialStock($cutting);
                }
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal memperbarui proses cutting: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

            throw ValidationException::withMessages([
                'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
            ]);
        }

        $description = $cutting->description ?? '-';
        $this->pushNotificationService->sendToRoles(
            '✏️ Proses Cutting Diperbarui',
            "Proses cutting dengan deskripsi '{$description}' telah diperbarui.",
            ['owner', 'developer'],
            route('admin.manage.cuttings.index'),
        );
    }

    public function delete(Cutting $cutting): void
    {
        if (! in_array($cutting->status, [CuttingStatus::IN_PROGRESS, CuttingStatus::REJECTED], true)) {
            throw ValidationException::withMessages([
                'status' => 'Proses cutting hanya dapat dihapus saat masih proses atau ditolak.',
            ]);
        }

        $description = $cutting->description ?? '-';

        try {
            DB::transaction(function () use ($cutting): void {
                $cutting->load(['materials.rawMaterialPrice.rawMaterial']);

                if ($cutting->status === CuttingStatus::IN_PROGRESS) {
                    $this->reverseTotalMaterialStock($cutting);
                } elseif ($cutting->status === CuttingStatus::REJECTED) {
                    $this->reverseMaterialStock($cutting);
                }

                $cutting->materials()->delete();
                $cutting->results()->delete();
                $cutting->delete();
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal menghapus proses cutting: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

            throw ValidationException::withMessages([
                'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
            ]);
        }

        $this->pushNotificationService->sendToRoles(
            '🗑️ Proses Cutting Dihapus',
            "Proses cutting dengan deskripsi {$description} telah dihapus.",
            ['owner', 'developer'],
            route('admin.manage.cuttings.index'),
        );
    }

    public function transitionStatus(
        Cutting $cutting,
        CuttingStatus $status,
        User $user,
        ?string $reason = null,
        ?string $verificationNote = null,
        ?array $results = null,
        ?array $resultPrices = null,
    ): void {
        if (! $cutting->status->canTransitionTo($status)) {
            throw ValidationException::withMessages([
                'status' => 'Status proses cutting tidak dapat diubah.',
            ]);
        }

        try {
            DB::transaction(function () use ($cutting, $status, $verificationNote, $results, $resultPrices, $user, $reason): void {
                $cutting->load(['materials.rawMaterialPrice', 'results']);

                if ($status === CuttingStatus::COMPLETED) {
                    $cutting->total_material_cost = $this->calculateTotalMaterialCost($cutting);
                    $cutting->cost_per_unit = $this->calculateCostPerUnit($cutting);
                }

                if ($status === CuttingStatus::IN_PROGRESS) {
                    $cutting->rejection()?->delete();
                }

                if ($status === CuttingStatus::REJECTED) {
                    $this->storeRejection($cutting, $reason, $user);
                }

                if ($status === CuttingStatus::VERIFIED) {
                    if ($results !== null) {
                        foreach ($results as $item) {
                            $cutting->results()
                                ->where('product_variant_id', $item['product_variant_id'])
                                ->update([
                                    'sample' => $item['sample'],
                                    'original_outside_sample' => $item['original_outside_sample'],
                                ]);
                        }
                        $cutting->load('results');
                    }
                    $this->applyProductStockOnVerify($cutting);
                    $this->storeResultPrices($cutting, $resultPrices ?? []);

                    if ($verificationNote !== null && trim($verificationNote) !== '') {
                        $cutting->rejection()->create([
                            'reason' => trim($verificationNote),
                            'rejected_by_id' => $user->id,
                        ]);
                    }
                }

                $cutting->status = $status;
                $cutting->save();
            });
        } catch (ValidationException $e) {
            throw $e;
        } catch (\Throwable $e) {
            Log::error('Gagal mengubah status proses cutting: '.$e->getMessage(), [
                'trace' => $e->getTraceAsString(),
            ]);

            throw ValidationException::withMessages([
                'system' => 'Terjadi kesalahan pada server. Silakan laporkan masalah ini ke pihak terkait.',
            ]);
        }

        $description = $cutting->description ?? '-';
        $message = match ($status) {
            CuttingStatus::COMPLETED => "Proses cutting dengan deskripsi '{$description}' telah selesai dan menunggu verifikasi.",
            CuttingStatus::VERIFIED => "Proses cutting dengan deskripsi '{$description}' telah diverifikasi.",
            CuttingStatus::REJECTED => "Proses cutting dengan deskripsi '{$description}' ditolak".($reason ? " dengan alasan: '{$reason}'" : '').'.',
            CuttingStatus::IN_PROGRESS => "Proses cutting dengan deskripsi '{$description}' dikembalikan ke proses.",
            default => "Status proses cutting dengan deskripsi '{$description}' telah diperbarui ke: {$status->label()}.",
        };

        $title = match ($status) {
            CuttingStatus::COMPLETED => '✂️ Proses Cutting Selesai',
            CuttingStatus::VERIFIED => '✂️ Proses Cutting Terverifikasi',
            CuttingStatus::REJECTED => '✂️ Proses Cutting Ditolak',
            CuttingStatus::IN_PROGRESS => '✂️ Proses Cutting Dikembalikan',
            default => '✂️ Proses Cutting Diperbarui',
        };

        $roles = $status === CuttingStatus::COMPLETED
            ? ['owner', 'developer', 'admin-toko']
            : ['owner', 'developer'];

        $this->pushNotificationService->sendToRoles(
            $title,
            $message,
            $roles,
            route('admin.manage.cuttings.index'),
        );
    }

    private function buildMaterials(array $materials): array
    {
        return collect($materials)
            ->map(function (array $itemData, int $index) {
                $price = RawMaterialPrice::query()
                    ->with('rawMaterial')
                    ->find($itemData['raw_material_price_id']);

                if ($price === null) {
                    throw ValidationException::withMessages([
                        "materials.{$index}.raw_material_price_id" => 'Bahan baku tidak ditemukan.',
                    ]);
                }

                $materialUsage = round((float) $itemData['material_usage'], 2);

                if ($materialUsage <= 0) {
                    throw ValidationException::withMessages([
                        "materials.{$index}.material_usage" => 'Pemakaian bahan harus lebih dari 0.',
                    ]);
                }

                return [
                    'raw_material_price_id' => $price->id,
                    'material_usage' => $materialUsage,
                ];
            })
            ->all();
    }

    private function buildResults(array $results): array
    {
        return collect($results)
            ->map(function (array $itemData, int $index) {
                $variant = ProductVariant::query()->find($itemData['product_variant_id']);

                if ($variant === null) {
                    throw ValidationException::withMessages([
                        "results.{$index}.product_variant_id" => 'Varian produk tidak ditemukan.',
                    ]);
                }

                $cuttingResult = (int) $itemData['cutting_result'];
                $sample = (int) $itemData['sample'];
                $originalOutsideSample = (int) ($itemData['original_outside_sample'] ?? 0);

                if ($cuttingResult < 1) {
                    throw ValidationException::withMessages([
                        "results.{$index}.cutting_result" => 'Hasil cutting minimal 1 pcs.',
                    ]);
                }

                if ($sample < 0) {
                    throw ValidationException::withMessages([
                        "results.{$index}.sample" => 'sample tidak boleh negatif.',
                    ]);
                }

                if ($originalOutsideSample < 0) {
                    throw ValidationException::withMessages([
                        "results.{$index}.original_outside_sample" => 'Hasil cutting diluar sample tidak boleh negatif.',
                    ]);
                }

                if (($sample + $originalOutsideSample) !== $cuttingResult) {
                    throw ValidationException::withMessages([
                        "results.{$index}.cutting_result" => 'Hasil cutting harus sama dengan sample ditambah hasil cutting diluar sample.',
                    ]);
                }

                return [
                    'product_variant_id' => $variant->id,
                    'cutting_result' => $cuttingResult,
                    'sample' => $sample,
                    'original_outside_sample' => $originalOutsideSample,
                ];
            })
            ->all();
    }

    private function deductMaterialStock(Cutting $cutting): void
    {
        foreach ($cutting->materials as $material) {
            $totalTaken = (float) $material->material_usage;
            $price = RawMaterialPrice::query()->lockForUpdate()->find($material->raw_material_price_id);

            if ($price === null) {
                throw ValidationException::withMessages([
                    'materials' => 'Bahan baku tidak ditemukan.',
                ]);
            }

            if ((float) $price->stock < $totalTaken) {
                throw ValidationException::withMessages([
                    'materials' => "Stok {$price->rawMaterial?->name} ({$price->variant}) tidak mencukupi.",
                ]);
            }

            $price->decrement('stock', $totalTaken);
        }
    }

    private function reverseTotalMaterialStock(Cutting $cutting): void
    {
        foreach ($cutting->materials as $material) {
            RawMaterialPrice::query()
                ->whereKey($material->raw_material_price_id)
                ->increment('stock', (float) $material->material_usage);
        }
    }

    private function reverseMaterialStock(Cutting $cutting): void
    {
        foreach ($cutting->materials as $material) {
            RawMaterialPrice::query()
                ->whereKey($material->raw_material_price_id)
                ->increment('stock', (float) $material->material_usage);
        }
    }

    private function applyProductStockOnVerify(Cutting $cutting): void
    {
        foreach ($cutting->results as $result) {
            if ($result->sample > 0) {
                ProductVariant::query()
                    ->whereKey($result->product_variant_id)
                    ->increment('stock', $result->sample);
            }

            if ($result->original_outside_sample > 0) {
                ProductVariant::query()
                    ->whereKey($result->product_variant_id)
                    ->increment('reject_stock', $result->original_outside_sample);
            }
        }
    }

    private function reverseProductStock(Cutting $cutting): void
    {
        foreach ($cutting->results as $result) {
            if ($result->sample > 0) {
                ProductVariant::query()
                    ->whereKey($result->product_variant_id)
                    ->decrement('stock', $result->sample);
            }

            if ($result->original_outside_sample > 0) {
                ProductVariant::query()
                    ->whereKey($result->product_variant_id)
                    ->decrement('reject_stock', $result->original_outside_sample);
            }
        }
    }

    private function storeRejection(Cutting $cutting, ?string $reason, User $user): void
    {
        if ($reason === null || trim($reason) === '') {
            throw ValidationException::withMessages([
                'reason' => 'Alasan penolakan wajib diisi.',
            ]);
        }

        $cutting->rejection()?->delete();

        $cutting->rejection()->create([
            'reason' => trim($reason),
            'rejected_by_id' => $user->id,
        ]);
    }

    private function breakMaterialCircularReference(CuttingMaterial $material): void
    {
        $price = $material->rawMaterialPrice;

        if ($price) {
            $rawMaterial = $price->rawMaterial;

            $material->setAttribute('variant', $price->variant);
            $material->setAttribute('stock_input', $price->stock_input);
            $material->setAttribute('images', $price->getAttribute('images') ?? []);

            if ($rawMaterial) {
                $unitAbbreviation = $rawMaterial->unit->abbreviation();
                $price->setAttribute('unit_abbreviation', $unitAbbreviation);
                $material->setAttribute('unit_abbreviation', $unitAbbreviation);
                $material->setAttribute('unit', $rawMaterial->unit->value);
                $material->setAttribute('raw_material_id', $rawMaterial->id);
                $material->setAttribute('raw_material_name', $rawMaterial->name);
                $material->setAttribute('raw_material_unit_label', $rawMaterial->unit->label());
            }

            $price->unsetRelation('rawMaterial');
        }

        $material->unsetRelation('rawMaterialPrice');
    }

    private function applySorting(Builder $query, string $sort, string $direction): void
    {
        if (in_array($sort, ['created_at', 'status'], true)) {
            $query->orderBy($sort, $direction);

            return;
        }

        $query->latest();
    }

    private function draftMaterialsQuery(User $user): Builder
    {
        return CuttingMaterial::query()
            ->whereNull('cutting_id')
            ->where('user_id', $user->id);
    }

    private function draftResultsQuery(User $user): Builder
    {
        return CuttingResult::query()
            ->whereNull('cutting_id')
            ->where('user_id', $user->id);
    }

    private function presentDraftMaterial(CuttingMaterial $item): array
    {
        $price = $item->rawMaterialPrice;
        $rawMaterial = $price?->rawMaterial;

        return [
            'raw_material_price_id' => $item->raw_material_price_id,
            'raw_material_name' => $rawMaterial?->name ?? '',
            'variant' => $price?->variant ?? '',
            'unit' => $rawMaterial?->unit?->value ?? '',
            'unit_abbreviation' => $rawMaterial?->unit?->abbreviation() ?? '',
            'stock_input' => $price?->stock_input ?? '',
            'material_usage' => $this->formatQuantityInput((float) $item->material_usage),
            'images' => $price ? MediaPresenter::collection($price, 'images') : [],
        ];
    }

    private function presentDraftResult(CuttingResult $item): array
    {
        $variant = $item->productVariant;

        return [
            'product_variant_id' => $item->product_variant_id,
            'product_name' => $variant?->product?->name ?? '',
            'variant_name' => $variant?->name ?? '',
            'stock' => $variant?->stock ?? 0,
            'cutting_result' => (string) $item->cutting_result,
            'sample' => (string) $item->sample,
            'original_outside_sample' => (string) $item->original_outside_sample,
            'images' => $variant ? MediaPresenter::collection($variant, 'images') : [],
        ];
    }

    private function formatQuantityInput(float $value): string
    {
        return rtrim(rtrim(number_format($value, 2, '.', ''), '0'), '.');
    }

    private function formatMaterialUsageSummary(Cutting $cutting): string
    {
        $groups = [];

        foreach ($cutting->materials as $material) {
            $unit = $material->rawMaterialPrice?->rawMaterial?->unit?->abbreviation() ?? '';
            $groups[$unit] = ($groups[$unit] ?? 0) + (float) $material->material_usage;
        }

        return collect($groups)
            ->filter(fn (float $total) => $total > 0)
            ->map(function (float $total, string $unit) {
                $formatted = rtrim(rtrim(number_format($total, 2, ',', '.'), '0'), ',');

                return "{$formatted} {$unit}";
            })
            ->join(', ');
    }

    /**
     * @return list<array{status: string, label: string, destructive: bool, permission: string, icon_only: bool}>
     */
    private function filterActionsForUser(Cutting $cutting, User $user): array
    {
        return collect($cutting->status->availableActions())
            ->filter(fn (array $action) => $user->can($action['permission']))
            ->filter(function (array $action) use ($cutting, $user): bool {
                // Only owner/developer can verify directly from COMPLETED status
                if ($cutting->status === CuttingStatus::COMPLETED && $action['status'] === CuttingStatus::VERIFIED->value) {
                    return $user->hasRole([Role::OWNER->value, Role::DEVELOPER->value]);
                }

                return true;
            })
            ->values()
            ->all();
    }

    private function appendCostPreview(Cutting $cutting): void
    {
        $cutting->setAttribute('total_result_pieces', (int) $cutting->results->sum('cutting_result'));
        $cutting->setAttribute('total_material_usage', (float) $cutting->materials->sum(fn (CuttingMaterial $material) => (float) $material->material_usage));
        $cutting->setAttribute('total_material_usage_summary_formatted', $this->formatMaterialUsageSummary($cutting));

        $totalMaterialCost = $cutting->total_material_cost ?? $this->calculateTotalMaterialCost($cutting);
        $sewingCost = (int) ($cutting->sewing_cost ?? 0);
        $otherCost = (int) ($cutting->other_cost ?? 0);
        $totalProductionCost = $totalMaterialCost + $sewingCost + $otherCost;
        $costPerUnit = $cutting->cost_per_unit ?? $this->calculateCostPerUnit($cutting);

        $cutting->setAttribute('total_material_cost', $totalMaterialCost);
        $cutting->setAttribute('total_material_cost_formatted', 'Rp '.number_format($totalMaterialCost, 0, ',', '.'));
        $cutting->setAttribute('sewing_cost', $sewingCost);
        $cutting->setAttribute('sewing_cost_formatted', 'Rp '.number_format($sewingCost, 0, ',', '.'));
        $cutting->setAttribute('other_cost', $otherCost);
        $cutting->setAttribute('other_cost_formatted', 'Rp '.number_format($otherCost, 0, ',', '.'));
        $cutting->setAttribute('total_production_cost', $totalProductionCost);
        $cutting->setAttribute('total_production_cost_formatted', 'Rp '.number_format($totalProductionCost, 0, ',', '.'));
        $cutting->setAttribute('estimated_cost_per_unit', $costPerUnit);
        $cutting->setAttribute('estimated_cost_per_unit_formatted', 'Rp '.number_format($costPerUnit, 0, ',', '.'));
    }

    public function calculateTotalMaterialCost(Cutting $cutting): int
    {
        return (int) $cutting->materials->sum(fn (CuttingMaterial $material) => $material->materialCost());
    }

    public function calculateTotalProductionCost(Cutting $cutting): int
    {
        return $this->calculateTotalMaterialCost($cutting)
            + (int) ($cutting->sewing_cost ?? 0)
            + (int) ($cutting->other_cost ?? 0);
    }

    public function calculateCostPerUnit(Cutting $cutting): int
    {
        $totalPieces = (int) $cutting->results->sum('cutting_result');

        if ($totalPieces <= 0) {
            return 0;
        }

        return (int) round($this->calculateTotalProductionCost($cutting) / $totalPieces);
    }

    private function storeResultPrices(Cutting $cutting, array $resultPrices): void
    {
        $costPerUnit = (int) ($cutting->cost_per_unit ?? $this->calculateCostPerUnit($cutting));

        foreach ($resultPrices as $resultData) {
            foreach ($resultData['prices'] as $priceData) {
                CuttingResultPrice::query()->create([
                    'cutting_id' => $cutting->id,
                    'product_variant_id' => $resultData['product_variant_id'],
                    'price_type' => $priceData['type'],
                    'price' => (int) $priceData['price'],
                    'cost_per_unit' => $costPerUnit,
                ]);
            }
        }
    }
}
