/**
 * TUI model selector — fuzzy-searchable list of available LLM models.
 *
 * Shows only models with configured API keys. Known providers are filtered
 * to a curated allowlist; unlisted providers show all models. Selecting a
 * model saves it as the new default via SettingsManager.
 */
import { modelsAreEqual } from '../../../ai/models.js';
import { SHORTCUT_MODEL_ID } from '../../../model-ids.js';
import { Container, fuzzyFilter, getEditorKeybindings, Input, Spacer, Text } from '../../../tui/index.js';
import { theme } from '../../theme/runtime.js';
import { DynamicBorder } from '../primitives/dynamic-border.js';
/** Only these model IDs are shown for known providers. Unlisted providers show all models. */
const ALLOWED_MODELS_PER_PROVIDER = {
    shortcut: new Set([
        SHORTCUT_MODEL_ID.ClaudeOpus48,
        SHORTCUT_MODEL_ID.ClaudeOpus47,
        SHORTCUT_MODEL_ID.ClaudeSonnet46,
        SHORTCUT_MODEL_ID.Gpt55
    ]),
    anthropic: new Set([SHORTCUT_MODEL_ID.ClaudeOpus48, SHORTCUT_MODEL_ID.ClaudeSonnet46]),
    openai: new Set(['gpt-5.5'])
};
/**
 * Component that renders a model selector with search.
 * Shows all available (authenticated) models.
 */
