use crate::AppState;
use crate::client_profile::ClientProfile;
use crate::protocol::Tool;
use crate::tool_defs::{
    ToolProfile, ToolSurface, is_deferred_control_tool, preferred_bootstrap_tools,
    preferred_namespaces, preferred_tier_labels, tool_namespace, tool_tier_label,
    visible_namespaces, visible_tiers, visible_tools,
};
use serde_json::{Value, json};

#[derive(Clone, Debug)]
pub(crate) struct ResourceRequestContext {
    pub(crate) session: crate::session_context::SessionRequestContext,
    pub(crate) deferred_loading_requested: bool,
    pub(crate) loaded_namespaces: Vec<String>,
    pub(crate) loaded_tiers: Vec<String>,
    pub(crate) full_tool_exposure: bool,
    pub(crate) requested_namespace: Option<String>,
    pub(crate) requested_tier: Option<String>,
    pub(crate) full_listing: bool,
    pub(crate) client_profile: ClientProfile,
    pub(crate) client_name: Option<String>,
}

impl Default for ResourceRequestContext {
    fn default() -> Self {
        Self {
            session: crate::session_context::SessionRequestContext::default(),
            deferred_loading_requested: false,
            loaded_namespaces: Vec::new(),
            loaded_tiers: Vec::new(),
            full_tool_exposure: false,
            requested_namespace: None,
            requested_tier: None,
            full_listing: false,
            client_profile: ClientProfile::Generic,
            client_name: None,
        }
    }
}

impl ResourceRequestContext {
    pub(crate) fn from_request(uri: &str, params: Option<&Value>) -> Self {
        let session = params
            .map(crate::session_context::SessionRequestContext::from_json)
            .unwrap_or_default();
        let loaded_namespaces = session.loaded_namespaces.clone();
        let loaded_tiers = session.loaded_tiers.clone();
        let full_tool_exposure = session.full_tool_exposure;
        let client_name = session.client_name.clone();
        let client_profile = session
            .client_name
            .as_deref()
            .map(|name| ClientProfile::detect(Some(name)))
            .unwrap_or(ClientProfile::Generic);
        let deferred_loading_requested = params
            .and_then(|value| value.get("_session_deferred_tool_loading"))
            .and_then(|value| value.as_bool())
            .or_else(|| client_profile.default_deferred_tool_loading())
            .unwrap_or(false);
        Self {
            session,
            deferred_loading_requested,
            loaded_namespaces,
            loaded_tiers,
            full_tool_exposure,
            requested_namespace: string_param(params, "namespace"),
            requested_tier: string_param(params, "tier"),
            full_listing: uri == "codelens://tools/list/full" || bool_param(params, "full"),
            client_profile,
            client_name,
        }
    }

    pub(crate) fn deferred_loading_active(&self) -> bool {
        self.deferred_loading_requested
            && self.requested_namespace.is_none()
            && self.requested_tier.is_none()
            && !self.full_listing
            && !self.full_tool_exposure
    }

    pub(crate) fn tool_contract_mode(&self) -> &'static str {
        self.client_profile.default_tool_contract_mode()
    }

    pub(crate) fn lean_tool_contract(&self) -> bool {
        self.tool_contract_mode() == "lean" && !self.full_listing && !self.full_tool_exposure
    }
}

pub(crate) struct VisibleToolContext {
    pub(crate) tools: Vec<&'static Tool>,
    pub(crate) total_tool_count: usize,
    pub(crate) all_namespaces: Vec<&'static str>,
    pub(crate) all_tiers: Vec<&'static str>,
    pub(crate) preferred_namespaces: Vec<&'static str>,
    pub(crate) preferred_tiers: Vec<&'static str>,
    pub(crate) loaded_namespaces: Vec<String>,
    pub(crate) loaded_tiers: Vec<String>,
    pub(crate) effective_namespaces: Vec<String>,
    pub(crate) effective_tiers: Vec<String>,
    pub(crate) selected_namespace: Option<String>,
    pub(crate) selected_tier: Option<String>,
    pub(crate) deferred_loading_active: bool,
    pub(crate) full_tool_exposure: bool,
}

