use anyhow::Result;
use std::sync::Arc;
use tokio::sync::Notify;

use tracing::warn;

use vtcode_core::config::loader::VTCodeConfig;
use vtcode_core::config::types::AgentConfig as CoreAgentConfig;
use vtcode_core::llm::provider::{self as uni};
use vtcode_core::utils::ansi::{AnsiRenderer, MessageStyle};
use vtcode_tui::app::{InlineHandle, InlineHeaderContext, InlineListSelection};

use crate::agent::runloop::model_picker::{
    ModelPickerProgress, ModelPickerStart, ModelPickerState,
};
use crate::agent::runloop::slash_commands::SessionPaletteMode;
use crate::agent::runloop::unified::hooks_browser::show_hooks_palette;
use crate::agent::runloop::unified::model_selection::finalize_model_selection;
use crate::agent::runloop::unified::palettes::{
    ActivePalette, LIGHTWEIGHT_MODEL_ACTION_PREFIX, MODEL_TARGET_ACTION_LIGHTWEIGHT,
    MODEL_TARGET_ACTION_MAIN, build_lightweight_palette_view, handle_palette_cancel,
    handle_palette_preview, handle_palette_selection, show_fork_mode_palette,
    show_lightweight_model_palette, show_model_target_palette, show_sessions_palette,
    show_theme_palette,
};
use crate::agent::runloop::unified::settings_interactive::{
    ACTION_CONFIGURE_EDITOR, ACTION_PICK_LIGHTWEIGHT_MODEL, ACTION_PICK_MAIN_MODEL,
    show_settings_palette,
};
use crate::agent::runloop::unified::ui_interaction::PlaceholderSpinner;
use crate::agent::runloop::unified::url_guard::{
    URL_GUARD_TITLE, UrlGuardDecision, UrlGuardPrompt, open_external_url, url_guard_decision,
};
use crate::agent::runloop::welcome::SessionBootstrap;

use super::action::InlineLoopAction;

pub(crate) struct InlineModalProcessor<'a> {
    model_picker: ModelPickerCoordinator<'a>,
    palette: PaletteCoordinator<'a>,
}

impl<'a> InlineModalProcessor<'a> {
    #[expect(clippy::too_many_arguments)]
    pub(crate) fn new(
        handle: &'a InlineHandle,
        header_context: &'a mut InlineHeaderContext,
        model_picker_state: &'a mut Option<ModelPickerState>,
        palette_state: &'a mut Option<ActivePalette>,
        config: &'a mut CoreAgentConfig,
        vt_cfg: &'a mut Option<VTCodeConfig>,
        provider_client: &'a mut Box<dyn uni::LLMProvider>,
        ctrl_c_state: &'a Arc<crate::agent::runloop::unified::state::CtrlCState>,
        ctrl_c_notify: &'a Arc<Notify>,
        session_bootstrap: &'a SessionBootstrap,
        full_auto: bool,
        conversation_history_len: usize,
    ) -> Self {
        let model_picker = ModelPickerCoordinator {
            state: model_picker_state,
            header_context,
            config,
            vt_cfg,
            provider_client,
            ctrl_c_state,
            ctrl_c_notify,
            session_bootstrap,
            handle,
            full_auto,
            conversation_history_len,
        };
        let palette = PaletteCoordinator {
            state: palette_state,
            handle,
        };

        Self {
            model_picker,
            palette,
        }
    }

    pub(crate) fn request_url_guard(
        &mut self,
        renderer: &mut AnsiRenderer,
        url: String,
    ) -> Result<InlineLoopAction> {
        if matches!(
            self.palette.state.as_ref(),
            Some(ActivePalette::UrlGuard { .. })
        ) {
            return Ok(InlineLoopAction::Continue);
        }

        let Some(prompt) = UrlGuardPrompt::parse(url) else {
            renderer.line(
                MessageStyle::Error,
                "Blocked unsupported external link target.",
            )?;
            return Ok(InlineLoopAction::Continue);
        };

        let previous = self.palette.state.take().map(Box::new);
        self.show_url_guard(renderer, &prompt);
        *self.palette.state = Some(ActivePalette::UrlGuard { prompt, previous });

        Ok(InlineLoopAction::Continue)
    }

