use rustc_hash::FxHashSet;
use tower_lsp::lsp_types::*;

use syntax::ast::Expression;
use syntax::program::DefinitionBody;
use syntax::types::Type;

use crate::definition::get_root_expression;
use crate::snapshot::AnalysisSnapshot;
use crate::traversal::{find_enclosing_impl_type, find_expression_at};
use crate::type_name;

pub(crate) fn get_module_prefix(source: &str, offset: usize) -> Option<&str> {
    let before = &source[..offset];
    if !before.ends_with('.') {
        return None;
    }
    let before_dot = &before[..before.len() - 1];

    let base = if before_dot.ends_with(']') {
        let bracket_start = before_dot.rfind('[')?;
        &before_dot[..bracket_start]
    } else {
        before_dot
    };

    let start = base
        .rfind(|c: char| !c.is_alphanumeric() && c != '_')
        .map(|i| i + 1)
        .unwrap_or(0);
    let identifier = base[start..].trim();
    if identifier.is_empty() || !identifier.starts_with(|c: char| c.is_alphabetic() || c == '_') {
        return None;
    }
    Some(identifier)
}

pub(crate) fn definition_to_completion_kind(
    definition: &syntax::program::Definition,
) -> CompletionItemKind {
    use syntax::program::DefinitionBody;
    match &definition.body {
        DefinitionBody::Struct { .. } => CompletionItemKind::STRUCT,
        DefinitionBody::Enum { .. } | DefinitionBody::ValueEnum { .. } => CompletionItemKind::ENUM,
        DefinitionBody::Interface { .. } => CompletionItemKind::INTERFACE,
        DefinitionBody::TypeAlias { .. } => CompletionItemKind::TYPE_PARAMETER,
        DefinitionBody::Value { .. } => {
            if matches!(
                &definition.ty,
                syntax::types::Type::Function(_) | syntax::types::Type::Forall { .. }
            ) {
                CompletionItemKind::FUNCTION
            } else {
                CompletionItemKind::CONSTANT
            }
        }
    }
}

/// Extract the element type from a collection type (Slice<T>, EnumeratedSlice<T>, Map<K, V>).
fn element_type_name(ty: &syntax::types::Type) -> Option<String> {
    use syntax::types::CompoundKind;
    match ty.as_compound()? {
        (CompoundKind::Slice | CompoundKind::EnumeratedSlice, args) => {
            args.first().and_then(type_name)
        }
        (CompoundKind::Map, args) => args.get(1).and_then(type_name),
        _ => None,
    }
}

/// Resolve a variable name to its type's qualified name by scanning usages.
/// When `indexed` is true, extracts the element type for collection types.
pub(crate) fn resolve_variable_type(
    var_name: &str,
    file: &syntax::program::File,
    offset: u32,
    snapshot: &AnalysisSnapshot,
    indexed: bool,
) -> Option<String> {
    let binding =
        snapshot.facts().bindings.values().find(|b| {
            b.name == var_name && b.span.file_id == file.id && b.span.byte_offset < offset
        })?;

    let expression = find_expression_at(&file.items, binding.span.byte_offset)?;
    let borrowed_ty = match expression {
        Expression::Let {
            binding: let_binding,
            ..
        } => {
            let matches_name = match &let_binding.pattern {
                syntax::ast::Pattern::Identifier { identifier, .. } => identifier == var_name,
                syntax::ast::Pattern::AsBinding { name, .. } => name == var_name,
                _ => false,
            };
            if matches_name {
                Some(&let_binding.ty)
            } else {
                None
            }
        }
        Expression::Identifier { ty, .. } => Some(ty),
        Expression::For {
            binding: for_binding,
            ..
        } => Some(&for_binding.ty),
        Expression::Function { params, .. } | Expression::Lambda { params, .. } => {
            let param = params.iter().find(|p| match &p.pattern {
                syntax::ast::Pattern::Identifier { identifier, .. } => identifier == var_name,
                syntax::ast::Pattern::AsBinding { name, .. } => name == var_name,
                _ => false,
            })?;
            Some(&param.ty)
        }
        _ => None,
    };

    let owned_ty;
    let ty = if let Some(t) = borrowed_ty {
        t
    } else {
        let (t, _) = crate::hover::get_hover_type_and_span(expression, binding.span.byte_offset);
        owned_ty = t;
        &owned_ty
    };

    let (resolved, _) = syntax::types::Type::remove_vars(&[ty]);
    let ty = &resolved[0];

    if indexed {
        element_type_name(ty)
    } else {
        type_name(ty)
    }
}