pub(crate) fn build_visible_tool_context(
    state: &AppState,
    request: &ResourceRequestContext,
) -> VisibleToolContext {
    let surface = state.execution_surface(&request.session);
    let all_tools = visible_tools(surface);
    let preferred = preferred_namespaces(surface);
    let preferred_bootstrap = preferred_bootstrap_tools(surface);
    let preferred_tiers = preferred_tier_labels(surface);
    let tools = all_tools
        .iter()
        .copied()
        .filter(|tool| match request.requested_namespace.as_deref() {
            _ if request.deferred_loading_active() && is_deferred_control_tool(tool.name) => true,
            Some(namespace) => tool_namespace(tool.name) == namespace,
            None if request.deferred_loading_active() => {
                if is_deferred_control_tool(tool.name) {
                    return true;
                }
                let namespace = tool_namespace(tool.name);
                preferred.contains(&namespace)
                    || request
                        .loaded_namespaces
                        .iter()
                        .any(|value| value == namespace)
            }
            None => true,
        })
        .filter(|tool| match request.requested_tier.as_deref() {
            _ if request.deferred_loading_active() && is_deferred_control_tool(tool.name) => true,
            Some(tier) => tool_tier_label(tool.name) == tier,
            None if request.deferred_loading_active() => {
                if is_deferred_control_tool(tool.name) {
                    return true;
                }
                let tier = tool_tier_label(tool.name);
                preferred_tiers.contains(&tier)
                    || request.loaded_tiers.iter().any(|value| value == tier)
            }
            None => true,
        })
        .filter(|tool| match preferred_bootstrap {
            _ if request.deferred_loading_active() && is_deferred_control_tool(tool.name) => true,
            Some(tool_names) if request.deferred_loading_active() => {
                tool_names.contains(&tool.name)
            }
            _ => true,
        })
        .collect::<Vec<_>>();

    let mut effective_namespaces = preferred
        .iter()
        .map(|value| (*value).to_owned())
        .collect::<Vec<_>>();
    for namespace in &request.loaded_namespaces {
        if !effective_namespaces.iter().any(|value| value == namespace) {
            effective_namespaces.push(namespace.clone());
        }
    }
    effective_namespaces.sort();

    let mut effective_tiers = preferred_tiers
        .iter()
        .map(|value| (*value).to_owned())
        .collect::<Vec<_>>();
    for tier in &request.loaded_tiers {
        if !effective_tiers.iter().any(|value| value == tier) {
            effective_tiers.push(tier.clone());
        }
    }
    effective_tiers.sort();

    VisibleToolContext {
        tools,
        total_tool_count: all_tools.len(),
        all_namespaces: visible_namespaces(surface),
        all_tiers: visible_tiers(surface),
        preferred_namespaces: preferred,
        preferred_tiers,
        loaded_namespaces: request.loaded_namespaces.clone(),
        loaded_tiers: request.loaded_tiers.clone(),
        effective_namespaces,
        effective_tiers,
        selected_namespace: request.requested_namespace.clone(),
        selected_tier: request.requested_tier.clone(),
        deferred_loading_active: request.deferred_loading_active(),
        full_tool_exposure: request.full_tool_exposure,
    }
}

pub(crate) fn build_http_session_payload(
    state: &AppState,
    request: &ResourceRequestContext,
) -> Value {
    let surface = state.execution_surface(&request.session);
    json!({
        "enabled": state.session_resume_supported(),
        "active_sessions": state.active_session_count(),
        "timeout_seconds": state.session_timeout_seconds(),
        "resume_supported": state.session_resume_supported(),
        "daemon_mode": state.daemon_mode().as_str(),
        "client_profile": request.client_profile.as_str(),
        "client_name": request.client_name,
        "active_surface": surface.as_label(),
        "deferred_loading_supported": true,
        "default_deferred_tool_loading": request.client_profile.default_deferred_tool_loading(),
        "default_tools_list_contract_mode": request.client_profile.default_tool_contract_mode(),
        "loaded_namespaces": request.loaded_namespaces,
        "loaded_tiers": request.loaded_tiers,
        "full_tool_exposure": request.full_tool_exposure,
        "deferred_namespace_gate": true,
        "deferred_tier_gate": true,
        "preferred_namespaces": preferred_namespaces(surface),
        "preferred_tiers": preferred_tier_labels(surface),
        "trusted_client_hook": true,
        "mutation_requires_trusted_client": matches!(
            state.daemon_mode(),
            crate::state::RuntimeDaemonMode::MutationEnabled
        ),
        "mutation_preflight_required": matches!(
            surface,
            ToolSurface::Profile(ToolProfile::RefactorFull)
        ),
        "preflight_ttl_seconds": state.preflight_ttl_seconds(),
        "rename_requires_symbol_preflight": true,
        "requires_namespace_listing_before_tool_call": true,
        "requires_tier_listing_before_tool_call": true
    })
}

fn bool_param(params: Option<&Value>, key: &str) -> bool {
    params
        .and_then(|params| params.get(key))
        .and_then(|value| value.as_bool())
        .unwrap_or(false)
}

fn string_param(params: Option<&Value>, key: &str) -> Option<String> {
    params
        .and_then(|params| params.get(key))
        .and_then(|value| value.as_str())
        .map(ToOwned::to_owned)
}
