<?php

namespace App\Livewire\Receptionist;

use App\Domain\Financial\Models\Installment;
use App\Domain\Financial\Models\Invoice;
use App\Domain\Financial\Models\PaymentPlan;
use App\Domain\Financial\Services\InvoiceService;
use App\Domain\Financial\Services\PaymentService;
use App\Domain\Inventory\Models\ProductInstallmentPlan;
use App\Domain\Identity\Models\Guardian;
use App\Domain\Identity\Models\Person;
use App\Domain\Identity\Models\Role;
use App\Domain\Identity\Services\EgyptianNidDecoder;
use App\Domain\Identity\Services\PersonService;
use App\Domain\Inventory\Models\Kit;
use App\Domain\Inventory\Models\Product;
use App\Domain\Participant\Models\Participant;
use App\Domain\Participant\Services\DuplicateDetectionService;
use App\Domain\Participant\Services\ParticipantService;
use App\Domain\Pricing\Models\BasePrice;
use App\Domain\Shared\DTOs\ProrationResult;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Services\PlatformFeeService;
use App\Domain\Shared\Services\ProrationService;
use App\Domain\Shared\Traits\UsesBranchScope;
use App\Domain\Training\Models\Activity;
use App\Domain\Training\Models\TrainingProgram;
use App\Domain\Training\Services\EnrollmentService;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Title;
use Livewire\Component;

#[Layout('layouts.app')]
#[Title('تسجيل جديد')]
class NewRegistrationWizard extends Component
{
    use UsesBranchScope;

    public int $currentStep = 1;
    public int $totalSteps = 7;

    // Step 1: Participant (the actual player/child) — FIRST now
    public string $participant_name_ar = '';
    public string $participant_name = '';
    public ?string $participant_date_of_birth = null;
    public string $participant_gender = 'male';
    public string $participant_phone = '';
    public string $participant_national_id = '';
    public string $participant_medical_notes = '';
    public string $membership_type = 'non_member';
    public string $membership_id = '';
    public bool $membershipIdSiblingConfirmed = false;
    public array $membershipIdSiblings = [];
    public string $participant_governorate = '';
    public bool $participant_is_foreign = false;
    public bool $participant_nid_decoded = false;
    public bool $is_free = false;

    // Step 2: Guardian info
    public string $guardian_name_ar = '';
    public string $guardian_name = '';
    public string $guardian_phone = '';
    public string $guardian_relation = 'father';
    public string $guardian_occupation = '';
    public string $guardian_workplace = '';

    // Step 3: Parent Account
    public bool $createParentAccount = true;
    public string $parentEmail = '';
    public string $parentPassword = '';
    public bool $sendParentCredentials = true;

    // Step 4: Program selection
    public ?int $selected_activity_id = null;
    public ?int $selected_program_id = null;

    // Step 5: Enrollment options
    public ?string $enrollment_start_date = null;

    // Step 6: Payment
    public bool $pay_now = false;
    public string $payment_method = 'cash';
    public string $payment_transaction_ref = '';    // for card/bank_transfer/online/cheque
    public string $payment_cheque_number = '';       // cheque only
    public string $payment_bank_name = '';           // cheque/bank_transfer
    public string $payment_notes = '';

    // Partial payment
    public bool $partial_payment = false;
    public string $partial_amount_input = '';        // EGP string from user (converted to piasters on save)

    // Split payment (two methods)
    public bool $split_payment = false;
    public string $split_method2 = 'cash';
    public string $split_amount1_input = '';         // amount for method 1
    public string $split_transaction_ref2 = '';
    public string $split_cheque_number2 = '';
    public string $split_bank_name2 = '';

    // Super-admin price override
    public bool $priceOverrideEnabled = false;
    public string $priceOverrideInput = '';
    public string $priceOverrideReason = '';

    // Guardian search (existing parent)
    public bool $searchExistingGuardian = false;
    public string $guardianSearchQuery = '';
    public array $guardianSearchResults = [];
    public bool $guardianSelected = false;

    // Duplicate detection
    public array $potentialDuplicates = [];
    public bool $duplicateCheckDone = false;
    public ?int $useExistingPersonId = null;

    // Result
    public bool $completed = false;
    public ?string $participant_number = null;
    public ?string $participant_uuid = null;
    public ?string $enrollment_summary = null;
    public ?int $invoice_amount = null;
    public ?string $invoice_number = null;
    // Locked: this id is what invoiceForPrint() renders, line items included.
    // Unlocked it was a printable copy of any invoice, on request.
    #[Locked]
    public ?int $invoiceId = null;
    public ?string $invoice_uuid = null;
    public bool $payment_recorded = false;

    // Hot-buy items (optional products/kits sold alongside registration)
    public string $hotbuy_search = '';

    // Locked, both. Every entry in the cart is built server-side by
    // addHotbuyItem(), which resolves the product or kit through Eloquent —
    // Product carries BranchScope, so another branch's stock never reaches the
    // cart — and reads the price off the record via priceForTier(). Unlocked,
    // the cart is a plain browser array: confirm() writes cartItem['id'] into
    // invoice_items.itemable_id and cartItem['price'] into unit_price, so the
    // page could both sell another branch's product and name its own price.
    // The blade only reads these two (wire:click handlers do the mutating), so
    // locking them costs nothing.
    #[Locked]
    public array $hotbuyCart = [];

    #[Locked]
    public array $hotbuyInstallmentsToPay = []; // key => number of installments to pay now

    // Pre-flight errors — blocks wizard from starting
    public array $systemErrors = [];

    public function mount(): void
    {
        $this->authorize('participants.create');

        // A registration desk stands in one branch. This used to fall back to
        // "the first active branch" when none was resolved, which filed the
        // whole registration — participant, invoice, payment — into whichever
        // branch happened to sort first. Refuse instead, and do not keep the id
        // on the component: a public property holding a branch is settable from
        // the browser, and this one decided where a player was filed.
        $this->getActiveBranchIdOrFail();

        $this->runPreflightChecks();
    }

    private function runPreflightChecks(): void
    {
        $errors = [];

        if (!app('current_academy')) {
            $errors[] = 'لا توجد أكاديمية مفعّلة — تواصل مع المسؤول';
        }

        $hasActivities = Activity::where('is_active', true)->exists();
        if (!$hasActivities) {
            $errors[] = 'لا توجد أنشطة مفعّلة — يجب إنشاء نشاط واحد على الأقل';
        }

        // No branch predicate on the groups: TrainingGroup is strictly scoped,
        // so whereHas already asks "a group of this programme, in this branch".
        // The old `OR branch_id IS NULL` was meaningless there — a group with no
        // branch belongs to no branch at all.
        $hasPrograms = TrainingProgram::where('status', 'active')
            ->whereHas('groups', fn ($q) => $q->whereIn('status', ['active', 'forming']))
            ->exists();
        if (!$hasPrograms) {
            $errors[] = 'لا توجد برامج متاحة بها مجموعات نشطة في هذا الفرع';
        }

        $hasPrices = BasePrice::where('is_active', true)
            ->where('priceable_type', TrainingProgram::class)
            ->where('effective_from', '<=', now())
            ->where(fn ($q) => $q->whereNull('effective_to')->orWhere('effective_to', '>=', now()))
            ->exists();
        if (!$hasPrices) {
            $errors[] = 'لا توجد أسعار محددة لأي برنامج — يجب تحديد سعر أساسي أولاً';
        }

        $this->systemErrors = $errors;
    }

