<?php

namespace App\Services\Care;

use App\Models\Appointment;
use App\Models\Bill;
use App\Models\Department;
use App\Models\Member;
use App\Models\Organization;
use App\Models\Patient;
use App\Models\Visit;
use App\Services\Care\BloodBank\BloodBankWorkflowService;
use App\Services\Care\Emergency\EmergencyWorkflowService;
use App\Services\Care\Ophthalmology\OphthalmologyWorkflowService;
use App\Services\Care\Physiotherapy\PhysiotherapyWorkflowService;
use App\Services\Care\WomensHealth\WomensHealthWorkflowService;
use App\Services\Care\Radiology\RadiologyWorkflowService;
use App\Services\Care\Cardiology\CardiologyWorkflowService;
use App\Services\Care\Psychiatry\PsychiatryWorkflowService;
use App\Services\Care\Pediatrics\PediatricsWorkflowService;
use App\Services\Care\Orthopedics\OrthopedicsWorkflowService;
use App\Services\Care\Ent\EntWorkflowService;
use App\Services\Care\Oncology\OncologyWorkflowService;
use App\Services\Care\Renal\RenalWorkflowService;
use App\Services\Care\Surgery\SurgeryWorkflowService;
use App\Services\Care\Vaccination\VaccinationWorkflowService;
use App\Services\Care\Pathology\PathologyWorkflowService;
use App\Services\Care\Infusion\InfusionWorkflowService;
use App\Services\Care\Dermatology\DermatologyWorkflowService;
use App\Services\Care\Podiatry\PodiatryWorkflowService;
use App\Services\Care\Fertility\FertilityWorkflowService;
use App\Services\Care\ChildWelfare\ChildWelfareWorkflowService;
use App\Services\Care\Ambulance\AmbulanceWorkflowService;
use Illuminate\Support\Collection;

/**
 * Shared specialty module shell — KPIs, Visit-backed lists, stage maps, service catalogs.
 */
class SpecialtyShellService
{
    public function __construct(
        protected SpecialtyModuleService $modules,
        protected BillService $bills,
    ) {}

    /**
     * @return array<string, mixed>
     */
    public function definition(string $moduleKey): array
    {
        $moduleKey = $this->modules->normalizeKey($moduleKey);
        $base = $this->modules->definition($moduleKey) ?? [];
        $shell = config("care_specialty_shell.modules.{$moduleKey}", []);
        $defaults = config('care_specialty_shell.defaults', []);

        return array_merge($defaults, $base, is_array($shell) ? $shell : []);
    }

    /**
     * @return list<array{code: string, label: string, queue_point: ?string}>
     */
    public function stages(string $moduleKey): array
    {
        $stages = $this->definition($moduleKey)['stages'] ?? [];

        return is_array($stages) ? array_values($stages) : [];
    }

    /**
     * Named route for advancing/setting specialty visit stage, if the module has one.
     */
    public function stageAdvanceRoute(string $moduleKey): ?string
    {
        return match ($moduleKey) {
            'dentistry' => 'care.specialty.dentistry.stage',
            'emergency' => 'care.specialty.emergency.stage',
            'blood_bank' => 'care.specialty.blood-bank.stage',
            'ophthalmology' => 'care.specialty.ophthalmology.stage',
            'physiotherapy' => 'care.specialty.physiotherapy.stage',
            'womens_health' => 'care.specialty.womens_health.stage',
            'radiology' => 'care.specialty.radiology.stage',
            'cardiology' => 'care.specialty.cardiology.stage',
            'psychiatry' => 'care.specialty.psychiatry.stage',
            'pediatrics' => 'care.specialty.pediatrics.stage',
            'orthopedics' => 'care.specialty.orthopedics.stage',
            'ent' => 'care.specialty.ent.stage',
            'oncology' => 'care.specialty.oncology.stage',
            'renal' => 'care.specialty.renal.stage',
            'surgery' => 'care.specialty.surgery.stage',
            'vaccination' => 'care.specialty.vaccination.stage',
            'pathology' => 'care.specialty.pathology.stage',
            'infusion' => 'care.specialty.infusion.stage',
            'dermatology' => 'care.specialty.dermatology.stage',
            'podiatry' => 'care.specialty.podiatry.stage',
            'fertility' => 'care.specialty.fertility.stage',
            'child_welfare' => 'care.specialty.child-welfare.stage',
            'ambulance' => 'care.specialty.ambulance.stage',
            default => null,
        };
    }

