<?php

namespace App\Services\Communications\Placeholders;

use App\Models\Claim\Claim;
use App\Models\Client\Client;
use App\Models\Institution\Institution;
use App\ValueObjects\Communications\PlaceholderContext;

/**
 * Registers the DSAR letter placeholder set onto the shared PlaceholderRegistry
 * (the same registry Contact Templates use). Token names are plain %snake_case%
 * strings, matching the existing DsarType content exactly, so no existing
 * dsar_types row needs to change syntax or wording.
 */
class DsarPlaceholders
{
    public static function register(PlaceholderRegistry $registry): void
    {
        // ---- Client / care-history fields (ported 1:1 from the old PlaceholderService::forDsar()) ----
        $registry->register('client_name', Client::class,
            fn (PlaceholderContext $c) => self::client($c)?->fullName ?? '',
            description: 'Client full name');

        $registry->register('client_dob', Client::class,
            fn (PlaceholderContext $c) => self::client($c)?->date_of_birth?->formatted('d/m/Y') ?? '',
            description: 'Client date of birth');

        $registry->register('client_also_known_as', Client::class,
            fn (PlaceholderContext $c) => self::previousNames(self::client($c)),
            description: 'Other names the client has been known by');

        $registry->register('name_in_care', Client::class,
            fn (PlaceholderContext $c) => self::previousNames(self::client($c)),
            description: 'Name the client used while in care');

        $registry->register('client_legal_name_change', Client::class,
            fn (PlaceholderContext $c) => self::previousNames(self::client($c)),
            description: 'Legal name change history');

        $registry->register('name_before_care', Client::class,
            fn (PlaceholderContext $c) => self::furtherInformation($c)?->name_before_care ?? '',
            description: 'Name used before entering care');

        $registry->register('mothers_name', Client::class,
            fn (PlaceholderContext $c) => self::furtherInformation($c)?->mothers_name ?? '',
            description: "Client's mother's name");

        $registry->register('fathers_name', Client::class,
            fn (PlaceholderContext $c) => self::furtherInformation($c)?->fathers_name ?? '',
            description: "Client's father's name");

        $registry->register('address_prior_to_care', Client::class,
            fn (PlaceholderContext $c) => self::addressPriorToCare($c),
            description: 'Address prior to entering care');

        $registry->register('client_address', Client::class,
            fn (PlaceholderContext $c) => self::client($c)?->currentAddress?->getFullAddress() ?? '',
            description: "Client's current address");

        $registry->register('care_settings', Client::class,
            fn (PlaceholderContext $c) => self::careSettingLists(self::client($c))[0],
            description: 'HTML list of care settings/institutions attached to the claim',
            isHtml: true);

        $registry->register('approx_dates', Client::class,
            fn (PlaceholderContext $c) => self::careSettingLists(self::client($c))[1],
            description: 'HTML list of care settings with their approximate dates',
            isHtml: true);

        $registry->register('approximate_dates', Client::class,
            fn (PlaceholderContext $c) => self::careSettingLists(self::client($c))[1],
            description: 'Alias of approx_dates',
            isHtml: true);

        $registry->register('client_reference', Client::class,
            fn (PlaceholderContext $c) => (string) (self::client($c)?->displayId ?? ''),
            description: 'Client reference / display id');

        $registry->register('dsar_reference', null,
            fn (PlaceholderContext $c) => (string) ($c->extra['dsar_reference'] ?? ''),
            description: 'Reference of this DSAR request');

        // ---- Solicitor ----
        $registry->register('solicitor_name', null,
            fn (PlaceholderContext $c) => $c->solicitor?->name ?? '',
            description: 'Solicitor / firm name');

        $registry->register('solicitor_email', null,
            fn (PlaceholderContext $c) => (string) ($c->solicitor?->email_address ?? ''),
            description: 'Solicitor primary email address');

        $registry->register('solicitor_phone', null,
            fn (PlaceholderContext $c) => (string) ($c->solicitor?->phone_number ?? ''),
            description: 'Solicitor primary phone number');

        // ---- Authenticated user (who created the DSAR) ----
        $registry->register('user_name', null,
            fn (PlaceholderContext $c) => trim(($c->user?->name_first ?? '').' '.($c->user?->name_last ?? '')),
            description: 'Name of the staff member who created the DSAR');

        $registry->register('user_email', null,
            fn (PlaceholderContext $c) => (string) ($c->user?->email_address ?? ''),
            description: 'Email of the staff member who created the DSAR');

        // ---- Redress case worker (stored on the claim) ----
        $registry->register('case_worker_name', Client::class,
            fn (PlaceholderContext $c) => self::claim($c)?->redress_case_worker ?? '',
            fallback: 'the case worker',
            description: 'Redress case worker name');

        $registry->register('case_worker_email', Client::class,
            fn (PlaceholderContext $c) => (string) (self::claim($c)?->redress_case_worker_email ?? ''),
            description: 'Redress case worker email address');

        // ---- Claim ----
        $registry->register('claim_reference', Client::class,
            fn (PlaceholderContext $c) => self::claim($c)?->claim_id ?? '',
            description: 'Claim internal reference');

        $registry->register('claim_status', Client::class,
            fn (PlaceholderContext $c) => self::claim($c)?->status?->display_name ?? '',
            description: 'Current claim status name');

        $registry->register('claim_redress_type', Client::class,
            fn (PlaceholderContext $c) => self::claim($c)?->redress_type ?? '',
            description: 'Claim redress type');

        $registry->register('claim_handler_name', Client::class,
            fn (PlaceholderContext $c) => optional(self::claim($c)?->handlers()->first())->name ?? '',
            description: 'Assigned claim handler name');

        // ---- Institution (the DSAR recipient) ----
        $registry->register('institution_name', null,
            fn (PlaceholderContext $c) => self::institution($c)?->display_name ?? '',
            description: 'Name of the institution the DSAR is addressed to');

        $registry->register('institution_reference', null,
            fn (PlaceholderContext $c) => (string) (self::institution($c)?->displayId ?? ''),
            description: 'Reference / display id of the institution');

        $registry->register('institution_address', null,
            fn (PlaceholderContext $c) => self::institutionAddress(self::institution($c)),
            description: 'Current address of the institution');

        // ---- System ----
        $registry->register('system_date', null,
            fn () => now()->format('d/m/Y'),
            description: "Today's date (d/m/Y)");

        $registry->register('system_date_long', null,
            fn () => now()->format('l jS F Y'),
            description: "Today's date (long form)");

        $registry->register('system_name', null,
            fn () => config('app.name'),
            description: 'System name');
    }

