<?php

namespace Modules\ClinicStaffPortal\Http\Controllers\Api;

use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Schema;
use Modules\ClinicCore\Entities\ClcAppointment;
use Modules\ClinicStaffPortal\Support\StaffDataRepository;

/**
 * JSON API consumed by the staff/doctor PWA (pwa-staff). All routes are behind
 * staff-api.auth, so auth()->user() is the resolved ERP user and every query
 * is scoped to their business + permissions via StaffDataRepository.
 */
class StaffController extends Controller
{
    private function repo(): StaffDataRepository
    {
        return StaffDataRepository::for(auth()->user());
    }

    public function me(): JsonResponse
    {
        return response()->json($this->repo()->me());
    }

    public function summary(): JsonResponse
    {
        return response()->json($this->repo()->summary());
    }

    public function schedule(Request $request): JsonResponse
    {
        $date  = $request->query('date');
        $date  = $date ? Carbon::parse($date)->toDateString() : Carbon::today()->toDateString();
        $scope = $request->query('scope', 'mine') === 'all' ? 'all' : 'mine';

        return response()->json($this->repo()->schedule($date, $scope));
    }

    /** Per-day appointment counts for the calendar (month/week views). */
    public function scheduleCounts(Request $request): JsonResponse
    {
        $from  = $request->query('from');
        $to    = $request->query('to');
        $from  = $from ? Carbon::parse($from)->toDateString() : Carbon::today()->startOfMonth()->toDateString();
        $to    = $to ? Carbon::parse($to)->toDateString() : Carbon::today()->endOfMonth()->toDateString();
        $scope = $request->query('scope', 'mine') === 'all' ? 'all' : 'mine';

        return response()->json($this->repo()->scheduleCounts($from, $to, $scope));
    }

    public function doctors(): JsonResponse
    {
        return response()->json($this->repo()->doctors());
    }

    public function patients(Request $request): JsonResponse
    {
        return response()->json($this->repo()->patients((string) $request->query('q', '')));
    }

    public function patient(int $id): JsonResponse
    {
        $p = $this->repo()->patient($id);
        if (! $p) return response()->json(['message' => 'Not found'], 404);
        return response()->json($p);
    }

    /**
     * Update an appointment's workflow status from the staff app.
     * Maps to the same status enum + time-tracking columns the web app uses:
     *   arrived â†’ in_progress â†’ completed  (+ confirmed / no_show / cancelled)
     */
    public function updateAppointmentStatus(Request $request, int $id): JsonResponse
    {
        $user = auth()->user();
        if (! $user->can('clinic.manage_appointments')) {
            return response()->json(['message' => 'You cannot change appointments.'], 403);
        }

        $data = $request->validate([
            'status' => 'required|in:confirmed,arrived,in_progress,completed,no_show,cancelled',
        ]);

        $appt = ClcAppointment::where('business_id', (int) $user->business_id)->find($id);
        if (! $appt) return response()->json(['message' => 'Not found'], 404);

        $status = $data['status'];

        // "arrived" is a time-stamp action, not a distinct enum value â€” keep the
        // row at confirmed/pending but record arrival time, matching web behaviour.
        if ($status === 'arrived') {
            $appt->arrived_at = now();
            if (in_array($appt->status, ['pending'])) $appt->status = 'confirmed';
        } else {
            $appt->status = $status;
            if ($status === 'in_progress' && empty($appt->consultation_started_at)) {
                $appt->consultation_started_at = now();
                if (empty($appt->arrived_at)) $appt->arrived_at = now();
            }
            if ($status === 'completed' && empty($appt->consultation_ended_at)) {
                $appt->consultation_ended_at = now();
            }
        }
        $appt->save();

        return response()->json([
            'ok'           => true,
            'id'           => $appt->id,
            'status'       => $appt->status,
            'arrived_at'   => optional($appt->arrived_at)->toIso8601String(),
        ]);
    }

    /* â”€â”€ Consultation (SOAP + vitals) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */

    public function consultation(int $id): JsonResponse
    {
        $c = $this->repo()->consultation($id);
        if (! $c) return response()->json(['message' => 'Not found'], 404);
        return response()->json($c);
    }

