<?php

namespace Modules\VetStaffPortal\Support;

use App\User;
use Illuminate\Support\Carbon;
use Modules\VetClinic\Entities\VcAppointment;
use Modules\VetClinic\Entities\VcDoctor;
use Modules\VetClinic\Entities\VcPet;
use Modules\VetStaffPortal\Http\Controllers\Api\AuthController;

/**
 * Real-data queries for the staff PWA, scoped to the authenticated user's
 * business. Every method is defensive: if a satellite table is missing the
 * method degrades to an empty result rather than 500-ing the whole app.
 */
class StaffDataRepository
{
    public function __construct(
        private User $user,
        private int $businessId,
        private ?VcDoctor $doctor,
    ) {}

    public static function for(User $user): self
    {
        return new self($user, (int) $user->business_id, AuthController::doctorForUser($user));
    }

    /* ── Identity ─────────────────────────────────────────────── */

    public function me(): array
    {
        return [
            'id'          => $this->user->id,
            'name'        => $this->displayName($this->user),
            'username'    => $this->user->username,
            'email'       => $this->user->email,
            'is_doctor'   => (bool) $this->doctor,
            'doctor_id'   => $this->doctor?->id,
            'doctor_name' => $this->doctor?->name,
            'business_id' => $this->businessId,
            'can'         => [
                'manage_appointments' => $this->user->can('vet_clinic.manage_appointments'),
                'view_appointments'   => $this->user->can('vet_clinic.view_appointments'),
                'manage_admissions'   => $this->user->can('vet_admission.manage_admissions'),
                'view_admissions'     => $this->user->can('vet_admission.view_admissions') || $this->user->can('vet_admission.manage_admissions'),
                'log_care'            => $this->user->can('vet_admission.log_care'),
            ],
        ];
    }

    /* ── Dashboard summary (badge counts) ─────────────────────── */

    public function summary(): array
    {
        $today = Carbon::today()->toDateString();

        $todayAppts = VcAppointment::where('business_id', $this->businessId)
            ->whereDate('appointment_date', $today)
            ->whereNotIn('status', ['cancelled', 'no_show'])
            ->when($this->scopeToMine(), fn ($q) => $q->where('doctor_id', $this->doctor->id))
            ->count();

        $admitted = $this->safe(fn () => \Modules\VetAdmission\Entities\VaAdmission::where('business_id', $this->businessId)
            ->where('status', 'admitted')->count(), 0);

        $openAlerts = $this->safe(fn () => \Modules\VetAdmission\Entities\VaAlert::where('business_id', $this->businessId)
            ->whereNull('acknowledged_at')->count(), 0);

        return [
            'today_appointments' => $todayAppts,
            'admitted_patients'  => $admitted,
            'open_alerts'        => $openAlerts,
            'as_of'              => now()->toIso8601String(),
        ];
    }

    /* ── Schedule ─────────────────────────────────────────────── */

    public function schedule(string $date, string $scope = 'mine'): array
    {
        $mine = $scope === 'mine' && $this->doctor && ! $this->user->can('vet_clinic.manage_appointments');
        // A doctor without manage rights is always scoped to their own column.
        $forceMine = $this->scopeToMine();

        $rows = VcAppointment::with(['pet', 'owner', 'doctor'])
            ->where('business_id', $this->businessId)
            ->whereDate('appointment_date', $date)
            ->when($mine || $forceMine, fn ($q) => $q->where('doctor_id', $this->doctor->id))
            ->orderBy('appointment_time')
            ->get();

        return [
            'date'     => $date,
            'scope'    => ($mine || $forceMine) ? 'mine' : 'all',
            'can_see_all' => $this->user->can('vet_clinic.manage_appointments'),
            'items'    => $rows->map(fn ($a) => $this->appointmentRow($a))->values()->all(),
        ];
    }

    /**
     * Per-day appointment counts for a date range, used by the calendar's
     * month/week views to show how busy each day is without loading every
     * appointment. Scope follows the same rules as schedule().
     */
    public function scheduleCounts(string $from, string $to, string $scope = 'mine'): array
    {
        $mine = $scope === 'mine' && $this->doctor && ! $this->user->can('vet_clinic.manage_appointments');
        $forceMine = $this->scopeToMine();

        $rows = VcAppointment::query()
            ->where('business_id', $this->businessId)
            ->whereDate('appointment_date', '>=', $from)
            ->whereDate('appointment_date', '<=', $to)
            ->whereNotIn('status', ['cancelled', 'no_show'])
            ->when($mine || $forceMine, fn ($q) => $q->where('doctor_id', $this->doctor->id))
            ->get(['appointment_date'])
            ->groupBy(fn ($a) => optional($a->appointment_date)->toDateString())
            ->map->count();

        return [
            'from'        => $from,
            'to'          => $to,
            'scope'       => ($mine || $forceMine) ? 'mine' : 'all',
            'can_see_all' => $this->user->can('vet_clinic.manage_appointments'),
            'counts'      => $rows->all(),   // { 'YYYY-MM-DD': n, ... }
        ];
    }

