use anyhow::{Context, Result, anyhow};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::Arc;
use tokio::sync::Notify;
use tokio::task;
use vtcode_config::{OpenAIServiceTier, VTCodeConfig};
use vtcode_core::config::models::{ModelId, Provider};
use vtcode_core::config::types::ReasoningEffortLevel;
use vtcode_core::ui::{
    InlineListSelection, OpenAIServiceTierChoice, reasoning_from_selection_string,
};
use vtcode_core::utils::ansi::{AnsiRenderer, MessageStyle};
use vtcode_tui::app::{
    InlineHandle, InlineListItem, InlineListSearchConfig, InlineSession, TransientSubmission,
    WizardModalMode, WizardStep,
};
use vtcode_tui::ui::interactive_list::SelectionInterrupted;

use crate::agent::runloop::unified::overlay_prompt::{
    OverlayWaitOutcome, wait_for_overlay_submission,
};
use crate::agent::runloop::unified::state::CtrlCState;
use crate::agent::runloop::unified::wizard_modal::{
    WizardModalOutcome, show_wizard_modal_and_wait,
};
use interaction::{
    ModelSelectionListOutcome, select_model_with_ratatui_list, select_reasoning_with_ratatui,
    select_service_tier_with_ratatui,
};
use options::{MODEL_OPTIONS, ModelOption, find_option_index};
use rendering::{
    CLOSE_THEME_MESSAGE, dynamic_model_subtitle, model_search_value, prompt_api_key_plain,
    prompt_custom_model_entry, prompt_reasoning_plain, prompt_service_tier_plain,
    render_reasoning_inline, render_service_tier_inline, render_step_one_inline,
    render_step_one_plain, show_secure_api_modal, static_model_search_terms, static_model_subtitle,
};
use selection::{
    ExistingKey, ReasoningChoice, SelectionDetail, ServiceTierChoice, is_cancel_command,
    parse_model_selection, reasoning_level_description, reasoning_level_label,
    selection_from_option, selections_from_custom_provider, supports_max_reasoning,
    supports_xhigh_reasoning,
};

mod config_persistence;
mod dynamic_models;
mod interaction;
mod lightweight_palette;
mod options;
mod rendering;
mod selection;
mod state_machine;

pub(crate) use self::config_persistence::persist_lightweight_selection;
pub(crate) use self::dynamic_models::DynamicModelRegistry;
#[cfg(test)]
pub(crate) use self::lightweight_palette::build_lightweight_model_palette_view;
pub(crate) use self::lightweight_palette::{
    LightweightModelPaletteView, prepare_lightweight_model_palette_view,
};
pub(crate) use selection::ModelSelectionResult;
pub(super) use vtcode_config::read_workspace_env_value as read_workspace_env;

const SUBAGENT_MODEL_ACTION_PREFIX: &str = "subagent-model:";
const SUBAGENT_REASONING_ACTION_PREFIX: &str = "subagent-reasoning:";
const SUBAGENT_MODEL_PROMPT_ID: &str = "subagent-model-id";
const SUBAGENT_SHORTCUTS: [(&str, &str); 5] = [
    ("inherit", "Use the parent session model and configuration."),
    (
        "small",
        "Use VT Code's lightweight delegated-model shortcut.",
    ),
    (
        "haiku",
        "Use the Anthropic Haiku shortcut alias for delegated work.",
    ),
    (
        "sonnet",
        "Use the Anthropic Sonnet shortcut alias for delegated work.",
    ),
    (
        "opus",
        "Use the Anthropic Opus shortcut alias for delegated work.",
    ),
];

#[derive(Debug, Clone, PartialEq, Eq)]
#[expect(clippy::enum_variant_names)]
enum PickerStep {
    AwaitModel,
    AwaitReasoning,
    AwaitServiceTier,
    AwaitApiKey,
}