    /**
     * Default stage progression for Move CTAs and action menus.
     *
     * @return array<string, array{next: string, label: string}>
     */
    public function stageFlow(string $moduleKey): array
    {
        return match ($moduleKey) {
            'emergency' => app(EmergencyWorkflowService::class)->stageFlow(),
            'blood_bank' => app(BloodBankWorkflowService::class)->stageFlow(),
            'ophthalmology' => app(OphthalmologyWorkflowService::class)->stageFlow(),
            'physiotherapy' => app(PhysiotherapyWorkflowService::class)->stageFlow(),
            'womens_health' => app(WomensHealthWorkflowService::class)->stageFlow(),
            'radiology' => app(RadiologyWorkflowService::class)->stageFlow(),
            'cardiology' => app(CardiologyWorkflowService::class)->stageFlow(),
            'psychiatry' => app(PsychiatryWorkflowService::class)->stageFlow(),
            'pediatrics' => app(PediatricsWorkflowService::class)->stageFlow(),
            'orthopedics' => app(OrthopedicsWorkflowService::class)->stageFlow(),
            'ent' => app(EntWorkflowService::class)->stageFlow(),
            'oncology' => app(OncologyWorkflowService::class)->stageFlow(),
            'renal' => app(RenalWorkflowService::class)->stageFlow(),
            'surgery' => app(SurgeryWorkflowService::class)->stageFlow(),
            'vaccination' => app(VaccinationWorkflowService::class)->stageFlow(),
            'pathology' => app(PathologyWorkflowService::class)->stageFlow(),
            'infusion' => app(InfusionWorkflowService::class)->stageFlow(),
            'dermatology' => app(DermatologyWorkflowService::class)->stageFlow(),
            'podiatry' => app(PodiatryWorkflowService::class)->stageFlow(),
            'fertility' => app(FertilityWorkflowService::class)->stageFlow(),
            'child_welfare' => app(ChildWelfareWorkflowService::class)->stageFlow(),
            'ambulance' => app(AmbulanceWorkflowService::class)->stageFlow(),
            'dentistry' => [
                'waiting' => ['next' => 'chair', 'label' => 'Seat at chair'],
                'chair' => ['next' => 'procedure', 'label' => 'Start procedure'],
                'procedure' => ['next' => 'recovery', 'label' => 'Move to recovery'],
                'recovery' => ['next' => 'completed', 'label' => 'Complete visit'],
            ],
            default => $this->sequentialStageFlow($moduleKey),
        };
    }

    /**
     * Build next-stage map from configured stage order when no suite workflow exists.
     *
     * @return array<string, array{next: string, label: string}>
     */
    protected function sequentialStageFlow(string $moduleKey): array
    {
        $stages = $this->stages($moduleKey);
        $flow = [];

        for ($i = 0, $count = count($stages); $i < $count; $i++) {
            $code = (string) ($stages[$i]['code'] ?? '');
            if ($code === '') {
                continue;
            }

            $next = $stages[$i + 1] ?? null;
            if ($next === null) {
                $flow[$code] = ['next' => $code, 'label' => 'Completed'];

                continue;
            }

            $nextCode = (string) ($next['code'] ?? '');
            if ($nextCode === '') {
                continue;
            }

            $nextLabel = (string) ($next['label'] ?? $nextCode);
            $flow[$code] = [
                'next' => $nextCode,
                'label' => 'Move to '.mb_strtolower($nextLabel),
            ];
        }

        return $flow;
    }

    /**
     * Catalog from config (authoritative template).
     *
     * @return list<array{code: string, label: string, amount_minor: int, type: string, service_line?: string}>
     */
    public function catalogServices(string $moduleKey, ?Organization $organization = null): array
    {
        $moduleKey = $this->modules->normalizeKey($moduleKey);
        $services = $this->definition($moduleKey)['services'] ?? [];
        if (! is_array($services)) {
            return [];
        }

        $enabledLines = $organization
            ? $this->modules->enabledServiceLines($organization, $moduleKey)
            : [];

        $out = [];
        foreach ($services as $row) {
            if (! is_array($row)) {
                continue;
            }
            $line = isset($row['service_line']) ? (string) $row['service_line'] : '';
            if ($organization && $enabledLines !== [] && $line !== '') {
                if (! ($enabledLines[$line] ?? false)) {
                    continue;
                }
            }
            $out[] = [
                'code' => (string) ($row['code'] ?? ''),
                'label' => (string) ($row['label'] ?? ''),
                'amount_minor' => (int) ($row['amount_minor'] ?? 0),
                'type' => (string) ($row['type'] ?? 'misc'),
                'service_line' => $line,
            ];
        }

        return array_values($out);
    }