    private function appointmentRow(VcAppointment $a): array
    {
        $time = $a->appointment_time ? Carbon::parse($a->appointment_time)->format('H:i') : '';
        return [
            'id'           => $a->id,
            'ref_no'       => $a->ref_no,
            'time'         => $time,
            'date'         => optional($a->appointment_date)->toDateString(),
            'duration_min' => (int) ($a->duration_minutes ?? 30),
            'type'         => $a->type,
            'reason'       => $a->reason,
            'status'       => $a->status,
            'status_label' => $this->statusLabel($a->status),
            'pet_id'       => $a->pet_id,
            'pet_name'     => optional($a->pet)->petname,
            'pet_species'  => optional($a->pet)->species,
            'owner_name'   => optional($a->owner)->name,
            'owner_phone'  => optional($a->owner)->mobile,
            'doctor_id'    => $a->doctor_id,
            'doctor_name'  => optional($a->doctor)->name,
            'arrived_at'   => optional($a->arrived_at)->toIso8601String(),
        ];
    }

    /* ── Ward board ───────────────────────────────────────────── */

    public function wardBoard(): array
    {
        return $this->safe(function () {
            $wards = \Modules\VetAdmission\Entities\VaWard::forBusiness($this->businessId)->active()
                ->with(['currentAdmissions' => fn ($q) => $q->with('pet', 'owner', 'attendingDoctor')])
                ->orderBy('name')->get();

            $unassigned = \Modules\VetAdmission\Entities\VaAdmission::with('pet', 'owner', 'attendingDoctor')
                ->where('business_id', $this->businessId)
                ->where('status', 'admitted')->whereNull('ward_id')->get();

            return [
                'wards' => $wards->map(fn ($w) => [
                    'id'        => $w->id,
                    'name'      => $w->name,
                    'capacity'  => $w->capacity ?? null,
                    'occupied'  => $w->currentAdmissions->count(),
                    'admissions'=> $w->currentAdmissions->map(fn ($a) => $this->admissionRow($a))->values()->all(),
                ])->values()->all(),
                'unassigned' => $unassigned->map(fn ($a) => $this->admissionRow($a))->values()->all(),
            ];
        }, ['wards' => [], 'unassigned' => []]);
    }

    /* ── Admissions ───────────────────────────────────────────── */

    public function admissions(string $scope = 'active'): array
    {
        return $this->safe(function () use ($scope) {
            $q = \Modules\VetAdmission\Entities\VaAdmission::with('pet', 'owner', 'ward', 'attendingDoctor')
                ->where('business_id', $this->businessId);

            if ($scope === 'active') {
                $q->where('status', 'admitted');
            }
            $q->orderByDesc('admitted_at');

            return ['items' => $q->limit(100)->get()->map(fn ($a) => $this->admissionRow($a))->values()->all()];
        }, ['items' => []]);
    }

    public function admission(int $id): ?array
    {
        return $this->safe(function () use ($id) {
            $a = \Modules\VetAdmission\Entities\VaAdmission::with('pet', 'owner', 'ward', 'attendingDoctor', 'admittingDoctor')
                ->where('business_id', $this->businessId)->find($id);
            if (! $a) return null;

            $row = $this->admissionRow($a);
            $row['admitting_doctor'] = optional($a->admittingDoctor)->name;
            $row['reason']           = $a->reason ?? null;
            $row['diagnosis']        = $a->diagnosis ?? null;
            $row['notes']            = $a->notes ?? null;
            $row['deposit_status']   = $a->deposit_status ?? 'pending';
            $row['has_invoice']      = ! empty($a->erp_sell_id);
            return $row;
        }, null);
    }

    private function admissionRow($a): array
    {
        return [
            'id'               => $a->id,
            'ref_no'           => $a->admission_no ?? ($a->ref_no ?? null),
            'status'           => $a->status,
            'type'             => $a->type ?? null,
            'pet_id'           => $a->pet_id,
            'pet_name'         => optional($a->pet)->petname,
            'pet_species'      => optional($a->pet)->species,
            'owner_name'       => optional($a->owner)->name,
            'ward_id'          => $a->ward_id,
            'ward_name'        => optional($a->ward)->name,
            'attending_doctor' => optional($a->attendingDoctor)->name,
            'admitted_at'      => optional($a->admitted_at)->toIso8601String(),
            'admitted_label'   => $a->admitted_at ? Carbon::parse($a->admitted_at)->format('d M · H:i') : null,
        ];
    }

