<?php

namespace Modules\AiAgent\Filament\Pages;

use BackedEnum;
use Filament\Actions\Action;
use Filament\Actions\BulkAction;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Schemas\Components\Component;
use Filament\Schemas\Components\Text;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
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;

/**
 * Manage bnfexpress's autocomplete "suggestions" phrase bank: CRUD, batch
 * import, batch delete, and syncing embeddings — folded into one page per
 * the sync-alongside-CRUD layout (rather than a separate sync-only page).
 * Data isn't Eloquent-backed, so the table is fed via Table::records() and
 * mutating actions use plain Filament\Actions\Action, same as ManageFaqs.
 */
class ManageSuggestions extends Page implements HasTable
{
    use HandlesBnfexpressErrors;
    use InteractsWithTable;
    use PaginatesBnfexpressLists;

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

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

    protected static ?string $navigationLabel = 'Suggestions';

    protected static ?string $title = 'Suggestions';

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

    public ?string $syncJobId = null;

    public ?string $syncStatus = null;

    /**
     * @var mixed
     */
    public $syncResult = null;

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

    public function table(Table $table): Table
    {
        return $table
            ->records(function (?string $search, int $page, int $recordsPerPage): LengthAwarePaginator {
                $result = app(BnfexpressAdminClient::class)->listSuggestions(
                    q: $search,
                    limit: $recordsPerPage,
                    offset: ($page - 1) * $recordsPerPage,
                );

                return $this->paginateBareList($result, 'suggestions', 'id', $page, $recordsPerPage);
            })
            ->columns([
                TextColumn::make('id'),
                TextColumn::make('text_display')
                    ->limit(80)
                    ->wrap(),
                TextColumn::make('lang'),
                TextColumn::make('intent')
                    ->placeholder('—'),
                TextColumn::make('weight')
                    ->sortable(false),
                TextColumn::make('source'),
                TextColumn::make('synced_at')
                    ->dateTime()
                    ->placeholder('Never')
                    ->toggleable(isToggledHiddenByDefault: true),
                TextColumn::make('updated_at')
                    ->dateTime(),
            ])
            ->searchable()
            ->recordActions([
                $this->editAction(),
                $this->deleteAction(),
                $this->syncEmbeddingAction(),
                $this->deleteEmbeddingAction(),
            ])
            ->toolbarActions([
                $this->deleteSelectedBulkAction(),
            ])
            ->headerActions([
                $this->createAction(),
                $this->createManyAction(),
                $this->syncAction(),
                $this->reloadIndexAction(),
            ]);
    }

    protected function createAction(): Action
    {
        return Action::make('create')
            ->label('New Suggestion')
            ->icon(Heroicon::OutlinedPlus)
            ->schema($this->formSchema())
            ->action(function (array $data): void {
                $this->callBnfexpress(
                    fn () => app(BnfexpressAdminClient::class)->createSuggestion(
                        $data['text_display'],
                        $data['lang'],
                        $data['intent'] ?: null,
                        (int) ($data['weight'] ?? 0),
                        $data['source'] ?: 'admin',
                    ),
                    successTitle: 'Suggestion created',
                    failureTitle: 'Failed to create suggestion',
                );

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

    protected function createManyAction(): Action
    {
        return Action::make('createMany')
            ->label('Create Many')
            ->icon(Heroicon::OutlinedQueueList)
            ->schema([
                // No source field here — bnfexpress's batch endpoint always tags
                // these rows source: "mined" server-side (it's a thin wrapper
                // around the same promote() misses-promotion uses), regardless
                // of what's sent, so exposing a picker would be misleading.
                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'),
            ])
            ->action(function (array $data): 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): void {
                        Notification::make()
                            ->title("{$result['created']} created, {$result['skipped']} skipped")
                            ->success()
                            ->send();

                        $this->resetTable();
                    },
                    failureTitle: 'Failed to create suggestions',
                );
            });
    }