    /**
     * Seeded services stored on the organization when the module was activated.
     *
     * @return list<array{code: string, label: string, amount_minor: int, type: string, service_line?: string}>
     */
    public function provisionedServices(Organization $organization, string $moduleKey): array
    {
        $moduleKey = $this->modules->normalizeKey($moduleKey);
        $catalog = collect($this->catalogServices($moduleKey, $organization))->keyBy(fn ($s) => (string) ($s['code'] ?? ''));
        $stored = data_get($organization->settings, "specialty_module_provisioning.{$moduleKey}.services");
        if (is_array($stored)) {
            foreach ($stored as $service) {
                $code = (string) ($service['code'] ?? '');
                if ($code === '') {
                    continue;
                }
                // Skip stored services for disabled service lines.
                $line = (string) ($service['service_line'] ?? $catalog->get($code)['service_line'] ?? '');
                if ($line !== '' && ! $this->modules->isServiceLineEnabled($organization, $moduleKey, $line)) {
                    continue;
                }
                $prior = $catalog->get($code, []);
                $catalog->put($code, array_merge(is_array($prior) ? $prior : [], $service));
            }
        }

        return $catalog->filter(fn ($s, $code) => $code !== '')->values()->all();
    }

    /**
     * Persist catalog services onto org settings (idempotent merge by code).
     *
     * @return list<array{code: string, label: string, amount_minor: int, type: string}>
     */
    public function seedServices(Organization $organization, string $moduleKey): array
    {
        $catalog = $this->catalogServices($moduleKey);
        $settings = $organization->settings ?? [];
        $provisioning = is_array($settings['specialty_module_provisioning'] ?? null)
            ? $settings['specialty_module_provisioning']
            : [];
        $record = is_array($provisioning[$moduleKey] ?? null) ? $provisioning[$moduleKey] : [];
        $existing = collect(is_array($record['services'] ?? null) ? $record['services'] : [])
            ->keyBy(fn ($s) => (string) ($s['code'] ?? ''));

        foreach ($catalog as $service) {
            if ($service['code'] === '') {
                continue;
            }
            $prior = $existing->get($service['code'], []);
            $existing->put($service['code'], array_merge(is_array($prior) ? $prior : [], $service));
        }

        $merged = $existing->filter(fn ($s, $code) => $code !== '')->values()->all();
        $record['services'] = $merged;
        $provisioning[$moduleKey] = $record;
        $settings['specialty_module_provisioning'] = $provisioning;
        $organization->update(['settings' => $settings]);

        return $merged;
    }

    /**
     * @return array<string, string>
     */
    public function workspaceTabs(string $moduleKey): array
    {
        $tabs = $this->definition($moduleKey)['workspace_tabs'] ?? [];
        if (! is_array($tabs)) {
            return [];
        }

        if (array_key_exists('timeline', $tabs)) {
            return $tabs;
        }

        $merged = [];
        foreach ($tabs as $key => $label) {
            $merged[$key] = $label;
            if ($key === 'overview') {
                $merged['timeline'] = 'Timeline';
            }
        }

        if (! array_key_exists('timeline', $merged)) {
            $merged['timeline'] = 'Timeline';
        }

        return $merged;
    }

    /**
     * Workspace tab to open after advancing/setting a visit specialty stage.
     * Uses module `stage_tabs` config, then same-named tab, then overview.
     */
    public function workspaceTabForStage(string $moduleKey, string $stage): string
    {
        $stage = trim($stage);
        $tabs = $this->workspaceTabs($moduleKey);
        $map = $this->definition($moduleKey)['stage_tabs'] ?? [];

        if (is_array($map) && isset($map[$stage]) && is_string($map[$stage]) && $map[$stage] !== '') {
            $candidate = $map[$stage];
            if (array_key_exists($candidate, $tabs)) {
                return $candidate;
            }
        }

        if ($stage !== '' && array_key_exists($stage, $tabs)) {
            return $stage;
        }

        return array_key_exists('overview', $tabs) ? 'overview' : (array_key_first($tabs) ?: 'overview');
    }