    // --- NID decode hooks ---

    public function updatedParticipantNationalId(): void
    {
        if ($this->participant_is_foreign) {
            return;
        }

        $nid = trim($this->participant_national_id);
        if (strlen($nid) !== 14) {
            $this->participant_nid_decoded = false;
            return;
        }

        $result = app(EgyptianNidDecoder::class)->decode($nid);

        if (!$result['valid']) {
            $this->participant_nid_decoded = false;
            return;
        }

        $this->participant_date_of_birth = $result['birth_date'];
        $this->participant_gender = $result['gender'];
        $this->participant_governorate = $result['governorate_ar'] ?? '';
        $this->participant_nid_decoded = true;
    }

    public function updatedParticipantIsForeign(): void
    {
        if ($this->participant_is_foreign) {
            // Unlock fields when switching to foreign
            $this->participant_nid_decoded = false;
        } else {
            // Re-decode if NID already filled
            $this->updatedParticipantNationalId();
        }
    }

    // --- Guardian name auto-fill from player name ---

    public function updatedParticipantNameAr(): void
    {
        $this->autoFillGuardianName();
    }

    private function autoFillGuardianName(): void
    {
        if (empty($this->participant_name_ar) || $this->guardianSelected) {
            return;
        }

        $parts = array_values(array_filter(explode(' ', trim($this->participant_name_ar))));

        if (count($parts) >= 2) {
            $this->guardian_name_ar = implode(' ', array_slice($parts, 1));
        }
    }

    public function confirmMembershipSibling(): void
    {
        $this->membershipIdSiblingConfirmed = true;
        $this->membershipIdSiblings = [];
        $this->nextStep();
    }

    public function cancelMembershipSibling(): void
    {
        $this->membershipIdSiblings = [];
        $this->membership_id = '';
    }

    public function updatedMembershipId(): void
    {
        $this->membershipIdSiblingConfirmed = false;
        $this->membershipIdSiblings = [];
    }

    // --- Step navigation ---

    public function nextStep(): void
    {
        $rules = $this->rulesForStep($this->currentStep);

        if (!empty($rules)) {
            $this->validate($rules, $this->messages());
        }

        // Membership ID sibling check on step 1
        if ($this->currentStep === 1 && $this->membership_type === 'member' && $this->membership_id && !$this->membershipIdSiblingConfirmed) {
            // Participant carries BranchScope; the branch predicate this query
            // used to repeat is applied for it.
            $existingSiblings = Participant::where('membership_id', $this->membership_id)
                ->whereNull('deleted_at')
                ->with('person')
                ->get();

            if ($existingSiblings->isNotEmpty()) {
                $this->membershipIdSiblings = $existingSiblings->map(fn ($p) => [
                    'id' => $p->id,
                    'name' => $p->person?->name_ar ?? '-',
                ])->toArray();
                return;
            }
        }

        // Duplicate check on step 2 (guardian) — if duplicates found, stay on step 2
        if ($this->currentStep === 2 && !$this->duplicateCheckDone) {
            $this->checkDuplicates();
            return;
        }

        // Pre-fill parent email from guardian person data when moving to step 3
        if ($this->currentStep === 2 && $this->duplicateCheckDone && empty($this->parentEmail)) {
            if ($this->useExistingPersonId) {
                $existingPerson = Person::find($this->useExistingPersonId);
                if ($existingPerson && $existingPerson->email) {
                    $this->parentEmail = $existingPerson->email;
                }
            }
        }

        // Price guard on step 4 — block if no base price for selected program (skip for free players)
        if ($this->currentStep === 4 && $this->selected_program_id && !$this->is_free) {
            $program = TrainingProgram::find($this->selected_program_id);
            if ($program && $this->resolveProgramBasePrice($program) === 0) {
                $this->addError('selected_program_id', 'لا يوجد سعر محدد لهذا البرنامج — تواصل مع المسؤول');
                return;
            }
        }

        $this->currentStep = min($this->currentStep + 1, $this->totalSteps);
    }

    private function rulesForStep(int $step): array
    {
        return match ($step) {
            1 => [
                'participant_name_ar' => 'required|string|max:255',
                'participant_date_of_birth' => 'required|date|before:today',
                'participant_gender' => 'required|in:male,female',
                'participant_phone' => 'nullable|string|max:20',
                'participant_national_id' => 'nullable|string|max:' . ($this->participant_is_foreign ? '30' : '14'),
                'participant_medical_notes' => 'nullable|string|max:1000',
                'membership_type' => 'required|in:member,non_member',
                'membership_id' => 'required_if:membership_type,member|nullable|string|max:50',
            ],
            2 => [
                'guardian_name_ar' => 'required|string|max:255',
                'guardian_phone' => 'required|string|max:20',
                'guardian_relation' => 'required|in:father,mother,brother,sister,uncle,aunt,grandfather,grandmother,guardian,other',
                'guardian_occupation' => 'nullable|string|max:150',
                'guardian_workplace' => 'nullable|string|max:150',
            ],
            3 => $this->createParentAccount ? [
                'parentEmail' => 'required|email|max:255|unique:users,email',
                'parentPassword' => 'required|string|min:6|max:100',
            ] : [],
            4 => [
                // activities are academy-level: they carry no branch.
                'selected_activity_id' => 'required|exists:activities,id',
                // `exists:` is a raw table query that no Eloquent global scope
                // reaches, and selected_program_id is a public property. The
                // dropdown is filtered; without this rule the dropdown was the
                // only thing standing between a foreign programme and an
                // enrolment. A branch runs its own programmes, so only this
                // branch's pass.
                'selected_program_id' => [
                    'required',
                    Rule::exists('training_programs', 'id')->where(
                        fn ($q) => $q->where('branch_id', $this->getActiveBranchIdOrFail())
                            
                    ),
                ],
            ],
            6 => $this->paymentStepRules(),
            default => [],
        };
    }

