<?php

namespace App\Trait;

use App\Jobs\Synapse\ExtractEvidenceFromAttachmentJob;
use App\Jobs\Synapse\ExtractEvidenceFromDocumentJob;
use App\Models\Contact\Hub\ContactMessageAttachment;
use App\Models\Document\Document;
use App\Models\Dsar\DsarEvidenceItem;
use App\Services\EvidenceCorpus;
use Illuminate\Support\Collection;
use Livewire\Attributes\On;

/**
 * The shared "evidence review" behaviour — filters, per-file actions
 * (extract / exclude / source-assignment) and the recipient → DSAR grouping —
 * used by both surfaces that render the review: the embedded client Evidence
 * tab (ClientEvidence) and the standalone workbench page (EvidenceWorkbench).
 *
 * The host component supplies `public Client $client` and `use AuthorizesRequests`.
 */
trait ManagesEvidenceReview
{
    public bool $showExcluded = false;

    // Filters (operate on evidence items).
    public ?string $filterCategory = null;
    public ?string $filterSeverity = null;
    public ?string $filterReview   = null; // confirmed | unreviewed | dismissed

    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',
    ];

    #[On('evidenceItemUpdated')]
    public function refreshEvidence(): void {}

    // ── Extraction ─────────────────────────────────────────────────────────────

    public function runAttachmentExtraction(int $attachmentId, int $dsarId): void
    {
        $this->authorize('synapse.use');
        ExtractEvidenceFromAttachmentJob::dispatch($attachmentId, $dsarId);
    }

    public function runDocumentExtraction(int $documentId): void
    {
        $this->authorize('synapse.use');
        ExtractEvidenceFromDocumentJob::dispatch($documentId);
    }

    // ── Junk (exclude / restore) ────────────────────────────────────────────────

    public function excludeFile(string $type, int $id): void
    {
        $this->authorize('dsars.view');
        $this->evidenceFileModel($type, $id)?->update(['evidence_excluded' => true]);
    }

    public function restoreFile(string $type, int $id): void
    {
        $this->authorize('dsars.view');
        $this->evidenceFileModel($type, $id)?->update(['evidence_excluded' => false]);
    }

    /**
     * Clear a stuck evidence_status (e.g. left on 'processing' when a worker died
     * mid-job), so the file can be extracted again. Does not itself run the AI.
     */
    public function resetStatus(string $type, int $id): void
    {
        $this->authorize('dsars.view');
        $this->evidenceFileModel($type, $id)?->update(['evidence_status' => null]);
    }

    private function evidenceFileModel(string $type, int $id): Document|ContactMessageAttachment|null
    {
        return $type === 'attachment'
            ? ContactMessageAttachment::find($id)
            : Document::find($id);
    }

    // ── Grouping ─────────────────────────────────────────────────────────────────

    /**
     * Build everything the shared review partial needs: the recipient → DSAR
     * grouping, the excluded-file count, the source-assignment search results and
     * the auto-refresh flag, plus headline totals for the summary strip.
     *
     * @return array<string, mixed>
     */
    protected function evidenceReviewData(): array
    {
        $rows          = app(EvidenceCorpus::class)->forClient($this->client);
        $excludedCount = $rows->where('excluded', true)->count();
        $filtersActive = $this->filterCategory || $this->filterSeverity || $this->filterReview;

        // Respect the excluded toggle, then drop files whose items don't match the
        // active item-level filters (category / severity / review state).
        $visibleRows = ($this->showExcluded ? $rows : $rows->where('excluded', false))
            ->filter(fn ($r) => !$filtersActive || $r['items']->contains(fn ($i) => $this->itemMatchesFilters($i)));

        // Top level: the recipient institution we DSAR'd. Within each recipient,
        // one sub-group per individual DSAR — the same institution may be DSAR'd
        // more than once (e.g. a re-request after "no records").
        $recipientGroups = $visibleRows
            ->groupBy(fn ($r) => $r['institution_id'] ?? 0)
            ->map(fn ($instRows) => $this->buildRecipientGroup($instRows))
            // Unassigned recipient sinks to the bottom; otherwise most items first.
            ->sortBy(fn ($g) => $g['is_unassigned'] ? PHP_INT_MAX : -$g['items'])
            ->values();

        $allItems = $rows->flatMap(fn ($r) => $r['items']);
        $toReview = $allItems->filter->isUnreviewed()->count();

        return [
            'recipientGroups'    => $recipientGroups,
            'excludedCount'      => $excludedCount,
            // Drive auto-refresh only while extraction/OCR is in flight.
            'hasProcessing'      => $rows->contains(fn ($r) => $r['status'] === 'processing'),
            // Headline totals for the summary strip. Terminology is fixed across
            // every evidence surface: Total · Confirmed · To review · Dismissed.
            'totalItems'         => $allItems->count(),
            'confirmed'          => $allItems->filter->isConfirmed()->count(),
            'toReview'           => $toReview,
            'dismissed'          => $allItems->filter->isDismissed()->count(),
            'productiveFiles'    => $rows->where('excluded', false)->count(),
            'excludedFiles'      => $excludedCount,
            // Deep-link for the primary "Review N findings" action: the viewer of
            // the first file that still has findings awaiting review, flagged so it
            // opens straight onto that file's first unreviewed finding.
            'reviewUrl'          => $this->firstReviewUrl($recipientGroups),
        ];
    }

    /**
     * The viewer URL of the first file (in display order) that still has findings
     * awaiting review, carrying ?start=review so the viewer auto-selects the first
     * unreviewed finding. Null when nothing is left to review.
     */
    private function firstReviewUrl(Collection $recipientGroups): ?string
    {
        foreach ($recipientGroups as $group) {
            foreach ($group['dsars'] as $sub) {
                foreach ($sub['rows'] as $row) {
                    if (($row['unreviewed'] ?? 0) > 0 && !empty($row['view_url'])) {
                        return $row['view_url'] . (str_contains($row['view_url'], '?') ? '&' : '?') . 'start=review';
                    }
                }
            }
        }

        return null;
    }

    private function itemMatchesFilters(DsarEvidenceItem $item): bool
    {
        if ($this->filterCategory && $item->category !== $this->filterCategory) {
            return false;
        }
        if ($this->filterSeverity && $item->severity !== $this->filterSeverity) {
            return false;
        }
        if ($this->filterReview === 'confirmed' && !$item->isConfirmed()) {
            return false;
        }
        if ($this->filterReview === 'unreviewed' && !$item->isUnreviewed()) {
            return false;
        }
        if ($this->filterReview === 'dismissed' && !$item->isDismissed()) {
            return false;
        }
        return true;
    }

    /**
     * Shape one recipient card: the institution header plus its DSAR sub-groups,
     * ordered by when each DSAR went out (newest first). Every evidence file is
     * DSAR-attributed, so there is no "unattributed uploads" bucket here.
     */
    private function buildRecipientGroup(Collection $instRows): array
    {
        // First non-null institution across the group's rows. (Not firstWhere(),
        // whose truthiness test misbehaves when the column value is a model.)
        $inst = $instRows->pluck('institution')->filter()->first();

        $dsarGroups = $instRows
            ->groupBy(fn ($r) => $r['dsar']->id)
            ->map(function ($dsarRows) {
                $dsar = $dsarRows->first()['dsar'];
                return [
                    'dsar'        => $dsar,
                    'reference'   => $dsar->reference ?: "DSAR #{$dsar->id}",
                    'status'      => $dsar->status,
                    'posted_at'   => $dsar->posted_at,
                    'received_at' => $dsar->received_at,
                    'rows'        => $dsarRows->values(),
                    'files'       => $dsarRows->count(),
                    'items'       => $dsarRows->sum('count'),
                    'confirmed'   => $dsarRows->sum('confirmed'),
                    'unreviewed'  => $dsarRows->sum('unreviewed'),
                ];
            })
            ->sortByDesc(fn ($g) => $g['posted_at']?->timestamp ?? 0)
            ->values();

        return [
            'label'           => $inst?->display_name ?? 'Unassigned / Archival',
            'local_authority' => $inst?->localAuthority(),
            'is_unassigned'   => $inst === null,
            'dsars'           => $dsarGroups,
            'dsar_count'      => $dsarGroups->count(),
            'files'           => $instRows->count(),
            'items'           => $instRows->sum('count'),
            'confirmed'       => $instRows->sum('confirmed'),
            'unreviewed'      => $instRows->sum('unreviewed'),
        ];
    }
}
