<?php

namespace App\Actions\Communications;

use App\Models\Claim\Claim;
use App\Models\Contact\AdHocRecipient;
use App\Models\Contact\ContactTemplate;
use App\Models\Institution\Institution;
use App\Models\RedressScheme;
use App\Models\User\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;

/**
 * Resolve the send-to recipient(s) for a template firing against a messageable,
 * honouring the template's recipient_type. This is the single source of truth for
 * recipient resolution shared by the manual composer (App\Components\Contact\ComposeMessage)
 * and automatic trigger sends (App\Actions\Communications\EvaluateContactTriggersAction).
 *
 * A list is returned because some recipient types fan out to several recipients:
 *   - 'user' broadcasts to every staff user configured on the template, and
 *   - 'institution' sends to each institution linked to the claim.
 */
class ResolveTemplateRecipientsAction
{
    /**
     * @return array<int, Model>
     */
    public function __invoke(ContactTemplate $template, Model $messageable): array
    {
        return match ($template->recipient_type) {
            'scheme' => array_values(array_filter([RedressScheme::first()])),
            'user' => $this->staffUsers($template),
            'institution' => $this->institutionContacts($messageable),
            'case_worker' => array_values(array_filter([$this->caseWorker($messageable)])),
            // 'client', null, or any legacy value send to the messageable itself.
            default => [$messageable],
        };
    }

    /**
     * The claim for a messageable — itself when it is a Claim, otherwise its related claim.
     */
    public function claimFor(Model $messageable): ?Claim
    {
        if ($messageable instanceof Claim) {
            return $messageable;
        }

        return method_exists($messageable, 'claim') ? $messageable->claim : null;
    }

    /**
     * Institutions linked to the claim — directly (claim_institution pivot) and
     * indirectly through the claim's care settings — merged, de-duplicated and
     * sorted, with contacts eager-loaded. Exposed so the composer can reuse the
     * same traversal to populate its institution picker.
     *
     * @return Collection<int, Institution>
     */
    public function institutionsFor(?Claim $claim): Collection
    {
        if (! $claim) {
            return collect();
        }

        $direct = $claim->institutions()->with('contacts')->get();

        $viaCareSettings = $claim->careSettings()
            ->with('institution.contacts')
            ->get()
            ->pluck('institution')
            ->filter();

        return $direct->concat($viaCareSettings)
            ->unique('id')
            ->sortBy('display_name')
            ->values();
    }

    /**
     * Staff users configured on the template (recipient_type = user) — broadcast.
     *
     * @return array<int, Model>
     */
    private function staffUsers(ContactTemplate $template): array
    {
        $ids = array_values(array_map('intval', $template->config['recipient_user_ids'] ?? []));

        if (empty($ids)) {
            return [];
        }

        return User::whereIn('id', $ids)
            ->orderBy('name_first')
            ->orderBy('name_last')
            ->get()
            ->all();
    }

    /**
     * The primary (or first) contact of each institution linked to the messageable's
     * claim. Institutions with no contacts are skipped.
     *
     * @return array<int, Model>
     */
    private function institutionContacts(Model $messageable): array
    {
        return $this->institutionsFor($this->claimFor($messageable))
            ->map(fn (Institution $institution) => $institution->contacts->firstWhere('is_primary', true)
                ?? $institution->contacts->first())
            ->filter()
            ->values()
            ->all();
    }

    /**
     * The redress case worker for the messageable's claim as a transient recipient
     * carrying the name + email stored as columns on the claim.
     */
    private function caseWorker(Model $messageable): ?Model
    {
        $claim = $this->claimFor($messageable);

        if (! $claim) {
            return null;
        }

        return new AdHocRecipient([
            'name' => $claim->redress_case_worker,
            'email' => $claim->redress_case_worker_email,
        ]);
    }
}