    /**
     * @return array<string, string>
     */
    public function actions(string $moduleKey): array
    {
        $actions = $this->definition($moduleKey)['actions'] ?? [];

        return is_array($actions) ? $actions : [];
    }

    /**
     * @return list<int>
     */
    public function departmentIds(
        Organization $organization,
        string $moduleKey,
        string $ownerRef,
        ?int $branchScope = null,
    ): array {
        $definition = $this->modules->definition($moduleKey);
        if (! $definition) {
            return [];
        }

        $fromSettings = data_get($organization->settings, "specialty_module_provisioning.{$moduleKey}.department_ids", []);
        if (is_array($fromSettings) && $fromSettings !== []) {
            $ids = array_map('intval', $fromSettings);
        } else {
            $ids = Department::owned($ownerRef)
                ->where('organization_id', $organization->id)
                ->where('type', $definition['department_type'] ?? 'general')
                ->where('is_active', true)
                ->when($branchScope, fn ($q) => $q->where('branch_id', $branchScope))
                ->pluck('id')
                ->map(fn ($id) => (int) $id)
                ->all();
        }

        if ($branchScope) {
            $ids = Department::owned($ownerRef)
                ->whereIn('id', $ids ?: [0])
                ->where('branch_id', $branchScope)
                ->pluck('id')
                ->map(fn ($id) => (int) $id)
                ->all();
        }

        return $ids;
    }