pub(crate) enum ModelPickerProgress {
    InProgress,
    NeedsRefresh,
    Completed(ModelSelectionResult),
    Cancelled,
    Exit,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SubagentModelSelection {
    pub(crate) model: String,
    pub(crate) reasoning_effort: Option<String>,
}

#[derive(Clone, Debug)]
enum SubagentModelTarget {
    Shortcut { model: String },
    Concrete(SelectionDetail),
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum SubagentReasoningChoice {
    KeepCurrent,
    Unset,
    Explicit(ReasoningEffortLevel),
}

pub(crate) struct ModelPickerState {
    options: &'static [ModelOption],
    step: PickerStep,
    inline_enabled: bool,
    vt_cfg: Option<VTCodeConfig>,
    current_reasoning: ReasoningEffortLevel,
    current_service_tier: Option<OpenAIServiceTier>,
    current_provider: String,
    current_model: String,
    selection: Option<SelectionDetail>,
    custom_providers: Vec<SelectionDetail>,
    selected_reasoning: Option<ReasoningEffortLevel>,
    selected_service_tier: Option<Option<OpenAIServiceTier>>,
    pending_api_key: Option<String>,
    workspace: Option<PathBuf>,
    ctrl_c_state: Option<Arc<CtrlCState>>,
    ctrl_c_notify: Option<Arc<Notify>>,
    dynamic_models: DynamicModelRegistry,
    plain_mode_active: bool,
}

pub(crate) enum ModelPickerStart {
    Completed {
        state: ModelPickerState,
        selection: ModelSelectionResult,
    },
    InProgress(ModelPickerState),
}

impl ModelPickerState {
    #[expect(clippy::new_ret_no_self)]
    pub(crate) async fn new(
        renderer: &mut AnsiRenderer,
        vt_cfg: Option<VTCodeConfig>,
        current_reasoning: ReasoningEffortLevel,
        current_service_tier: Option<OpenAIServiceTier>,
        workspace: Option<PathBuf>,
        current_provider: String,
        current_model: String,
        ctrl_c_state: Option<Arc<CtrlCState>>,
        ctrl_c_notify: Option<Arc<Notify>>,
    ) -> Result<ModelPickerStart> {
        let options = MODEL_OPTIONS.as_slice();
        let inline_enabled = renderer.supports_inline_ui();
        let dynamic_models =
            DynamicModelRegistry::load(options, workspace.as_deref(), vt_cfg.as_ref()).await;
        let custom_providers = vt_cfg
            .as_ref()
            .map(|cfg| {
                cfg.custom_providers
                    .iter()
                    .flat_map(selections_from_custom_provider)
                    .collect()
            })
            .unwrap_or_default();

        let mut state = Self {
            options,
            step: PickerStep::AwaitModel,
            inline_enabled,
            vt_cfg,
            current_reasoning,
            current_service_tier,
            current_provider,
            current_model,
            selection: None,
            custom_providers,
            selected_reasoning: None,
            selected_service_tier: None,
            pending_api_key: None,
            workspace,
            ctrl_c_state,
            ctrl_c_notify,
            dynamic_models,
            plain_mode_active: false,
        };

        if inline_enabled {
            render_step_one_inline(
                renderer,
                options,
                current_reasoning,
                &state.dynamic_models,
                state.preferred_model_selection(),
                &state.current_provider,
                &state.current_model,
                &state.custom_providers,
            )?;
        }

        if !inline_enabled {
            loop {
                match select_model_with_ratatui_list(
                    options,
                    current_reasoning,
                    &state.dynamic_models,
                    &state.custom_providers,
                ) {
                    Ok(ModelSelectionListOutcome::Predefined(detail)) => {
                        match state.process_model_selection(renderer, detail)? {
                            ModelPickerProgress::Completed(result) => {
                                return Ok(ModelPickerStart::Completed {
                                    state,
                                    selection: result,
                                });
                            }
                            ModelPickerProgress::InProgress => {
                                return Ok(ModelPickerStart::InProgress(state));
                            }
                            ModelPickerProgress::Cancelled => {
                                renderer.line(MessageStyle::Info, "Model picker cancelled.")?;
                                return Ok(ModelPickerStart::InProgress(state));
                            }
                            ModelPickerProgress::Exit => {
                                renderer.line(MessageStyle::Info, "Model picker cancelled.")?;
                                return Ok(ModelPickerStart::InProgress(state));
                            }
                            ModelPickerProgress::NeedsRefresh => {
                                state
                                    .refresh_dynamic_models(renderer)
                                    .await
                                    .context("Failed to refresh local models")?;
                                continue;
                            }
                        }
                    }
                    Ok(ModelSelectionListOutcome::Manual) => {
                        state.plain_mode_active = true;
                        render_step_one_plain(
                            renderer,
                            options,
                            &state.dynamic_models,
                            &state.custom_providers,
                            &state.current_provider,
                        )?;
                        prompt_custom_model_entry(renderer)?;
                        break;
                    }
                    Ok(ModelSelectionListOutcome::Cancelled) => {
                        state.plain_mode_active = true;
                        render_step_one_plain(
                            renderer,
                            options,
                            &state.dynamic_models,
                            &state.custom_providers,
                            &state.current_provider,
                        )?;
                        prompt_custom_model_entry(renderer)?;
                        break;
                    }
                    Ok(ModelSelectionListOutcome::Refresh) => {
                        state
                            .refresh_dynamic_models(renderer)
                            .await
                            .context("Failed to refresh local models")?;
                        continue;
                    }
                    Err(err) => {
                        if err.is::<SelectionInterrupted>() {
                            return Err(err);
                        }
                        renderer.line(
                            MessageStyle::Info,
                            &format!(
                                "Interactive model picker unavailable ({}). Falling back to manual input.",
                                err
                            ),
                        )?;
                        state.plain_mode_active = true;
                        render_step_one_plain(
                            renderer,
                            options,
                            &state.dynamic_models,
                            &state.custom_providers,
                            &state.current_provider,
                        )?;
                        prompt_custom_model_entry(renderer)?;
                        break;
                    }
                }
            }
        }

        Ok(ModelPickerStart::InProgress(state))
    }

