<?php

namespace App\Components\Dsar;

use App\Models\Claim\Claim;
use App\Models\Contact\Hub\ContactMessageAttachment;
use App\Models\Document\Document;
use App\Models\Dsar\Dsar;
use App\Models\Dsar\DsarEvidenceItem;
use App\Models\Solicitor\Solicitor;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;

class EvidenceViewer extends Component
{
    use AuthorizesRequests;

    public ?ContactMessageAttachment $attachment = null;
    public ?Document $document = null;

    /** Exposed as a reactive Livewire property so Alpine's $wire.itemsData stays current after confirm/dismiss/save. */
    public array $itemsData = [];

    public ?int    $dsarId          = null;
    public ?string $filterState    = null; // null | confirmed | dismissed | unreviewed
    public ?string $filterCategory = null; // null | any category key
    public ?string $filterSource   = null; // null | ai | human
    public ?string $filterLocation = null; // null | located | unlocated

    /** Compact-header context, supplied by the hosting viewer page. */
    public ?string $backUrl      = null;
    public ?string $fileName     = null;
    public ?string $contextLabel = null;

    /** Set when the review was opened via "Review N findings" (?start=review). */
    public bool $autoReview = false;

    /** Inline "Edit interpretation" state. */
    public ?int    $editingId      = null;
    public string  $editingComment = '';

    /** Bulk-selection: evidence-item ids ticked for a bulk confirm/dismiss. */
    public array $selected = [];

    // Human annotation form (populated from JS via dispatch)
    public bool    $showAddForm     = false;
    public ?int    $addPage         = null;
    public ?string $addQuote        = null;
    public ?array  $addBbox         = null;
    public string  $addCategory     = 'other';
    public string  $addSeverity     = 'medium';
    public string  $addComment      = '';
    public ?int    $addCareSettingId = null;

    /** [care_setting_id => "Institution (dates)"] — the claim's care settings, for tagging highlights. */
    public array $careSettings = [];

    public array $categories = [
        'abuse'         => 'Abuse',
        'neglect'       => 'Neglect',
        'trauma'        => 'Trauma',
        'mental_health' => 'Mental Health',
        'addiction'     => 'Addiction',
        'violence'      => 'Violence',
        'drug_alcohol'  => 'Drug / Alcohol',
        'social_care'   => 'Social Care',
        'suicide'       => 'Suicide',
        'other'         => 'Other',
    ];

    public function mount(
        ?ContactMessageAttachment $attachment = null,
        ?Document $document = null,
        ?int $dsarId = null,
        ?string $backUrl = null,
        ?string $fileName = null,
        ?string $contextLabel = null,
    ): void {
        // Livewire dependency-injects an EMPTY model for an unpassed class-typed mount param,
        // so a document-only viewer arrives with a blank (non-existent) $attachment rather than
        // null. Normalise those back to null so origin checks (render/findItem/loadCareSettings)
        // and the PDF URL resolve correctly.
        $this->attachment   = ($attachment && $attachment->exists) ? $attachment : null;
        $this->document     = ($document && $document->exists) ? $document : null;
        $this->dsarId       = $dsarId;
        $this->backUrl      = $backUrl;
        $this->fileName     = $fileName;
        $this->contextLabel = $contextLabel;
        $this->careSettings = $this->loadCareSettings();

        // Opened via the overview's "Review N findings" action: default the state
        // filter to the review queue so the viewer lands on work still to do.
        if (request()->query('start') === 'review') {
            $this->autoReview  = true;
            $this->filterState = 'unreviewed';
        }
    }

