<?php

declare(strict_types=1);

namespace App\Livewire;

use App\Support\Badges\TagBadgeDomain;
use App\Support\Badges\TagBadgeRenderer;
use App\Support\Baselines\BaselineCompareEvidenceGapDetails;
use App\Support\Filament\TablePaginationProfiles;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Filament\Tables\TableComponent;
use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;

class BaselineCompareEvidenceGapTable extends TableComponent
{
    /**
     * @var list<array<string, mixed>>
     */
    public array $gapRows = [];

    public string $context = 'default';

    /**
     * @param  list<array<string, mixed>>  $buckets
     */
    public function mount(array $buckets = [], string $context = 'default'): void
    {
        $this->gapRows = BaselineCompareEvidenceGapDetails::tableRows($buckets);
        $this->context = $context;
    }

    public function table(Table $table): Table
    {
        return $table
            ->queryStringIdentifier('baselineCompareEvidenceGap'.Str::studly($this->context))
            ->defaultSort('reason_label')
            ->defaultPaginationPageOption(10)
            ->paginated(TablePaginationProfiles::picker())
            ->searchable()
            ->searchPlaceholder(__('baseline-compare.evidence_gap_search_placeholder'))
            ->records(function (
                ?string $sortColumn,
                ?string $sortDirection,
                ?string $search,
                array $filters,
                int $page,
                int $recordsPerPage
            ): LengthAwarePaginator {
                $rows = $this->filterRows(
                    rows: collect($this->gapRows),
                    search: $search,
                    filters: $filters,
                );

                $rows = $this->sortRows(
                    rows: $rows,
                    sortColumn: $sortColumn,
                    sortDirection: $sortDirection,
                );

                return $this->paginateRows(
                    rows: $rows,
                    page: $page,
                    recordsPerPage: $recordsPerPage,
                );
            })
            ->filters([
                SelectFilter::make('reason_code')
                    ->label(__('baseline-compare.evidence_gap_reason'))
                    ->options(BaselineCompareEvidenceGapDetails::reasonFilterOptions($this->gapRows)),
                SelectFilter::make('policy_type')
                    ->label(__('baseline-compare.evidence_gap_policy_type'))
                    ->options(BaselineCompareEvidenceGapDetails::policyTypeFilterOptions($this->gapRows)),
                SelectFilter::make('subject_class')
                    ->label(__('baseline-compare.evidence_gap_subject_class'))
                    ->options(BaselineCompareEvidenceGapDetails::subjectClassFilterOptions($this->gapRows)),
                SelectFilter::make('operator_action_category')
                    ->label(__('baseline-compare.evidence_gap_next_action'))
                    ->options(BaselineCompareEvidenceGapDetails::actionCategoryFilterOptions($this->gapRows)),
            ])
            ->striped()
            ->deferLoading(! app()->runningUnitTests())
            ->columns([
                TextColumn::make('reason_label')
                    ->label(__('baseline-compare.evidence_gap_reason'))
                    ->searchable()
                    ->sortable()
                    ->wrap()
                    ->toggleable(isToggledHiddenByDefault: true),
                TextColumn::make('governed_subject_label')
                    ->label(__('baseline-compare.evidence_gap_policy_type'))
                    ->badge()
                    ->formatStateUsing(fn (mixed $state): string => is_string($state) && trim($state) !== '' ? $state : 'Unknown governed subject')
                    ->color(fn (mixed $state, Model $record): string => TagBadgeRenderer::spec(TagBadgeDomain::PolicyType, $record->getAttribute('policy_type'))->color)
                    ->icon(fn (mixed $state, Model $record): ?string => TagBadgeRenderer::spec(TagBadgeDomain::PolicyType, $record->getAttribute('policy_type'))->icon)
                    ->iconColor(fn (mixed $state, Model $record): ?string => TagBadgeRenderer::spec(TagBadgeDomain::PolicyType, $record->getAttribute('policy_type'))->iconColor)
                    ->searchable()
                    ->sortable()
                    ->wrap(),
                TextColumn::make('subject_class_label')
                    ->label(__('baseline-compare.evidence_gap_subject_class'))
                    ->badge()
                    ->searchable()
                    ->sortable()
                    ->wrap(),
                TextColumn::make('resolution_outcome_label')
                    ->label(__('baseline-compare.evidence_gap_outcome'))
                    ->searchable()
                    ->sortable()
                    ->wrap(),
                TextColumn::make('operator_action_category_label')
                    ->label(__('baseline-compare.evidence_gap_next_action'))
                    ->searchable()
                    ->sortable()
                    ->wrap(),
                TextColumn::make('subject_key')
                    ->label(__('baseline-compare.evidence_gap_subject_key'))
                    ->searchable()
                    ->sortable()
                    ->wrap(),
            ])
            ->actions([])
            ->bulkActions([])
            ->emptyStateHeading(__('baseline-compare.evidence_gap_table_empty_heading'))
            ->emptyStateDescription(__('baseline-compare.evidence_gap_table_empty_description'));
    }