    pub async fn refresh_dynamic_models(&mut self, renderer: &mut AnsiRenderer) -> Result<()> {
        renderer.line(MessageStyle::Info, "Refreshing local model inventory...")?;
        self.dynamic_models = DynamicModelRegistry::load(
            self.options,
            self.workspace.as_deref(),
            self.vt_cfg.as_ref(),
        )
        .await;
        self.custom_providers = self
            .vt_cfg
            .as_ref()
            .map(|cfg| {
                cfg.custom_providers
                    .iter()
                    .flat_map(selections_from_custom_provider)
                    .collect()
            })
            .unwrap_or_default();
        self.selection = None;
        self.selected_reasoning = None;
        self.selected_service_tier = None;
        self.pending_api_key = None;
        self.step = PickerStep::AwaitModel;
        if self.inline_enabled {
            render_step_one_inline(
                renderer,
                self.options,
                self.current_reasoning,
                &self.dynamic_models,
                self.preferred_model_selection(),
                &self.current_provider,
                &self.current_model,
                &self.custom_providers,
            )?;
        } else if self.plain_mode_active {
            render_step_one_plain(
                renderer,
                self.options,
                &self.dynamic_models,
                &self.custom_providers,
                &self.current_provider,
            )?;
            if matches!(self.step, PickerStep::AwaitModel) {
                prompt_custom_model_entry(renderer)?;
            }
        }
        Ok(())
    }

    pub async fn handle_input(
        &mut self,
        renderer: &mut AnsiRenderer,
        input: &str,
        url_guard: crate::agent::runloop::unified::external_url_guard::ExternalUrlGuardContext<'_>,
    ) -> Result<ModelPickerProgress> {
        let trimmed = input.trim();
        if trimmed.is_empty() {
            renderer.line(
                MessageStyle::Error,
                "Please enter a value or type 'cancel'.",
            )?;
            return Ok(ModelPickerProgress::InProgress);
        }
        if is_cancel_command(trimmed) {
            if !self.inline_enabled {
                renderer.line(MessageStyle::Info, "Model picker cancelled.")?;
            }
            return Ok(ModelPickerProgress::Cancelled);
        }

        if matches!(self.step, PickerStep::AwaitModel) && trimmed.eq_ignore_ascii_case("refresh") {
            return Ok(ModelPickerProgress::NeedsRefresh);
        }

        match self.step {
            PickerStep::AwaitModel => self.handle_model_selection(renderer, trimmed),
            PickerStep::AwaitReasoning => self.handle_reasoning(renderer, trimmed),
            PickerStep::AwaitServiceTier => self.handle_service_tier(renderer, trimmed),
            PickerStep::AwaitApiKey => self.handle_api_key(renderer, trimmed, url_guard).await,
        }
    }

    pub async fn persist_selection(
        &self,
        workspace: &Path,
        selection: &ModelSelectionResult,
    ) -> Result<VTCodeConfig> {
        config_persistence::persist_selection(workspace, selection).await
    }