    /* ── Patients ─────────────────────────────────────────────── */

    public function patients(string $q): array
    {
        $term = trim($q);
        $query = VcPet::with('owner')
            ->where('business_id', $this->businessId)
            ->where('deceased', false);

        if ($term !== '') {
            $query->where(function ($w) use ($term) {
                $w->where('petname', 'like', "%{$term}%")
                  ->orWhere('microchip_no', 'like', "%{$term}%")
                  ->orWhere('species', 'like', "%{$term}%")
                  ->orWhereHas('owner', fn ($o) => $o->where('name', 'like', "%{$term}%")->orWhere('mobile', 'like', "%{$term}%"));
            });
        }

        $rows = $query->orderBy('petname')->limit(50)->get();

        return ['items' => $rows->map(fn ($p) => $this->petRow($p))->values()->all()];
    }

    private function petRow(VcPet $p): array
    {
        return [
            'id'          => $p->id,
            'name'        => $p->petname,
            'species'     => $p->species,
            'breed'       => $p->breed,
            'gender'      => $p->gender,
            'age_label'   => $p->age !== null ? $p->age . 'y' : null,
            'microchip_no'=> $p->microchip_no,
            'photo_url'   => $p->photo_url,
            'owner_id'    => $p->contact_id,
            'owner_name'  => optional($p->owner)->name,
            'owner_phone' => optional($p->owner)->mobile,
        ];
    }

    public function patient(int $id): ?array
    {
        $p = VcPet::with('owner')->where('business_id', $this->businessId)->find($id);
        if (! $p) return null;

        $row = $this->petRow($p);
        $row['dob_label']  = $p->dob ? Carbon::parse($p->dob)->format('d M Y') : null;
        $row['neutered']   = (bool) $p->neutered;
        $row['allergies']  = $this->safe(fn () => $p->conditions()->where('status', '!=', 'resolved')->pluck('name')->all(), []);

        $row['vaccinations'] = $this->safe(fn () => $p->vaccinations()->with('vaccineType')->limit(20)->get()->map(fn ($v) => [
            'id'             => $v->id,
            'vaccine_name'   => optional($v->vaccineType)->name ?? $v->batch_no ?? 'Vaccine',
            'given_label'    => $v->administered_date ? Carbon::parse($v->administered_date)->format('d M Y') : null,
            'next_due_label' => $v->next_due_date ? Carbon::parse($v->next_due_date)->format('d M Y') : null,
        ])->values()->all(), []);

        $row['recent_visits'] = VcAppointment::with('doctor')
            ->where('business_id', $this->businessId)->where('pet_id', $p->id)
            ->orderByDesc('appointment_date')->limit(10)->get()
            ->map(fn ($a) => [
                'id'          => $a->id,
                'date_label'  => optional($a->appointment_date)->format('d M Y'),
                'type'        => $a->type,
                'status'      => $a->status,
                'doctor_name' => optional($a->doctor)->name,
            ])->values()->all();

        $row['admissions'] = $this->safe(fn () => \Modules\VetAdmission\Entities\VaAdmission::with('ward')
            ->where('business_id', $this->businessId)->where('pet_id', $p->id)
            ->orderByDesc('admitted_at')->limit(5)->get()
            ->map(fn ($a) => $this->admissionRow($a))->values()->all(), []);

        return $row;
    }

    /* ── Tasks / alerts ───────────────────────────────────────── */

    public function alerts(): array
    {
        return $this->safe(function () {
            $rows = \Modules\VetAdmission\Entities\VaAlert::with('admission.pet')
                ->where('business_id', $this->businessId)
                ->whereNull('acknowledged_at')
                ->orderByDesc('raised_at')->limit(50)->get();

            return ['items' => $rows->map(fn ($al) => [
                'id'         => $al->id,
                'severity'   => $al->severity,
                'message'    => $al->message,
                'source'     => $al->source,
                'pet_name'   => optional(optional($al->admission)->pet)->petname,
                'admission_id'=> $al->admission_id,
                'raised_label'=> $al->raised_at ? Carbon::parse($al->raised_at)->diffForHumans() : null,
            ])->values()->all()];
        }, ['items' => []]);
    }

    /* ── Consultation (SOAP + vitals) ─────────────────────────── */