    /**
     * @param  list<int>|null  $practitionerScope
     * @return array{
     *     waiting: int,
     *     in_progress: int,
     *     completed_today: int,
     *     open_visits: int,
     *     revenue_today_minor: int,
     *     stages: list<array{code: string, label: string, count: int}>
     * }
     */
    public function kpis(
        Organization $organization,
        string $moduleKey,
        string $ownerRef,
        ?int $branchScope = null,
        ?array $practitionerScope = null,
    ): array {
        $departmentIds = $this->departmentIds($organization, $moduleKey, $ownerRef, $branchScope);
        $todayStart = now()->startOfDay();

        $appointmentBase = Appointment::owned($ownerRef)
            ->where('organization_id', $organization->id)
            ->when($departmentIds !== [], fn ($q) => $q->whereIn('department_id', $departmentIds))
            ->when($departmentIds === [], fn ($q) => $q->whereRaw('1 = 0'))
            ->when($branchScope, fn ($q) => $q->where('branch_id', $branchScope))
            ->when(
                $practitionerScope !== null,
                fn ($q) => $q->whereIn('practitioner_id', $practitionerScope ?: [0]),
            );

        $waiting = (clone $appointmentBase)
            ->whereIn('status', [Appointment::STATUS_WAITING, Appointment::STATUS_CHECKED_IN])
            ->count();

        $inProgress = (clone $appointmentBase)
            ->where('status', Appointment::STATUS_IN_CONSULTATION)
            ->count();

        $completedToday = (clone $appointmentBase)
            ->where('status', Appointment::STATUS_COMPLETED)
            ->where('completed_at', '>=', $todayStart)
            ->count();

        $visitIds = (clone $appointmentBase)->whereNotNull('visit_id')->pluck('visit_id');

        $openVisits = Visit::owned($ownerRef)
            ->where('organization_id', $organization->id)
            ->whereIn('id', $visitIds->isEmpty() ? [0] : $visitIds)
            ->whereIn('status', [Visit::STATUS_OPEN, Visit::STATUS_IN_PROGRESS])
            ->count();

        $revenueToday = (int) Bill::owned($ownerRef)
            ->where('organization_id', $organization->id)
            ->whereIn('visit_id', $visitIds->isEmpty() ? [0] : $visitIds)
            ->where('created_at', '>=', $todayStart)
            ->whereNotIn('status', [Bill::STATUS_VOID])
            ->sum('amount_paid_minor');

        $stages = collect($this->stages($moduleKey))->map(function (array $stage) use (
            $waiting,
            $inProgress,
            $completedToday,
            $moduleKey,
            $organization,
            $ownerRef,
            $visitIds,
        ) {
            $code = (string) ($stage['code'] ?? '');

            if (in_array($moduleKey, ['dentistry', 'emergency', 'blood_bank', 'ophthalmology', 'physiotherapy', 'womens_health', 'radiology', 'cardiology', 'psychiatry', 'pediatrics', 'orthopedics', 'ent', 'oncology', 'renal', 'surgery', 'vaccination', 'pathology', 'infusion', 'dermatology', 'podiatry', 'fertility', 'child_welfare', 'ambulance'], true) && $visitIds->isNotEmpty()) {
                $count = Visit::owned($ownerRef)
                    ->where('organization_id', $organization->id)
                    ->whereIn('id', $visitIds)
                    ->where('specialty_stage', $code)
                    ->when(
                        ! in_array($code, ['completed', 'disposition'], true),
                        fn ($q) => $q->whereIn('status', [Visit::STATUS_OPEN, Visit::STATUS_IN_PROGRESS]),
                    )
                    ->count();
            } else {
                $count = match (true) {
                    in_array($code, ['waiting', 'arrival', 'request'], true) => $waiting,
                    in_array($code, ['in_care', 'chair', 'procedure', 'treatment', 'resus', 'crossmatch', 'issue', 'observation'], true) => $inProgress,
                    in_array($code, ['completed', 'disposition', 'transfusion', 'recovery'], true) => $completedToday,
                    default => 0,
                };
            }

            return [
                'code' => $code,
                'label' => (string) ($stage['label'] ?? $code),
                'count' => $count,
            ];
        })->all();

        $clinical = app(SpecialtyClinicalRecordService::class);
        $clinicalOpen = match ($moduleKey) {
            'emergency' => $clinical->countOpenByType($organization, 'emergency', 'triage', $branchScope),
            'blood_bank' => $clinical->countOpenByType($organization, 'blood_bank', 'blood_request', $branchScope),
            'ophthalmology' => $clinical->countOpenByType($organization, 'ophthalmology', 'eye_exam', $branchScope),
            'physiotherapy' => $clinical->countOpenByType($organization, 'physiotherapy', 'pt_assessment', $branchScope),
            'womens_health' => $clinical->countOpenByType($organization, 'womens_health', 'anc_history', $branchScope),
            'radiology' => $clinical->countOpenByType($organization, 'radiology', 'imaging_request', $branchScope),
            'cardiology' => $clinical->countOpenByType($organization, 'cardiology', 'cardiac_exam', $branchScope),
            'psychiatry' => $clinical->countOpenByType($organization, 'psychiatry', 'mental_status', $branchScope),
            'pediatrics' => $clinical->countOpenByType($organization, 'pediatrics', 'pediatric_exam', $branchScope),
            'orthopedics' => $clinical->countOpenByType($organization, 'orthopedics', 'ortho_exam', $branchScope),
            'ent' => $clinical->countOpenByType($organization, 'ent', 'ent_exam', $branchScope),
            'oncology' => $clinical->countOpenByType($organization, 'oncology', 'onc_staging', $branchScope),
            'renal' => $clinical->countOpenByType($organization, 'renal', 'renal_exam', $branchScope),
            'surgery' => $clinical->countOpenByType($organization, 'surgery', 'surg_exam', $branchScope),
            'vaccination' => $clinical->countOpenByType($organization, 'vaccination', 'vax_eligibility', $branchScope),
            'pathology' => $clinical->countOpenByType($organization, 'pathology', 'path_request', $branchScope),
            'infusion' => $clinical->countOpenByType($organization, 'infusion', 'infusion_assessment', $branchScope),
            'dermatology' => $clinical->countOpenByType($organization, 'dermatology', 'skin_exam', $branchScope),
            'podiatry' => $clinical->countOpenByType($organization, 'podiatry', 'foot_exam', $branchScope),
            'fertility' => $clinical->countOpenByType($organization, 'fertility', 'fert_exam', $branchScope),
            'child_welfare' => $clinical->countOpenByType($organization, 'child_welfare', 'cwc_assessment', $branchScope),
            'ambulance' => $clinical->countOpenByType($organization, 'ambulance', 'amb_scene', $branchScope),
            'dentistry' => \App\Models\DentalPlanItem::query()
                ->whereHas('plan', function ($q) use ($organization, $ownerRef) {
                    $q->owned($ownerRef)
                        ->where('organization_id', $organization->id)
                        ->whereIn('status', [
                            \App\Models\DentalTreatmentPlan::STATUS_ACCEPTED,
                            \App\Models\DentalTreatmentPlan::STATUS_IN_PROGRESS,
                        ]);
                })
                ->whereIn('status', [
                    \App\Models\DentalPlanItem::STATUS_ACCEPTED,
                    \App\Models\DentalPlanItem::STATUS_PROPOSED,
                ])
                ->count(),
            default => 0,
        };

        return [
            'waiting' => $waiting,
            'in_progress' => $inProgress,
            'completed_today' => $completedToday,
            'open_visits' => $openVisits,
            'revenue_today_minor' => $revenueToday,
            'clinical_open' => $clinicalOpen,
            'stages' => $stages,
        ];
    }