    pub fn handle_list_selection(
        &mut self,
        renderer: &mut AnsiRenderer,
        choice: InlineListSelection,
    ) -> Result<ModelPickerProgress> {
        match self.step {
            PickerStep::AwaitModel => match choice {
                InlineListSelection::Model(index) => {
                    let Some(option) = self.options.get(index) else {
                        renderer.line(
                            MessageStyle::Error,
                            "Unable to locate the selected model option.",
                        )?;
                        return Ok(ModelPickerProgress::InProgress);
                    };
                    let detail = selection_from_option(option);
                    self.process_model_selection(renderer, detail)
                }
                InlineListSelection::DynamicModel(entry_index) => {
                    let Some(detail) = self.dynamic_models.dynamic_detail(entry_index) else {
                        renderer.line(
                            MessageStyle::Error,
                            "Unable to locate the selected dynamic model.",
                        )?;
                        return Ok(ModelPickerProgress::InProgress);
                    };
                    self.process_model_selection(renderer, detail)
                }
                InlineListSelection::CustomProvider(entry_index) => {
                    let Some(detail) = self.custom_providers.get(entry_index).cloned() else {
                        renderer.line(
                            MessageStyle::Error,
                            "Unable to locate the selected custom provider.",
                        )?;
                        return Ok(ModelPickerProgress::InProgress);
                    };
                    self.process_model_selection(renderer, detail)
                }
                InlineListSelection::RefreshDynamicModels => Ok(ModelPickerProgress::NeedsRefresh),
                InlineListSelection::CustomModel => {
                    prompt_custom_model_entry(renderer)?;
                    Ok(ModelPickerProgress::InProgress)
                }
                InlineListSelection::Reasoning(_) => {
                    renderer.line(
                        MessageStyle::Error,
                        "Select a model before configuring reasoning effort.",
                    )?;
                    Ok(ModelPickerProgress::InProgress)
                }
                InlineListSelection::DisableReasoning => {
                    renderer.line(
                        MessageStyle::Error,
                        "Select a model before disabling reasoning.",
                    )?;
                    Ok(ModelPickerProgress::InProgress)
                }
                InlineListSelection::OpenAIServiceTier(_) => {
                    renderer.line(
                        MessageStyle::Error,
                        "Select a model before choosing a service tier.",
                    )?;
                    Ok(ModelPickerProgress::InProgress)
                }
                InlineListSelection::Theme(_) => {
                    renderer.line(MessageStyle::Error, CLOSE_THEME_MESSAGE)?;
                    Ok(ModelPickerProgress::InProgress)
                }
                _ => Ok(ModelPickerProgress::InProgress),
            },
            PickerStep::AwaitReasoning => match choice {
                InlineListSelection::Reasoning(level) => {
                    self.apply_reasoning_choice(renderer, reasoning_from_selection_string(&level))
                }
                InlineListSelection::DisableReasoning => self.apply_reasoning_off_choice(renderer),
                InlineListSelection::OpenAIServiceTier(_) => {
                    renderer.line(
                        MessageStyle::Error,
                        "Reasoning selection is active. Choose a reasoning level or press Esc to cancel.",
                    )?;
                    Ok(ModelPickerProgress::InProgress)
                }
                InlineListSelection::CustomModel
                | InlineListSelection::Model(_)
                | InlineListSelection::RefreshDynamicModels
                | InlineListSelection::DynamicModel(_)
                | InlineListSelection::CustomProvider(_)
                | InlineListSelection::ToolApproval(_)
                | InlineListSelection::ToolApprovalDenyOnce
                | InlineListSelection::ToolApprovalSession
                | InlineListSelection::ToolApprovalPermanent => {
                    renderer.line(
                        MessageStyle::Error,
                        "Reasoning selection is active. Choose a reasoning level or press Esc to cancel.",
                    )?;
                    Ok(ModelPickerProgress::InProgress)
                }
                InlineListSelection::Theme(_) => {
                    renderer.line(MessageStyle::Error, CLOSE_THEME_MESSAGE)?;
                    Ok(ModelPickerProgress::InProgress)
                }
                _ => Ok(ModelPickerProgress::InProgress),
            },
            PickerStep::AwaitServiceTier => match choice {
                InlineListSelection::OpenAIServiceTier(choice) => {
                    let service_tier = match choice {
                        OpenAIServiceTierChoice::ProjectDefault => None,
                        OpenAIServiceTierChoice::Flex => Some(OpenAIServiceTier::Flex),
                        OpenAIServiceTierChoice::Priority => Some(OpenAIServiceTier::Priority),
                    };
                    self.apply_service_tier_choice(renderer, service_tier)
                }
                InlineListSelection::CustomModel
                | InlineListSelection::Model(_)
                | InlineListSelection::RefreshDynamicModels
                | InlineListSelection::DynamicModel(_)
                | InlineListSelection::CustomProvider(_)
                | InlineListSelection::Reasoning(_)
                | InlineListSelection::DisableReasoning
                | InlineListSelection::ToolApproval(_)
                | InlineListSelection::ToolApprovalDenyOnce
                | InlineListSelection::ToolApprovalSession
                | InlineListSelection::ToolApprovalPermanent => {
                    renderer.line(
                        MessageStyle::Error,
                        "Service tier selection is active. Choose a value or press Esc to cancel.",
                    )?;
                    Ok(ModelPickerProgress::InProgress)
                }
                InlineListSelection::Theme(_) => {
                    renderer.line(MessageStyle::Error, CLOSE_THEME_MESSAGE)?;
                    Ok(ModelPickerProgress::InProgress)
                }
                _ => Ok(ModelPickerProgress::InProgress),
            },
            PickerStep::AwaitApiKey => {
                renderer.line(
                    MessageStyle::Info,
                    "Enter the API key in the input field or type 'skip'.",
                )?;
                Ok(ModelPickerProgress::InProgress)
            }
        }
    }

    fn handle_model_selection(
        &mut self,
        renderer: &mut AnsiRenderer,
        input: &str,
    ) -> Result<ModelPickerProgress> {
        let selection = match parse_model_selection(self.options, input, self.vt_cfg.as_ref()) {
            Ok(detail) => detail,
            Err(err) => {
                renderer.line(MessageStyle::Error, &err.to_string())?;
                renderer.line(
                    MessageStyle::Info,
                    "Try again with '<provider> <model-id>'.",
                )?;
                return Ok(ModelPickerProgress::InProgress);
            }
        };

        self.process_model_selection(renderer, selection)
    }