    public function messages(): array
    {
        return [
            'participant_name_ar.required' => 'اسم المشترك مطلوب',
            'participant_date_of_birth.required' => 'تاريخ الميلاد مطلوب',
            'participant_date_of_birth.before' => 'تاريخ الميلاد يجب أن يكون في الماضي',
            'participant_gender.required' => 'الجنس مطلوب',
            'participant_gender.in' => 'الجنس غير صالح',
            'membership_type.required' => 'نوع العضوية مطلوب',
            'membership_type.in' => 'نوع العضوية غير صالح',
            'membership_id.required_if' => 'رقم عضوية النادي مطلوب للأعضاء',
            'guardian_name_ar.required' => 'اسم ولي الأمر مطلوب',
            'guardian_phone.required' => 'رقم الهاتف مطلوب',
            'guardian_relation.required' => 'صلة القرابة مطلوبة',
            'guardian_relation.in' => 'صلة القرابة غير صالحة',
            'parentEmail.required' => 'البريد الإلكتروني مطلوب لإنشاء الحساب',
            'parentEmail.email' => 'صيغة البريد الإلكتروني غير صالحة',
            'parentEmail.unique' => 'البريد الإلكتروني مستخدم بالفعل',
            'parentPassword.required' => 'كلمة المرور مطلوبة',
            'parentPassword.min' => 'كلمة المرور يجب أن تكون 6 أحرف على الأقل',
            'selected_activity_id.required' => 'يرجى اختيار النشاط',
            'selected_activity_id.exists' => 'النشاط المختار غير موجود',
            'selected_program_id.required' => 'يرجى اختيار البرنامج',
            'selected_program_id.exists' => 'البرنامج المختار غير موجود',
            'payment_method.required_if'          => 'يرجى اختيار طريقة الدفع',
            'payment_method.in'                    => 'طريقة الدفع غير صالحة',
            'payment_transaction_ref.required_if'  => 'رقم المرجع / رقم العملية مطلوب لهذه الطريقة',
            'payment_cheque_number.required_if'    => 'رقم الشيك مطلوب',
            'partial_amount_input.required_if'     => 'المبلغ المدفوع مطلوب',
            'partial_amount_input.numeric'         => 'المبلغ يجب أن يكون رقماً',
            'partial_amount_input.min'             => 'المبلغ يجب أن يكون أكبر من صفر',
            'split_amount1_input.required_if'      => 'مبلغ الطريقة الأولى مطلوب',
            'split_amount1_input.numeric'          => 'المبلغ يجب أن يكون رقماً',
            'split_amount1_input.min'              => 'المبلغ يجب أن يكون أكبر من صفر',
            'split_method2.required_if'            => 'يرجى اختيار طريقة الدفع الثانية',
        ];
    }

    private function paymentStepRules(): array
    {
        if (!$this->pay_now) return [];

        $methodsRequiringRef = ['card', 'bank_transfer', 'online'];
        $rules = [
            'payment_method' => 'required|in:cash,card,bank_transfer,wallet,online,cheque,other',
        ];

        if (in_array($this->payment_method, $methodsRequiringRef)) {
            $rules['payment_transaction_ref'] = 'required|string|max:100';
        }
        if ($this->payment_method === 'cheque') {
            $rules['payment_cheque_number'] = 'required|string|max:50';
        }
        if ($this->partial_payment && !$this->split_payment) {
            $rules['partial_amount_input'] = 'required|numeric|min:0.01';
        }
        if ($this->split_payment) {
            $rules['split_amount1_input'] = 'required|numeric|min:0.01';
            $rules['split_method2'] = 'required|in:cash,card,bank_transfer,wallet,online,cheque,other';
            if (in_array($this->split_method2, $methodsRequiringRef)) {
                $rules['split_transaction_ref2'] = 'required|string|max:100';
            }
            if ($this->split_method2 === 'cheque') {
                $rules['split_cheque_number2'] = 'required|string|max:50';
            }
        }
        return $rules;
    }

    public function updatedPayNow(): void
    {
        if (!$this->pay_now) {
            $this->partial_payment = false;
            $this->split_payment = false;
        }
    }

    public function updatedPartialPayment(): void
    {
        if ($this->partial_payment) {
            $this->split_payment = false;
        }
    }

    public function updatedSplitPayment(): void
    {
        if ($this->split_payment) {
            $this->partial_payment = false;
            // Pre-fill split amount 1 with half the total as a starting point
            if (empty($this->split_amount1_input) && $this->effectiveTotal > 0) {
                $this->split_amount1_input = number_format($this->effectiveTotal / 100 / 2, 2);
            }
        }
    }

    public function checkDuplicates(): void
    {
        $service = app(DuplicateDetectionService::class);

        $matches = $service->findPotentialDuplicates(
            name: $this->guardian_name_ar,
            phone: $this->guardian_phone ?: null,
        );

        $highConfidence = $matches->filter(fn ($m) => $m['confidence'] >= 80);

        $this->potentialDuplicates = $highConfidence->map(fn ($m) => [
            'person_id' => $m['person']->id,
            'name' => $m['person']->name_ar ?: $m['person']->name,
            'phone' => $m['person']->phone,
            'confidence' => $m['confidence'],
            'match_reason' => $m['match_reason'],
            'has_participant' => $m['person']->participant !== null,
            'participant_number' => $m['person']->participant?->participant_number,
        ])->toArray();

        $this->duplicateCheckDone = true;

        if (empty($this->potentialDuplicates)) {
            $this->currentStep = min($this->currentStep + 1, $this->totalSteps);
        }
    }

    public function proceedDespiteDuplicates(): void
    {
        $this->potentialDuplicates = [];
        $this->currentStep = min($this->currentStep + 1, $this->totalSteps);
    }

    public function useExistingPerson(int $personId): void
    {
        $this->useExistingPersonId = $personId;
        $this->potentialDuplicates = [];
        $this->currentStep = min($this->currentStep + 1, $this->totalSteps);
    }

    public function updatedGuardianSearchQuery(): void
    {
        if (strlen($this->guardianSearchQuery) < 2) {
            $this->guardianSearchResults = [];
            return;
        }

        $search = $this->guardianSearchQuery;
        $this->guardianSearchResults = Guardian::with('person')
            ->whereHas('person', function ($q) use ($search) {
                $q->where('name_ar', 'ilike', "%{$search}%")
                  ->orWhere('phone', 'like', "%{$search}%");
            })
            ->limit(10)
            ->get()
            ->map(fn ($g) => [
                'id' => $g->id,
                'person_id' => $g->person_id,
                'name' => $g->person?->name_ar ?? $g->person?->name,
                'phone' => $g->person?->phone,
                'relation' => $g->relation_type,
                'children_count' => $g->participants()->count(),
            ])
            ->toArray();
    }

