<?php

namespace App\Components\Dialler;

use App\Enum\Communication\CallProvider;
use App\Enum\Communication\CommunicationChannel;
use App\Enum\Communication\CommunicationDirection;
use App\Enum\Communication\CommunicationEventType;
use App\Enum\Communication\CommunicationStatus;
use App\Interface\CallableEntity;
use App\Models\Communication\Communication;
use App\Models\Communication\SolicitorPhoneNumber;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Computed;
use Livewire\Attributes\On;
use Livewire\Component;
use RuntimeException;

/**
 * CallManager — per-client dialler panel (contact tab)
 *
 * Responsibility: initiate outbound calls and display recent communications.
 * The full in-call experience (ringing, active call controls, and disposition
 * wrap-up) is handled by the global CallOverlay component mounted in the header.
 */
class CallManager extends Component {
    public Model $communicable;
    public ?int  $caseId = null;

    // Device status (used only to gate the dial button)
    public bool    $deviceReady       = false;
    public bool    $deviceRegistering = false;
    public ?string $errorMessage      = null;

    public function mount(Model $communicable, ?int $caseId = null): void
    {
        if (! $communicable instanceof CallableEntity) {
            throw new RuntimeException('CallManager requires a model implementing CallableEntity.');
        }

        $this->communicable = $communicable;
        $this->caseId       = $caseId;
    }

    // ── Outbound call ─────────────────────────────────────────────────────────

    public function startCall(): void
    {
        abort_unless(auth()->check(), 403);

        if (! $this->deviceReady) {
            $this->errorMessage = 'The browser phone is not ready yet.';
            return;
        }

        /** @var CallableEntity $target */
        $target   = $this->communicable;
        $toNumber = $target->getCallableNumber();

        if (! $toNumber) {
            $this->errorMessage = 'This record does not have a callable phone number.';
            return;
        }

        $this->errorMessage = null;
        $fromNumber         = $this->resolveOutboundNumber();

        try {
            $communication = DB::transaction(function () use ($target, $toNumber, $fromNumber) {
                $communication = Communication::query()->create([
                    'solicitor_id'      => $target->solicitor_id,
                    'claim_id'          => $this->caseId,
                    'agent_id'          => auth()->id(),
                    'communicable_type' => $target->getMorphClass(),
                    'communicable_id'   => $target->getKey(),
                    'channel'           => CommunicationChannel::Voice->value,
                    'direction'         => CommunicationDirection::Outbound->value,
                    'status'            => CommunicationStatus::Initiated->value,
                    'started_at'        => now(),
                    'preview'           => 'Outbound call to ' . $target->getCallableLabel(),
                    'meta'              => [
                        'provider'  => CallProvider::Twilio->value,
                        'ui_origin' => 'call_manager',
                    ],
                ]);

                $call = $communication->call()->create([
                    'solicitor_id'              => $target->solicitor_id,
                    'assigned_agent_id'         => auth()->id(),
                    'solicitor_phone_number_id' => $fromNumber->id,
                    'provider'                  => CallProvider::Twilio->value,
                    'provider_call_sid'         => null,
                    'from_number'               => $fromNumber->normalized_phone_number,
                    'to_number'                 => $toNumber,
                    'phone_number_type_slug'    => $fromNumber->type?->slug,
                    'call_type'                 => $target->getCallableType(),
                    'target_label'              => $target->getCallableLabel(),
                    'target_type'               => $target->getCallableType(),
                    'is_recorded'               => (bool) ($fromNumber->type?->auto_record ?? true),
                    'recording_notice_played'   => false,
                    'meta'                      => ['browser_sdk' => true],
                ]);

                $communication->events()->create([
                    'solicitor_id' => $communication->solicitor_id,
                    'call_id'      => $call->id,
                    'agent_id'     => auth()->id(),
                    'event'        => CommunicationEventType::OutboundCallCreated->value,
                    'source'       => 'agent',
                    'description'  => 'Outbound browser call created.',
                    'occurred_at'  => now(),
                    'payload'      => [
                        'from_number'  => $fromNumber->normalized_phone_number,
                        'to_number'    => $toNumber,
                        'target_label' => $target->getCallableLabel(),
                    ],
                ]);

                return $communication->load('call');
            });

            // Tell the global CallOverlay about this outbound call so it can
            // manage the in-call UI and disposition form.
            $this->dispatch('dialler-call-started',
                communication_id: $communication->id,
                call_id:          $communication->call->id,
                label:            $target->getCallableLabel(),
                direction:        'outbound',
            );

            // Tell the JS layer to actually connect via Twilio SDK.
            $this->dispatch('twilio-start-browser-call', call_id: $communication->call->id);

        } catch (\Throwable $e) {
            $this->errorMessage = $e->getMessage();
            report($e);
        }
    }

    // ── Device events (gate the dial button) ─────────────────────────────────

    #[On('twilio-device-registering')]
    public function handleDeviceRegistering(): void
    {
        $this->deviceRegistering = true;
        $this->deviceReady       = false;
    }

    #[On('twilio-device-registered')]
    public function handleDeviceRegistered(): void
    {
        $this->deviceRegistering = false;
        $this->deviceReady       = true;
        $this->errorMessage      = null;
    }

    #[On('twilio-device-unregistered')]
    public function handleDeviceUnregistered(): void
    {
        $this->deviceRegistering = false;
        $this->deviceReady       = false;
    }

    #[On('twilio-device-error')]
    public function handleDeviceError($message = null): void
    {
        $this->deviceRegistering = false;
        $this->deviceReady       = false;
        $this->errorMessage      = is_string($message) ? $message : 'Softphone error.';
    }

    // ── Computed ──────────────────────────────────────────────────────────────

    #[Computed]
    public function callableNumber(): ?string
    {
        return $this->communicable->getCallableNumber();
    }

    #[Computed]
    public function callableLabel(): string
    {
        return $this->communicable->getCallableLabel();
    }

    #[Computed]
    public function recentCommunications()
    {
        return Communication::query()
            ->with(['call', 'agent', 'recordings'])
            ->where('communicable_type', $this->communicable->getMorphClass())
            ->where('communicable_id', $this->communicable->getKey())
            ->latest('started_at')
            ->limit(8)
            ->get();
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    protected function resolveOutboundNumber(): SolicitorPhoneNumber
    {
        $preferredSlug = match (strtolower(class_basename($this->communicable))) {
            'client', 'contact', 'lead' => 'client_line',
            default                      => 'external_line',
        };

        $number = SolicitorPhoneNumber::query()
            ->with('type')
            ->where('solicitor_id', $this->communicable->solicitor_id)
            ->where('is_active', true)
            ->whereHas('type', fn ($q) => $q->where('slug', $preferredSlug))
            ->orderByDesc('is_primary')
            ->first()
            ?? SolicitorPhoneNumber::query()
                ->with('type')
                ->where('solicitor_id', $this->communicable->solicitor_id)
                ->where('is_active', true)
                ->orderByDesc('is_primary')
                ->first();

        if (! $number) {
            throw new RuntimeException('No active solicitor phone number is configured.');
        }

        return $number;
    }

    public function render() {
        return view('components.dialler.call-manager');
    }
}