    pub(crate) async fn handle_submit(
        &mut self,
        renderer: &mut AnsiRenderer,
        selection: InlineListSelection,
    ) -> Result<InlineLoopAction> {
        // Handle plan approval selections (Claude Code style HITL)
        match &selection {
            InlineListSelection::PlanApprovalExecute => {
                return Ok(InlineLoopAction::PlanApproved { auto_accept: false });
            }
            InlineListSelection::PlanApprovalAutoAccept => {
                return Ok(InlineLoopAction::PlanApproved { auto_accept: true });
            }
            InlineListSelection::PlanApprovalEditPlan => {
                return Ok(InlineLoopAction::PlanEditRequested);
            }
            _ => {}
        }

        if let Some(action) = self.handle_url_guard_submit(renderer, &selection)? {
            return Ok(action);
        }

        match self.model_picker.handle_submit(renderer, selection).await? {
            ModelPickerOutcome::SkipPalette => Ok(InlineLoopAction::Continue),
            ModelPickerOutcome::ForwardToPalette(selection) => {
                if let Some(action) = self.handle_settings_submit(&selection) {
                    return Ok(action);
                }
                if self.handle_palette_redirect(renderer, &selection).await? {
                    return Ok(InlineLoopAction::Continue);
                }
                self.palette
                    .handle_submit(
                        renderer,
                        selection,
                        self.model_picker.config,
                        self.model_picker.vt_cfg,
                        self.model_picker.provider_client.as_ref(),
                        self.model_picker.session_bootstrap,
                        self.model_picker.full_auto,
                    )
                    .await
            }
            ModelPickerOutcome::Continue => Ok(InlineLoopAction::Continue),
        }
    }

    async fn open_lightweight_palette(&mut self, renderer: &mut AnsiRenderer) -> Result<()> {
        let vt_cfg = self.model_picker.vt_cfg.clone();
        let view = {
            let loading_spinner = if renderer.supports_inline_ui() {
                let spinner = PlaceholderSpinner::new(
                    self.model_picker.handle,
                    None,
                    None,
                    "Loading lightweight model lists...",
                );
                spinner.set_defer_restore(true);
                Some(spinner)
            } else {
                renderer.line(MessageStyle::Info, "Loading lightweight model lists...")?;
                None
            };
            let result =
                build_lightweight_palette_view(self.model_picker.config, vt_cfg.as_ref()).await;
            if let Some(ref s) = loading_spinner {
                // Explicitly restore after loading is done (override defer_restore).
                s.finish_with_restore(true);
            }
            drop(loading_spinner);
            result
        };

        if show_lightweight_model_palette(renderer, &view, None)? {
            *self.palette.state = Some(ActivePalette::LightweightModel {
                view: Box::new(view),
            });
        }

        Ok(())
    }

    pub(crate) fn handle_cancel(
        &mut self,
        renderer: &mut AnsiRenderer,
    ) -> Result<InlineLoopAction> {
        if self.handle_url_guard_cancel(renderer)? {
            return Ok(InlineLoopAction::Continue);
        }

        if self.model_picker.handle_cancel(renderer)? {
            return Ok(InlineLoopAction::Continue);
        }

        self.palette.handle_cancel(renderer)?;
        Ok(InlineLoopAction::Continue)
    }

    pub(crate) fn handle_preview(
        &mut self,
        renderer: &mut AnsiRenderer,
        selection: InlineListSelection,
    ) -> Result<InlineLoopAction> {
        if matches!(
            self.palette.state.as_ref(),
            Some(ActivePalette::UrlGuard { .. })
        ) {
            return Ok(InlineLoopAction::Continue);
        }

        self.palette.handle_preview(renderer, selection)
    }