    /**
     * @param  list<int>|null  $practitionerScope
     * @return Collection<int, Visit>
     */
    public function openVisits(
        Organization $organization,
        string $moduleKey,
        string $ownerRef,
        ?int $branchScope = null,
        ?array $practitionerScope = null,
        int $limit = 40,
    ): Collection {
        $departmentIds = $this->departmentIds($organization, $moduleKey, $ownerRef, $branchScope);

        return Visit::owned($ownerRef)
            ->where('organization_id', $organization->id)
            ->whereIn('status', [Visit::STATUS_OPEN, Visit::STATUS_IN_PROGRESS])
            ->when($branchScope, fn ($q) => $q->where('branch_id', $branchScope))
            ->whereHas('appointment', function ($q) use ($departmentIds, $practitionerScope) {
                $q->when($departmentIds !== [], fn ($qq) => $qq->whereIn('department_id', $departmentIds))
                    ->when($departmentIds === [], fn ($qq) => $qq->whereRaw('1 = 0'))
                    ->when(
                        $practitionerScope !== null,
                        fn ($qq) => $qq->whereIn('practitioner_id', $practitionerScope ?: [0]),
                    );
            })
            ->with(['patient', 'appointment.practitioner', 'appointment.department', 'bill'])
            ->orderByDesc('checked_in_at')
            ->limit($limit)
            ->get();
    }

    /**
     * @param  list<int>|null  $practitionerScope
     * @return Collection<int, Visit>
     */
    public function visitHistory(
        Organization $organization,
        string $moduleKey,
        string $ownerRef,
        ?int $branchScope = null,
        ?array $practitionerScope = null,
        int $limit = 40,
    ): Collection {
        $departmentIds = $this->departmentIds($organization, $moduleKey, $ownerRef, $branchScope);

        return Visit::owned($ownerRef)
            ->where('organization_id', $organization->id)
            ->where('status', Visit::STATUS_COMPLETED)
            ->when($branchScope, fn ($q) => $q->where('branch_id', $branchScope))
            ->whereHas('appointment', function ($q) use ($departmentIds, $practitionerScope) {
                $q->when($departmentIds !== [], fn ($qq) => $qq->whereIn('department_id', $departmentIds))
                    ->when($departmentIds === [], fn ($qq) => $qq->whereRaw('1 = 0'))
                    ->when(
                        $practitionerScope !== null,
                        fn ($qq) => $qq->whereIn('practitioner_id', $practitionerScope ?: [0]),
                    );
            })
            ->with(['patient', 'appointment.practitioner', 'appointment.department', 'bill'])
            ->orderByDesc('completed_at')
            ->limit($limit)
            ->get();
    }