    public function selectExistingGuardian(int $guardianId): void
    {
        $guardian = Guardian::with('person')->find($guardianId);
        if (!$guardian) {
            return;
        }

        $this->useExistingPersonId = $guardian->person_id;
        $this->guardian_name_ar = $guardian->person->name_ar ?? '';
        $this->guardian_name = $guardian->person->name ?? '';
        $this->guardian_phone = $guardian->person->phone ?? '';
        $this->guardian_relation = $guardian->relation_type ?? 'father';
        $this->guardian_occupation = $guardian->occupation ?? '';
        $this->guardian_workplace = $guardian->workplace ?? '';
        $this->guardianSelected = true;
        $this->guardianSearchResults = [];
    }

    public function clearSelectedGuardian(): void
    {
        $this->useExistingPersonId = null;
        $this->guardian_name_ar = '';
        $this->guardian_name = '';
        $this->guardian_phone = '';
        $this->guardian_relation = 'father';
        $this->guardian_occupation = '';
        $this->guardian_workplace = '';
        $this->guardianSelected = false;
        $this->searchExistingGuardian = false;
        $this->guardianSearchQuery = '';
    }

    public function previousStep(): void
    {
        $this->currentStep = max($this->currentStep - 1, 1);
    }

    public function goToStep(int $step): void
    {
        if ($step < $this->currentStep) {
            $this->currentStep = $step;
        }
    }

    public function updatedGuardianNameAr(): void
    {
        $this->resetDuplicateCheck();
    }

    public function updatedGuardianPhone(): void
    {
        $this->resetDuplicateCheck();
    }

    private function resetDuplicateCheck(): void
    {
        $this->duplicateCheckDone = false;
        $this->potentialDuplicates = [];
        $this->useExistingPersonId = null;
    }

    public function generateParentPassword(): void
    {
        $this->parentPassword = Str::random(8);
    }

    public function updatedSelectedActivityId(): void
    {
        $this->selected_program_id = null;
    }

    // --- Price override (super admin only) ---

    public function updatedPriceOverrideEnabled(): void
    {
        if (!$this->priceOverrideEnabled) {
            $this->priceOverrideInput = '';
            $this->priceOverrideReason = '';
        }
    }

    // Returns the effective total in piasters, respecting override
    #[Computed]
    public function effectiveTotal(): int
    {
        if ($this->priceOverrideEnabled && $this->priceOverrideInput !== '') {
            $overridePiasters = (int) round((float) $this->priceOverrideInput * 100);
            return max(0, $overridePiasters);
        }
        return $this->totalWithFee;
    }

    // --- Hot-buy methods ---

    #[Computed]
    public function essentialProducts(): array
    {
        $tier = $this->membership_type;

        return Product::where('is_active', true)
            ->where('is_essential', true)
            ->with(['installmentPlans' => fn ($q) => $q->active()->forTier($tier)->orderBy('sort_order')])
            ->orderBy('sort_order')
            ->orderBy('name_ar')
            ->get()
            ->map(fn ($p) => [
                'id'           => $p->id,
                'type'         => 'product',
                'name_ar'      => $p->name_ar,
                'name'         => $p->name,
                'sku'          => $p->sku,
                'billing_cycle'=> $p->billing_cycle,
                'price'        => $p->priceForTier($tier),
                'member_price' => $p->member_price,
                'non_member_price' => $p->non_member_price,
                'selling_price'=> $p->selling_price,
                'plans'        => $p->installmentPlans->map(fn ($pl) => [
                    'id'                  => $pl->id,
                    'label_ar'            => $pl->label_ar,
                    'installments'        => $pl->installments,
                    'frequency'           => $pl->frequency,
                    'frequency_label'     => $pl->frequencyLabel(),
                    'down_payment_pct'    => $pl->down_payment_pct,
                    'installment_amounts' => $pl->installment_amounts ?? [],
                ])->toArray(),
            ])->toArray();
    }

    #[Computed]
    public function hotbuyResults(): array
    {
        if (strlen($this->hotbuy_search) < 2) {
            return [];
        }

        $search = $this->hotbuy_search;

        $tier = $this->membership_type;

        $products = Product::where('is_active', true)
            ->where(fn ($q) => $q->where('name_ar', 'ilike', "%{$search}%")
                ->orWhere('name', 'ilike', "%{$search}%")
                ->orWhere('sku', 'ilike', "%{$search}%")
                ->orWhere('barcode', $search))
            ->with(['installmentPlans' => fn ($q) => $q->active()->forTier($tier)->orderBy('sort_order')])
            ->limit(5)
            ->get()
            ->map(fn ($p) => [
                'id'            => $p->id,
                'type'          => 'product',
                'name_ar'       => $p->name_ar,
                'name'          => $p->name,
                'sku'           => $p->sku,
                'billing_cycle' => $p->billing_cycle ?? 'one_time',
                'price'         => $p->priceForTier($tier),
                'plans'         => $p->installmentPlans->map(fn ($pl) => [
                    'id'                  => $pl->id,
                    'label_ar'            => $pl->label_ar,
                    'installments'        => $pl->installments,
                    'frequency'           => $pl->frequency,
                    'frequency_label'     => $pl->frequencyLabel(),
                    'down_payment_pct'    => $pl->down_payment_pct,
                    'installment_amounts' => $pl->installment_amounts ?? [],
                ])->toArray(),
            ])->toArray();

        $kits = Kit::where('is_active', true)
            ->where(fn ($q) => $q->where('name_ar', 'ilike', "%{$search}%")
                ->orWhere('name', 'ilike', "%{$search}%")
                ->orWhere('sku', 'ilike', "%{$search}%"))
            ->limit(5)
            ->get()
            ->map(fn ($k) => [
                'id' => $k->id,
                'type' => 'kit',
                'name_ar' => $k->name_ar,
                'name' => $k->name,
                'sku' => $k->sku,
                'price' => $k->selling_price,
            ])->toArray();

        return array_merge($products, $kits);
    }

    public function addHotbuyItem(int $id, string $type): void
    {
        $key = "{$type}_{$id}";

        if (isset($this->hotbuyCart[$key])) {
            $this->hotbuyCart[$key]['quantity']++;
        } else {
            $item = $type === 'product'
                ? Product::find($id)
                : Kit::find($id);

            if (!$item) {
                return;
            }

            $price = ($type === 'product')
                ? $item->priceForTier($this->membership_type)
                : $item->selling_price;

            $plans = [];
            if ($type === 'product' && ($item->billing_cycle ?? 'one_time') === 'annual') {
                $tier = $this->membership_type;
                $plans = $item->installmentPlans()
                    ->active()
                    ->forTier($tier)
                    ->orderBy('sort_order')
                    ->get()
                    ->map(fn ($pl) => [
                        'id'               => $pl->id,
                        'label_ar'         => $pl->label_ar,
                        'installments'     => $pl->installments,
                        'frequency'        => $pl->frequency,
                        'frequency_label'  => $pl->frequencyLabel(),
                        'down_payment_pct' => $pl->down_payment_pct,
                    ])->toArray();
            }

            $this->hotbuyCart[$key] = [
                'id'            => $item->id,
                'type'          => $type,
                'name_ar'       => $item->name_ar,
                'price'         => $price,
                'billing_cycle' => $type === 'product' ? ($item->billing_cycle ?? 'one_time') : 'one_time',
                'quantity'      => 1,
                'plan_id'       => null,
                'plans'         => $plans,
            ];
        }

        $this->hotbuy_search = '';
    }