    public function saveConsultation(Request $request, int $id): JsonResponse
    {
        $user = auth()->user();
        if (! $user->can('clinic.manage_appointments')) {
            return response()->json(['message' => 'You cannot record consultations.'], 403);
        }

        $data = $request->validate([
            'weight_kg'        => 'nullable|numeric',
            'temperature'      => 'nullable|numeric',
            'heart_rate'       => 'nullable|integer',
            'respiratory_rate' => 'nullable|integer',
            'soap_subjective'  => 'nullable|string',
            'soap_objective'   => 'nullable|string',
            'soap_assessment'  => 'nullable|string',
            'soap_plan'        => 'nullable|string',
            'doctor_notes'     => 'nullable|string',
            'complete'         => 'nullable|boolean',
            'follow_up'            => 'nullable|array',
            'follow_up.date'       => 'nullable|date',
            'follow_up.time'       => 'nullable|string|max:8',
            'follow_up.reason'     => 'nullable|string|max:255',
            'follow_up.doctor_id'  => 'nullable|integer',
        ]);

        $appt = ClcAppointment::where('business_id', (int) $user->business_id)->find($id);
        if (! $appt) return response()->json(['message' => 'Not found'], 404);

        // Only persist columns that actually exist on this install.
        foreach (['weight_kg','temperature','heart_rate','respiratory_rate',
                  'soap_subjective','soap_objective','soap_assessment','soap_plan','doctor_notes'] as $col) {
            if (array_key_exists($col, $data) && Schema::hasColumn('clc_appointments', $col)) {
                $appt->{$col} = $data[$col];
            }
        }

        if (! empty($data['complete'])) {
            $appt->status = 'completed';
            if (Schema::hasColumn('clc_appointments', 'consultation_ended_at') && empty($appt->consultation_ended_at)) {
                $appt->consultation_ended_at = now();
            }
        }
        $appt->save();

        // Downstream side-effects (vital critical-flag scoring, follow-up creation)
        // were provided by VetClinic\Services\ConsultationService, which is not part
        // of the ClinicCore human stack. Guarded so the consultation save always
        // succeeds; follow-up creation runs only if a ClinicCore service exists.
        $follow = null;
        $consultationServiceClass = 'Modules\\ClinicCore\\Entities\\Services\\ConsultationService';
        if (class_exists($consultationServiceClass)) {
            $svc = app($consultationServiceClass);
            $hasExam = \Modules\ClinicCore\Entities\ClcAppointmentExamFinding::where('appointment_id', $appt->id)->exists();
            if (method_exists($svc, 'applyPostSave')) {
                $svc->applyPostSave($appt, $hasExam);
            }
            if (! empty($data['follow_up']['date']) && method_exists($svc, 'createFollowUp')) {
                $next = $svc->createFollowUp($appt, [
                    'date'      => $data['follow_up']['date'],
                    'time'      => $data['follow_up']['time'] ?? null,
                    'reason'    => $data['follow_up']['reason'] ?? null,
                    'doctor_id' => $data['follow_up']['doctor_id'] ?? null,
                ]);
                if ($next) {
                    $follow = [
                        'id'         => $next->id,
                        'ref_no'     => $next->ref_no,
                        'date_label' => optional($next->appointment_date)->format('d M Y')
                                      . ' · ' . \Illuminate\Support\Carbon::parse($next->appointment_time)->format('H:i'),
                    ];
                }
            }
        }

        return response()->json(['ok' => true, 'id' => $appt->id, 'status' => $appt->status, 'follow_up' => $follow]);
    }

    /* â”€â”€ Structured exam findings (objective exam templates) â”€â”€â”€â”€â”€â”€â”€ */

    public function examTemplates(int $id): JsonResponse
    {
        $user = auth()->user();
        $biz  = (int) $user->business_id;
        $appt = ClcAppointment::with('pet')->where('business_id', $biz)->find($id);
        if (! $appt) return response()->json(['message' => 'Not found'], 404);

        $species  = strtolower(optional($appt->pet)->species ?? '');
        $settings = \Modules\ClinicCore\Entities\ClcExamSetting::forBusiness($biz);
        $T = \Modules\ClinicCore\Entities\ClcExamTemplate::class;

        $general = null;
        if ($settings->active_general_template_id) {
            $general = $T::active()->forBusiness($biz)->where('id', $settings->active_general_template_id)->first();
        }
        if (! $general) {
            $general = $T::active()->general()->forBusiness($biz)->forSpecies($species)
                ->orderBy('sort_order')->orderBy('id')->first();
        }

        $sq = $T::active()->specialist()->forBusiness($biz)->forSpecies($species)
            ->orderBy('sort_order')->orderBy('display_label');
        if (is_array($settings->visible_specialist_template_ids) && count($settings->visible_specialist_template_ids)) {
            $sq->whereIn('id', $settings->visible_specialist_template_ids);
        }

        return response()->json([
            'general'     => $this->serialiseExamTemplate($general),
            'specialists' => $sq->get()->map(fn ($t) => $this->serialiseExamTemplate($t))->values(),
        ]);
    }