    fn handle_url_guard_submit(
        &mut self,
        renderer: &mut AnsiRenderer,
        selection: &InlineListSelection,
    ) -> Result<Option<InlineLoopAction>> {
        if !matches!(
            self.palette.state.as_ref(),
            Some(ActivePalette::UrlGuard { .. })
        ) {
            return Ok(None);
        }

        let Some(ActivePalette::UrlGuard { prompt, previous }) = self.palette.state.take() else {
            return Ok(None);
        };

        match url_guard_decision(selection) {
            Some(UrlGuardDecision::Approve) => {
                if let Err(err) = open_external_url(prompt.url()) {
                    renderer.line(MessageStyle::Error, &format!("Failed to open link: {err}"))?;
                }
                self.restore_previous_palette(renderer, previous)?;
                Ok(Some(InlineLoopAction::Continue))
            }
            Some(UrlGuardDecision::Deny) => {
                self.restore_previous_palette(renderer, previous)?;
                Ok(Some(InlineLoopAction::Continue))
            }
            _ => {
                self.show_url_guard(renderer, &prompt);
                *self.palette.state = Some(ActivePalette::UrlGuard { prompt, previous });
                Ok(Some(InlineLoopAction::Continue))
            }
        }
    }

    fn handle_url_guard_cancel(&mut self, renderer: &mut AnsiRenderer) -> Result<bool> {
        if !matches!(
            self.palette.state.as_ref(),
            Some(ActivePalette::UrlGuard { .. })
        ) {
            return Ok(false);
        }

        let Some(ActivePalette::UrlGuard { previous, .. }) = self.palette.state.take() else {
            return Ok(false);
        };

        self.restore_previous_palette(renderer, previous)?;
        Ok(true)
    }

    fn show_url_guard(&mut self, renderer: &mut AnsiRenderer, prompt: &UrlGuardPrompt) {
        renderer.show_list_modal(
            URL_GUARD_TITLE,
            prompt.lines(),
            prompt.items(),
            Some(prompt.default_selection()),
            None,
        );
    }

    fn restore_previous_palette(
        &mut self,
        renderer: &mut AnsiRenderer,
        previous: Option<Box<ActivePalette>>,
    ) -> Result<()> {
        let Some(previous) = previous else {
            return Ok(());
        };

        self.restore_palette(renderer, *previous)
    }

    fn restore_palette(
        &mut self,
        renderer: &mut AnsiRenderer,
        palette: ActivePalette,
    ) -> Result<()> {
        match palette {
            ActivePalette::Theme {
                mode,
                original_theme_id,
            } => {
                if show_theme_palette(renderer, mode)? {
                    *self.palette.state = Some(ActivePalette::Theme {
                        mode,
                        original_theme_id,
                    });
                }
            }
            ActivePalette::Sessions {
                mode,
                listings,
                limit,
                show_all,
            } => {
                if show_sessions_palette(renderer, mode, &listings, limit, show_all)? {
                    *self.palette.state = Some(ActivePalette::Sessions {
                        mode,
                        listings,
                        limit,
                        show_all,
                    });
                }
            }
            ActivePalette::ForkMode {
                session_id,
                listings,
                limit,
                show_all,
            } => {
                if show_fork_mode_palette(renderer, &session_id)? {
                    *self.palette.state = Some(ActivePalette::ForkMode {
                        session_id,
                        listings,
                        limit,
                        show_all,
                    });
                }
            }
            ActivePalette::Settings { state, .. } => {
                if show_settings_palette(renderer, state.as_ref(), None)? {
                    *self.palette.state = Some(ActivePalette::Settings {
                        state,
                        esc_armed: false,
                    });
                }
            }
            ActivePalette::Hooks { state, .. } => {
                if show_hooks_palette(renderer, state.as_ref(), None)? {
                    *self.palette.state = Some(ActivePalette::Hooks {
                        state,
                        esc_armed: false,
                    });
                }
            }
            ActivePalette::ModelTarget => {
                if show_model_target_palette(renderer)? {
                    *self.palette.state = Some(ActivePalette::ModelTarget);
                }
            }
            ActivePalette::LightweightModel { view } => {
                if show_lightweight_model_palette(renderer, view.as_ref(), None)? {
                    *self.palette.state = Some(ActivePalette::LightweightModel { view });
                }
            }
            ActivePalette::UrlGuard { previous, .. } => {
                self.restore_previous_palette(renderer, previous)?;
            }
        }

        Ok(())
    }