    public function consultation(int $apptId): ?array
    {
        $a = VcAppointment::with(['pet', 'owner', 'doctor'])
            ->where('business_id', $this->businessId)->find($apptId);
        if (! $a) return null;

        return [
            'id'           => $a->id,
            'ref_no'       => $a->ref_no,
            'status'       => $a->status,
            'date_label'   => optional($a->appointment_date)->format('d M Y') . ($a->appointment_time ? ' · ' . Carbon::parse($a->appointment_time)->format('H:i') : ''),
            'type'         => $a->type,
            'reason'       => $a->reason,
            'pet_id'       => $a->pet_id,
            'pet_name'     => optional($a->pet)->petname,
            'pet_species'  => optional($a->pet)->species,
            'owner_name'   => optional($a->owner)->name,
            'doctor_name'  => optional($a->doctor)->name,
            'vitals'       => [
                'weight_kg'        => $a->weight_kg ?? null,
                'temperature'      => $a->temperature ?? null,
                'heart_rate'       => $a->heart_rate ?? null,
                'respiratory_rate' => $a->respiratory_rate ?? null,
            ],
            'soap'         => [
                'subjective' => $a->soap_subjective ?? null,
                'objective'  => $a->soap_objective ?? null,
                'assessment' => $a->soap_assessment ?? null,
                'plan'       => $a->soap_plan ?? null,
            ],
            'soap_options' => $this->soapOptions(optional($a->pet)->species),
            'doctor_notes' => $a->doctor_notes ?? null,
        ];
    }

    /**
     * Quick-pick SOAP chips (system defaults + business overrides) the way the
     * web consultation form offers them. Grouped for the Subjective/Objective
     * fields; safe no-op when the table/model isn't present.
     */
    private function soapOptions(?string $species): array
    {
        $empty = ['complaint' => [], 'exam' => [], 'systems' => []];
        if (! \Illuminate\Support\Facades\Schema::hasTable('vc_soap_options')
            || ! class_exists(\Modules\VetClinic\Entities\VcSoapOption::class)) {
            return $empty;
        }
        try {
            $sp = $species ? strtolower($species) : 'all';
            $labels = fn (string $section) => \Modules\VetClinic\Entities\VcSoapOption::forBusiness($this->businessId, $section, $sp)
                ->pluck('label')->filter()->values()->all();
            return [
                'complaint' => $labels('complaint'),    // → Subjective
                'exam'      => $labels('exam_test'),     // → Objective
                'systems'   => $labels('body_system'),   // → Objective
            ];
        } catch (\Throwable $e) {
            return $empty;
        }
    }

    /* ── Admission care (plans + today's slots + vitals) ──────── */

    public function care(int $admissionId): ?array
    {
        return $this->safe(function () use ($admissionId) {
            $admission = \Modules\VetAdmission\Entities\VaAdmission::with('pet')
                ->where('business_id', $this->businessId)->find($admissionId);
            if (! $admission) return null;

            $today = Carbon::today()->toDateString();

            $plans = \Modules\VetAdmission\Entities\VaAdmissionCarePlan::with('taskType')
                ->where('admission_id', $admission->id)->where('active', true)->get();

            $logs = \Modules\VetAdmission\Entities\VaCareLog::where('admission_id', $admission->id)
                ->whereDate('care_date', $today)->get()
                ->keyBy(fn ($l) => $l->care_plan_id . ':' . $l->slot_index);

            $planRows = $plans->map(function ($p) use ($logs) {
                $times = is_array($p->schedule_times) ? $p->schedule_times : [];
                $count = (int) ($p->times_per_day ?: max(1, count($times)));
                $slots = [];
                for ($i = 0; $i < $count; $i++) {
                    $log = $logs->get($p->id . ':' . $i);
                    $slots[] = [
                        'slot_index' => $i,
                        'time_label' => $times[$i] ?? null,
                        'status'     => $log->status ?? 'pending',
                    ];
                }
                return [
                    'care_plan_id' => $p->id,
                    'task'         => optional($p->taskType)->name ?? 'Care task',
                    'icon'         => optional($p->taskType)->icon,
                    'instructions' => $p->instructions,
                    'slots'        => $slots,
                ];
            })->values()->all();

            $vitals = \Modules\VetAdmission\Entities\VaAdmissionVital::where('admission_id', $admission->id)
                ->orderByDesc('recorded_at')->limit(12)->get()->map(fn ($v) => [
                    'id'               => $v->id,
                    'recorded_label'   => $v->recorded_at ? Carbon::parse($v->recorded_at)->format('d M · H:i') : null,
                    'temperature'      => $v->temperature,
                    'weight_kg'        => $v->weight_kg,
                    'heart_rate'       => $v->heart_rate,
                    'respiratory_rate' => $v->respiratory_rate,
                    'notes'            => $v->notes,
                ])->values()->all();

            return [
                'admission_id' => $admission->id,
                'pet_name'     => optional($admission->pet)->petname,
                'care_date'    => $today,
                'plans'        => $planRows,
                'vitals'       => $vitals,
            ];
        }, null);
    }