pub(crate) enum DotContext {
    Instance(String),
    TypeLevel(String),
}

pub(crate) fn detect_dot_context(
    file: &syntax::program::File,
    offset: u32,
    snapshot: &AnalysisSnapshot,
) -> Option<DotContext> {
    let Expression::DotAccess {
        expression, member, ..
    } = find_expression_at(&file.items, offset.saturating_sub(1))?
    else {
        return None;
    };
    if !member.is_empty() {
        if !matches!(
            get_root_expression(expression),
            Expression::Identifier {
                binding_id: None,
                ..
            }
        ) {
            let ty = expression.get_type();
            return type_name(&ty).map(DotContext::Instance);
        }
        return None;
    }

    if let Expression::Identifier { value, .. } = expression.as_ref() {
        for prefix in [file.module_id.as_str(), "prelude"] {
            let qualified = format!("{prefix}.{value}");
            if let Some(definition) = snapshot.definitions().get(qualified.as_str())
                && definition.is_type_definition()
            {
                return Some(DotContext::TypeLevel(qualified));
            }
        }
    }

    let ty = expression.get_type();
    if let Some(type_id) = type_name(&ty) {
        return Some(DotContext::Instance(type_id));
    }

    if let Expression::Identifier { value, .. } = expression.as_ref()
        && value == "self"
        && let Some(impl_type) = find_enclosing_impl_type(&file.items, offset)
    {
        let qualified = format!("{}.{}", file.module_id, impl_type);
        return Some(DotContext::Instance(qualified));
    }

    None
}

pub(crate) fn get_instance_completions(
    type_id: &str,
    snapshot: &AnalysisSnapshot,
    same_module: bool,
) -> Vec<CompletionItem> {
    let mut items = Vec::new();

    if let Some(syntax::program::Definition {
        body: syntax::program::DefinitionBody::Interface { definition },
        ..
    }) = snapshot.definitions().get(type_id)
    {
        let mut visited = FxHashSet::default();
        let mut seen = FxHashSet::default();
        collect_interface_methods(
            type_id,
            definition,
            snapshot,
            &mut visited,
            &mut seen,
            &mut items,
        );
        return items;
    }

    if let Some(syntax::program::Definition {
        body: syntax::program::DefinitionBody::Struct { fields, .. },
        ..
    }) = snapshot.definitions().get(type_id)
    {
        for field in fields {
            if same_module || field.visibility.is_public() {
                items.push(CompletionItem {
                    label: field.name.to_string(),
                    kind: Some(CompletionItemKind::FIELD),
                    detail: Some(field.ty.to_string()),
                    ..Default::default()
                });
            }
        }
    }

    let method_prefix = format!("{type_id}.");
    for (qname, definition) in snapshot.definitions().iter() {
        if let Some(method_name) = qname.strip_prefix(method_prefix.as_str())
            && !method_name.contains('.')
            && matches!(
                definition.body,
                syntax::program::DefinitionBody::Value { .. }
            )
            && is_instance_method(definition.ty(), type_id)
            && (same_module || definition.visibility().is_public())
        {
            items.push(CompletionItem {
                label: method_name.to_string(),
                kind: Some(CompletionItemKind::METHOD),
                detail: Some(definition.ty().to_string()),
                ..Default::default()
            });
        }
    }

    items
}

