<?php

namespace App\Services\Communications\Placeholders;

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

/**
 * Registers the default set of placeholders available to every template.
 * Additional tenant/custom placeholders can be added via
 * PlaceholderRegistry::register() from any service provider's boot() method.
 */
class DefaultPlaceholders
{
    /**
     * Known UK honorifics — used by smartName() to detect and repair legacy
     * data that was corrupted by the HasName::fullName setter (which split
     * "Mr John Smith" into name_first="Mr", name_last="John Smith").
     */
    private const KNOWN_TITLES = [
        'mr', 'mrs', 'ms', 'miss', 'mx', 'dr', 'prof', 'professor',
        'sir', 'dame', 'lord', 'lady', 'rev', 'reverend', 'hon',
    ];

    public static function register(PlaceholderRegistry $registry): void
    {
        // ---- Client ----
        $registry->register('client.title', Client::class,
            fn (PlaceholderContext $c) => self::smartName(self::client($c))['title'],
            fallback: '',
            description: 'Client title / honorific (e.g. Mr, Mrs, Dr)');

        $registry->register('client.first_name', Client::class,
            fn (PlaceholderContext $c) => self::smartName(self::client($c))['first'],
            fallback: 'there',
            description: 'Client first name');

        $registry->register('client.last_name', Client::class,
            fn (PlaceholderContext $c) => self::smartName(self::client($c))['last'],
            fallback: '',
            description: 'Client last name');

        $registry->register('client.preferred_name', Client::class,
            fn (PlaceholderContext $c) => self::client($c)?->getRawOriginal('name_preferred')
                ?: self::smartName(self::client($c))['first'],
            fallback: 'there',
            description: 'Preferred name if set, otherwise first name');

        $registry->register('client.full_name', Client::class,
            function (PlaceholderContext $c) {
                $parts = self::smartName(self::client($c));
                $middle = self::client($c)?->getRawOriginal('name_middle');
                return trim(implode(' ', array_filter([
                    $parts['title'],
                    $parts['first'],
                    $middle,
                    $parts['last'],
                ])));
            },
            fallback: 'there',
            description: 'Client full name (title + first + middle + last)');

        $registry->register('client.email', Client::class,
            fn (PlaceholderContext $c) => (string) (self::client($c)?->email_address ?? ''),
            description: 'Client email address');

        $registry->register('client.phone', Client::class,
            fn (PlaceholderContext $c) => (string) (self::client($c)?->phone_number ?? ''),
            description: 'Client phone number');

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

        // ---- Claim (also works when messageable is the Claim or when a Client with a Claim relation is passed) ----
        $registry->register('claim.reference', Claim::class,
            fn (PlaceholderContext $c) => self::claim($c)?->claim_id ?? '',
            description: 'Claim internal reference');

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

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

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

        // ---- 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');

        // ---- Sender identity ----
        $registry->register('sender.display_name', null,
            fn (PlaceholderContext $c) => $c->sender?->label() ?? '',
            description: 'Configured sender display name');

        $registry->register('sender.email', null,
            fn (PlaceholderContext $c) => $c->sender?->value() ?? '',
            description: 'Configured sender email or phone');

        // ---- Authenticated user ----
        $registry->register('user.name', null,
            fn (PlaceholderContext $c) => $c->user?->name ?? '',
            description: 'Current user name');

        $registry->register('user.email', null,
            fn (PlaceholderContext $c) => $c->user?->email ?? '',
            description: 'Current user email');

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

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

        $registry->register('system.system_name', null,
            fn () => config('app.name'),
            description: '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;
    }

    /**
     * Defensive name parser.  Returns ['title', 'first', 'last'] from a Client,
     * repairing legacy data corruption where the HasName::fullName setter has
     * cracked a value like "Mr John Smith" into name_first="Mr",
     * name_last="John Smith".
     *
     * Heuristic: when name_first matches a known UK honorific (Mr, Mrs, Dr, ...)
     * and name_last contains a space, treat name_first as the title and re-split
     * name_last into first + remaining parts.
     */
    private static function smartName(?Client $client): array
    {
        if (!$client) {
            return ['title' => null, 'first' => null, 'last' => null];
        }

        // Read the original DB values via getRawOriginal so we are immune to
        // any in-memory mutations triggered by Eloquent's cached cast merge
        // (HasName::fullName has a SET mutator + shouldCache that, when read
        // earlier in the request, can corrupt name_first / name_last on the
        // model instance later in the same request).
        $title = $client->getRawOriginal('title') ?: null;
        $first = $client->getRawOriginal('name_first') ?: null;
        $last = $client->getRawOriginal('name_last') ?: null;

        if ($first && $last && str_contains($last, ' ')) {
            $stripped = strtolower(rtrim(trim($first), '.'));
            if (in_array($stripped, self::KNOWN_TITLES, true)) {
                // Promote name_first to title if title is blank, then re-split last.
                $title = $title ?: $first;
                $parts = preg_split('/\s+/', trim($last));
                $first = array_shift($parts);
                $last = implode(' ', $parts);
            }
        }

        return ['title' => $title, 'first' => $first, 'last' => $last];
    }

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