    /* ── Prescriptions ────────────────────────────────────────── */

    public function patientPrescriptions(int $petId): array
    {
        return $this->safe(function () use ($petId) {
            $rows = \Modules\VetClinic\Entities\VcPrescription::with(['items', 'doctor'])
                ->where('business_id', $this->businessId)->where('pet_id', $petId)
                ->orderByDesc('prescribed_date')->limit(30)->get();

            // Resolve catalogue links (product link + controlled schedule) once.
            $medicineIds = $rows->flatMap(fn ($p) => $p->items->pluck('medicine_id'))->filter()->unique()->all();
            $hasControlled = \Illuminate\Support\Facades\Schema::hasColumn('vc_medicines', 'controlled_schedule');
            $medMap = $medicineIds
                ? \Modules\VetClinic\Entities\VcMedicine::whereIn('id', $medicineIds)->get()->keyBy('id')
                : collect();

            return ['items' => $rows->map(fn ($p) => [
                'id'            => $p->id,
                'ref_no'        => $p->ref_no,
                'date_label'    => $p->prescribed_date ? Carbon::parse($p->prescribed_date)->format('d M Y') : null,
                'valid_until'   => $p->valid_until ? Carbon::parse($p->valid_until)->format('d M Y') : null,
                'status'        => $p->status,
                'diagnosis'     => $p->diagnosis,
                'doctor_name'   => optional($p->doctor)->name,
                'items'         => $p->items->map(function ($it) use ($medMap, $hasControlled) {
                    $med = $it->medicine_id ? $medMap->get($it->medicine_id) : null;
                    $schedule = ($hasControlled && $med) ? $med->controlled_schedule : null;
                    return [
                        'id'                  => $it->id,
                        'medicine_id'         => $it->medicine_id,
                        'medicine'            => $it->medicine_name,
                        'dosage'              => $it->dosage,
                        'frequency'           => $it->frequency,
                        'duration_days'       => $it->duration_days,
                        'instructions'        => $it->instructions,
                        'quantity'            => $it->quantity,
                        'controlled_schedule' => $schedule,
                        'is_controlled'       => ! empty($schedule),
                        'dispensable'         => $med && ! empty($med->product_id),
                        'dispensed'           => ! empty($it->dispensed_at),
                        'dispensed_qty'       => $it->dispensed_qty,
                        'dispensed_label'     => $it->dispensed_at ? Carbon::parse($it->dispensed_at)->format('d M Y · H:i') : null,
                    ];
                })->values()->all(),
            ])->values()->all()];
        }, ['items' => []]);
    }

    /** FEFO batches + business locations for dispensing one prescription item. */
    public function dispenseOptions(int $itemId): ?array
    {
        return $this->safe(function () use ($itemId) {
            $item = \Modules\VetClinic\Entities\VcPrescriptionItem::with('prescription')->find($itemId);
            if (! $item || ! $item->prescription || (int) $item->prescription->business_id !== $this->businessId) {
                return null;
            }
            $locations = \App\BusinessLocation::where('business_id', $this->businessId)
                ->get(['id', 'name'])->map(fn ($l) => ['id' => (int) $l->id, 'name' => $l->name])->values()->all();

            if (! $item->medicine_id) {
                return ['linked' => false, 'msg' => 'This item is not linked to a medicine.', 'batches' => [], 'locations' => $locations];
            }
            $medicine = \Modules\VetClinic\Entities\VcMedicine::find($item->medicine_id);
            if (! $medicine || empty($medicine->product_id)) {
                return [
                    'linked'  => false,
                    'msg'     => "The medicine '{$item->medicine_name}' has no ERP product link — set it on the medicine catalogue first.",
                    'batches' => [], 'locations' => $locations,
                ];
            }
            $batches = app(\Modules\VetClinic\Entities\Services\VcMedicineDispenseService::class)
                ->fefoSuggestions($medicine)
                ->map(fn ($b) => [
                    'purchase_line_id' => (int) $b->purchase_line_id,
                    'lot_number'       => $b->lot_number,
                    'exp_date'         => $b->exp_date,
                    'qty_available'    => (float) $b->qty_available,
                ])->values()->all();

            return [
                'linked'            => true,
                'unit'              => $medicine->unit,
                'already_dispensed' => ! empty($item->dispensed_at),
                'batches'           => $batches,
                'locations'         => $locations,
            ];
        }, null);
    }