    async fn handle_palette_redirect(
        &mut self,
        renderer: &mut AnsiRenderer,
        selection: &InlineListSelection,
    ) -> Result<bool> {
        let Some(active) = self.palette.state.as_ref() else {
            return Ok(false);
        };

        match (active, selection) {
            (ActivePalette::ModelTarget, InlineListSelection::ConfigAction(action))
                if action == MODEL_TARGET_ACTION_MAIN =>
            {
                self.palette.state.take();
                self.model_picker.start_picker(renderer).await?;
                Ok(true)
            }
            (ActivePalette::ModelTarget, InlineListSelection::ConfigAction(action))
                if action == MODEL_TARGET_ACTION_LIGHTWEIGHT =>
            {
                self.palette.state.take();
                self.open_lightweight_palette(renderer).await?;
                Ok(true)
            }
            (ActivePalette::Settings { .. }, InlineListSelection::ConfigAction(action))
                if action == ACTION_PICK_MAIN_MODEL =>
            {
                self.palette.state.take();
                self.model_picker.start_picker(renderer).await?;
                Ok(true)
            }
            (ActivePalette::Settings { .. }, InlineListSelection::ConfigAction(action))
                if action == ACTION_PICK_LIGHTWEIGHT_MODEL =>
            {
                self.palette.state.take();
                self.open_lightweight_palette(renderer).await?;
                Ok(true)
            }
            (ActivePalette::LightweightModel { .. }, InlineListSelection::ConfigAction(action))
                if action.starts_with(LIGHTWEIGHT_MODEL_ACTION_PREFIX) =>
            {
                Ok(false)
            }
            (ActivePalette::UrlGuard { .. }, _) => Ok(true),
            _ => Ok(false),
        }
    }

    fn handle_settings_submit(
        &mut self,
        selection: &InlineListSelection,
    ) -> Option<InlineLoopAction> {
        match (self.palette.state.as_ref(), selection) {
            (Some(ActivePalette::Settings { .. }), InlineListSelection::ConfigAction(action))
                if action == ACTION_CONFIGURE_EDITOR =>
            {
                self.palette.state.take();
                Some(InlineLoopAction::Submit("/config tools.editor".to_string()))
            }
            _ => None,
        }
    }
}

struct PaletteCoordinator<'a> {
    state: &'a mut Option<ActivePalette>,
    handle: &'a InlineHandle,
}