    public function examFindings(int $id, Request $request): JsonResponse
    {
        $biz = (int) auth()->user()->business_id;
        if (! ClcAppointment::where('business_id', $biz)->whereKey($id)->exists()) {
            return response()->json(['message' => 'Not found'], 404);
        }
        $q = \Modules\ClinicCore\Entities\ClcAppointmentExamFinding::where('appointment_id', $id)->where('business_id', $biz);
        if ($request->filled('template_id')) {
            $q->where('template_id', (int) $request->query('template_id'));
        }
        return response()->json(['items' => $q->get()->map(fn ($f) => [
            'template_id'      => $f->template_id,
            'template_item_id' => $f->template_item_id,
            'side'             => $f->side,
            'value'            => $f->value,
            'values_json'      => $f->values_json,
            'free_text'        => $f->free_text,
            'severity'         => $f->severity,
            'is_critical'      => (bool) $f->is_critical,
        ])->values()->all()]);
    }

    public function saveExamFinding(int $id, Request $request): JsonResponse
    {
        $user = auth()->user();
        if (! $user->can('clinic.manage_appointments') && ! $user->can('clinic.record_exam_findings')) {
            return response()->json(['message' => 'You cannot record exam findings.'], 403);
        }
        $biz = (int) $user->business_id;
        if (! ClcAppointment::where('business_id', $biz)->whereKey($id)->exists()) {
            return response()->json(['message' => 'Not found'], 404);
        }

        $data = $request->validate([
            'template_item_id' => 'required|integer|exists:vc_exam_template_items,id',
            'side'             => 'nullable|string|max:8',
            'value'            => 'nullable|string|max:500',
            'values_json'      => 'nullable|array',
            'free_text'        => 'nullable|string|max:2000',
        ]);

        $item = \Modules\ClinicCore\Entities\ClcExamTemplateItem::find($data['template_item_id']);
        $F = \Modules\ClinicCore\Entities\ClcAppointmentExamFinding::class;

        $hasValue = ($data['value'] ?? '') !== '' || ($data['free_text'] ?? '') !== ''
            || (is_array($data['values_json'] ?? null) && count($data['values_json']));
        if (! $hasValue) {
            $F::where('appointment_id', $id)->where('template_item_id', $item->id)
                ->where('side', $data['side'] ?? null)->delete();
            return response()->json(['ok' => true, 'deleted' => true]);
        }

        $valueForScoring = ($data['value'] ?? '') !== '' ? $data['value'] : ($data['values_json'] ?? null);
        $score = $item->scoreValue($valueForScoring);

        $finding = $F::updateOrCreate(
            ['appointment_id' => $id, 'template_item_id' => $item->id, 'side' => $data['side'] ?? null],
            [
                'business_id'         => $biz,
                'template_id'         => $item->template_id,
                'region_snapshot'     => $item->region,
                'item_label_snapshot' => $item->item_label,
                'value'               => $data['value'] ?? null,
                'numeric_value'       => $score['numeric'],
                'values_json'         => $data['values_json'] ?? null,
                'free_text'           => $data['free_text'] ?? null,
                'severity'            => $score['severity'],
                'is_critical'         => $score['is_critical'],
                'recorded_by'         => $user->id,
            ]
        );

        return response()->json(['ok' => true, 'severity' => $finding->severity, 'is_critical' => (bool) $finding->is_critical]);
    }