    /* ── Medications (MAR) + fluids ───────────────────────────── */

    public function medications(int $admissionId): ?array
    {
        return $this->safe(function () use ($admissionId) {
            $admission = \Modules\VetAdmission\Entities\VaAdmission::with('pet')
                ->where('business_id', $this->businessId)->find($admissionId);
            if (! $admission) return null;

            $meds = \Modules\VetAdmission\Entities\VaAdmissionMedication::with('administrations')
                ->where('admission_id', $admission->id)
                ->orderByDesc('active')->orderByDesc('id')->get();

            // Controlled-schedule lookup for any linked catalogue medicines.
            $medicineIds = $meds->pluck('medicine_id')->filter()->unique()->all();
            $hasControlled = \Illuminate\Support\Facades\Schema::hasColumn('vc_medicines', 'controlled_schedule');
            $ctrlMap = ($medicineIds && $hasControlled)
                ? \Modules\VetClinic\Entities\VcMedicine::whereIn('id', $medicineIds)->pluck('controlled_schedule', 'id')
                : collect();
            $injectable = fn (?string $route) => in_array(strtolower((string) $route), ['iv', 'im', 'sc', 'sq', 'io', 'ic'], true);

            $endOfToday = Carbon::today()->endOfDay();

            return [
                'admission_id' => $admission->id,
                'pet_name'     => optional($admission->pet)->petname,
                'medications'  => $meds->map(function ($m) use ($endOfToday, $ctrlMap, $injectable) {
                    // Due today + any overdue/pending; cap to keep the list mobile-friendly.
                    $doses = $m->administrations
                        ->filter(fn ($a) => $a->due_at && Carbon::parse($a->due_at)->lte($endOfToday))
                        ->sortBy('due_at')
                        ->take(16)
                        ->map(fn ($a) => [
                            'id'             => $a->id,
                            'due_label'      => $a->due_at ? Carbon::parse($a->due_at)->format('d M · H:i') : null,
                            'status'         => $a->status,
                            'administered_at'=> $a->administered_at ? Carbon::parse($a->administered_at)->format('H:i') : null,
                        ])->values()->all();

                    $schedule = $m->medicine_id ? ($ctrlMap[$m->medicine_id] ?? null) : null;
                    return [
                        'id'                  => $m->id,
                        'name'                => $m->medicine_name,
                        'route'               => $m->route,
                        'dose_label'          => $this->doseLabel($m),
                        'freq_label'          => $m->frequency_hours ? ('q' . $m->frequency_hours . 'h') : null,
                        'active'              => (bool) $m->active,
                        'controlled_schedule' => $schedule,
                        'is_controlled'       => ! empty($schedule),
                        // Prompt for actual volume/dose on controlled or injectable orders.
                        'capture_actual'      => ! empty($schedule) || $injectable($m->route),
                        'doses'               => $doses,
                    ];
                })->values()->all(),
            ];
        }, null);
    }

    private function doseLabel($m): ?string
    {
        if ($m->dose_total_mg)            return rtrim(rtrim((string) $m->dose_total_mg, '0'), '.') . ' mg';
        if ($m->volume_per_dose_ml)       return rtrim(rtrim((string) $m->volume_per_dose_ml, '0'), '.') . ' ml';
        if ($m->dose_mg_per_kg)           return rtrim(rtrim((string) $m->dose_mg_per_kg, '0'), '.') . ' mg/kg';
        return null;
    }

    public function fluids(int $admissionId): ?array
    {
        return $this->safe(function () use ($admissionId) {
            $admission = \Modules\VetAdmission\Entities\VaAdmission::with('pet')
                ->where('business_id', $this->businessId)->find($admissionId);
            if (! $admission) return null;

            $fluids = \Modules\VetAdmission\Entities\VaAdmissionFluid::with('bagChanges')
                ->where('admission_id', $admission->id)
                ->orderByDesc('active')->orderByDesc('id')->get();

            return [
                'admission_id' => $admission->id,
                'pet_name'     => optional($admission->pet)->petname,
                'fluids'       => $fluids->map(fn ($f) => [
                    'id'             => $f->id,
                    'fluid_type'     => $f->fluid_type,
                    'rate_ml_per_hr' => $f->rate_ml_per_hr,
                    'bag_size_ml'    => $f->bag_size_ml,
                    'active'         => (bool) $f->active,
                    'started_label'  => $f->started_at ? Carbon::parse($f->started_at)->format('d M · H:i') : null,
                    'est_end_label'  => $f->estimated_end_at ? Carbon::parse($f->estimated_end_at)->format('d M · H:i') : null,
                    'bag_changes'    => $f->bagChanges->count(),
                ])->values()->all(),
            ];
        }, null);
    }