impl<'a> PaletteCoordinator<'a> {
    #[expect(clippy::too_many_arguments)]
    async fn handle_submit(
        &mut self,
        renderer: &mut AnsiRenderer,
        selection: InlineListSelection,
        config: &mut CoreAgentConfig,
        vt_cfg: &mut Option<VTCodeConfig>,
        provider_client: &dyn uni::LLMProvider,
        session_bootstrap: &SessionBootstrap,
        full_auto: bool,
    ) -> Result<InlineLoopAction> {
        if let Some(active) = self.state.take() {
            match (&active, &selection) {
                (
                    ActivePalette::Sessions {
                        mode: SessionPaletteMode::Resume,
                        ..
                    },
                    InlineListSelection::Session(session_id),
                ) => {
                    renderer.line(
                        MessageStyle::Info,
                        &format!("Resuming session: {}", session_id),
                    )?;
                    return Ok(InlineLoopAction::ResumeSession(session_id.clone()));
                }
                (
                    ActivePalette::Sessions {
                        mode: SessionPaletteMode::Fork,
                        listings,
                        limit,
                        show_all,
                    },
                    InlineListSelection::Session(session_id),
                ) => {
                    show_fork_mode_palette(renderer, session_id)?;
                    *self.state = Some(ActivePalette::ForkMode {
                        session_id: session_id.clone(),
                        listings: listings.clone(),
                        limit: *limit,
                        show_all: *show_all,
                    });
                    return Ok(InlineLoopAction::Continue);
                }
                (
                    ActivePalette::ForkMode { .. },
                    InlineListSelection::SessionForkMode {
                        session_id,
                        summarize,
                    },
                ) => {
                    let mode_label = if *summarize {
                        "summarized"
                    } else {
                        "full-copy"
                    };
                    renderer.line(
                        MessageStyle::Info,
                        &format!("Forking session: {} ({mode_label})", session_id),
                    )?;
                    return Ok(InlineLoopAction::ForkSession {
                        session_id: session_id.clone(),
                        summarize: *summarize,
                    });
                }
                _ => {}
            }

            let restore = handle_palette_selection(
                active,
                selection,
                renderer,
                self.handle,
                config,
                vt_cfg,
                provider_client,
                session_bootstrap,
                full_auto,
            )
            .await?;
            if let Some(state) = restore {
                *self.state = Some(state);
            }
            return Ok(InlineLoopAction::Continue);
        }

        // Safety net: If palette state is missing, log and inform the user
        warn!(
            "Palette selection {:?} dropped because no active palette was tracked",
            selection
        );
        renderer.line(
            MessageStyle::Error,
            "Selection dismissed because the palette is no longer active. Please try again.",
        )?;

        Ok(InlineLoopAction::Continue)
    }

    fn handle_cancel(&mut self, renderer: &mut AnsiRenderer) -> Result<()> {
        if let Some(active) = self.state.take() {
            let restore = handle_palette_cancel(active, renderer, self.handle)?;
            if let Some(state) = restore {
                *self.state = Some(state);
            }
        }

        Ok(())
    }

    fn handle_preview(
        &mut self,
        renderer: &mut AnsiRenderer,
        selection: InlineListSelection,
    ) -> Result<InlineLoopAction> {
        if let Some(active) = self.state.take() {
            let restore = handle_palette_preview(active, selection, renderer, self.handle)?;
            if let Some(state) = restore {
                *self.state = Some(state);
            }
        }

        Ok(InlineLoopAction::Continue)
    }
}

struct ModelPickerCoordinator<'a> {
    state: &'a mut Option<ModelPickerState>,
    header_context: &'a mut InlineHeaderContext,
    config: &'a mut CoreAgentConfig,
    vt_cfg: &'a mut Option<VTCodeConfig>,
    provider_client: &'a mut Box<dyn uni::LLMProvider>,
    ctrl_c_state: &'a Arc<crate::agent::runloop::unified::state::CtrlCState>,
    ctrl_c_notify: &'a Arc<Notify>,
    session_bootstrap: &'a SessionBootstrap,
    handle: &'a InlineHandle,
    full_auto: bool,
    conversation_history_len: usize,
}