    fn preferred_model_selection(&self) -> Option<InlineListSelection> {
        let provider_key = self.current_provider.trim().to_ascii_lowercase();
        let model_key = self.current_model.trim();
        if provider_key.is_empty() || model_key.is_empty() {
            return None;
        }

        if let Ok(provider) = Provider::from_str(provider_key.as_str()) {
            if let Some(index) = find_option_index(provider, model_key) {
                return Some(InlineListSelection::Model(index));
            }
            for entry_index in self.dynamic_models.indexes_for(provider) {
                if let Some(detail) = self.dynamic_models.detail(*entry_index)
                    && detail.model_id.eq_ignore_ascii_case(model_key)
                {
                    return Some(InlineListSelection::DynamicModel(*entry_index));
                }
            }
        }

        for (entry_index, detail) in self.custom_providers.iter().enumerate() {
            if detail.provider_key.eq_ignore_ascii_case(&provider_key)
                && detail.model_id.eq_ignore_ascii_case(model_key)
            {
                return Some(InlineListSelection::CustomProvider(entry_index));
            }
        }

        None
    }
}

pub(crate) async fn pick_subagent_model(
    renderer: &mut AnsiRenderer,
    handle: &InlineHandle,
    session: &mut InlineSession,
    ctrl_c_state: &Arc<CtrlCState>,
    ctrl_c_notify: &Arc<Notify>,
    vt_cfg: Option<&VTCodeConfig>,
    workspace: Option<&Path>,
    current_model: &str,
    current_reasoning_effort: Option<&str>,
) -> Result<Option<SubagentModelSelection>> {
    if !renderer.supports_inline_ui() {
        renderer.line(
            MessageStyle::Info,
            "Interactive subagent model selection requires inline UI.",
        )?;
        return Ok(None);
    }

    let mut dynamic_models =
        DynamicModelRegistry::load(MODEL_OPTIONS.as_slice(), workspace, vt_cfg).await;
    loop {
        let Some(target) = select_subagent_model_target(
            handle,
            session,
            ctrl_c_state,
            ctrl_c_notify,
            &dynamic_models,
            current_model,
        )
        .await?
        else {
            return Ok(None);
        };

        let target = match target {
            SubagentModelChoice::Target(target) => target,
            SubagentModelChoice::Refresh => {
                renderer.line(MessageStyle::Info, "Refreshing local model inventory...")?;
                dynamic_models =
                    DynamicModelRegistry::load(MODEL_OPTIONS.as_slice(), workspace, vt_cfg).await;
                continue;
            }
            SubagentModelChoice::Manual => {
                let Some(target) = prompt_subagent_model_id(
                    renderer,
                    handle,
                    session,
                    ctrl_c_state,
                    ctrl_c_notify,
                )
                .await?
                else {
                    return Ok(None);
                };
                target
            }
        };

        let Some(selection) = select_subagent_reasoning(
            handle,
            session,
            ctrl_c_state,
            ctrl_c_notify,
            target,
            current_reasoning_effort,
        )
        .await?
        else {
            return Ok(None);
        };

        return Ok(Some(selection));
    }
}

#[derive(Clone, Debug)]
enum SubagentModelChoice {
    Target(SubagentModelTarget),
    Refresh,
    Manual,
}

async fn select_subagent_model_target(
    handle: &InlineHandle,
    session: &mut InlineSession,
    ctrl_c_state: &Arc<CtrlCState>,
    ctrl_c_notify: &Arc<Notify>,
    dynamic_models: &DynamicModelRegistry,
    current_model: &str,
) -> Result<Option<SubagentModelChoice>> {
    let mut items = Vec::new();
    for (shortcut, description) in subagent_model_shortcuts() {
        items.push(InlineListItem {
            title: (*shortcut).to_string(),
            subtitle: Some((*description).to_string()),
            badge: Some("Shortcut".to_string()),
            indent: 0,
            selection: Some(InlineListSelection::ConfigAction(format!(
                "{SUBAGENT_MODEL_ACTION_PREFIX}shortcut:{shortcut}"
            ))),
            search_value: Some(format!(
                "{shortcut} shortcut alias delegated model {description}"
            )),
        });
    }

    for (index, option) in MODEL_OPTIONS.iter().enumerate() {
        let current_provider = if current_model.eq_ignore_ascii_case(option.id) {
            option.provider.as_ref()
        } else {
            ""
        };
        items.push(InlineListItem {
            title: option.display.to_string(),
            subtitle: Some(format!(
                "{} • {}",
                option.provider.label(),
                static_model_subtitle(option, current_provider, current_model)
            )),
            badge: Some(option.provider.label().to_string()),
            indent: 0,
            selection: Some(InlineListSelection::Model(index)),
            search_value: Some(model_search_value(
                option.provider,
                option.display,
                option.id,
                Some(option.description),
                &static_model_search_terms(option.model, option.supports_reasoning),
            )),
        });
    }

    for entry_index in parseable_subagent_dynamic_indexes(dynamic_models) {
        let Some(detail) = dynamic_models.detail(entry_index) else {
            continue;
        };
        let Some(provider) = detail.provider_enum else {
            continue;
        };
        let current_provider = if current_model.eq_ignore_ascii_case(&detail.model_id) {
            provider.as_ref()
        } else {
            ""
        };
        items.push(InlineListItem {
            title: detail.model_display.clone(),
            subtitle: Some(format!(
                "{} • {}",
                provider.label(),
                dynamic_model_subtitle(
                    provider,
                    &detail.model_id,
                    detail.reasoning_supported,
                    current_provider,
                    current_model,
                )
            )),
            badge: Some(provider.label().to_string()),
            indent: 0,
            selection: Some(InlineListSelection::DynamicModel(entry_index)),
            search_value: Some(model_search_value(
                provider,
                &detail.model_display,
                &detail.model_id,
                None,
                &[provider.label().to_string(), "dynamic".to_string()],
            )),
        });
    }

    items.push(InlineListItem {
        title: "Refresh local models".to_string(),
        subtitle: Some(
            "Re-query dynamic model inventories without changing workspace config.".to_string(),
        ),
        badge: Some("Refresh".to_string()),
        indent: 0,
        selection: Some(InlineListSelection::RefreshDynamicModels),
        search_value: Some("refresh dynamic local models".to_string()),
    });
    items.push(InlineListItem {
        title: "Enter exact model id".to_string(),
        subtitle: Some(
            "Provide a concrete VT Code model id such as `gpt-5.4` or `claude-sonnet-4-6`."
                .to_string(),
        ),
        badge: Some("Manual".to_string()),
        indent: 0,
        selection: Some(InlineListSelection::CustomModel),
        search_value: Some("manual exact model id".to_string()),
    });

    let selected = preferred_subagent_model_selection(dynamic_models, current_model)
        .or_else(|| items.first().and_then(|item| item.selection.clone()));
    handle.show_list_modal(
        "Subagent model".to_string(),
        vec![
            "Choose a shortcut alias or a concrete VT Code model id for this subagent.".to_string(),
            "This picker only stores `model` and `reasoning_effort`; it never prompts for API keys or service tier.".to_string(),
        ],
        items,
        selected,
        Some(InlineListSearchConfig {
            label: "Search subagent models".to_string(),
            placeholder: Some("shortcut, provider, model id".to_string()),
        }),
    );

    let Some(selection) =
        wait_for_inline_list_selection(handle, session, ctrl_c_state, ctrl_c_notify).await?
    else {
        return Ok(None);
    };

    let choice = match selection {
        InlineListSelection::ConfigAction(action) => {
            if let Some(shortcut) =
                action.strip_prefix(&format!("{SUBAGENT_MODEL_ACTION_PREFIX}shortcut:"))
            {
                SubagentModelChoice::Target(SubagentModelTarget::Shortcut {
                    model: shortcut.to_string(),
                })
            } else {
                return Ok(None);
            }
        }
        InlineListSelection::Model(index) => {
            let option = MODEL_OPTIONS
                .get(index)
                .ok_or_else(|| anyhow!("Unable to locate the selected model option"))?;
            SubagentModelChoice::Target(SubagentModelTarget::Concrete(selection_from_option(
                option,
            )))
        }
        InlineListSelection::DynamicModel(index) => {
            let detail = dynamic_models
                .dynamic_detail(index)
                .ok_or_else(|| anyhow!("Unable to locate the selected dynamic model"))?;
            SubagentModelChoice::Target(SubagentModelTarget::Concrete(detail))
        }
        InlineListSelection::RefreshDynamicModels => SubagentModelChoice::Refresh,
        InlineListSelection::CustomModel => SubagentModelChoice::Manual,
        _ => return Ok(None),
    };

    Ok(Some(choice))
}

async fn select_subagent_reasoning(
    handle: &InlineHandle,
    session: &mut InlineSession,
    ctrl_c_state: &Arc<CtrlCState>,
    ctrl_c_notify: &Arc<Notify>,
    target: SubagentModelTarget,
    current_reasoning_effort: Option<&str>,
) -> Result<Option<SubagentModelSelection>> {
    let model = target.model().to_string();
    if !target.supports_reasoning() {
        return Ok(Some(SubagentModelSelection {
            model,
            reasoning_effort: None,
        }));
    }

    let current_reasoning_effort = normalized_subagent_reasoning(&target, current_reasoning_effort);
    let current_label = current_reasoning_effort
        .as_deref()
        .and_then(ReasoningEffortLevel::parse)
        .map(reasoning_level_label)
        .unwrap_or("unset");
    let mut items = vec![
        InlineListItem {
            title: format!("Keep current ({current_label})"),
            subtitle: Some("Retain the current reasoning override for this subagent.".to_string()),
            badge: Some("Current".to_string()),
            indent: 0,
            selection: Some(InlineListSelection::ConfigAction(format!(
                "{SUBAGENT_REASONING_ACTION_PREFIX}keep"
            ))),
            search_value: Some("keep current reasoning".to_string()),
        },
        InlineListItem {
            title: "Unset reasoning override".to_string(),
            subtitle: Some(
                "Do not store a `reasoning_effort` override for this subagent.".to_string(),
            ),
            badge: Some("Unset".to_string()),
            indent: 0,
            selection: Some(InlineListSelection::ConfigAction(format!(
                "{SUBAGENT_REASONING_ACTION_PREFIX}unset"
            ))),
            search_value: Some("unset clear reasoning".to_string()),
        },
    ];

    for level in subagent_reasoning_levels(target.model(), target.supports_reasoning()) {
        items.push(InlineListItem {
            title: reasoning_level_label(level).to_string(),
            subtitle: Some(reasoning_level_description(level).to_string()),
            badge: None,
            indent: 0,
            selection: Some(InlineListSelection::ConfigAction(format!(
                "{SUBAGENT_REASONING_ACTION_PREFIX}{}",
                level.as_str()
            ))),
            search_value: Some(format!(
                "{} {}",
                level.as_str(),
                reasoning_level_label(level)
            )),
        });
    }

    handle.show_list_modal(
        "Subagent reasoning".to_string(),
        vec![format!(
            "Choose the reasoning override for `{}`. Esc cancels the picker.",
            target.model()
        )],
        items,
        Some(InlineListSelection::ConfigAction(format!(
            "{SUBAGENT_REASONING_ACTION_PREFIX}keep"
        ))),
        Some(InlineListSearchConfig {
            label: "Search reasoning".to_string(),
            placeholder: Some("keep, unset, high".to_string()),
        }),
    );

    let Some(selection) =
        wait_for_inline_list_selection(handle, session, ctrl_c_state, ctrl_c_notify).await?
    else {
        return Ok(None);
    };

    let reasoning_choice = match selection {
        InlineListSelection::ConfigAction(action)
            if action == format!("{SUBAGENT_REASONING_ACTION_PREFIX}keep") =>
        {
            SubagentReasoningChoice::KeepCurrent
        }
        InlineListSelection::ConfigAction(action)
            if action == format!("{SUBAGENT_REASONING_ACTION_PREFIX}unset") =>
        {
            SubagentReasoningChoice::Unset
        }
        InlineListSelection::ConfigAction(action) => {
            let level_key = action
                .strip_prefix(SUBAGENT_REASONING_ACTION_PREFIX)
                .ok_or_else(|| anyhow!("Unknown subagent reasoning selection"))?;
            let level = ReasoningEffortLevel::parse(level_key)
                .ok_or_else(|| anyhow!("Unknown reasoning effort level `{level_key}`"))?;
            SubagentReasoningChoice::Explicit(level)
        }
        _ => return Ok(None),
    };

    let reasoning_effort = match reasoning_choice {
        SubagentReasoningChoice::KeepCurrent => current_reasoning_effort,
        SubagentReasoningChoice::Unset => None,
        SubagentReasoningChoice::Explicit(level) => Some(level.as_str().to_string()),
    };

    Ok(Some(SubagentModelSelection {
        model,
        reasoning_effort,
    }))
}

async fn prompt_subagent_model_id(
    renderer: &mut AnsiRenderer,
    handle: &InlineHandle,
    session: &mut InlineSession,
    ctrl_c_state: &Arc<CtrlCState>,
    ctrl_c_notify: &Arc<Notify>,
) -> Result<Option<SubagentModelTarget>> {
    loop {
        let outcome = show_wizard_modal_and_wait(
            handle,
            session,
            "Subagent model id".to_string(),
            vec![WizardStep {
                title: "Model id".to_string(),
                question: "Enter a concrete VT Code model id. Shortcut aliases such as `inherit` and `small` are available in the list view.".to_string(),
                items: vec![InlineListItem {
                    title: "Enter a model id".to_string(),
                    subtitle: Some(
                        "Press Tab to type inline, then Enter to confirm the model id."
                            .to_string(),
                    ),
                    badge: Some("Input".to_string()),
                    indent: 0,
                    selection: Some(InlineListSelection::RequestUserInputAnswer {
                        question_id: SUBAGENT_MODEL_PROMPT_ID.to_string(),
                        selected: vec![],
                        other: Some(String::new()),
                    }),
                    search_value: Some("manual model id".to_string()),
                }],
                completed: false,
                answer: None,
                allow_freeform: true,
                freeform_label: Some("Model id".to_string()),
                freeform_placeholder: Some("gpt-5.4".to_string()),
                freeform_default: None,
            }],
            0,
            None,
            WizardModalMode::MultiStep,
            ctrl_c_state,
            ctrl_c_notify,
        )
        .await?;

        let Some(selection) = (match outcome {
            WizardModalOutcome::Submitted(selections) => selections.into_iter().next(),
            WizardModalOutcome::Cancelled { .. } => None,
        }) else {
            return Ok(None);
        };

        let InlineListSelection::RequestUserInputAnswer {
            other, selected, ..
        } = selection
        else {
            return Ok(None);
        };
        let raw_value = other
            .or_else(|| selected.first().cloned())
            .unwrap_or_default();
        let trimmed = raw_value.trim();
        if trimmed.is_empty() {
            continue;
        }

        let model_id = match trimmed.parse::<ModelId>() {
            Ok(model_id) => model_id,
            Err(_) => {
                renderer.line(
                    MessageStyle::Error,
                    &format!("`{trimmed}` is not a recognized VT Code model id."),
                )?;
                continue;
            }
        };
        let detail = parse_model_selection(
            MODEL_OPTIONS.as_slice(),
            &format!("{} {}", model_id.provider(), model_id.as_str()),
            None,
        )?;
        return Ok(Some(SubagentModelTarget::Concrete(detail)));
    }
}

fn preferred_subagent_model_selection(
    dynamic_models: &DynamicModelRegistry,
    current_model: &str,
) -> Option<InlineListSelection> {
    let current_trimmed = current_model.trim();
    if current_trimmed.is_empty() {
        return None;
    }
    if let Some(shortcut) = canonical_subagent_shortcut(current_trimmed) {
        return Some(InlineListSelection::ConfigAction(format!(
            "{SUBAGENT_MODEL_ACTION_PREFIX}shortcut:{shortcut}"
        )));
    }
    if let Ok(model_id) = current_trimmed.parse::<ModelId>()
        && let Some(index) = find_option_index(model_id.provider(), model_id.as_str())
    {
        return Some(InlineListSelection::Model(index));
    }
    parseable_subagent_dynamic_indexes(dynamic_models)
        .into_iter()
        .find_map(|index| {
            dynamic_models
                .detail(index)
                .filter(|detail| detail.model_id.eq_ignore_ascii_case(current_trimmed))
                .map(|_| InlineListSelection::DynamicModel(index))
        })
}

fn normalized_subagent_reasoning(
    target: &SubagentModelTarget,
    current_reasoning_effort: Option<&str>,
) -> Option<String> {
    let level = parse_subagent_reasoning_effort(current_reasoning_effort)?;
    subagent_supports_reasoning_level(target, level).then(|| level.as_str().to_string())
}

fn is_subagent_shortcut(model: &str) -> bool {
    canonical_subagent_shortcut(model).is_some()
}

fn canonical_subagent_shortcut(model: &str) -> Option<&'static str> {
    subagent_model_shortcuts()
        .iter()
        .find(|(shortcut, _)| shortcut.eq_ignore_ascii_case(model.trim()))
        .map(|(shortcut, _)| *shortcut)
}

fn subagent_model_shortcuts() -> &'static [(&'static str, &'static str)] {
    &SUBAGENT_SHORTCUTS
}