    public function setHotbuyPlan(string $key, ?int $planId): void
    {
        if (isset($this->hotbuyCart[$key])) {
            $this->hotbuyCart[$key]['plan_id'] = $planId;
            // Reset installments-to-pay to 1 (down payment) when plan changes
            if ($planId) {
                $this->hotbuyInstallmentsToPay[$key] = 1;
            } else {
                unset($this->hotbuyInstallmentsToPay[$key]);
            }
        }
    }

    public function setInstallmentsToPay(string $key, int $count): void
    {
        if (!isset($this->hotbuyCart[$key]) || empty($this->hotbuyCart[$key]['plan_id'])) {
            return;
        }
        $planId = $this->hotbuyCart[$key]['plan_id'];
        $plan = ProductInstallmentPlan::find($planId);
        if (!$plan) {
            return;
        }
        $this->hotbuyInstallmentsToPay[$key] = max(1, min($count, $plan->installments));
    }

    public function removeHotbuyItem(string $key): void
    {
        unset($this->hotbuyCart[$key]);
        unset($this->hotbuyInstallmentsToPay[$key]);
    }

    public function updateHotbuyQuantity(string $key, int $quantity): void
    {
        if ($quantity <= 0) {
            unset($this->hotbuyCart[$key]);
            return;
        }
        if (isset($this->hotbuyCart[$key])) {
            $this->hotbuyCart[$key]['quantity'] = $quantity;
        }
    }

    #[Computed]
    public function hotbuyTotal(): int
    {
        return collect($this->hotbuyCart)->sum(fn ($item) => $item['price'] * $item['quantity']);
    }

    #[Computed]
    public function hotbuyDueNow(): int
    {
        $total = 0;
        foreach ($this->hotbuyCart as $key => $item) {
            $itemTotal = $item['price'] * $item['quantity'];
            if (!empty($item['plan_id']) && isset($this->hotbuyInstallmentsToPay[$key])) {
                $plan = ProductInstallmentPlan::find($item['plan_id']);
                if ($plan) {
                    $schedule = $plan->buildSchedule($itemTotal);
                    $toPay = min($this->hotbuyInstallmentsToPay[$key], count($schedule));
                    $total += array_sum(array_slice($schedule, 0, $toPay));
                    continue;
                }
            }
            $total += $itemTotal;
        }
        return $total;
    }

    // --- Proration ---

    #[Computed]
    public function proratedProgramFee(): ProrationResult
    {
        $baseFee = $this->selectedProgramFee;
        $service = app(ProrationService::class);
        if (!$service->isEnabled() || $baseFee === 0) {
            return new ProrationResult(
                applied: false,
                originalAmount: $baseFee,
                proratedAmount: $baseFee,
                remainingDays: 30,
                renewalDay: $service->renewalDay(),
                description: '',
            );
        }
        $startDate = $this->enrollment_start_date ? \Carbon\Carbon::parse($this->enrollment_start_date) : null;
        return $service->calculate($baseFee, $startDate);
    }

    #[Computed]
    public function selectedProgram(): ?TrainingProgram
    {
        if (!$this->selected_program_id) {
            return null;
        }
        return TrainingProgram::with('activity')->find($this->selected_program_id);
    }

    #[Computed]
    public function selectedProgramFee(): int
    {
        if (!$this->selected_program_id) {
            return 0;
        }
        $program = TrainingProgram::find($this->selected_program_id);
        return $program ? $this->resolveProgramFee($program) : 0;
    }

    #[Computed]
    public function platformFee(): int
    {
        $service = app(PlatformFeeService::class);
        if (!$service->customerPays()) {
            return 0;
        }
        $subtotal = $this->proratedProgramFee->proratedAmount + $this->hotbuyDueNow;
        return $service->calculate($subtotal);
    }

    #[Computed]
    public function totalWithFee(): int
    {
        return $this->proratedProgramFee->proratedAmount + $this->hotbuyDueNow + $this->platformFee;
    }

    #[Computed]
    public function invoiceForPrint(): ?Invoice
    {
        if (!$this->invoiceId) {
            return null;
        }
        return Invoice::with('items')->find($this->invoiceId);
    }

