<?php

namespace App\Livewire\Modals;

use App\Models\AuditEvent;
use App\Support\Confirm\ConfirmToken;
use App\Support\Confirm\InvalidConfirmToken;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Locked;
use LivewireUI\Modal\ModalComponent;

/**
 * Generic R5 confirmation dialog for destructive / stateful actions.
 *
 * The opener mints a signed, single-use {@see ConfirmToken} that seals the follow-up
 * event + its params + the audit descriptor, and passes ONLY that token (plus display
 * copy) to the modal. On confirm we re-verify the token, persist exactly one
 * AuditEvent from the SEALED descriptor (never from client input), and re-dispatch the
 * sealed event so the originating page can apply its own state change — handing the
 * same token on so the apply handler can burn it (single-use). The modal stays
 * domain-agnostic and is reused for every confirm in the app (R5).
 *
 * Routing lives only in the token: the modal exposes no client-mutable event/params/
 * audit fields, and the token itself is #[Locked] so it cannot be swapped after open.
 */
class ConfirmAction extends ModalComponent
{
    public string $heading = '';

    public string $body = '';

    public string $confirmLabel = '';

    public bool $danger = false;

    public string $icon = '';

    /** Toast shown on confirm. Pass '' to defer the notification to the handler
     *  (e.g. when the real outcome is only known after a remote command runs);
     *  leave null to fall back to the generic default toast. */
    public ?string $notify = null;

    /** The sealed routing input — the ONLY thing that decides what runs/gets audited. */
    #[Locked]
    public string $token = '';

    public function mount(
        string $token,
        string $heading = '',
        string $body = '',
        string $confirmLabel = '',
        bool $danger = false,
        string $icon = '',
        ?string $notify = null,
    ): void {
        $this->token = $token;
        $this->body = $body;
        $this->danger = $danger;
        $this->icon = $icon;

        // Localized fallbacks for any copy the opener did not pass.
        $this->heading = $heading !== '' ? $heading : __('modals.confirm_action.default_heading');
        $this->confirmLabel = $confirmLabel !== '' ? $confirmLabel : __('common.confirm');
        $this->notify = $notify ?? __('modals.confirm_action.default_notify');
    }

    public static function modalMaxWidth(): string
    {
        return 'md';
    }

    /** Audit-target line for the view — read from the signed token, not client state. */
    #[Computed]
    public function targetLabel(): ?string
    {
        return ConfirmToken::peek($this->token)['auditTarget'] ?? null;
    }

    public function confirm(): void
    {
        try {
            // Flip the token pending → confirmed (single transition). This IS the
            // confirmation: an apply handler may only consume a confirmed token.
            $payload = ConfirmToken::confirm($this->token);
        } catch (InvalidConfirmToken) {
            // Forged / tampered / expired / already-confirmed — never audit or dispatch.
            $this->dispatch('notify', message: __('modals.confirm_action.rejected'));
            $this->closeModal();

            return;
        }

        if (($payload['auditAction'] ?? '') !== '') {
            AuditEvent::create([
                'user_id' => Auth::id(),
                'server_id' => $payload['serverId'] ?? null,
                'actor' => Auth::user()?->name ?? 'system',
                'action' => $payload['auditAction'],
                'target' => $payload['auditTarget'] ?? null,
                'ip' => request()->ip(),
            ]);
        }

        // Re-dispatch the sealed event, handing the token on so the apply handler can
        // burn it (single-use) and read the sealed params itself.
        $this->dispatch($payload['event'], confirmToken: $this->token);

        // Empty notify = the handler will report the real outcome itself.
        if ($this->notify !== '') {
            $this->dispatch('notify', message: $this->notify);
        }

        $this->closeModal();
    }

    public function render()
    {
        return view('livewire.modals.confirm-action');
    }
}