    /** Compact template shape (mirrors VcExamController::serialiseTemplate). */
    private function serialiseExamTemplate($tpl): ?array
    {
        if (! $tpl) return null;
        $tpl->loadMissing('items');
        return [
            'id'            => $tpl->id,
            'name'          => $tpl->name,
            'kind'          => $tpl->kind,
            'display_label' => $tpl->display_label ?: $tpl->name,
            'icon'          => $tpl->icon,
            'colour'        => $tpl->colour,
            'items'         => $tpl->items->map(fn ($i) => [
                'id'             => $i->id,
                'region'         => $i->region,
                'item_label'     => $i->item_label,
                'response_type'  => $i->response_type,
                'options_json'   => $i->options_json,
                'unit'           => $i->unit,
                'normal_low'     => $i->normal_low,
                'normal_high'    => $i->normal_high,
                'critical_low'   => $i->critical_low,
                'critical_high'  => $i->critical_high,
                'lateralisation' => $i->lateralisation,
                'is_required'    => (bool) $i->is_required,
                'sort_order'     => $i->sort_order,
                'help_text'      => $i->help_text,
            ])->values(),
        ];
    }

    /* â”€â”€ Prescriptions â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */

    public function patientPrescriptions(int $id): JsonResponse
    {
        return response()->json($this->repo()->patientPrescriptions($id));
    }

    public function patientLabSessions(int $id): JsonResponse
    {
        return response()->json($this->repo()->petLabSessions($id));
    }

    public function labSession(int $id): JsonResponse
    {
        $s = $this->repo()->labSession($id);
        if (! $s) return response()->json(['message' => 'Not found'], 404);
        return response()->json($s);
    }

    /* â”€â”€ Lab ordering (LabCore worklist) â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */

    public function labDevices(): JsonResponse
    {
        return response()->json($this->repo()->labDevices());
    }

    public function patientLabWorklist(int $id): JsonResponse
    {
        return response()->json($this->repo()->petLabWorklist($id));
    }

    /** Queue a lab test for a patient â€” creates a pending LabCore worklist entry. */
    public function orderLab(Request $request, int $id): JsonResponse
    {
        $user = auth()->user();
        if (! $user->can('clinic.manage_appointments')) {
            return response()->json(['message' => 'You cannot order labs.'], 403);
        }
        if (! Schema::hasTable('lab_worklist')) {
            return response()->json(['message' => 'Lab module not available.'], 422);
        }

        $data = $request->validate([
            'vendor'    => 'required|string|max:30',
            'device_id' => 'nullable|integer',
            'tests'     => 'nullable|string|max:255',
        ]);

        $businessId = (int) $user->business_id;
        $pet = \Modules\ClinicCore\Entities\ClcPatient::where('business_id', $businessId)->find($id);
        if (! $pet) return response()->json(['message' => 'Patient not found.'], 404);

        $device = ! empty($data['device_id'])
            ? \Illuminate\Support\Facades\DB::table('lab_devices')->where('business_id', $businessId)->find($data['device_id'])
            : null;

        $sampleNo = 'LW-' . $businessId . '-' . strtoupper(substr(uniqid(), -6));
        $tests = ! empty($data['tests'])
            ? array_values(array_filter(array_map('trim', explode(',', $data['tests']))))
            : null;

        $wlId = \Illuminate\Support\Facades\DB::table('lab_worklist')->insertGetId([
            'business_id'  => $businessId,
            'agent_id'     => $device->agent_id ?? null,
            'device_id'    => $device->id ?? ($data['device_id'] ?? null),
            'vendor'       => $data['vendor'],
            'sample_no'    => $sampleNo,
            'patient_name' => $pet->patient_name,
            'species'      => $pet->species,
            'vc_pet_id'    => $pet->id,
            'contact_id'   => $pet->contact_id,
            'tests'        => $tests ? json_encode($tests) : null,
            'status'       => 'pending',
            'created_by'   => $user->id,
            'created_at'   => now(),
            'updated_at'   => now(),
        ]);

        return response()->json(['ok' => true, 'id' => $wlId, 'sample_no' => $sampleNo]);
    }

    public function medicines(Request $request): JsonResponse
    {
        return response()->json($this->repo()->medicines((string) $request->query('q', '')));
    }

    /* â”€â”€ Vaccinations â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€ */

    public function vaccineTypes(): JsonResponse
    {
        return response()->json($this->repo()->vaccineTypes());
    }

