<?php

namespace Modules\AiAgent\Filament\Pages;

use BackedEnum;
use Filament\Actions\Action;
use Filament\Actions\BulkAction;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Schemas\Components\Text;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Modules\AiAgent\Filament\Concerns\HandlesBnfexpressErrors;
use Modules\AiAgent\Filament\Concerns\PaginatesBnfexpressLists;
use Modules\Shared\Bnfexpress\BnfexpressAdminClient;
use Modules\Shared\Bnfexpress\Exceptions\BnfexpressApiException;
use UnitEnum;

/**
 * Browse bnfexpress's "suggestion misses" — queries typed by real users that
 * no suggestion tier answered — and either dismiss them (noise) or promote
 * a batch straight into the suggestions bank. Read-mostly: no create/edit,
 * these rows are only ever produced by bnfexpress's own suggest pipeline.
 */
class ManageSuggestionMisses extends Page implements HasTable
{
    use HandlesBnfexpressErrors;
    use InteractsWithTable;
    use PaginatesBnfexpressLists;

    protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedMagnifyingGlassCircle;

    protected static string|UnitEnum|null $navigationGroup = 'AI Agent';

    protected static ?string $navigationLabel = 'Suggestion Misses';

    protected static ?string $title = 'Suggestion Misses';

    protected string $view = 'ai-agent::filament.pages.manage-suggestion-misses';

    public static function canAccess(): bool
    {
        return auth()->user()?->can('manage_ai_agent') ?? false;
    }

    public function table(Table $table): Table
    {
        return $table
            ->records(function (array $filters, int $page, int $recordsPerPage): LengthAwarePaginator {
                $wasUsed = $filters['was_used']['value'] ?? null;

                $result = app(BnfexpressAdminClient::class)->listSuggestionMisses(
                    wasUsed: $wasUsed === null || $wasUsed === '' ? null : (bool) $wasUsed,
                    limit: $recordsPerPage,
                    offset: ($page - 1) * $recordsPerPage,
                );

                return $this->paginateBareList($result, 'misses', 'id', $page, $recordsPerPage);
            })
            ->columns([
                TextColumn::make('id'),
                TextColumn::make('text_norm')
                    ->limit(80)
                    ->wrap(),
                TextColumn::make('lang')
                    ->placeholder('—'),
                TextColumn::make('syllables')
                    ->placeholder('—'),
                IconColumn::make('was_used')
                    ->boolean(),
                TextColumn::make('created_at')
                    ->dateTime()
                    ->sortable(),
            ])
            ->defaultSort('created_at', 'desc')
            // SelectFilter (not TernaryFilter) so the value lands in
            // $filters['was_used']['value'] predictably — TernaryFilter's
            // internal field key isn't documented for the custom-data path.
            ->filters([
                SelectFilter::make('was_used')
                    ->label('Used?')
                    ->options(['1' => 'Used', '0' => 'Not used']),
            ])
            ->recordActions([
                $this->dismissAction(),
            ])
            ->toolbarActions([
                $this->promoteBulkAction(),
            ]);
    }

    protected function dismissAction(): Action
    {
        return Action::make('dismiss')
            ->color('danger')
            ->icon(Heroicon::OutlinedTrash)
            ->requiresConfirmation()
            ->action(function (array $record): void {
                $this->callBnfexpress(
                    fn () => app(BnfexpressAdminClient::class)->dismissSuggestionMiss($record['id']),
                    successTitle: 'Miss dismissed',
                    failureTitle: 'Failed to dismiss miss',
                );

                $this->resetTable();
            });
    }

    /**
     * Opens the same "bulk add" modal shape as ManageSuggestions'
     * createManyAction() (info text + editable items textarea + lang/intent),
     * prefilled with the selected misses' own text — one per line — so an
     * admin can review/tweak the batch before it's created. Submits via
     * batchCreateSuggestions() (same endpoint createManyAction uses, and per
     * its own comment, bnfexpress's thin wrapper around the misses-promotion
     * promote()), not promoteSuggestionMisses() — editing the textarea means
     * the created rows no longer map 1:1 to the original miss ids. Once that
     * succeeds, the selected misses are deleted via batchDeleteSuggestionMisses()
     * so they don't linger in this list now that they're suggestions — best
     * effort, since editing the textarea already broke the id mapping and the
     * promotion itself is done by that point either way.
     */
    protected function promoteBulkAction(): BulkAction
    {
        return BulkAction::make('promote')
            ->label('Promote Selected')
            ->icon(Heroicon::OutlinedArrowUp)
            ->fillForm(fn (Collection $records): array => [
                'items_raw' => $records->pluck('text_norm')->implode("\n"),
                'lang' => $records->pluck('lang')->filter()->unique()->count() === 1
                    ? $records->pluck('lang')->filter()->first()
                    : null,
            ])
            ->schema([
                Text::make('Created rows are tagged source: mined by bnfexpress.')
                    ->color('gray'),
                Textarea::make('items_raw')
                    ->label('Phrases (one per line)')
                    ->required()
                    ->rows(8),
                TextInput::make('lang')
                    ->required()
                    ->maxLength(10),
                TextInput::make('intent'),
            ])
            ->deselectRecordsAfterCompletion()
            ->action(function (array $data, Collection $records): void {
                $items = collect(preg_split('/\r\n|\r|\n/', (string) $data['items_raw']))
                    ->map(fn (string $line): string => trim($line))
                    ->filter()
                    ->map(fn (string $text): array => array_filter([
                        'text' => $text,
                        'lang' => $data['lang'],
                        'intent' => $data['intent'] ?: null,
                    ], fn (mixed $value): bool => $value !== null))
                    ->values()
                    ->all();

                $this->callBnfexpressForResult(
                    fn () => app(BnfexpressAdminClient::class)->batchCreateSuggestions($items),
                    function (array $result) use ($records): void {
                        Notification::make()
                            ->title("{$result['created']} promoted, {$result['skipped']} skipped")
                            ->success()
                            ->send();

                        try {
                            app(BnfexpressAdminClient::class)->batchDeleteSuggestionMisses($records->keys()->all());
                        } catch (BnfexpressApiException $exception) {
                            Notification::make()
                                ->title('Promoted, but failed to remove the original misses')
                                ->body($exception->getMessage())
                                ->warning()
                                ->send();
                        }

                        $this->resetTable();
                    },
                    failureTitle: 'Failed to promote suggestion misses',
                );
            });
    }
}