fn collect_interface_methods(
    interface_id: &str,
    interface: &syntax::program::Interface,
    snapshot: &AnalysisSnapshot,
    visited: &mut FxHashSet<String>,
    seen: &mut FxHashSet<String>,
    items: &mut Vec<CompletionItem>,
) {
    if !visited.insert(interface_id.to_string()) {
        return;
    }
    for (name, method_ty) in &interface.methods {
        if seen.insert(name.to_string()) {
            items.push(CompletionItem {
                label: name.to_string(),
                kind: Some(CompletionItemKind::METHOD),
                detail: Some(method_ty.to_string()),
                ..Default::default()
            });
        }
    }
    for parent in &interface.parents {
        if let Some(parent_id) = parent.get_qualified_id()
            && let Some(syntax::program::Definition {
                body: syntax::program::DefinitionBody::Interface { definition },
                ..
            }) = snapshot.definitions().get(parent_id)
        {
            collect_interface_methods(parent_id, definition, snapshot, visited, seen, items);
        }
    }
}

pub(crate) fn get_type_completions(
    type_id: &str,
    snapshot: &AnalysisSnapshot,
    current_module: &str,
) -> Vec<CompletionItem> {
    let target = alias_target(type_id, snapshot);
    let method_id = target.as_deref().unwrap_or(type_id);

    let mut items = enum_variant_items(method_id, snapshot).unwrap_or_default();

    let same_module = id_is_in_module(method_id, current_module);
    let method_prefix = format!("{method_id}.");
    for (qname, definition) in snapshot.definitions().iter() {
        if let Some(method_name) = qname.strip_prefix(method_prefix.as_str())
            && !method_name.contains('.')
            && matches!(definition.body, DefinitionBody::Value { .. })
            && !is_instance_method(definition.ty(), method_id)
            && (same_module || definition.visibility().is_public())
            && !items.iter().any(|i| i.label == method_name)
        {
            items.push(CompletionItem {
                label: method_name.to_string(),
                kind: Some(CompletionItemKind::METHOD),
                detail: Some(definition.ty().to_string()),
                ..Default::default()
            });
        }
    }

    items
}

fn id_is_in_module(qualified_id: &str, module: &str) -> bool {
    qualified_id.starts_with(module) && qualified_id.as_bytes().get(module.len()) == Some(&b'.')
}

fn enum_variant_items(type_id: &str, snapshot: &AnalysisSnapshot) -> Option<Vec<CompletionItem>> {
    let to_item = |name: &str| CompletionItem {
        label: name.to_string(),
        kind: Some(CompletionItemKind::ENUM_MEMBER),
        ..Default::default()
    };
    match &snapshot.definitions().get(type_id)?.body {
        DefinitionBody::Enum { variants, .. } => {
            Some(variants.iter().map(|v| to_item(&v.name)).collect())
        }
        DefinitionBody::ValueEnum { variants, .. } => {
            Some(variants.iter().map(|v| to_item(&v.name)).collect())
        }
        _ => None,
    }
}

/// Returns the target id of a non-generic alias.
fn alias_target(type_id: &str, snapshot: &AnalysisSnapshot) -> Option<String> {
    let def = snapshot.definitions().get(type_id)?;
    let DefinitionBody::TypeAlias { generics, .. } = &def.body else {
        return None;
    };
    if !generics.is_empty() {
        return None;
    }
    let target = match &def.ty {
        Type::Nominal { id, .. } => id.to_string(),
        Type::Simple(kind) => format!("prelude.{}", kind.leaf_name()),
        Type::Compound { kind, .. } => format!("prelude.{}", kind.leaf_name()),
        _ => return None,
    };
    (target != type_id).then_some(target)
}

fn is_instance_method(ty: &syntax::types::Type, type_id: &str) -> bool {
    let func_ty = match ty {
        syntax::types::Type::Forall { body, .. } => body,
        other => other,
    };
    match func_ty {
        syntax::types::Type::Function(f) if !f.params.is_empty() => {
            type_name(&f.params[0]).is_some_and(|name| name == type_id)
        }
        _ => false,
    }
}