    /**
     * The owning claim's care settings as [id => "Institution (dates)"], for tagging highlights.
     * Resolved from the DSAR, the document owner, or the attachment's message.
     */
    private function loadCareSettings(): array
    {
        $claim = null;

        if ($this->dsarId) {
            $claim = Dsar::find($this->dsarId)?->claim;
        }
        if (!$claim && $this->document) {
            $owner = $this->document->owner;
            if ($owner instanceof Claim) {
                $claim = $owner;
            } elseif ($owner instanceof Dsar) {
                $claim = $owner->claim;
            } elseif ($owner && method_exists($owner, 'claim')) {
                $claim = $owner->claim;
            }
        }
        if (!$claim && $this->attachment) {
            $messageable = $this->attachment->message?->messageable;
            if ($messageable instanceof Dsar) {
                $claim = $messageable->claim;
            }
        }

        if (!$claim) {
            return [];
        }

        return $claim->careSettings()
            ->with('institution')
            ->get()
            ->mapWithKeys(fn ($cs) => [
                $cs->id => trim(($cs->institution?->display_name ?? 'Unknown care setting')
                    . ($cs->dates ? " ({$cs->dates})" : '')),
            ])
            ->toArray();
    }

    private function findItem(int $itemId): DsarEvidenceItem
    {
        if ($this->attachment) {
            return DsarEvidenceItem::where('contact_message_attachment_id', $this->attachment->id)->findOrFail($itemId);
        }
        return DsarEvidenceItem::where('document_id', $this->document->id)->findOrFail($itemId);
    }

    /** Emit a transient toast to the viewer's Alpine layer (with an optional Undo). */
    private function toast(string $message, ?int $undoId = null, ?string $undoAction = null): void
    {
        $this->dispatch('evidence-toast', message: $message, undoId: $undoId, undoAction: $undoAction);
    }

    public function confirm(int $itemId): void
    {
        $item = $this->findItem($itemId);
        $item->update(['confirmed_at' => now(), 'dismissed_at' => null]);
        $this->dispatch('evidenceItemUpdated');
        $this->toast('Finding confirmed', $item->id, 'unreview');
    }

    public function dismiss(int $itemId): void
    {
        $item = $this->findItem($itemId);
        $item->update(['dismissed_at' => now(), 'confirmed_at' => null]);
        $this->dispatch('evidenceItemUpdated');
        $this->toast('Finding dismissed', $item->id, 'unreview');
    }

    /** Return a finding to the review queue (undo a confirm/dismiss). */
    public function unreview(int $itemId): void
    {
        $item = $this->findItem($itemId);
        $item->update(['confirmed_at' => null, 'dismissed_at' => null]);
        $this->dispatch('evidenceItemUpdated');
        $this->toast('Finding returned to review');
    }

    // ── Edit interpretation ─────────────────────────────────────────────────────

    public function startEdit(int $itemId): void
    {
        $item                 = $this->findItem($itemId);
        $this->editingId      = $item->id;
        $this->editingComment = (string) $item->comment;
    }

    public function saveEdit(): void
    {
        if (!$this->editingId) {
            return;
        }
        $this->validate(['editingComment' => 'required|string|min:3'], [], ['editingComment' => 'interpretation']);

        $item = $this->findItem($this->editingId);
        $item->update(['comment' => trim($this->editingComment)]);

        $this->editingId      = null;
        $this->editingComment = '';
        $this->dispatch('evidenceItemUpdated');
    }

    public function cancelEdit(): void
    {
        $this->editingId      = null;
        $this->editingComment = '';
        $this->resetErrorBag('editingComment');
    }

    /**
     * Attach a page + bounding box to an existing finding that was extracted from
     * document text but never linked to an exact page location. Reuses the same
     * normalised bbox shape drawn for human annotations.
     */
    public function locateItem(int $itemId, ?int $page, ?array $bbox): void
    {
        if (!$page) {
            return;
        }
        $item = $this->findItem($itemId);
        $item->update(['page' => $page, 'bbox' => $bbox]);
        $this->dispatch('evidenceItemUpdated');
        $this->toast('Evidence location saved', $item->id, 'clearLocation');
    }

    /** Remove a finding's page + bounding box, returning it to the unlocated group. */
    public function clearLocation(int $itemId): void
    {
        $item = $this->findItem($itemId);
        $item->update(['page' => null, 'bbox' => null]);
        $this->dispatch('evidenceItemUpdated');
        $this->toast('Evidence location removed');
    }