    public function recordVaccination(Request $request, int $id): JsonResponse
    {
        $user = auth()->user();
        if (! $user->can('clinic.manage_vaccinations')) {
            return response()->json(['message' => 'You cannot record vaccinations.'], 403);
        }

        $data = $request->validate([
            'vaccine_type_id'    => 'required|integer',
            'administered_date'  => 'nullable|date',
            'next_due_date'      => 'nullable|date',
            'batch_no'           => 'nullable|string|max:191',
            'dose'               => 'nullable|string|max:50',
            'weight_at_time'     => 'nullable|numeric',
            'temperature_at_time'=> 'nullable|numeric',
            'taken_status'       => 'nullable|in:taken_in,taken_out,not_taken',
            'notes'              => 'nullable|string',
            'appointment_id'     => 'nullable|integer',
        ]);

        $businessId = (int) $user->business_id;
        $pet = \Modules\ClinicCore\Entities\ClcPatient::where('business_id', $businessId)->find($id);
        if (! $pet) return response()->json(['message' => 'Patient not found.'], 404);

        // Vaccine types are global (business_id null) with optional per-business additions.
        $type = \Modules\ClinicCore\Entities\ClcVaccineType::where('id', $data['vaccine_type_id'])
            ->where(fn ($q) => $q->whereNull('business_id')->orWhere('business_id', $businessId))
            ->first();
        if (! $type) return response()->json(['message' => 'Vaccine type not found.'], 422);

        // taken_in = given at the clinic, taken_out = administered externally,
        // not_taken = a future dose scheduled (the date is its due date).
        $takenStatus  = $data['taken_status'] ?? 'taken_in';
        $isTaken      = in_array($takenStatus, ['taken_in', 'taken_out'], true);
        $recordStatus = $isTaken ? 'administered' : 'scheduled';

        $givenDate = $data['administered_date'] ?? Carbon::today()->toDateString();
        $nextDue   = $data['next_due_date']
            ?? ($type->valid_for_months ? Carbon::parse($givenDate)->addMonths($type->valid_for_months)->toDateString() : null);

        $doctor = AuthController::doctorForUser($user);

        $vax = \Modules\ClinicCore\Entities\ClcVaccination::create([
            'business_id'         => $businessId,
            'pet_id'              => $pet->id,
            'contact_id'          => $pet->contact_id,
            'vaccine_type_id'     => $type->id,
            'appointment_id'      => $data['appointment_id'] ?? null,
            'dose'                => $data['dose'] ?? null,
            'batch_no'            => $data['batch_no'] ?? null,
            'weight_at_time'      => $data['weight_at_time'] ?? null,
            'temperature_at_time' => $data['temperature_at_time'] ?? null,
            'administered_date'   => $givenDate,
            'next_due_date'       => $nextDue,
            'administered_by'     => $isTaken ? $doctor?->id : null,
            'status'              => $recordStatus,
            'taken_status'        => $takenStatus,
            'notes'               => $data['notes'] ?? null,
        ]);

        // Mirror the module: when actually administered, auto-create the next
        // scheduled (not_taken) dose at its due date so reminders fire later.
        $scheduledNext = false;
        if ($isTaken && $nextDue) {
            $exists = \Modules\ClinicCore\Entities\ClcVaccination::where('pet_id', $pet->id)
                ->where('vaccine_type_id', $type->id)
                ->where('administered_date', $nextDue)
                ->where('taken_status', 'not_taken')
                ->exists();
            if (! $exists) {
                \Modules\ClinicCore\Entities\ClcVaccination::create([
                    'business_id'       => $businessId,
                    'pet_id'            => $pet->id,
                    'contact_id'        => $pet->contact_id,
                    'vaccine_type_id'   => $type->id,
                    'administered_date' => $nextDue,
                    'next_due_date'     => $type->valid_for_months
                        ? Carbon::parse($nextDue)->addMonths($type->valid_for_months)->toDateString() : null,
                    'status'            => 'scheduled',
                    'taken_status'      => 'not_taken',
                ]);
                $scheduledNext = true;
            }
        }

        return response()->json([
            'ok'             => true,
            'id'             => $vax->id,
            'next_due_date'  => $nextDue,
            'scheduled_next' => $scheduledNext,
        ]);
    }