    public function confirm(): void
    {
        try {
            DB::transaction(function () {
                $actor = auth()->user();
                $personService = app(PersonService::class);
                $participantService = app(ParticipantService::class);
                $enrollmentService = app(EnrollmentService::class);
                $invoiceService = app(InvoiceService::class);
                $paymentService = app(PaymentService::class);

                // 1. Create or reuse guardian's Person record
                if ($this->useExistingPersonId) {
                    $guardianPerson = Person::findOrFail($this->useExistingPersonId);
                } else {
                    $guardianPerson = Person::where('phone', $this->guardian_phone)->first();
                    if (!$guardianPerson) {
                        $guardianPerson = $personService->create([
                            'name_ar' => $this->guardian_name_ar,
                            'name' => $this->guardian_name ?: $this->guardian_name_ar,
                            'phone' => $this->guardian_phone,
                        ], $actor);
                    }
                }

                // 2. Create or find the Guardian record
                $guardian = Guardian::firstOrCreate(
                    ['person_id' => $guardianPerson->id],
                    [
                        'academy_id' => app('current_academy')->id,
                        'relationship_type' => $this->guardian_relation,
                        'occupation' => $this->guardian_occupation ?: null,
                        'workplace' => $this->guardian_workplace ?: null,
                        'is_emergency_contact' => true,
                        'is_financial_responsible' => true,
                        'can_pickup' => true,
                    ]
                );
                // Update occupation/workplace if guardian already existed
                if (!$guardian->wasRecentlyCreated && ($this->guardian_occupation || $this->guardian_workplace)) {
                    $guardian->update([
                        'occupation' => $this->guardian_occupation ?: $guardian->occupation,
                        'workplace' => $this->guardian_workplace ?: $guardian->workplace,
                    ]);
                }

                // 2b. Create parent User account if requested
                if ($this->createParentAccount && $this->parentEmail) {
                    $parentRole = Role::where('slug', 'parent')
                        ->where('academy_id', app('current_academy')->id)
                        ->first();

                    if (!$guardian->user_id) {
                        $parentUser = User::create([
                            'academy_id' => app('current_academy')->id,
                            'name' => $guardianPerson->name ?: $guardianPerson->name_ar,
                            'name_ar' => $guardianPerson->name_ar,
                            'email' => $this->parentEmail,
                            'phone' => $guardianPerson->phone,
                            'password' => Hash::make($this->parentPassword),
                            'person_id' => $guardianPerson->id,
                            'role_id' => $parentRole?->id,
                            'status' => 'active',
                        ]);

                        $guardian->update(['user_id' => $parentUser->id]);
                        $guardianPerson->update(['user_id' => $parentUser->id]);

                        if ($parentRole) {
                            $parentUser->roles()->attach($parentRole->id, [
                                'assigned_by' => $actor->id,
                                'created_at' => now(),
                            ]);
                        }
                    }
                }

                // 3. Create participant's Person record
                $participantPerson = $personService->create([
                    'name_ar' => $this->participant_name_ar,
                    'name' => $this->participant_name ?: $this->participant_name_ar,
                    'date_of_birth' => $this->participant_date_of_birth,
                    'gender' => $this->participant_gender,
                    'phone' => $this->participant_phone ?: null,
                    'national_id' => $this->participant_national_id ?: null,
                    'governorate' => $this->participant_governorate ?: null,
                    'medical_notes' => $this->participant_medical_notes ?: null,
                ], $actor);

                // 4. Create Participant record via service
                $participant = $participantService->register([
                    'person_id' => $participantPerson->id,
                    'branch_id' => $this->getActiveBranchIdOrFail(),
                    'registration_source' => 'walk_in',
                    'primary_guardian_id' => $guardian->id,
                    'primary_activity_id' => $this->selected_activity_id,
                    'membership_type' => $this->membership_type,
                    'membership_id' => $this->membership_type === 'member' ? $this->membership_id : null,
                    'is_free' => $this->is_free,
                ], $actor);

                // 5. Link guardian to participant via pivot
                $participant->guardians()->attach($guardian->id, [
                    'relationship_type' => $this->guardian_relation,
                    'is_primary' => true,
                    'is_emergency_contact' => true,
                    'can_pickup' => true,
                    'receives_notifications' => true,
                    'can_authorize_payment' => true,
                ]);

                // 6. Enroll in program
                $program = TrainingProgram::findOrFail($this->selected_program_id);
                $enrollment = $enrollmentService->enrollInProgram(
                    $participant,
                    $program,
                    $actor
                );

                // 7. Resolve program fee with optional proration, hot-buy, platform fee
                $prorationResult = $this->proratedProgramFee;
                $programFee = $prorationResult->proratedAmount;
                $hotbuyTotal = $this->hotbuyTotal;
                $subtotal = $programFee + $hotbuyTotal;

                $platformFeeService = app(PlatformFeeService::class);
                $customerPays = $platformFeeService->customerPays();
                $serviceFee = $customerPays ? $platformFeeService->calculate($subtotal) : 0;
                $computedTotal = $subtotal + $serviceFee;

                // Super-admin override
                $finalTotal = $computedTotal;
                if ($this->priceOverrideEnabled && $this->priceOverrideInput !== '' && $actor->is_super_admin) {
                    $overrideAmount = (int) round((float) $this->priceOverrideInput * 100);
                    $finalTotal = max(0, $overrideAmount);

                    // Audit the override
                    \Log::channel('audit')->info('super_admin_price_override', [
                        'actor_id' => $actor->id,
                        'actor_name' => $actor->name,
                        'participant_name' => $this->participant_name_ar,
                        'program' => $program->name_ar,
                        'original_piasters' => $computedTotal,
                        'override_piasters' => $finalTotal,
                        'reason' => $this->priceOverrideReason,
                    ]);
                }

                // 8. Create invoice if there's a fee or hot-buy items (skip for free players)
                $invoice = null;
                if (!$this->is_free && ($subtotal > 0 || $finalTotal !== $computedTotal)) {
                    $invoiceItems = [];

                    // When override is active, compute how much the program fee is adjusted.
                    // The override replaces the entire order total, so the delta comes off the program item.
                    $effectiveProgramFee = $programFee;
                    $programItemMeta = [];
                    if ($this->priceOverrideEnabled && $actor->is_super_admin && $finalTotal !== $computedTotal && $programFee > 0) {
                        // Override delta = difference between computed and final totals (hotbuy items are untouched)
                        $overrideDelta    = $finalTotal - $computedTotal; // negative = discount, positive = surcharge
                        $effectiveProgramFee = max(0, $programFee + $overrideDelta);
                        $programItemMeta = [
                            'original_price'  => $programFee,           // per-item original, not order total
                            'overridden_price' => $effectiveProgramFee,
                            'override_reason'  => $this->priceOverrideReason,
                            'overridden_by'    => $actor->name,
                        ];
                    }

                    if ($programFee > 0) {
                        $description = $program->name_ar;
                        if ($prorationResult->applied) {
                            $description .= ' (' . $prorationResult->description . ')';
                        }
                        $invoiceItems[] = [
                            'description'     => $description,
                            'quantity'        => 1,
                            'unit_price'      => $effectiveProgramFee,  // ← overridden value, not original
                            'discount_amount' => 0,
                            'tax_amount'      => 0,
                            'metadata'        => $programItemMeta ?: [],
                        ];
                    }

                    foreach ($this->hotbuyCart as $cartItem) {
                        $itemEntry = [
                            'description'     => $cartItem['name_ar'],
                            'quantity'        => $cartItem['quantity'],
                            'unit_price'      => $cartItem['price'],
                            'discount_amount' => 0,
                            'tax_amount'      => 0,
                        ];
                        if ($cartItem['type'] === 'product') {
                            $itemEntry['itemable_type'] = Product::class;
                            $itemEntry['itemable_id'] = $cartItem['id'];
                        } elseif ($cartItem['type'] === 'kit') {
                            $itemEntry['itemable_type'] = Kit::class;
                            $itemEntry['itemable_id'] = $cartItem['id'];
                        }
                        $invoiceItems[] = $itemEntry;
                    }

                    // No separate discount line needed — the program item already carries the overridden price,
                    // so recalculateTotals() will sum to $finalTotal correctly.
                    $discountAmount = 0;

                    $invoice = $invoiceService->create([
                        'academy_id' => app('current_academy')->id,
                        'number' => $invoiceService->generateNumber(app('current_academy')->id),
                        'type' => 'standard',
                        'billable_type' => Participant::class,
                        'billable_id' => $participant->id,
                        'contact_name' => $participant->person?->name_ar ?? $this->guardian_name_ar,
                        'contact_phone' => $participant->person?->phone ?? $this->guardian_phone,
                        'subtotal_amount' => $subtotal,
                        'discount_amount' => $discountAmount,
                        'tax_amount' => 0,
                        'service_fee_amount' => $serviceFee,
                        'total_amount' => $finalTotal,
                        'currency' => 'EGP',
                        'issue_date' => now()->toDateString(),
                        'due_date' => now()->addDays(7)->toDateString(),
                        'notes' => 'اشتراك: ' . $program->name_ar,
                    ], $invoiceItems, $actor);

                    $invoice->update(['status' => 'sent']);
                    $enrollment->update(['invoice_id' => $invoice->id]);

                    $this->invoice_amount = $invoice->total_amount;
                    $this->invoice_number = $invoice->number;
                    $this->invoiceId = $invoice->id;

                    // 8b. Create installment payment plans for annual products
                    foreach ($this->hotbuyCart as $cartKey => $cartItem) {
                        if (empty($cartItem['plan_id'])) {
                            continue;
                        }
                        $planTemplate = ProductInstallmentPlan::find($cartItem['plan_id']);
                        if (!$planTemplate) {
                            continue;
                        }

                        $itemTotal = $cartItem['price'] * $cartItem['quantity'];
                        $schedule = $planTemplate->buildSchedule($itemTotal);
                        $regularAmount = count($schedule) > 1 ? ($schedule[1] ?? $schedule[0]) : $schedule[0];
                        $installmentsToPay = $this->hotbuyInstallmentsToPay[$cartKey] ?? 1;

                        $paymentPlan = PaymentPlan::create([
                            'academy_id'         => app('current_academy')->id,
                            'invoice_id'         => $invoice->id,
                            'status'             => 'active',
                            'total_installments' => $planTemplate->installments,
                            'paid_installments'  => $this->pay_now ? $installmentsToPay : 0,
                            'installment_amount' => $regularAmount,
                            'frequency'          => $planTemplate->frequency,
                            'start_date'         => now()->toDateString(),
                            'next_due_date'      => now()->toDateString(),
                            'notes'              => $planTemplate->label_ar . ' — ' . $cartItem['name_ar'],
                        ]);

                        // Generate installment schedule using explicit or auto-calculated amounts
                        $dueDate = now();
                        foreach ($schedule as $idx => $amount) {
                            $seq = $idx + 1;
                            $isPaidNow = $this->pay_now && $seq <= $installmentsToPay;
                            Installment::create([
                                'payment_plan_id' => $paymentPlan->id,
                                'sequence'        => $seq,
                                'amount'          => $amount,
                                'due_date'        => $dueDate->toDateString(),
                                'status'          => $isPaidNow ? 'paid' : 'pending',
                                'paid_at'         => $isPaidNow ? now() : null,
                            ]);
                            $dueDate = match ($planTemplate->frequency) {
                                'weekly'    => $dueDate->addWeek(),
                                'biweekly'  => $dueDate->addWeeks(2),
                                'quarterly' => $dueDate->addMonths(3),
                                default     => $dueDate->addMonth(),
                            };
                        }

                        // Update next_due_date to the first unpaid installment
                        if ($this->pay_now && $installmentsToPay > 0) {
                            $nextDue = $paymentPlan->installments()
                                ->where('status', 'pending')
                                ->orderBy('sequence')
                                ->value('due_date');
                            if ($nextDue) {
                                $paymentPlan->update(['next_due_date' => $nextDue]);
                            }
                        }
                    }

                    // 9. Record payment(s) if paying now
                    if ($this->pay_now) {
                        $basePaymentData = [
                            'academy_id'   => app('current_academy')->id,
                            'branch_id'    => $this->getActiveBranchIdOrFail(),
                            'invoice_id'   => $invoice->id,
                            'direction'    => 'inbound',
                            'payer_type'   => Participant::class,
                            'payer_id'     => $participant->id,
                            'currency'     => 'EGP',
                            'payment_date' => now()->toDateString(),
                        ];

                        if ($this->split_payment) {
                            // Two payments: amount1 + remainder of what's due now
                            $amount1 = (int) round((float) $this->split_amount1_input * 100);
                            $amount2 = $this->effectiveTotal - $amount1;

                            if ($amount1 > 0) {
                                $paymentService->recordPayment(array_merge($basePaymentData, [
                                    'reference' => 'PAY-' . now()->format('YmdHis') . 'A-' . $participant->id,
                                    'method'    => $this->payment_method,
                                    'amount'    => $amount1,
                                    'notes'     => $this->buildPaymentNotes($this->payment_method, $this->payment_transaction_ref, $this->payment_cheque_number, $this->payment_bank_name, $program->name_ar),
                                    'gateway_data' => $this->buildGatewayData($this->payment_method, $this->payment_transaction_ref, $this->payment_cheque_number, $this->payment_bank_name),
                                ]), $actor);
                            }
                            if ($amount2 > 0) {
                                $paymentService->recordPayment(array_merge($basePaymentData, [
                                    'reference' => 'PAY-' . now()->format('YmdHis') . 'B-' . $participant->id,
                                    'method'    => $this->split_method2,
                                    'amount'    => $amount2,
                                    'notes'     => $this->buildPaymentNotes($this->split_method2, $this->split_transaction_ref2, $this->split_cheque_number2, $this->split_bank_name2, $program->name_ar),
                                    'gateway_data' => $this->buildGatewayData($this->split_method2, $this->split_transaction_ref2, $this->split_cheque_number2, $this->split_bank_name2),
                                ]), $actor);
                            }
                        } elseif ($this->partial_payment) {
                            // One partial payment — balance stays on invoice
                            $paidAmount = (int) round((float) $this->partial_amount_input * 100);
                            if ($paidAmount > 0) {
                                $paymentService->recordPayment(array_merge($basePaymentData, [
                                    'reference' => 'PAY-' . now()->format('YmdHis') . '-' . $participant->id,
                                    'method'    => $this->payment_method,
                                    'amount'    => $paidAmount,
                                    'notes'     => $this->buildPaymentNotes($this->payment_method, $this->payment_transaction_ref, $this->payment_cheque_number, $this->payment_bank_name, $program->name_ar),
                                    'gateway_data' => $this->buildGatewayData($this->payment_method, $this->payment_transaction_ref, $this->payment_cheque_number, $this->payment_bank_name),
                                ]), $actor);
                            }
                        } else {
                            // Full payment (= effectiveTotal which accounts for installment selections)
                            $fullPayAmount = $this->effectiveTotal;
                            $paymentService->recordPayment(array_merge($basePaymentData, [
                                'reference' => 'PAY-' . now()->format('YmdHis') . '-' . $participant->id,
                                'method'    => $this->payment_method,
                                'amount'    => $fullPayAmount,
                                'notes'     => $this->buildPaymentNotes($this->payment_method, $this->payment_transaction_ref, $this->payment_cheque_number, $this->payment_bank_name, $program->name_ar),
                                'gateway_data' => $this->buildGatewayData($this->payment_method, $this->payment_transaction_ref, $this->payment_cheque_number, $this->payment_bank_name),
                            ]), $actor);
                        }

                        $this->payment_recorded = true;
                        // Only mark enrollment paid if full amount covered
                        $paidSoFar = $invoice->fresh()->paid_amount ?? 0;
                        if ($paidSoFar >= $invoice->total_amount) {
                            $enrollment->update(['payment_status' => 'paid']);
                        }
                    }
                }

                $this->completed = true;
                $this->participant_number = $participant->participant_number;
                $this->participant_uuid = $participant->uuid;
                $this->enrollment_summary = $program->name_ar . ' — ' . ($enrollment->group?->name_ar ?? '');
                if ($invoice) {
                    $this->invoice_uuid = $invoice->uuid;
                }
                $this->currentStep = 7;
            });

            session()->flash('success', __('تم التسجيل بنجاح'));
        } catch (DomainException $e) {
            session()->flash('error', $e->getMessage());
        } catch (\Throwable $e) {
            session()->flash('error', 'حدث خطأ: ' . $e->getMessage());
        }
    }