fn parse_subagent_reasoning_effort(
    current_reasoning_effort: Option<&str>,
) -> Option<ReasoningEffortLevel> {
    current_reasoning_effort
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .and_then(ReasoningEffortLevel::parse)
}

fn parseable_subagent_dynamic_indexes(dynamic_models: &DynamicModelRegistry) -> Vec<usize> {
    dynamic_models
        .entries
        .iter()
        .enumerate()
        .filter_map(|(index, detail)| detail.model_id.parse::<ModelId>().ok().map(|_| index))
        .collect()
}

fn subagent_reasoning_levels(model: &str, supports_reasoning: bool) -> Vec<ReasoningEffortLevel> {
    if !supports_reasoning {
        return Vec::new();
    }

    let mut levels = vec![
        ReasoningEffortLevel::None,
        ReasoningEffortLevel::Minimal,
        ReasoningEffortLevel::Low,
        ReasoningEffortLevel::Medium,
        ReasoningEffortLevel::High,
    ];
    if !is_subagent_shortcut(model) && supports_xhigh_reasoning(model) {
        levels.push(ReasoningEffortLevel::XHigh);
    }
    if !is_subagent_shortcut(model) && supports_max_reasoning(model) {
        levels.push(ReasoningEffortLevel::Max);
    }
    levels
}