    /* ── Lab results ──────────────────────────────────────────── */

    public function petLabSessions(int $petId): array
    {
        return $this->safe(function () use ($petId) {
            if (! \Illuminate\Support\Facades\Schema::hasTable('lab_test_sessions')) return ['items' => []];
            $rows = \Illuminate\Support\Facades\DB::table('lab_test_sessions')
                ->where('business_id', $this->businessId)
                ->where('vc_pet_id', $petId)
                ->orderByDesc('test_date')->orderByDesc('test_time')
                ->limit(30)
                ->get(['id', 'sample_no', 'vendor', 'test_date', 'test_time', 'sample_type']);

            // Abnormal count per session for an at-a-glance flag.
            $abn = \Illuminate\Support\Facades\DB::table('lab_test_results')
                ->whereIn('test_session_id', $rows->pluck('id'))
                ->where('is_abnormal', 1)
                ->select('test_session_id', \Illuminate\Support\Facades\DB::raw('count(*) as n'))
                ->groupBy('test_session_id')->pluck('n', 'test_session_id');

            return ['items' => $rows->map(fn ($r) => [
                'id'           => $r->id,
                'sample_no'    => $r->sample_no,
                'vendor'       => $r->vendor,
                'sample_type'  => $r->sample_type,
                'date_label'   => $r->test_date ? Carbon::parse($r->test_date . ' ' . ($r->test_time ?? '00:00'))->format('d M Y · H:i') : '—',
                'abnormal'     => (int) ($abn[$r->id] ?? 0),
            ])->values()->all()];
        }, ['items' => []]);
    }

    public function labSession(int $sessionId): ?array
    {
        return $this->safe(function () use ($sessionId) {
            $s = \Illuminate\Support\Facades\DB::table('lab_test_sessions')
                ->where('id', $sessionId)->where('business_id', $this->businessId)->first();
            if (! $s) return null;
            $results = \Illuminate\Support\Facades\DB::table('lab_test_results')
                ->where('test_session_id', $sessionId)->orderBy('analyte_name')
                ->get(['analyte_name', 'value', 'unit', 'ref_lower', 'ref_upper', 'is_abnormal', 'flags']);
            $pet = $s->vc_pet_id ? VcPet::find($s->vc_pet_id) : null;
            return [
                'id'          => $s->id,
                'sample_no'   => $s->sample_no,
                'vendor'      => $s->vendor,
                'sample_type' => $s->sample_type,
                'date_label'  => $s->test_date ? Carbon::parse($s->test_date . ' ' . ($s->test_time ?? '00:00'))->format('d M Y · H:i') : '—',
                'pet_id'      => $s->vc_pet_id,
                'pet_name'    => optional($pet)->petname,
                'results'     => $results->map(fn ($r) => [
                    'analyte'     => $r->analyte_name,
                    'value'       => $r->value,
                    'unit'        => $r->unit,
                    'ref_lower'   => $r->ref_lower,
                    'ref_upper'   => $r->ref_upper,
                    'is_abnormal' => (bool) $r->is_abnormal,
                    'flag'        => $r->flags,
                ])->values()->all(),
            ];
        }, null);
    }

    /* ── Lab ordering (LabCore worklist) ──────────────────────── */

    public function labDevices(): array
    {
        return $this->safe(function () {
            if (! \Illuminate\Support\Facades\Schema::hasTable('lab_devices')) return ['items' => []];
            $rows = \Illuminate\Support\Facades\DB::table('lab_devices')
                ->where('business_id', $this->businessId)->orderBy('name')
                ->get(['id', 'vendor', 'model', 'name', 'status']);
            return ['items' => $rows->map(fn ($d) => [
                'id'     => (int) $d->id,
                'vendor' => $d->vendor,
                'model'  => $d->model,
                'name'   => $d->name,
                'status' => $d->status,
            ])->values()->all()];
        }, ['items' => []]);
    }