impl<'a> ModelPickerCoordinator<'a> {
    async fn start_picker(&mut self, renderer: &mut AnsiRenderer) -> Result<()> {
        if self.state.is_some() {
            renderer.line(
                MessageStyle::Error,
                "A model picker session is already active. Complete or cancel it before starting another.",
            )?;
            return Ok(());
        }

        let reasoning = self
            .vt_cfg
            .as_ref()
            .map(|cfg| cfg.agent.reasoning_effort)
            .unwrap_or(self.config.reasoning_effort);
        let service_tier = self
            .vt_cfg
            .as_ref()
            .and_then(|cfg| cfg.provider.openai.service_tier);
        let workspace_hint = Some(self.config.workspace.clone());
        let picker_start = {
            let loading_spinner = if renderer.supports_inline_ui() {
                let spinner =
                    PlaceholderSpinner::new(self.handle, None, None, "Loading model lists...");
                spinner.set_defer_restore(true);
                Some(spinner)
            } else {
                renderer.line(MessageStyle::Info, "Loading model lists...")?;
                None
            };
            let result = ModelPickerState::new(
                renderer,
                self.vt_cfg.clone(),
                reasoning,
                service_tier,
                workspace_hint,
                self.config.provider.clone(),
                self.config.model.clone(),
                Some(Arc::clone(self.ctrl_c_state)),
                Some(Arc::clone(self.ctrl_c_notify)),
            )
            .await;
            if let Some(ref s) = loading_spinner {
                // Explicitly restore after loading is done (override defer_restore).
                s.finish_with_restore(true);
            }
            drop(loading_spinner);
            result
        };

        match picker_start {
            Ok(ModelPickerStart::InProgress(picker)) => {
                *self.state = Some(picker);
            }
            Ok(ModelPickerStart::Completed { state, selection }) => {
                if let Err(err) = finalize_model_selection(
                    renderer,
                    &state,
                    selection,
                    self.config,
                    self.vt_cfg,
                    self.provider_client,
                    self.session_bootstrap,
                    self.handle,
                    self.header_context,
                    self.full_auto,
                    self.conversation_history_len,
                )
                .await
                {
                    renderer.line(
                        MessageStyle::Error,
                        &format!("Failed to apply model selection: {}", err),
                    )?;
                }
            }
            Err(err) => {
                renderer.line(
                    MessageStyle::Error,
                    &format!("Failed to start model picker: {}", err),
                )?;
            }
        }

        Ok(())
    }

    async fn handle_submit(
        &mut self,
        renderer: &mut AnsiRenderer,
        selection: InlineListSelection,
    ) -> Result<ModelPickerOutcome> {
        let Some(picker) = self.state.as_mut() else {
            return Ok(ModelPickerOutcome::ForwardToPalette(selection));
        };

        let progress = picker.handle_list_selection(renderer, selection)?;
        match progress {
            ModelPickerProgress::InProgress => {}
            ModelPickerProgress::NeedsRefresh => {
                picker.refresh_dynamic_models(renderer).await?;
                return Ok(ModelPickerOutcome::SkipPalette);
            }
            ModelPickerProgress::Cancelled => {
                *self.state = None;
                if !renderer.supports_inline_ui() {
                    renderer.line(MessageStyle::Info, "Model picker cancelled.")?;
                }
            }
            ModelPickerProgress::Exit => {
                *self.state = None;
                if !renderer.supports_inline_ui() {
                    renderer.line(MessageStyle::Info, "Model picker cancelled.")?;
                }
            }
            ModelPickerProgress::Completed(selection) => {
                let Some(picker_state) = self.state.take() else {
                    warn!("Model picker completed but no state was available");
                    return Ok(ModelPickerOutcome::Continue);
                };
                if let Err(err) = finalize_model_selection(
                    renderer,
                    &picker_state,
                    selection,
                    self.config,
                    self.vt_cfg,
                    self.provider_client,
                    self.session_bootstrap,
                    self.handle,
                    self.header_context,
                    self.full_auto,
                    self.conversation_history_len,
                )
                .await
                {
                    renderer.line(
                        MessageStyle::Error,
                        &format!("Failed to apply model selection: {}", err),
                    )?;
                }
            }
        }

        Ok(ModelPickerOutcome::Continue)
    }

    fn handle_cancel(&mut self, renderer: &mut AnsiRenderer) -> Result<bool> {
        if self.state.take().is_some() {
            if !renderer.supports_inline_ui() {
                renderer.line(MessageStyle::Info, "Model picker cancelled.")?;
            }
            return Ok(true);
        }

        Ok(false)
    }
}

enum ModelPickerOutcome {
    Continue,
    ForwardToPalette(InlineListSelection),
    SkipPalette,
}