    public function createPrescription(Request $request): JsonResponse
    {
        $user = auth()->user();
        if (! $user->can('clinic.manage_prescriptions')) {
            return response()->json(['message' => 'You cannot create prescriptions.'], 403);
        }

        $data = $request->validate([
            'pet_id'                 => 'required|integer',
            'appointment_id'         => 'nullable|integer',
            'diagnosis'              => 'nullable|string',
            'notes'                  => 'nullable|string',
            'valid_until'            => 'nullable|date',
            'items'                  => 'required|array|min:1',
            'items.*.medicine_name'  => 'required|string',
            'items.*.medicine_id'    => 'nullable|integer',
            'items.*.dosage'         => 'nullable|string',
            'items.*.frequency'      => 'nullable|string',
            'items.*.duration_days'  => 'nullable|integer',
            'items.*.instructions'   => 'nullable|string',
        ]);

        $businessId = (int) $user->business_id;

        $pet = \Modules\ClinicCore\Entities\ClcPatient::where('business_id', $businessId)->find($data['pet_id']);
        if (! $pet) return response()->json(['message' => 'Patient not found.'], 404);

        $doctor = AuthController::doctorForUser($user);

        $rx = \Modules\ClinicCore\Entities\ClcPrescription::create([
            'business_id'     => $businessId,
            'ref_no'          => 'RX-' . date('Ymd') . '-' . strtoupper(substr(uniqid(), -5)),
            'contact_id'      => $pet->contact_id,
            'pet_id'          => $pet->id,
            'doctor_id'       => $doctor?->id,
            'appointment_id'  => $data['appointment_id'] ?? null,
            'prescribed_date' => Carbon::today()->toDateString(),
            'valid_until'     => $data['valid_until'] ?? null,
            'diagnosis'       => $data['diagnosis'] ?? null,
            'notes'           => $data['notes'] ?? null,
            'status'          => 'active',
        ]);

        foreach ($data['items'] as $it) {
            \Modules\ClinicCore\Entities\ClcPrescriptionItem::create([
                'prescription_id' => $rx->id,
                'medicine_id'     => $it['medicine_id'] ?? null,
                'medicine_name'   => $it['medicine_name'],
                'dosage'          => $it['dosage'] ?? null,
                'frequency'       => $it['frequency'] ?? null,
                'duration_days'   => $it['duration_days'] ?? null,
                'instructions'    => $it['instructions'] ?? null,
                'quantity'        => $it['quantity'] ?? 1,
            ]);
        }

        return response()->json(['ok' => true, 'id' => $rx->id, 'ref_no' => $rx->ref_no]);
    }

    /* â”€â”€ Dispensing (FEFO batch â†’ ERP stock decrement, mirrors the module) â”€â”€ */

    /** FEFO batches + business locations available to dispense one Rx item. */
    public function dispenseOptions(int $id): JsonResponse
    {
        if (! auth()->user()->can('clinic.manage_prescriptions')) {
            return response()->json(['message' => 'You cannot dispense.'], 403);
        }
        $opts = $this->repo()->dispenseOptions($id);
        if ($opts === null) return response()->json(['message' => 'Prescription item not found.'], 404);
        return response()->json($opts);
    }

    /** Dispense an Rx item against a chosen purchase_line, decrementing ERP stock. */
    public function dispenseItem(Request $request, int $id): JsonResponse
    {
        $user = auth()->user();
        if (! $user->can('clinic.manage_prescriptions')) {
            return response()->json(['message' => 'You cannot dispense.'], 403);
        }

        $data = $request->validate([
            'purchase_line_id' => 'required|integer',
            'qty'              => 'required|numeric|gt:0',
            'location_id'      => 'required|integer',
        ]);

        $businessId = (int) $user->business_id;
        $item = \Modules\ClinicCore\Entities\ClcPrescriptionItem::with('prescription')->find($id);
        if (! $item || ! $item->prescription || (int) $item->prescription->business_id !== $businessId) {
            return response()->json(['message' => 'Prescription item not found.'], 404);
        }
        if (! empty($item->dispensed_at)) {
            return response()->json(['message' => 'This item has already been dispensed.'], 422);
        }

        try {
            $updated = app(\Modules\ClinicCore\Entities\Services\ClcMedicineDispenseService::class)->dispense(
                $item,
                (int) $data['purchase_line_id'],
                (float) $data['qty'],
                (int) $data['location_id'],
                (int) $user->id
            );
        } catch (\InvalidArgumentException $e) {
            return response()->json(['message' => $e->getMessage()], 422);
        } catch (\Throwable $e) {
            return response()->json(['message' => 'Could not dispense.'], 422);
        }

        return response()->json([
            'ok'            => true,
            'dispensed_qty' => $updated->dispensed_qty,
            'dispensed_at'  => $updated->dispensed_at?->toIso8601String(),
        ]);
    }
}