    public function petLabWorklist(int $petId): array
    {
        return $this->safe(function () use ($petId) {
            if (! \Illuminate\Support\Facades\Schema::hasTable('lab_worklist')) return ['items' => []];
            $rows = \Illuminate\Support\Facades\DB::table('lab_worklist')
                ->where('business_id', $this->businessId)->where('vc_pet_id', $petId)
                ->orderByDesc('created_at')->limit(20)
                ->get(['id', 'sample_no', 'vendor', 'tests', 'status', 'created_at']);
            return ['items' => $rows->map(function ($r) {
                $tests = $r->tests ? json_decode($r->tests, true) : null;
                return [
                    'id'         => $r->id,
                    'sample_no'  => $r->sample_no,
                    'vendor'     => $r->vendor,
                    'tests'      => is_array($tests) ? $tests : [],
                    'status'     => $r->status,
                    'date_label' => $r->created_at ? Carbon::parse($r->created_at)->format('d M Y · H:i') : null,
                ];
            })->values()->all()];
        }, ['items' => []]);
    }

    public function wards(): array
    {
        return $this->safe(function () {
            $rows = \Modules\VetAdmission\Entities\VaWard::forBusiness($this->businessId)->active()
                ->withCount('currentAdmissions')->orderBy('name')->get();
            return ['items' => $rows->map(fn ($w) => [
                'id'       => $w->id,
                'name'     => $w->name,
                'capacity' => $w->capacity ?? null,
                'occupied' => $w->current_admissions_count ?? 0,
            ])->values()->all()];
        }, ['items' => []]);
    }

    public function doctors(): array
    {
        return $this->safe(function () {
            $rows = \Modules\VetClinic\Entities\VcDoctor::where('business_id', $this->businessId)
                ->where('status', 'active')->orderBy('name')->get();
            return ['items' => $rows->map(fn ($d) => [
                'id'             => $d->id,
                'name'           => $d->name,
                'specialization' => $d->specialization,
            ])->values()->all()];
        }, ['items' => []]);
    }

    public function vaccineTypes(): array
    {
        return $this->safe(function () {
            // Vaccine types are global (business_id null) in the module, with
            // optional per-business additions — include both.
            $rows = \Modules\VetClinic\Entities\VcVaccineType::where('status', 'active')
                ->where(fn ($q) => $q->whereNull('business_id')->orWhere('business_id', $this->businessId))
                ->orderBy('name')->get();
            return ['items' => $rows->map(fn ($v) => [
                'id'               => $v->id,
                'name'             => $v->name,
                'valid_for_months' => $v->valid_for_months,
            ])->values()->all()];
        }, ['items' => []]);
    }

    public function medicines(string $q): array
    {
        return $this->safe(function () use ($q) {
            $term = trim($q);
            $hasControlled = \Illuminate\Support\Facades\Schema::hasColumn('vc_medicines', 'controlled_schedule');
            $rows = \Modules\VetClinic\Entities\VcMedicine::where('business_id', $this->businessId)
                ->when($term !== '', fn ($w) => $w->where(fn ($s) => $s
                    ->where('name', 'like', "%{$term}%")
                    ->orWhere('generic_name', 'like', "%{$term}%")))
                ->orderBy('name')->limit(25)->get();
            return ['items' => $rows->map(fn ($m) => [
                'id'                  => $m->id,
                'name'                => $m->name,
                'generic_name'        => $m->generic_name,
                'unit'                => $m->unit,
                'controlled_schedule' => $hasControlled ? $m->controlled_schedule : null,
                'is_controlled'       => $hasControlled ? ! empty($m->controlled_schedule) : false,
                'dispensable'         => ! empty($m->product_id),
            ])->values()->all()];
        }, ['items' => []]);
    }

    /* ── Helpers ──────────────────────────────────────────────── */

    private function scopeToMine(): bool
    {
        // A doctor who lacks the "manage all appointments" right only ever
        // sees their own column.
        return $this->doctor && ! $this->user->can('vet_clinic.manage_appointments');
    }

    private function displayName(User $u): string
    {
        $name = trim(($u->surname ? $u->surname . ' ' : '') . $u->first_name . ' ' . ($u->last_name ?? ''));
        return $name !== '' ? $name : $u->username;
    }

    private function statusLabel(?string $s): string
    {
        return [
            'pending'     => 'Pending',
            'confirmed'   => 'Confirmed',
            'in_progress' => 'In Consultation',
            'completed'   => 'Completed',
            'cancelled'   => 'Cancelled',
            'no_show'     => 'No-show',
        ][$s] ?? ucfirst((string) $s);
    }

    /** Run a closure, returning $fallback if anything throws (missing table etc). */
    private function safe(callable $fn, $fallback)
    {
        try { return $fn(); } catch (\Throwable $e) { return $fallback; }
    }
}