    // ── Bulk actions (explicit selection required) ──────────────────────────────

    /** @return \Illuminate\Support\Collection<int, DsarEvidenceItem> the selected items belonging to this file */
    private function selectedItems()
    {
        $ids = array_values(array_unique(array_map('intval', $this->selected)));
        if (empty($ids)) {
            return collect();
        }

        $source = $this->attachment
            ? DsarEvidenceItem::where('contact_message_attachment_id', $this->attachment->id)
            : DsarEvidenceItem::where('document_id', $this->document->id);

        return $source->whereIn('id', $ids)->get();
    }

    public function confirmSelected(): void
    {
        $items = $this->selectedItems();
        if ($items->isEmpty()) {
            return;
        }
        foreach ($items as $item) {
            $item->update(['confirmed_at' => now(), 'dismissed_at' => null]);
        }
        $count = $items->count();
        $this->selected = [];
        $this->dispatch('evidenceItemUpdated');
        $this->toast($count . ' ' . ($count === 1 ? 'finding' : 'findings') . ' confirmed');
    }

    public function dismissSelected(): void
    {
        $items = $this->selectedItems();
        if ($items->isEmpty()) {
            return;
        }
        foreach ($items as $item) {
            $item->update(['dismissed_at' => now(), 'confirmed_at' => null]);
        }
        $count = $items->count();
        $this->selected = [];
        $this->dispatch('evidenceItemUpdated');
        $this->toast($count . ' ' . ($count === 1 ? 'finding' : 'findings') . ' dismissed');
    }

    public function clearSelection(): void
    {
        $this->selected = [];
    }

    public function saveNote(int $itemId, string $note): void
    {
        $item = $this->findItem($itemId);
        $item->update(['human_notes' => $note]);
    }

    /**
     * Set (or clear) the care-setting tag on a highlight. A human touch confirms the
     * tag, so the AI-suggested flag is cleared.
     */
    public function setCareSetting(int $itemId, $careSettingId = null): void
    {
        // The value arrives as a string from the <select> ('' = none); coerce safely.
        $careSettingId = ($careSettingId === '' || $careSettingId === null) ? null : (int) $careSettingId;

        if ($careSettingId !== null && !array_key_exists($careSettingId, $this->careSettings)) {
            return; // only accept care settings belonging to this claim
        }

        $item = $this->findItem($itemId);
        $item->update([
            'care_setting_id'           => $careSettingId,
            'care_setting_is_suggested' => false,
        ]);
        $this->dispatch('evidenceItemUpdated');
    }

    /** Accept an AI-suggested care-setting tag as-is (clears the pending flag). */
    public function acceptCareSetting(int $itemId): void
    {
        $item = $this->findItem($itemId);
        if ($item->care_setting_id) {
            $item->update(['care_setting_is_suggested' => false]);
            $this->dispatch('evidenceItemUpdated');
        }
    }

    public function openAddForm(?int $page, ?string $quote, ?array $bbox): void
    {
        $this->addPage         = $page;
        $this->addQuote        = $quote;
        $this->addBbox         = $bbox;
        $this->addCategory     = 'other';
        $this->addSeverity     = 'medium';
        $this->addComment      = '';
        $this->addCareSettingId = null;
        $this->showAddForm     = true;
    }