    public function render(): View
    {
        return view('livewire.baseline-compare-evidence-gap-table');
    }

    /**
     * @param  Collection<int, array<string, mixed>>  $rows
     * @param  array<string, mixed>  $filters
     * @return Collection<int, array<string, mixed>>
     */
    private function filterRows(Collection $rows, ?string $search, array $filters): Collection
    {
        $normalizedSearch = Str::lower(trim((string) $search));
        $reasonCode = $filters['reason_code']['value'] ?? null;
        $policyType = $filters['policy_type']['value'] ?? null;
        $subjectClass = $filters['subject_class']['value'] ?? null;
        $operatorActionCategory = $filters['operator_action_category']['value'] ?? null;

        return $rows
            ->when(
                $normalizedSearch !== '',
                function (Collection $rows) use ($normalizedSearch): Collection {
                    return $rows->filter(function (array $row) use ($normalizedSearch): bool {
                        return str_contains(Str::lower((string) ($row['search_text'] ?? '')), $normalizedSearch);
                    });
                }
            )
            ->when(
                filled($reasonCode),
                fn (Collection $rows): Collection => $rows->where('reason_code', (string) $reasonCode)
            )
            ->when(
                filled($policyType),
                fn (Collection $rows): Collection => $rows->where('policy_type', (string) $policyType)
            )
            ->when(
                filled($subjectClass),
                fn (Collection $rows): Collection => $rows->where('subject_class', (string) $subjectClass)
            )
            ->when(
                filled($operatorActionCategory),
                fn (Collection $rows): Collection => $rows->where('operator_action_category', (string) $operatorActionCategory)
            )
            ->values();
    }

    /**
     * @param  Collection<int, array<string, mixed>>  $rows
     * @return Collection<int, array<string, mixed>>
     */
    private function sortRows(Collection $rows, ?string $sortColumn, ?string $sortDirection): Collection
    {
        if (! filled($sortColumn)) {
            return $rows;
        }

        $direction = Str::lower((string) ($sortDirection ?? 'asc')) === 'desc' ? 'desc' : 'asc';

        return $rows->sortBy(
            fn (array $row): string => (string) ($row[$sortColumn] ?? ''),
            SORT_NATURAL | SORT_FLAG_CASE,
            $direction === 'desc'
        )->values();
    }

    /**
     * @param  Collection<int, array<string, mixed>>  $rows
     */
    private function paginateRows(Collection $rows, int $page, int $recordsPerPage): LengthAwarePaginator
    {
        $perPage = max(1, $recordsPerPage);
        $currentPage = max(1, $page);
        $total = $rows->count();
        $items = $rows->forPage($currentPage, $perPage)
            ->values()
            ->map(fn (array $row, int $index): Model => $this->toTableRecord(
                row: $row,
                index: (($currentPage - 1) * $perPage) + $index,
            ));

        return new LengthAwarePaginator(
            $items,
            $total,
            $perPage,
            $currentPage,
        );
    }

    /**
     * @param  array<string, mixed>  $row
     */
    private function toTableRecord(array $row, int $index): Model
    {
        $record = new class extends Model
        {
            public $timestamps = false;

            public $incrementing = false;

            protected $keyType = 'string';

            protected $guarded = [];

            protected $table = 'baseline_compare_evidence_gap_rows';
        };

        $record->forceFill([
            'id' => implode(':', array_filter([
                (string) ($row['reason_code'] ?? 'reason'),
                (string) ($row['policy_type'] ?? 'policy'),
                (string) ($row['subject_key'] ?? 'subject'),
                (string) $index,
            ])),
            ...$row,
        ]);
        $record->exists = true;

        return $record;
    }
}