fn subagent_supports_reasoning_level(
    target: &SubagentModelTarget,
    level: ReasoningEffortLevel,
) -> bool {
    if !target.supports_reasoning() {
        return false;
    }

    match level {
        ReasoningEffortLevel::None
        | ReasoningEffortLevel::Minimal
        | ReasoningEffortLevel::Low
        | ReasoningEffortLevel::Medium
        | ReasoningEffortLevel::High => true,
        ReasoningEffortLevel::XHigh => {
            !is_subagent_shortcut(target.model()) && supports_xhigh_reasoning(target.model())
        }
        ReasoningEffortLevel::Max => {
            !is_subagent_shortcut(target.model()) && supports_max_reasoning(target.model())
        }
    }
}

async fn wait_for_inline_list_selection(
    handle: &InlineHandle,
    session: &mut InlineSession,
    ctrl_c_state: &Arc<CtrlCState>,
    ctrl_c_notify: &Arc<Notify>,
) -> Result<Option<InlineListSelection>> {
    let outcome =
        wait_for_overlay_submission(handle, session, ctrl_c_state, ctrl_c_notify, |submission| {
            match submission {
                TransientSubmission::Selection(selection) => Some(selection),
                _ => None,
            }
        })
        .await?;

    handle.close_modal();
    handle.force_redraw();
    task::yield_now().await;

    Ok(match outcome {
        OverlayWaitOutcome::Submitted(selection) => Some(selection),
        OverlayWaitOutcome::Cancelled
        | OverlayWaitOutcome::Interrupted
        | OverlayWaitOutcome::Exit => None,
    })
}

impl SubagentModelTarget {
    fn model(&self) -> &str {
        match self {
            Self::Shortcut { model } => model,
            Self::Concrete(detail) => &detail.model_id,
        }
    }

    fn supports_reasoning(&self) -> bool {
        match self {
            Self::Shortcut { .. } => true,
            Self::Concrete(detail) => detail.reasoning_supported,
        }
    }
}

#[cfg(test)]
mod tests;