    private function buildPaymentNotes(string $method, string $ref, string $cheque, string $bank, string $programName): string
    {
        $parts = ['دفع اشتراك: ' . $programName];
        if ($ref)    $parts[] = 'رقم العملية: ' . $ref;
        if ($cheque) $parts[] = 'رقم الشيك: ' . $cheque;
        if ($bank)   $parts[] = 'البنك: ' . $bank;
        if ($this->payment_notes) $parts[] = $this->payment_notes;
        return implode(' | ', $parts);
    }

    private function buildGatewayData(string $method, string $ref, string $cheque, string $bank): array
    {
        $data = [];
        if ($ref)    $data['transaction_reference'] = $ref;
        if ($cheque) $data['cheque_number'] = $cheque;
        if ($bank)   $data['bank_name'] = $bank;
        return $data;
    }

    /**
     * The price actually charged — base price WITH pricing rules applied.
     *
     * This flow used to read BasePrice directly, so no sibling discount, early
     * bird, coupon or promotion could ever apply at new registration. The
     * participant row does not exist yet, so the engine is given a provisional
     * context built from the form.
     */
    private function resolveProgramFee(TrainingProgram $program): int
    {
        try {
            return app(\App\Domain\Pricing\Services\PricingService::class)->calculate(
                priceable: $program,
                participant: null,
                branchId: $this->getActiveBranchIdOrFail(),
                date: null,
                contextOverride: $this->provisionalContext(),
            )->finalAmount;
        } catch (\App\Domain\Shared\Exceptions\DomainException $e) {
            // No active base price — the step-4 guard reports this properly.
            return 0;
        }
    }