    /**
     * The full DSAR placeholder catalog grouped by category, for the DSAR Type
     * Editor's "Available placeholders" panel. Token names aren't dotted (unlike
     * the Contact Template registry), so grouping is maintained explicitly here
     * rather than derived from the token string.
     *
     * @return array<string, array<int, string>>
     */
    public static function grouped(): array
    {
        return [
            'Client' => [
                'client_name', 'client_dob', 'client_also_known_as', 'name_in_care',
                'client_legal_name_change', 'name_before_care', 'mothers_name', 'fathers_name',
                'address_prior_to_care', 'client_address', 'care_settings', 'approx_dates',
                'approximate_dates', 'client_reference',
            ],
            'DSAR' => ['dsar_reference'],
            'Case Worker' => ['case_worker_name', 'case_worker_email'],
            'Claim' => ['claim_reference', 'claim_status', 'claim_redress_type', 'claim_handler_name'],
            'Institution' => ['institution_name', 'institution_reference', 'institution_address'],
            'Solicitor' => ['solicitor_name', 'solicitor_email', 'solicitor_phone'],
            'User' => ['user_name', 'user_email'],
            'System' => ['system_date', 'system_date_long', 'system_name'],
        ];
    }

    private static function client(PlaceholderContext $c): ?Client
    {
        if ($c->messageable instanceof Client) return $c->messageable;
        if ($c->related instanceof Client) return $c->related;
        if ($c->messageable instanceof Claim) return $c->messageable->client;
        if ($c->related instanceof Claim) return $c->related->client;
        return null;
    }

    private static function claim(PlaceholderContext $c): ?Claim
    {
        if ($c->messageable instanceof Claim) return $c->messageable;
        if ($c->related instanceof Claim) return $c->related;
        return self::client($c)?->claim;
    }

    private static function institution(PlaceholderContext $c): ?Institution
    {
        if ($c->related instanceof Institution) return $c->related;
        if ($c->messageable instanceof Institution) return $c->messageable;
        return null;
    }

    private static function previousNames(?Client $client): string
    {
        if (!$client) {
            return '';
        }

        return $client->previousNames
            ->map(fn ($n) => $n->fullName)
            ->filter()
            ->join(', ');
    }

    private static function furtherInformation(PlaceholderContext $c)
    {
        return self::client($c)?->claim?->furtherInformation;
    }

    private static function addressPriorToCare(PlaceholderContext $c): string
    {
        $fi = self::furtherInformation($c);

        return collect([
            $fi?->address_prior_line_1,
            $fi?->address_prior_line_2,
            $fi?->address_prior_line_3,
            $fi?->address_prior_postcode,
        ])->filter()->join(', ');
    }

    /**
     * @var array<int, array{0: string, 1: string}>
     */
    private static array $careSettingListCache = [];

    /**
     * Build parallel HTML lists for care_settings / approx_dates, memoized per
     * client per request so both tokens don't each re-run the same query.
     *
     * @return array{0: string, 1: string} [careSettingsHtml, approxDatesHtml]
     */
    private static function careSettingLists(?Client $client): array
    {
        if (!$client) {
            return ['', ''];
        }

        if (isset(self::$careSettingListCache[$client->id])) {
            return self::$careSettingListCache[$client->id];
        }

        $careSettings = $client->claim?->careSettings()->with('institution')->get() ?? collect();

        if ($careSettings->isEmpty()) {
            return self::$careSettingListCache[$client->id] = ['', ''];
        }

        $settingItems = '';
        $dateItems = '';

        foreach ($careSettings as $cs) {
            $name = e($cs->institution?->display_name ?? 'Unknown');
            $dates = e($cs->dates ?? 'Unknown');

            $settingItems .= "<li>{$name}</li>";
            $dateItems .= "<li>{$name}: {$dates}</li>";
        }

        return self::$careSettingListCache[$client->id] = [
            "<ul>{$settingItems}</ul>",
            "<ul>{$dateItems}</ul>",
        ];
    }

    private static function institutionAddress(?Institution $institution): string
    {
        if (!$institution) {
            return '';
        }

        $address = $institution->addresses()->where('is_current', true)->first()
            ?? $institution->addresses()->latest()->first();

        return $address?->getFullAddress() ?? '';
    }
}