export class ModelSelectorComponent extends Container {
    searchInput;
    // Focusable implementation - propagate to searchInput for IME cursor positioning
    _focused = false;
    /** Whether this component currently has focus. */
    get focused() {
        return this._focused;
    }
    /** Propagate focus state to the underlying search input. */
    set focused(value) {
        this._focused = value;
        this.searchInput.focused = value;
    }
    listContainer;
    allModels = [];
    filteredModels = [];
    selectedIndex = 0;
    currentModel;
    settingsManager;
    modelRegistry;
    onSelectCallback;
    onCancelCallback;
    errorMessage;
    tui;
    /** Build the model selector with search input, list, and async model load. */
    constructor(tui, currentModel, settingsManager, modelRegistry, _scopedModels, // kept for call-site compat, ignored
    onSelect, onCancel, initialSearchInput) {
        super();
        this.tui = tui;
        this.currentModel = currentModel;
        this.settingsManager = settingsManager;
        this.modelRegistry = modelRegistry;
        this.onSelectCallback = onSelect;
        this.onCancelCallback = onCancel;
        // Add top border
        this.addChild(new DynamicBorder());
        this.addChild(new Spacer(1));
        const hintText = 'Only showing models with configured API keys (see README for details)';
        this.addChild(new Text(theme.fg('warning', hintText), 0, 0));
        this.addChild(new Spacer(1));
        // Create search input
        this.searchInput = new Input();
        if (initialSearchInput) {
            this.searchInput.setValue(initialSearchInput);
        }
        this.searchInput.onSubmit = () => {
            // Enter on search input selects the first filtered item
            if (this.filteredModels[this.selectedIndex]) {
                this.handleSelect(this.filteredModels[this.selectedIndex].model);
            }
        };
        this.addChild(this.searchInput);
        this.addChild(new Spacer(1));
        // Create list container
        this.listContainer = new Container();
        this.addChild(this.listContainer);
        this.addChild(new Spacer(1));
        // Add bottom border
        this.addChild(new DynamicBorder());
        // Load models and do initial render
        this.loadModels().then(() => {
            if (initialSearchInput) {
                this.filterModels(initialSearchInput);
            }
            else {
                this.updateList();
            }
            // Request re-render after models are loaded
            this.tui.requestRender();
        });
    }
    /** Refresh registry and load the filtered list of available models. */
    async loadModels() {
        let models;
        // Refresh to pick up any changes to models.json
        this.modelRegistry.refresh();
        // Check for models.json errors
        const loadError = this.modelRegistry.getError();
        if (loadError) {
            this.errorMessage = loadError;
        }
        // Load available models (built-in models still work even if models.json failed)
        // For known providers, only show curated models to keep the list clean.
        try {
            const availableModels = await this.modelRegistry.getAvailable();
            models = availableModels
                .filter((model) => {
                const allowed = ALLOWED_MODELS_PER_PROVIDER[model.provider];
                // If no allowlist for this provider, show all its models
                if (!allowed)
                    return true;
                return allowed.has(model.id);
            })
                .map((model) => ({
                provider: model.provider,
                id: model.id,
                model
            }));
        }
        catch (error) {
            this.allModels = [];
            this.filteredModels = [];
            this.errorMessage = error instanceof Error ? error.message : String(error);
            return;
        }
        this.allModels = this.sortModels(models);
        this.filteredModels = this.allModels;
        this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredModels.length - 1));
    }
    /** Sort with the current model first, then alphabetically by provider. */
    sortModels(models) {
        const sorted = [...models];
        // Sort: current model first, then by provider
        sorted.sort((a, b) => {
            const aIsCurrent = modelsAreEqual(this.currentModel, a.model);
            const bIsCurrent = modelsAreEqual(this.currentModel, b.model);
            if (aIsCurrent && !bIsCurrent)
                return -1;
            if (!aIsCurrent && bIsCurrent)
                return 1;
            return a.provider.localeCompare(b.provider);
        });
        return sorted;
    }
    /** Apply a fuzzy filter to the model list and refresh the rendered slice. */
    filterModels(query) {
        this.filteredModels = query
            ? fuzzyFilter(this.allModels, query, ({ id, provider }) => `${id} ${provider}`)
            : this.allModels;
        this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredModels.length - 1));
        this.updateList();
    }
    /** Re-render the visible list slice, scroll indicator, and footer info. */
    updateList() {
        this.listContainer.clear();
        const maxVisible = 10;
        const startIndex = Math.max(0, Math.min(this.selectedIndex - Math.floor(maxVisible / 2), this.filteredModels.length - maxVisible));
        const endIndex = Math.min(startIndex + maxVisible, this.filteredModels.length);
        // Show visible slice of filtered models
        for (let i = startIndex; i < endIndex; i++) {
            const item = this.filteredModels[i];
            if (!item)
                continue;
            const isSelected = i === this.selectedIndex;
            const isCurrent = modelsAreEqual(this.currentModel, item.model);
            let line = '';
            if (isSelected) {
                const prefix = theme.fg('accent', '→ ');
                const modelText = `${item.id}`;
                const providerBadge = theme.fg('muted', `[${item.provider}]`);
                const checkmark = isCurrent ? theme.fg('success', ' ✓') : '';
                line = `${prefix + theme.fg('accent', modelText)} ${providerBadge}${checkmark}`;
            }
            else {
                const modelText = `  ${item.id}`;
                const providerBadge = theme.fg('muted', `[${item.provider}]`);
                const checkmark = isCurrent ? theme.fg('success', ' ✓') : '';
                line = `${modelText} ${providerBadge}${checkmark}`;
            }
            this.listContainer.addChild(new Text(line, 0, 0));
        }
        // Add scroll indicator if needed
        if (startIndex > 0 || endIndex < this.filteredModels.length) {
            const scrollInfo = theme.fg('muted', `  (${this.selectedIndex + 1}/${this.filteredModels.length})`);
            this.listContainer.addChild(new Text(scrollInfo, 0, 0));
        }
        // Show error message or "no results" if empty
        if (this.errorMessage) {
            // Show error in red
            const errorLines = this.errorMessage.split('\n');
            for (const line of errorLines) {
                this.listContainer.addChild(new Text(theme.fg('error', line), 0, 0));
            }
        }
        else if (this.filteredModels.length === 0) {
            this.listContainer.addChild(new Text(theme.fg('muted', '  No matching models'), 0, 0));
        }
        else {
            const selected = this.filteredModels[this.selectedIndex];
            this.listContainer.addChild(new Spacer(1));
            this.listContainer.addChild(new Text(theme.fg('muted', `  Model Name: ${selected.model.name}`), 0, 0));
        }
    }
    /** Handle navigation, confirm/cancel, and pass-through to the search input. */
    handleInput(keyData) {
        const kb = getEditorKeybindings();
        // Up arrow - wrap to bottom when at top
        if (kb.matches(keyData, 'selectUp')) {
            if (this.filteredModels.length === 0)
                return;
            this.selectedIndex =
                this.selectedIndex === 0 ? this.filteredModels.length - 1 : this.selectedIndex - 1;
            this.updateList();
        }
        // Down arrow - wrap to top when at bottom
        else if (kb.matches(keyData, 'selectDown')) {
            if (this.filteredModels.length === 0)
                return;
            this.selectedIndex =
                this.selectedIndex === this.filteredModels.length - 1 ? 0 : this.selectedIndex + 1;
            this.updateList();
        }
        // Enter
        else if (kb.matches(keyData, 'selectConfirm')) {
            const selectedModel = this.filteredModels[this.selectedIndex];
            if (selectedModel) {
                this.handleSelect(selectedModel.model);
            }
        }
        // Escape or Ctrl+C
        else if (kb.matches(keyData, 'selectCancel')) {
            this.onCancelCallback();
        }
        // Pass everything else to search input
        else {
            this.searchInput.handleInput(keyData);
            this.filterModels(this.searchInput.getValue());
        }
    }
    /** Persist the chosen model as default and notify the caller. */
    handleSelect(model) {
        // Save as new default
        this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
        this.onSelectCallback(model);
    }
    /** Expose the search input so callers can manage focus or state. */
    getSearchInput() {
        return this.searchInput;
    }
}
//# sourceMappingURL=model-selector.js.map