    /**
     * Participant context derived from the form, for pricing before the row exists.
     */
    private function provisionalContext(): array
    {
        $age = $this->participant_date_of_birth
            ? (int) \Carbon\Carbon::parse($this->participant_date_of_birth)->diffInYears(now())
            : null;

        [$familySize, $siblingOrder] = $this->resolveSiblingPosition();

        return [
            'age' => $age,
            'gender' => $this->participant_gender ?: null,
            'classification' => 'regular',
            'membership_type' => $this->membership_type,
            'membership_duration_months' => 0,
            'family_size' => $familySize,
            'sibling_order' => $siblingOrder,
            'enrollment_count' => 0,
            'branch_id' => $this->getActiveBranchIdOrFail(),
        ];
    }

    /**
     * Siblings are found through the guardian's phone — the only identity we
     * have before the participant is saved.
     *
     * @return array{0:int,1:int} [family size, this child's order]
     */
    private function resolveSiblingPosition(): array
    {
        if (! $this->guardian_phone) {
            return [1, 1];
        }

        $guardian = Guardian::whereHas('person', fn ($q) => $q->where('phone', $this->guardian_phone))->first();

        if (! $guardian) {
            return [1, 1];
        }

        // The child being registered is the next one in the family.
        $existing = $guardian->participants()->count();

        return [$existing + 1, $existing + 1];
    }

    /** Base price only — used by the step-4 guard, which must not see discounts. */
    private function resolveProgramBasePrice(TrainingProgram $program): int
    {
        $query = BasePrice::where('priceable_type', TrainingProgram::class)
            ->where('priceable_id', $program->id)
            ->where('is_active', true)
            ->where('effective_from', '<=', now())
            // No forBranch(): BasePrice is a shared model, so its BranchScope
            // already reads "this branch, or the academy-wide row". Passing a
            // branch here as well would only repeat it — and passing the wrong
            // one would hide the shared price, which is a hard failure that
            // stops the sale.
            ->where(fn ($q) => $q->whereNull('effective_to')->orWhere('effective_to', '>=', now()));

        $specificPrice = (clone $query)
            ->whereJsonContains('metadata->membership_type', $this->membership_type)
            ->orderByDesc('priority')
            ->first();

        if ($specificPrice) {
            return $specificPrice->amount;
        }

        $genericPrice = $query
            ->where(fn ($q) => $q
                ->whereNull('metadata->membership_type')
                ->orWhereRaw("metadata->>'membership_type' = ''")
                ->orWhereRaw("metadata = '{}'::jsonb")
            )
            ->orderByDesc('priority')
            ->first();

        return $genericPrice?->amount ?? 0;
    }

    public function render()
    {
        $activities = Activity::where('is_active', true)->orderBy('name_ar')->get();
        $programs = collect();

        if ($this->selected_activity_id) {
            // Same as the preflight check: TrainingGroup's own scope supplies
            // the branch, so this asks only for a group that is running.
            $programs = TrainingProgram::where('activity_id', $this->selected_activity_id)
                ->where('status', 'active')
                ->whereHas('groups', fn ($q) => $q->whereIn('status', ['active', 'forming']))
                ->orderBy('name_ar')
                ->get();
        }

        return view('livewire.receptionist.new-registration-wizard', [
            'activities' => $activities,
            'programs' => $programs,
            'isSuperAdmin' => auth()->user()?->is_super_admin ?? false,
            'relationOptions' => [
                'father' => 'أب',
                'mother' => 'أم',
                'brother' => 'أخ',
                'sister' => 'أخت',
                'uncle' => 'عم/خال',
                'aunt' => 'عمة/خالة',
                'grandfather' => 'جد',
                'grandmother' => 'جدة',
                'guardian' => 'وصي',
                'other' => 'أخرى',
            ],
        ]);
    }
}