    /**
     * @return list<array{at: string, label: string, detail: ?string}>
     */
    public function patientTimeline(Patient $patient, int $limit = 20): array
    {
        $patient->loadMissing([
            'visits' => fn ($q) => $q->orderByDesc('checked_in_at')->limit(10),
            'appointments' => fn ($q) => $q->orderByDesc('scheduled_at')->limit(10),
            'bills' => fn ($q) => $q->orderByDesc('created_at')->limit(10),
        ]);

        $events = collect();

        foreach ($patient->visits as $visit) {
            if ($visit->checked_in_at) {
                $events->push([
                    'at' => $visit->checked_in_at->toIso8601String(),
                    'label' => 'Visit opened',
                    'detail' => 'Visit '.$visit->uuid,
                ]);
            }
            if ($visit->completed_at) {
                $events->push([
                    'at' => $visit->completed_at->toIso8601String(),
                    'label' => 'Visit completed',
                    'detail' => null,
                ]);
            }
        }

        foreach ($patient->appointments as $appointment) {
            if ($appointment->waiting_at) {
                $events->push([
                    'at' => $appointment->waiting_at->toIso8601String(),
                    'label' => 'Joined waiting list',
                    'detail' => $appointment->queue_ticket_number
                        ? 'Ticket '.$appointment->queue_ticket_number
                        : null,
                ]);
            }
            if ($appointment->started_at) {
                $events->push([
                    'at' => $appointment->started_at->toIso8601String(),
                    'label' => 'Consultation started',
                    'detail' => $appointment->practitioner?->name,
                ]);
            }
            if ($appointment->completed_at) {
                $events->push([
                    'at' => $appointment->completed_at->toIso8601String(),
                    'label' => 'Consultation completed',
                    'detail' => null,
                ]);
            }
        }

        foreach ($patient->bills as $bill) {
            $events->push([
                'at' => $bill->created_at?->toIso8601String() ?? now()->toIso8601String(),
                'label' => 'Invoice '.$bill->invoice_number,
                'detail' => strtoupper((string) $bill->status),
            ]);
        }

        return $events
            ->sortByDesc('at')
            ->take($limit)
            ->values()
            ->all();
    }

    /**
     * @return array{
     *     outstanding_minor: int,
     *     allergies: list<string>,
     *     insurance_summary: ?string,
     *     emergency: bool
     * }
     */
    public function patientHeaderMeta(Patient $patient): array
    {
        $patient->loadMissing(['allergies', 'insurancePolicies', 'emergencyContacts']);

        $outstanding = (int) Bill::owned($patient->owner_ref)
            ->where('patient_id', $patient->id)
            ->whereIn('status', [Bill::STATUS_OPEN, Bill::STATUS_PARTIAL, Bill::STATUS_DRAFT])
            ->sum('balance_minor');

        $allergies = $patient->allergies->map(fn ($a) => (string) $a->allergen)->filter()->values()->all();

        $policy = $patient->insurancePolicies->first();
        $insurance = $policy
            ? trim(implode(' · ', array_filter([(string) ($policy->provider_name ?? ''), (string) ($policy->policy_number ?? '')])))
            : null;

        return [
            'outstanding_minor' => $outstanding,
            'allergies' => $allergies,
            'insurance_summary' => $insurance !== '' ? $insurance : null,
            'emergency' => $patient->emergencyContacts->isNotEmpty(),
        ];
    }

    public function addCatalogServiceToVisit(
        Organization $organization,
        Visit $visit,
        string $moduleKey,
        string $serviceCode,
        string $ownerRef,
        ?string $actorRef = null,
        int $quantity = 1,
    ): Bill {
        $service = collect($this->provisionedServices($organization, $moduleKey))
            ->first(fn ($s) => ($s['code'] ?? '') === $serviceCode);

        if (! $service) {
            throw new \InvalidArgumentException("Unknown specialty service [{$serviceCode}].");
        }

        // Reuse an open visit bill without syncLineItemsFromVisit — that sync deletes
        // prior specialty_service lines and would wipe multi-item issue/billing batches.
        $bill = Bill::owned($ownerRef)
            ->where('visit_id', $visit->id)
            ->whereNotIn('status', [Bill::STATUS_VOID])
            ->first();

        if (! $bill) {
            $bill = $this->bills->generateFromVisit($visit, $ownerRef, $actorRef);
        }

        return $this->bills->addManualLineItem($bill, $ownerRef, [
            'type' => $service['type'] ?? 'misc',
            'description' => $service['label'],
            'quantity' => max(1, $quantity),
            'unit_price_minor' => (int) ($service['amount_minor'] ?? 0),
            'source_type' => 'specialty_service',
            'source_id' => null,
        ], $actorRef);
    }

    public function navItems(string $moduleKey): array
    {
        // Queue home is the primary surface; history/billing are secondary links in the shell header.
        return [
            'history' => ['label' => 'History', 'route' => 'care.specialty.history'],
            'billing' => ['label' => 'Billing', 'route' => 'care.specialty.billing'],
        ];
    }

    public function memberCanAccess(Organization $organization, ?Member $member, string $moduleKey): bool
    {
        return $this->modules->memberCanAccess($organization, $member, $moduleKey);
    }
}