    protected function editAction(): Action
    {
        return Action::make('edit')
            ->icon(Heroicon::OutlinedPencilSquare)
            ->fillForm(fn (array $record): array => $record)
            ->schema($this->formSchema())
            ->action(function (array $data, array $record): void {
                $this->callBnfexpress(
                    fn () => app(BnfexpressAdminClient::class)->updateSuggestion(
                        $record['id'],
                        $data['text_display'],
                        $data['lang'],
                        $data['intent'] ?: null,
                        (int) ($data['weight'] ?? 0),
                        $data['source'] ?: null,
                    ),
                    successTitle: 'Suggestion updated',
                    failureTitle: 'Failed to update suggestion',
                );

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

    protected function deleteAction(): Action
    {
        return Action::make('delete')
            ->color('danger')
            ->icon(Heroicon::OutlinedTrash)
            ->requiresConfirmation()
            ->action(function (array $record): void {
                $this->callBnfexpress(
                    fn () => app(BnfexpressAdminClient::class)->deleteSuggestion($record['id']),
                    successTitle: 'Suggestion deleted',
                    failureTitle: 'Failed to delete suggestion',
                );

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

    protected function deleteSelectedBulkAction(): BulkAction
    {
        return BulkAction::make('deleteSelected')
            ->label('Delete Selected')
            ->color('danger')
            ->icon(Heroicon::OutlinedTrash)
            ->requiresConfirmation()
            ->fetchSelectedRecords(false)
            ->deselectRecordsAfterCompletion()
            ->action(function (Collection $records): void {
                // fetchSelectedRecords(false) still resolves full row arrays for a
                // custom-data table (there's no cheap ID-only path like an Eloquent
                // query) — the record keys (our suggestion ids) are what's wanted here.
                $this->callBnfexpressForResult(
                    fn () => app(BnfexpressAdminClient::class)->batchDeleteSuggestions($records->keys()->all()),
                    function (array $result): void {
                        Notification::make()
                            ->title("{$result['deleted']} deleted, {$result['skipped']} skipped")
                            ->success()
                            ->send();

                        $this->resetTable();
                    },
                    failureTitle: 'Failed to delete suggestions',
                );
            });
    }

    protected function syncEmbeddingAction(): Action
    {
        return Action::make('syncEmbedding')
            ->label('Sync Embedding')
            ->icon(Heroicon::OutlinedArrowPath)
            ->action(function (array $record): void {
                $this->callBnfexpress(
                    fn () => app(BnfexpressAdminClient::class)->syncOneSuggestion($record['id']),
                    successTitle: 'Embedding synced',
                    failureTitle: 'Failed to sync embedding',
                );

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

    protected function deleteEmbeddingAction(): Action
    {
        return Action::make('deleteEmbedding')
            ->label('Delete Embedding')
            ->color('danger')
            ->icon(Heroicon::OutlinedXCircle)
            ->requiresConfirmation()
            ->action(function (array $record): void {
                $this->callBnfexpress(
                    fn () => app(BnfexpressAdminClient::class)->deleteSuggestionEmbedding($record['id']),
                    successTitle: 'Embedding deleted',
                    failureTitle: 'Failed to delete embedding',
                );

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

    /**
     * Kicks off a full re-embed job and starts polling for it (pollSyncStatus(),
     * driven by wire:poll in the view) rather than notifying immediately —
     * the real outcome only lands once the job finishes.
     */
    protected function syncAction(): Action
    {
        return Action::make('sync')
            ->label('Sync to Chroma')
            ->icon(Heroicon::OutlinedArrowPath)
            ->action(function (): void {
                try {
                    $result = app(BnfexpressAdminClient::class)->syncSuggestions();
                    $this->syncJobId = $result['job_id'] ?? null;
                    $this->syncStatus = 'queued';
                    $this->syncResult = null;
                } catch (BnfexpressApiException $exception) {
                    Notification::make()
                        ->title('Failed to start sync')
                        ->body($exception->getMessage())
                        ->danger()
                        ->send();
                }
            });
    }

    protected function reloadIndexAction(): Action
    {
        return Action::make('reloadIndex')
            ->label('Reload Index')
            ->icon(Heroicon::OutlinedArrowPath)
            ->requiresConfirmation()
            ->action(function (): void {
                $this->callBnfexpress(
                    fn () => app(BnfexpressAdminClient::class)->reloadSuggestionIndex(),
                    successTitle: 'Index reloaded',
                    failureTitle: 'Failed to reload index',
                );
            });
    }

    /**
     * Polls a sync job's status (wire:poll.2s, see the view). On "finished",
     * chains a reload-index call — same two-step flow shweai_backend's admin
     * JS does (POST sync → poll → POST reload-index) — and refreshes
     * the table. On "failed", notifies and stops polling.
     */
    public function pollSyncStatus(): void
    {
        if ($this->syncJobId === null) {
            return;
        }

        try {
            $status = app(BnfexpressAdminClient::class)->getSuggestionSyncStatus($this->syncJobId);
        } catch (BnfexpressApiException $exception) {
            $this->syncJobId = null;
            Notification::make()
                ->title('Failed to check sync status')
                ->body($exception->getMessage())
                ->danger()
                ->send();

            return;
        }

        $this->syncStatus = $status['status'] ?? null;

        if ($this->syncStatus === 'finished') {
            $this->syncResult = $status['result'] ?? null;
            $this->syncJobId = null;

            $this->callBnfexpress(
                fn () => app(BnfexpressAdminClient::class)->reloadSuggestionIndex(),
                successTitle: 'Sync completed and index reloaded',
                failureTitle: 'Sync finished but reloading the index failed',
            );

            $this->resetTable();
        } elseif ($this->syncStatus === 'failed') {
            $this->syncJobId = null;

            Notification::make()
                ->title('Sync failed')
                ->body(is_string($status['result'] ?? null) ? $status['result'] : 'Unknown error.')
                ->danger()
                ->send();
        }
        // else: still queued/running — the view keeps polling.
    }

    /**
     * @return array<int, Component>
     */
    protected function formSchema(): array
    {
        return [
            Textarea::make('text_display')
                ->required()
                ->rows(3),
            TextInput::make('lang')
                ->required()
                ->maxLength(10),
            TextInput::make('intent'),
            TextInput::make('weight')
                ->numeric()
                ->default(0),
            // Fixed choices rather than free text (matching shweai_backend's
            // seed/mined dropdown) — 'admin' is bnfexpress's own default for
            // a CRUD-created row (SuggestionAdminCreate.source), 'mined' is
            // what promote() tags a row with. 'seed' isn't used by any
            // bnfexpress code path today (unlike the other two, which are
            // hardcoded/defaulted server-side) — source is just a free string
            // there, so this is offered for admins bootstrapping initial
            // phrases who want that distinct from an ad-hoc manual entry,
            // same convention as shweai_backend's own seed/mined dropdown.
            Select::make('source')
                ->options([
                    'admin' => 'Admin (manual entry)',
                    'seed' => 'Seed (initial/bootstrap data)',
                    'mined' => 'Mined (promoted from a miss)',
                ])
                ->default('admin')
                ->native(false)
                ->required(),
        ];
    }
}