    public function saveEvidence(): void
    {
        $this->validate([
            'addCategory' => 'required|string',
            'addSeverity' => 'required|in:high,medium,low',
            'addComment'  => 'required|string|min:3',
        ]);

        $careSettingId = ($this->addCareSettingId && array_key_exists($this->addCareSettingId, $this->careSettings))
            ? $this->addCareSettingId
            : null;

        DsarEvidenceItem::create([
            'solicitor_id'                  => app(Solicitor::class)?->id,
            'contact_message_attachment_id' => $this->attachment?->id,
            'document_id'                   => $this->document?->id,
            'dsar_id'                       => $this->dsarId,
            'care_setting_id'               => $careSettingId,
            'care_setting_is_suggested'     => false,
            'page'                          => $this->addPage,
            'quote'                         => $this->addQuote,
            'bbox'                          => $this->addBbox,
            'category'                      => $this->addCategory,
            'severity'                      => $this->addSeverity,
            'comment'                       => $this->addComment,
            'source'                        => 'human',
        ]);

        $this->showAddForm = false;
        $this->dispatch('evidenceItemUpdated');
    }

    public function cancelAdd(): void
    {
        $this->showAddForm = false;
    }

    public function deleteItem(int $itemId): void
    {
        $item = $this->findItem($itemId);
        abort_if($item->source !== 'human', 403);
        $item->delete();
        $this->dispatch('evidenceItemUpdated');
    }

    /** Fresh base query over this file's evidence items (attachment or document). */
    private function baseQuery()
    {
        return $this->attachment
            ? $this->attachment->evidenceItems()
            : $this->document->evidenceItems();
    }

    public function render()
    {
        $query = $this->baseQuery()->orderBy('page')->orderBy('id');

        if ($this->filterState === 'confirmed') {
            $query->confirmed();
        } elseif ($this->filterState === 'dismissed') {
            $query->dismissed();
        } elseif ($this->filterState === 'unreviewed') {
            $query->unreviewed();
        }

        if ($this->filterCategory) {
            $query->where('category', $this->filterCategory);
        }
        if ($this->filterSource === 'human') {
            $query->where('source', 'human');
        } elseif ($this->filterSource === 'ai') {
            $query->where('source', '!=', 'human');
        }
        if ($this->filterLocation === 'located') {
            $query->whereNotNull('page');
        } elseif ($this->filterLocation === 'unlocated') {
            $query->whereNull('page');
        }

        $items = $query->get();

        $this->itemsData = $items->map(fn ($item) => [
            'id'                     => $item->id,
            'page'                   => $item->page,
            'quote'                  => $item->quote,
            'bbox'                   => $item->bbox,
            'category'               => $item->category,
            'category_label'         => DsarEvidenceItem::categoryLabel($item->category),
            'colour'                 => DsarEvidenceItem::categoryColour($item->category),
            'severity'               => $item->severity,
            'comment'                => $item->comment,
            'source'                 => $item->source,
            'care_setting_id'        => $item->care_setting_id,
            'care_setting_label'     => $item->care_setting_id ? ($this->careSettings[$item->care_setting_id] ?? null) : null,
            'care_setting_suggested' => (bool) $item->care_setting_is_suggested,
            'state'                  => $item->isConfirmed() ? 'confirmed' : ($item->isDismissed() ? 'dismissed' : 'unreviewed'),
        ])->values()->toArray();

        // Group items by page for the sidebar (unlocated findings collect under 0).
        $itemsByPage = $items->groupBy(fn ($i) => $i->page ?? 0)->sortKeys();

        // Unfiltered totals so the header progress stays stable across filter changes.
        $total          = $this->baseQuery()->count();
        $confirmedCount = $this->baseQuery()->confirmed()->count();
        $dismissedCount = $this->baseQuery()->dismissed()->count();
        $toReviewCount  = $this->baseQuery()->unreviewed()->count();

        // First unreviewed finding in the current (filtered, ordered) set — the
        // viewer auto-selects it on load when opened via "Review N findings".
        $firstUnreviewedId = $items->first(fn ($i) => $i->isUnreviewed())?->id;

        return view('components.dsar.evidence-viewer', [
            'items'             => $items,
            'itemsByPage'       => $itemsByPage,
            'totalCount'        => $total,
            'confirmedCount'    => $confirmedCount,
            'dismissedCount'    => $dismissedCount,
            'toReviewCount'     => $toReviewCount,
            'firstUnreviewedId' => $firstUnreviewedId,
        ]);
    }
}
