use std::cmp::Ordering;
use std::collections::{BinaryHeap, binary_heap};

use compact_str::CompactString;
use ruff_db::files::File;
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
use ruff_db::source::{SourceText, source_text};
use ruff_diagnostics::Edit;
use ruff_python_ast::find_node::{CoveringNode, covering_node};
use ruff_python_ast::name::{Name, UnqualifiedName};
use ruff_python_ast::str::Quote;
use ruff_python_ast::token::{Token, TokenKind, Tokens};
use ruff_python_ast::{self as ast, AnyNodeRef};
use ruff_python_codegen::Stylist;
use ruff_python_literal::escape::{Escape, UnicodeEscape};
use ruff_text_size::{Ranged, TextRange, TextSize};
use rustc_hash::FxHashSet;
use ty_module_resolver::{KnownModule, Module, ModuleName};
use ty_python_semantic::HasType;
use ty_python_semantic::types::{SpecialFormType, UnionType};
use ty_python_semantic::{
    Completion as SemanticCompletion, NameKind, SemanticModel,
    types::{CycleDetector, KnownClass, Type},
};

use crate::docstring::Docstring;
use crate::goto::Definitions;
use crate::importer::{ImportRequest, Importer};
use crate::symbols::QueryPattern;
use crate::{Db, all_symbols, signature_help};

pub fn completion<'db>(
    db: &'db dyn Db,
    settings: &CompletionSettings,
    capabilities: CompletionCapabilities,
    file: File,
    offset: TextSize,
) -> Vec<Completion<'db>> {
    let parsed = parsed_module(db, file).load(db);
    let source = source_text(db, file);

    let Some(context) = Context::new(db, file, &parsed, &source, offset) else {
        return vec![];
    };
    let model = SemanticModel::new(db, file);

    if !matches!(context.kind, ContextKind::Keywords(_)) && context.cursor.is_in_string() {
        let Some(string_expr) = context.cursor.enclosing_string_literal_expr() else {
            return vec![];
        };

        let mut completions =
            Completions::new(db, CollectionContext::none(), UserQuery::fuzzy(None));

        add_string_literal_completions(
            &model,
            string_expr,
            context.cursor.string_quote_style(),
            &mut completions,
        );

        return completions.into_completions();
    }

    let query = UserQuery::fuzzy(context.cursor.typed);
    let mut completions = Completions::new(
        db,
        context.collection_context(db, &model, settings, capabilities),
        query,
    );
    match context.kind {
        ContextKind::Keywords(keywords) => {
            for &keyword in keywords {
                completions
                    .add(CompletionBuilder::keyword(keyword.as_str()).context_specific(true));
            }
        }
        ContextKind::Import(ref import) => {
            import.add_completions(db, file, &mut completions);
        }
        ContextKind::NonImport(ref non_import) => match non_import.target {
            CompletionTargetAst::ObjectDot { expr } => {
                completions.extend(model.attribute_completions(expr));
            }
            CompletionTargetAst::Scoped(scoped) => {
                for semantic_completion in model.scoped_completions(scoped.node) {
                    let module_dependency_kind = if semantic_completion.builtin {
                        ModuleDependencyKind::Builtin
                    } else {
                        ModuleDependencyKind::Current
                    };
                    completions.add(
                        CompletionBuilder::from_semantic_completion(db, semantic_completion)
                            .module_dependency_kind(module_dependency_kind),
                    );
                }
                add_keyword_completions(db, &mut completions);
                add_argument_completions(db, &model, &context.cursor, &mut completions);
                if settings.auto_import {
                    add_unimported_completions(
                        db,
                        file,
                        &parsed,
                        scoped,
                        |module_name: &ModuleName, symbol: &str| {
                            ImportRequest::import_from(module_name.as_str(), symbol)
                        },
                        &mut completions,
                    );
                }
            }
        },
    }

    completions.into_completions()
}

#[derive(Copy, Clone, Debug, Default)]
pub struct CompletionCapabilities {
    /// Whether the client supports snippets.
    snippets: bool,
}

impl CompletionCapabilities {
    #[must_use]
    pub fn snippets(self, snippets: bool) -> Self {
        Self { snippets }
    }
}

/// A collection of completions built up from various sources.
struct Completions<'db> {
    db: &'db dyn Db,
    context: CollectionContext<'db>,
    items: BinaryHeap<CompletionRanker<'db>>,
    /// The query used to match against candidate completions.
    ///
    /// If a completion's name doesn't match this query, then
    /// it isn't included in the collection.
    query: UserQuery,
}

impl<'db> Completions<'db> {
    /// A limit on the total number of completions we'll return.
    ///
    /// A user should refine its completion request if the searched symbol
    /// doesn't appear in the first 1k results. Serializing/deserializing 1k
    /// completions can be expensive and result in noticeable lag.
    const LIMIT: usize = 1_000;

    /// Create a new empty collection of completions.
    ///
    /// The given typed text should correspond to what we believe
    /// the user has typed as part of the next symbol they are writing.
    /// This collection will treat it as a query when present, and only
    /// add completions that match it.
    fn new(db: &'db dyn Db, context: CollectionContext<'db>, query: UserQuery) -> Completions<'db> {
        Completions {
            db,
            context,
            items: BinaryHeap::new(),
            query,
        }
    }

    /// Convert this collection into a simple
    /// sequence of completions.
    fn into_completions(self) -> Vec<Completion<'db>> {
        self.items
            .into_sorted_vec()
            .into_iter()
            .map(|CompletionRanker(c)| c)
            .collect()
    }

    // Convert this collection into a list of "import..." fixes
    fn into_imports(self) -> Vec<ImportEdit> {
        self.items
            .into_sorted_vec()
            .into_iter()
            .map(|CompletionRanker(c)| c)
            .filter_map(|item| {
                Some(ImportEdit {
                    label: format!("import {}", item.qualified?),
                    edit: item.import?,
                })
            })
            .collect()
    }

    // Convert this collection into a list of "qualify..." fixes
    fn into_qualifications(self, range: TextRange) -> Vec<ImportEdit> {
        self.items
            .into_sorted_vec()
            .into_iter()
            .map(|CompletionRanker(c)| c)
            .filter_map(|item| {
                // If we would have to actually import something, don't suggest the qualification
                // (we could, maybe we should, but for now, we don't)
                if item.import.is_some() {
                    return None;
                }

                Some(ImportEdit {
                    label: format!("qualify {}", item.insert.as_ref()?),
                    edit: Edit::replacement(item.insert?.to_string(), range.start(), range.end()),
                })
            })
            .collect()
    }

    /// Attempts to adds the given completion to this collection.
    ///
    /// When added, `true` is returned.
    ///
    /// This might not add the completion for a variety of reasons.
    /// For example, if the symbol name does not match this collection's
    /// query.
    fn add(&mut self, builder: CompletionBuilder<'db>) -> bool {
        if !self.query.is_match(builder.name.as_str()) {
            return false;
        }
        self.add_skip_query(builder)
    }

    /// Attempts to add the given semantic completion to this collection.
    ///
    /// When added, `true` is returned.
    fn add_semantic(&mut self, completion: SemanticCompletion<'db>) -> bool {
        self.add(CompletionBuilder::from_semantic_completion(
            self.db, completion,
        ))
    }

    /// Attempts to add the given completion to this collection.
    ///
    /// Unlike `Completion::add`, this will skip matching the query
    /// pattern associated with this collection of completions. This
    /// is useful, for example, when the completions have been filtered
    /// by the query pattern already.
    ///
    /// This may still choose not to add the completion. For example,
    /// when the completion context determines that the given suggestion
    /// is never valid.
    fn add_skip_query(&mut self, builder: CompletionBuilder<'db>) -> bool {
        if self.context.exclude(self.db, &builder) {
            return false;
        }
        let completion = CompletionRanker(builder.build(self.db, &self.context, &self.query));
        if self.items.len() >= Completions::LIMIT {
            // OK because `self.items` is guaranteed to be non-empty here.
            let worst = self.items.peek_mut().unwrap();
            if *worst <= completion {
                return false;
            }
            binary_heap::PeekMut::pop(worst);
        }
        self.items.push(completion);
        true
    }
}

impl<'db> Extend<SemanticCompletion<'db>> for Completions<'db> {
    fn extend<T>(&mut self, it: T)
    where
        T: IntoIterator<Item = SemanticCompletion<'db>>,
    {
        for c in it {
            self.add_semantic(c);
        }
    }
}

impl<'db> Extend<CompletionBuilder<'db>> for Completions<'db> {
    fn extend<T>(&mut self, it: T)
    where
        T: IntoIterator<Item = CompletionBuilder<'db>>,
    {
        for builder in it {
            self.add(builder);
        }
    }
}

#[derive(Clone, Debug)]
pub struct Completion<'db> {
    /// The name used when matching the query and ranking this suggestion.
    pub name: CompactString,
    /// The label shown to the user for this suggestion.
    pub label: CompactString,
    /// The fully qualified name, when available.
    ///
    /// This is only set when `module_name` is available.
    pub qualified: Option<CompactString>,
    /// The text that should be inserted at the cursor
    /// when the completion is selected.
    ///
    /// When this is not set, [`Self::label`] is used.
    pub insert: Option<CompactString>,
    /// The format of [`Self::insert`].
    pub insert_text_format: CompletionInsertTextFormat,
    /// The type of this completion, if available.
    ///
    /// Generally speaking, this is always available
    /// *unless* this was a completion corresponding to
    /// an unimported symbol. In that case, computing the
    /// type of all such symbols could be quite expensive.
    pub ty: Option<Type<'db>>,
    /// The "kind" of this completion.
    ///
    /// Usually this is set when `ty` is `None`, since it
    /// may be cheaper to compute at scale (e.g., for
    /// unimported symbol completions).
    pub kind: Option<CompletionKind>,
    /// The name of the module that this completion comes from.
    ///
    /// This is generally only present when this is a completion
    /// suggestion for an unimported symbol.
    pub module_name: Option<&'db ModuleName>,
    /// The broad origin of the module that this completion comes from,
    /// when available.
    ///
    /// This is used to rank completions, but doesn't appear in the LSP
    /// response.
    module_dependency_kind: Option<ModuleDependencyKind>,
    /// An import statement to insert (or ensure is already
    /// present) when this completion is selected.
    pub import: Option<Edit>,
    /// Whether this suggestion came from builtins or not.
    ///
    /// At time of writing (2025-06-26), this information
    /// doesn't make it into the LSP response. Instead, we
    /// use it mainly in tests so that we can write less
    /// noisy tests.
    pub builtin: bool,
    /// Whether this item only exists for type checking purposes and
    /// will be missing at runtime
    pub is_type_check_only: bool,
    /// Whether this item can definitively be used in the current context.
    ///
    /// Some completions are computed based on contextual information.
    /// If that's the case, we know this is a very precise completion
    /// that should always be valid and can be preferred when
    /// ordering completions.
    pub is_context_specific: bool,
    /// The documentation associated with this item, if
    /// available.
    pub documentation: Option<Docstring>,
    /// Information used to sort this completion relative to others
    /// in the same collection.
    relevance: Relevance,
}

impl<'db> Completion<'db> {
    fn builder(name: impl Into<CompactString>) -> CompletionBuilder<'db> {
        CompletionBuilder::new(name)
    }
}

/// A builder for construction a `Completion`.
#[derive(Debug)]
#[expect(clippy::struct_excessive_bools)]
struct CompletionBuilder<'db> {
    // See comments on `Completion` for the meaning of fields.
    name: CompactString,
    qualified: Option<CompactString>,
    insert: Option<CompactString>,
    ty: Option<Type<'db>>,
    kind: Option<CompletionKind>,
    module_name: Option<&'db ModuleName>,
    import: Option<Edit>,
    builtin: bool,
    is_context_specific: bool,
    is_type_check_only: bool,
    documentation: Option<Docstring>,
    module_dependency_kind: Option<ModuleDependencyKind>,
    deprecated: bool,
}

impl<'db> CompletionBuilder<'db> {
    /// Start building a new completion with the given name.
    ///
    /// All other values given to the completion by default are
    /// valid, but callers will generally want to fill in as much
    /// as is appropriate.
    fn new(name: impl Into<CompactString>) -> CompletionBuilder<'db> {
        CompletionBuilder {
            name: name.into(),
            qualified: None,
            insert: None,
            ty: None,
            kind: None,
            module_name: None,
            import: None,
            builtin: false,
            is_context_specific: false,
            is_type_check_only: false,
            documentation: None,
            module_dependency_kind: None,
            deprecated: false,
        }
    }

    fn from_semantic_completion(
        db: &'db dyn Db,
        semantic: SemanticCompletion<'db>,
    ) -> CompletionBuilder<'db> {
        let definition = semantic.ty.and_then(|ty| Definitions::from_ty(db, ty));
        let documentation = definition.and_then(|def| def.docstring(db));
        Completion::builder(semantic.name)
            .ty(semantic.ty)
            .builtin(semantic.builtin)
            .docstring(documentation)
    }

    /// A convenience constructor for a "keyword" completion.
    ///
    /// This is just like `CompletionBuilder::new`, but sets the kind
    /// to "keyword."
    fn keyword(name: impl Into<CompactString>) -> CompletionBuilder<'db> {
        Completion::builder(name).kind(CompletionKind::Keyword)
    }

    /// A convenience constructor for an "argument" completion.
    ///
    /// This is used for either class or function based arguments.
    fn argument(name: impl Into<Name>) -> CompletionBuilder<'db> {
        let name = name.into();
        let insert = compact_str::format_compact!("{name}=");
        Completion::builder(name)
            .kind(CompletionKind::Variable)
            .insert(insert)
            .context_specific(true)
    }

    /// Use this builder to construct a `Completion`.
    ///
    /// `ctx` is any information about the position of the
    /// cursor in the source code that could impact the relevance
    /// ranking of the completion.
    ///
    /// `query` is the pattern that the completion must match in
    /// order to be suggested as a candidate.
    fn build(
        mut self,
        db: &'db dyn Db,
        ctx: &CollectionContext<'db>,
        query: &UserQuery,
    ) -> Completion<'db> {
        if let Some(ty) = self.ty {
            self.is_type_check_only = ty.is_type_check_only(db);
            // Tags completions with context-specific if they are
            // known to be usable in an exception context and we have
            // determined an `exception_ty`.
            //
            // It's possible that some completions are usable in an exception
            // but aren't marked here. That is, false negatives are
            // possible but false positives are not.
            if let Some(exception_ty) = ctx.exception_ty {
                self.is_context_specific |= ty.is_assignable_to(db, exception_ty);
            }
            if ctx.is_in_class_def() {
                self.is_context_specific |= ty.is_class_literal()
                    || matches!(
                        ty,
                        Type::SpecialForm(
                            SpecialFormType::Protocol
                                | SpecialFormType::Generic
                                | SpecialFormType::TypedDict(_)
                                | SpecialFormType::NamedTuple
                        )
                    );
            }

            self.deprecated = ty.is_deprecated(db);
        }
        let kind = self
            .kind
            .or_else(|| self.ty.and_then(|ty| completion_kind_from_type(db, ty)));
        let relevance = Relevance::new(ctx, query, &self);
        let label = self.insert.as_ref().unwrap_or(&self.name).clone();
        let (insert, insert_text_format) = if ctx.should_complete_callable_parentheses(kind) {
            if ctx.capabilities.snippets {
                let insert = compact_str::format_compact!("{label}($0)");
                (Some(insert), CompletionInsertTextFormat::Snippet)
            } else {
                let insert = compact_str::format_compact!("{label}()");
                (Some(insert), CompletionInsertTextFormat::PlainText)
            }
        } else {
            (self.insert, CompletionInsertTextFormat::PlainText)
        };
        Completion {
            name: self.name,
            label,
            qualified: self.qualified,
            insert,
            insert_text_format,
            ty: self.ty,
            kind,
            module_name: self.module_name,
            module_dependency_kind: self.module_dependency_kind,
            import: self.import,
            builtin: self.builtin,
            is_type_check_only: self.is_type_check_only,
            is_context_specific: self.is_context_specific,
            documentation: self.documentation,
            relevance,
        }
    }

    fn qualified(mut self, qualified: impl Into<CompactString>) -> CompletionBuilder<'db> {
        self.qualified = Some(qualified.into());
        self
    }

    fn insert(mut self, insert: impl Into<CompactString>) -> CompletionBuilder<'db> {
        self.insert = Some(insert.into());
        self
    }

    fn ty(mut self, ty: impl Into<Option<Type<'db>>>) -> CompletionBuilder<'db> {
        self.ty = ty.into();
        self
    }

    fn kind(mut self, kind: impl Into<Option<CompletionKind>>) -> CompletionBuilder<'db> {
        self.kind = kind.into();
        self
    }

    fn module_name(mut self, name: &'db ModuleName) -> CompletionBuilder<'db> {
        self.module_name = Some(name);
        self
    }

    fn import(mut self, edit: impl Into<Option<Edit>>) -> CompletionBuilder<'db> {
        self.import = edit.into();
        self
    }

    fn deprecated(mut self, deprecated: bool) -> CompletionBuilder<'db> {
        self.deprecated = deprecated;
        self
    }

    fn builtin(mut self, yes: bool) -> CompletionBuilder<'db> {
        self.builtin = yes;
        self
    }

    fn context_specific(mut self, yes: bool) -> CompletionBuilder<'db> {
        self.is_context_specific = yes;
        self
    }

    fn documentation(self, docs: impl Into<String>) -> CompletionBuilder<'db> {
        self.docstring(Docstring::new(docs.into()))
    }

    fn docstring(mut self, docs: impl Into<Option<Docstring>>) -> CompletionBuilder<'db> {
        self.documentation = docs.into();
        self
    }

    fn module_dependency_kind(mut self, kind: ModuleDependencyKind) -> CompletionBuilder<'db> {
        self.module_dependency_kind = Some(kind);
        self
    }

    /// Returns true when this completion refers to the
    /// `NotImplemented` builtin.
    fn is_notimplemented(&self, db: &dyn Db) -> bool {
        let Some(ty) = self.ty else { return false };
        ty.is_notimplemented(db)
    }
}

/// The "kind" of a completion.
///
/// This is taken directly from the LSP completion specification:
/// <https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionItemKind>
///
/// The idea here is that [`Completion::kind`] defines the mapping to this from
/// `Type` (and possibly other information), which might be interesting and
/// contentious. Then the outer edges map this to the LSP types, which is
/// expected to be mundane and boring.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CompletionKind {
    Text,
    Method,
    Function,
    Constructor,
    Field,
    Variable,
    Class,
    Interface,
    Module,
    Property,
    Unit,
    Value,
    Enum,
    Keyword,
    Snippet,
    Color,
    File,
    Reference,
    Folder,
    EnumMember,
    Constant,
    Struct,
    Event,
    Operator,
    TypeParameter,
}

/// The format of a completion's insertion text.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum CompletionInsertTextFormat {
    /// Plain text to insert as-is.
    #[default]
    PlainText,
    /// [Snippet syntax] text.
    ///
    /// [Snippet syntax]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#snippet_syntax
    Snippet,
}

#[derive(Clone, Debug)]
pub struct CompletionSettings {
    pub auto_import: bool,
    /// Whether callable completions should insert parentheses.
    pub complete_function_parentheses: bool,
}

// N.B. It's important for the defaults here to match the defaults
// established by `CompletionOptions::into_settings`. This is
// because `WorkspaceSettings::default()` uses this definition.
// But `WorkspaceOptions::default().into_settings()` will use the
// `CompletionOptions::into_settings` definition.
impl Default for CompletionSettings {
    fn default() -> CompletionSettings {
        CompletionSettings {
            auto_import: true,
            complete_function_parentheses: false,
        }
    }
}

/// The completion context.
///
/// This context is used to determine how to find the
/// initial set of completions to offer to a user.
///
/// The lifetime parameter `'m` refers to the parsed module containing
/// the cursor.
struct Context<'m> {
    kind: ContextKind<'m>,
    cursor: ContextCursor<'m>,
}

#[derive(Debug)]
enum ContextKind<'m> {
    Keywords(&'static [ContextualKeyword]),
    Import(ImportStatement<'m>),
    NonImport(ContextNonImport<'m>),
}

/// A Python keyword offered only when it is valid in the incomplete syntax at the cursor.
///
/// ```python
/// for item <CURSOR>          # `in`
/// with resource <CURSOR>     # `as`
/// match value:
///     case pattern <CURSOR>  # `as` or `if`
/// ```
#[derive(Clone, Copy, Debug)]
enum ContextualKeyword {
    As,
    If,
    In,
}

impl ContextualKeyword {
    const fn as_str(self) -> &'static str {
        match self {
            ContextualKeyword::As => "as",
            ContextualKeyword::If => "if",
            ContextualKeyword::In => "in",
        }
    }

    const fn token_kind(self) -> TokenKind {
        match self {
            ContextualKeyword::As => TokenKind::As,
            ContextualKeyword::If => TokenKind::If,
            ContextualKeyword::In => TokenKind::In,
        }
    }
}

/// Context for non-import completions.
#[derive(Debug)]
struct ContextNonImport<'m> {
    /// The AST of the completion target.
    target: CompletionTargetAst<'m>,
}

impl<'m> Context<'m> {
    /// Create a new context for finding completions.
    fn new(
        db: &'_ dyn Db,
        file: File,
        parsed: &'m ParsedModuleRef,
        source: &'m SourceText,
        offset: TextSize,
    ) -> Option<Context<'m>> {
        let cursor = ContextCursor::new(parsed, source, offset);
        if cursor.is_in_comment() {
            return None;
        }

        let kind = if let Some(keywords) = cursor.incomplete_keywords() {
            ContextKind::Keywords(keywords)
        } else if cursor.is_in_definition_place() {
            return None;
        } else if let Some(import) = ImportStatement::detect(db, file, &cursor) {
            ContextKind::Import(import)
        } else {
            let target_token = CompletionTargetTokens::find(&cursor)?;
            let target = target_token.ast(&cursor)?;
            ContextKind::NonImport(ContextNonImport { target })
        };

        Some(Context { kind, cursor })
    }

    /// Returns a filtering context for use with a completion collector.
    fn collection_context<'db>(
        &self,
        db: &'db dyn Db,
        model: &SemanticModel<'db>,
        settings: &CompletionSettings,
        capabilities: CompletionCapabilities,
    ) -> CollectionContext<'db> {
        match self.kind {
            ContextKind::Keywords(_) | ContextKind::Import(_) => CollectionContext::none(),
            ContextKind::NonImport(_) => {
                let exception_ty = self.cursor.exception_ty(db);
                let complete_callable_parentheses = settings.complete_function_parentheses
                    && !self.cursor.suppress_callable_parentheses();
                let existing_class_bases = self.cursor.enclosing_class_def().map(|class_def| {
                    let mut bases = extract_base_class_names(class_def);
                    // Exclude the class being defined from its own base class
                    // completions, unless the name was previously bound in the
                    // same scope (in which case the bases refer to the prior
                    // definition).
                    if !model.is_class_name_reassigned(class_def) {
                        bases.insert(CompactString::new(class_def.name.as_str()));
                    }
                    bases
                });
                CollectionContext {
                    exception_ty,
                    is_raising_exception: exception_ty.is_some(),
                    complete_class_parentheses: complete_callable_parentheses
                        && existing_class_bases.is_none()
                        && !self.cursor.suppress_class_parentheses(model),
                    existing_class_bases,
                    valid_keywords: self.cursor.valid_keywords(),
                    complete_function_and_method_parentheses: complete_callable_parentheses,
                    capabilities,
                }
            }
        }
    }
}

/// Extracts information about the cursor position.
///
/// This includes text that was typed and the cursor's
/// byte offset in the source code.
///
/// The lifetime parameter `'m` refers to the shorter of the following
/// lifetimes: the parsed module the cursor is in and the actual bytes
/// making up the source file containing the cursor.
struct ContextCursor<'m> {
    /// The parsed module containing the cursor.
    parsed: &'m ParsedModuleRef,
    /// The source code of the module containing the cursor.
    source: &'m SourceText,
    /// The typed text up to the cursor offset.
    ///
    /// When `Some`, the text is guaranteed to be non-empty.
    typed: Option<&'m str>,
    /// The byte offset of the cursor.
    offset: TextSize,
    /// The byte range of the typed text when non-empty.
    /// When empty, this is just an empty range at the
    /// position of the cursor.
    range: TextRange,
    /// The tokens that appear before the cursor.
    tokens_before: &'m [Token],
    /// The covering node based on `parsed` and `range`.
    covering_node: CoveringNode<'m>,
}

/// The cursor position relative to an AST range and its surrounding parentheses.
enum RangeEndPosition {
    /// The cursor follows the complete range, including any surrounding parentheses.
    After,
    /// The cursor follows the AST range but precedes a surrounding closing parenthesis.
    InsideParentheses,
}

impl RangeEndPosition {
    /// Selects the keywords valid at this position.
    ///
    /// `after` applies after the complete, possibly parenthesized range, while
    /// `inside_parentheses` applies before its closing parenthesis. For example:
    ///
    /// ```python
    /// case (400 <CURSOR>)  # `as`
    /// case (400) <CURSOR>  # `as` or `if`
    /// ```
    fn select_keywords(
        self,
        after: &'static [ContextualKeyword],
        inside_parentheses: &'static [ContextualKeyword],
    ) -> &'static [ContextualKeyword] {
        match self {
            RangeEndPosition::After => after,
            RangeEndPosition::InsideParentheses => inside_parentheses,
        }
    }
}

impl<'m> ContextCursor<'m> {
    /// Returns information about the context of the cursor.
    fn new(
        parsed: &'m ParsedModuleRef,
        source: &'m SourceText,
        offset: TextSize,
    ) -> ContextCursor<'m> {
        let tokens_before = tokens_start_before(parsed.tokens(), offset);
        let Some(range) = ContextCursor::find_typed_text_range(tokens_before, offset) else {
            let range = TextRange::empty(offset);
            let covering_node = covering_node(parsed.syntax().into(), range);
            return ContextCursor {
                parsed,
                source,
                typed: None,
                offset,
                range,
                tokens_before,
                covering_node,
            };
        };

        let text = &source[range];
        assert!(
            !text.is_empty(),
            "expected typed text, when found, to be non-empty"
        );

        let covering_node = covering_node(parsed.syntax().into(), range);
        ContextCursor {
            parsed,
            source,
            typed: Some(text),
            offset,
            range,
            tokens_before,
            covering_node,
        }
    }

    /// Looks for the byte range of the text typed immediately before
    /// the cursor offset given. `tokens_before` should be the tokens
    /// from the start of the file up until `offset`.
    ///
    /// If there isn't any typed text or it could not otherwise be
    /// found, then `None` is returned.
    ///
    /// When `Some` is returned, the range guaranteed to be non-empty.
    fn find_typed_text_range(tokens_before: &[Token], offset: TextSize) -> Option<TextRange> {
        let last = tokens_before.last()?;
        // It's odd to include `TokenKind::Import` here (among other
        // keywords), but it indicates that the user has typed
        // `import`. This is useful to know in some contexts. And this
        // applies also to the other keywords.
        if !is_name_like_token(last) {
            return None;
        }
        // This one's weird, but if the cursor is beyond
        // what is in the closest `Name` token, then it's
        // likely we can't infer anything about what has
        // been typed. This likely means there is whitespace
        // or something that isn't represented in the token
        // stream. So just give up.
        if last.end() < offset || last.range().is_empty() {
            return None;
        }
        Some(TextRange::new(last.start(), offset))
    }

    /// Convenience method for `covering_node(cursor.parsed.syntax().into(), ...)`.
    fn covering_node(&self, range: TextRange) -> CoveringNode<'m> {
        covering_node(self.parsed.syntax().into(), range)
    }

    /// Whether the last token is within a comment or not.
    fn is_in_comment(&self) -> bool {
        self.tokens_before
            .last()
            .is_some_and(|t| t.kind().is_comment())
    }

    /// Whether the last token is positioned within a string token (regular, f-string, t-string, etc).
    ///
    /// Note that this will return `false` when the last token is positioned within an
    /// interpolation block in an f-string or a t-string.
    fn is_in_string(&self) -> bool {
        self.tokens_before.last().is_some_and(|t| {
            matches!(
                t.kind(),
                TokenKind::String | TokenKind::FStringMiddle | TokenKind::TStringMiddle
            )
        })
    }

    /// Returns the string literal expression that the cursor is positioned within, if any.
    fn enclosing_string_literal_expr(&self) -> Option<&'m ast::ExprStringLiteral> {
        match self.covering_node.parent() {
            Some(ast::AnyNodeRef::ExprStringLiteral(string_expr)) => Some(string_expr),
            _ => None,
        }
    }

    /// Returns the quote style of the string literal that the cursor is positioned within, if any.
    fn string_quote_style(&self) -> Option<Quote> {
        self.tokens_before
            .last()
            .map(|token| token.string_quote_style())
    }

    fn suppress_callable_parentheses(&self) -> bool {
        self.next_token_is_open_parenthesis() || self.is_in_decorator_name()
    }

    fn next_token_is_open_parenthesis(&self) -> bool {
        self.parsed
            .tokens()
            .at_offset(self.offset)
            .last()
            .is_some_and(|token| token.kind() == TokenKind::Lpar)
    }

    fn is_in_decorator_name(&self) -> bool {
        self.covering_node
            .ancestors()
            // We bail if we're specifying arguments as we don't want
            // to suppress suggestions there.
            .take_while(|node| {
                !matches!(node, ast::AnyNodeRef::Arguments(_)) && !node.is_statement()
            })
            .any(|node| matches!(node, ast::AnyNodeRef::Decorator(_)))
    }

    fn suppress_class_parentheses(&self, model: &SemanticModel<'_>) -> bool {
        let contains = |expr: &ast::Expr| expr.range().contains_range(self.range);

        self.covering_node.ancestors().any(|node| match node {
            ast::AnyNodeRef::StmtAnnAssign(stmt) => {
                contains(&stmt.annotation)
                    || (stmt.value.as_deref().is_some_and(contains)
                        && model.is_type_alias_annotation(&stmt.annotation))
            }
            ast::AnyNodeRef::StmtFunctionDef(stmt) => stmt.returns.as_deref().is_some_and(contains),
            ast::AnyNodeRef::StmtTypeAlias(stmt) => contains(&stmt.value),
            ast::AnyNodeRef::Parameter(param) => param.annotation.as_deref().is_some_and(contains),
            ast::AnyNodeRef::TypeParamTypeVar(type_param) => {
                type_param.bound.as_deref().is_some_and(contains)
                    || type_param.default.as_deref().is_some_and(contains)
            }
            ast::AnyNodeRef::TypeParamTypeVarTuple(type_param) => {
                type_param.default.as_deref().is_some_and(contains)
            }
            ast::AnyNodeRef::TypeParamParamSpec(type_param) => {
                type_param.default.as_deref().is_some_and(contains)
            }
            ast::AnyNodeRef::ExceptHandlerExceptHandler(handler) => {
                handler.type_.as_deref().is_some_and(contains)
            }
            _ => false,
        })
    }

    /// Returns true when the tokens indicate that the definition of a new
    /// name is being introduced at the end.
    fn is_in_definition_place(&self) -> bool {
        fn is_definition_token(token: &Token) -> bool {
            matches!(
                token.kind(),
                TokenKind::Def
                    | TokenKind::Class
                    | TokenKind::Type
                    | TokenKind::As
                    | TokenKind::For
            )
        }

        let is_definition_keyword = |token: &Token| {
            if is_definition_token(token) {
                true
            } else if token.kind() == TokenKind::Name {
                &self.source[token.range()] == "type"
            } else {
                false
            }
        };
        if match self.tokens_before {
            [.., penultimate, _] if self.typed.is_some() => is_definition_keyword(penultimate),
            [.., last] if self.typed.is_none() => is_definition_keyword(last),
            _ => false,
        } {
            return true;
        }
        // Analyze the AST if token matching is insufficient
        // to determine if we're inside a name definition.
        self.is_in_variable_binding()
    }

    /// Returns true when the cursor sits on a binding statement.
    /// E.g. naming a parameter, type parameter, or `for` `<name>`).
    fn is_in_variable_binding(&self) -> bool {
        self.covering_node.ancestors().any(|node| match node {
            ast::AnyNodeRef::Parameter(param) => param.name.range.contains_range(self.range),
            ast::AnyNodeRef::TypeParamTypeVar(type_param) => {
                type_param.name.range.contains_range(self.range)
            }
            ast::AnyNodeRef::StmtFor(stmt_for) => {
                stmt_for.target.range().contains_range(self.range)
            }
            // The AST does not produce `ast::AnyNodeRef::Parameter` nodes for keywords
            // or otherwise invalid syntax. Rather they are captured in a
            // `ast::AnyNodeRef::Parameters` node as "empty space". To ensure
            // we still suppress suggestions even when the syntax is technically
            // invalid we extract the token under the self and check if it makes
            // up that "empty space" inside the Parameters Node. If it does, we know
            // that we are still binding variables, just that the current state is
            // syntactically invalid. Hence we suppress autocomplete suggestions
            // also in those cases.
            ast::AnyNodeRef::Parameters(params) => {
                if !params.range.contains_range(self.range) {
                    return false;
                }
                params
                    .iter()
                    .map(|param| param.range())
                    .all(|r| !r.contains_range(self.range))
            }
            _ => false,
        })
    }

    /// Returns the paired closing parentheses surrounding `range`, from innermost to outermost.
    fn closing_parentheses(
        &self,
        range: TextRange,
        parent: AnyNodeRef,
    ) -> impl Iterator<Item = &Token> {
        let right_parentheses = self
            .parsed
            .tokens()
            .in_range(TextRange::new(range.end(), parent.end()))
            .iter()
            .filter(|token| !token.kind().is_trivia())
            .take_while(|token| token.kind() == TokenKind::Rpar);
        let left_parentheses = self
            .parsed
            .tokens()
            .before(range.start())
            .iter()
            .rev()
            .filter(|token| !token.kind().is_trivia())
            .take_while(|token| token.kind() == TokenKind::Lpar);

        right_parentheses
            .zip(left_parentheses)
            .map(|(right, _)| right)
    }

    /// Returns the cursor position after `range`, accounting for surrounding parentheses.
    fn range_end_position(
        &self,
        range: TextRange,
        parent: AnyNodeRef,
        token: &Token,
    ) -> Option<RangeEndPosition> {
        let range_ends_at_token = range.contains_range(token.range()) && range.end() == token.end();
        let mut closing_parentheses = self.closing_parentheses(range, parent).peekable();
        let position = |inside_parentheses| {
            if inside_parentheses {
                RangeEndPosition::InsideParentheses
            } else {
                RangeEndPosition::After
            }
        };

        if range_ends_at_token {
            return Some(position(closing_parentheses.peek().is_some()));
        }

        while let Some(right) = closing_parentheses.next() {
            if right.range() == token.range() {
                return Some(position(closing_parentheses.peek().is_some()));
            }
        }

        None
    }

    /// Returns only the candidates that can precede an already present contextual keyword.
    ///
    /// `candidates` must be ordered as the keywords can appear in source.
    fn before_already_present(
        &self,
        candidates: &'static [ContextualKeyword],
    ) -> &'static [ContextualKeyword] {
        if self
            .tokens_before
            .last()
            .is_some_and(|token| token.end() > self.offset)
        {
            return candidates;
        }

        let Some(next) = self
            .parsed
            .tokens()
            .after(self.offset)
            .iter()
            .find(|token| !token.kind().is_trivia())
        else {
            return candidates;
        };
        candidates
            .iter()
            .position(|keyword| keyword.token_kind() == next.kind())
            .map_or(candidates, |position| &candidates[..position])
    }

    /// Returns keywords when the cursor follows syntax that can only be
    /// continued by those keywords.
    ///
    /// This first identifies keywords compatible with the typed prefix, then finds the
    /// complete token immediately before the cursor, and finally checks its enclosing syntax.
    fn incomplete_keywords(&self) -> Option<&'static [ContextualKeyword]> {
        const IN: &[ContextualKeyword] = std::slice::from_ref(&ContextualKeyword::In);
        const AS: &[ContextualKeyword] = std::slice::from_ref(&ContextualKeyword::As);
        const IF: &[ContextualKeyword] = std::slice::from_ref(&ContextualKeyword::If);
        const MATCH: &[ContextualKeyword] = &[ContextualKeyword::As, ContextualKeyword::If];
        const NONE: &[ContextualKeyword] = &[];

        // Phase 1: Identify the keyword groups compatible with the typed prefix.
        let typed_matches = |keywords: &[ContextualKeyword]| {
            self.typed.is_none_or(|typed| {
                keywords
                    .iter()
                    .any(|keyword| keyword.as_str().starts_with(typed))
            })
        };
        let matches_in = typed_matches(IN);
        let matches_as = typed_matches(AS);
        let matches_if = typed_matches(IF);
        if !matches_in && !matches_as && !matches_if {
            return None;
        }

        // Phase 2: Find the complete token immediately preceding the completion target.
        let (tokens, start) = match self.typed {
            Some(_) => (
                self.tokens_before.get(..self.tokens_before.len() - 1)?,
                self.range.start(),
            ),
            None => (self.tokens_before, self.offset),
        };
        let token = tokens
            .iter()
            .rev()
            .find(|token| !token.kind().is_trivia())?;
        if token.kind() == TokenKind::Newline
            || token.end() >= start
            || self
                .parsed
                .tokens()
                .in_range(TextRange::new(token.end(), start))
                .iter()
                .any(|token| !token.kind().is_trivia())
        {
            return None;
        }
        let covering_node = self.covering_node(token.range());

        // Phase 3: Determine which contextual keywords can follow that token from its
        // enclosing syntax.

        // Loop and comprehension targets can only be followed by `in`.
        if matches_in
            && let Some(keywords) = covering_node.ancestors().find_map(|node| match node {
                ast::AnyNodeRef::StmtFor(stmt) => self
                    .range_end_position(stmt.target.range(), node, token)
                    .map(|position| position.select_keywords(IN, NONE)),
                ast::AnyNodeRef::Comprehension(comprehension) => self
                    .range_end_position(comprehension.target.range(), node, token)
                    .map(|position| position.select_keywords(IN, NONE)),
                _ => None,
            })
        {
            return Some(self.before_already_present(keywords));
        }

        // Context managers and exception types can be followed by `as`.
        if matches_as
            && let Some(keywords) = covering_node.ancestors().find_map(|node| match node {
                ast::AnyNodeRef::StmtWith(stmt) => stmt.items.iter().find_map(|item| {
                    let range = item.context_expr.range();
                    self.range_end_position(range, item.into(), token)
                        .map(|position| position.select_keywords(AS, NONE))
                        .or_else(|| self.range_end_position(range, node, token).map(|_| AS))
                }),
                ast::AnyNodeRef::ExceptHandlerExceptHandler(handler) => handler
                    .type_
                    .as_deref()
                    .and_then(|type_| self.range_end_position(type_.range(), node, token))
                    .map(|position| position.select_keywords(AS, NONE)),
                _ => None,
            })
        {
            return Some(self.before_already_present(keywords));
        }

        // Match patterns can be followed by `as` or `if`; an existing alias can only be
        // followed by `if`.
        if (matches_as || matches_if)
            && let Some(keywords) = covering_node.ancestors().find_map(|node| {
                let ast::AnyNodeRef::MatchCase(case) = node else {
                    return None;
                };
                match &case.pattern {
                    ast::Pattern::MatchAs(ast::PatternMatchAs {
                        pattern: Some(pattern),
                        name: Some(name),
                        ..
                    }) => {
                        if name.is_valid()
                            && let Some(position) =
                                self.range_end_position(case.pattern.range(), node, token)
                        {
                            Some(position.select_keywords(IF, NONE))
                        } else {
                            self.range_end_position(pattern.range(), node, token)
                                .map(|position| position.select_keywords(MATCH, AS))
                        }
                    }
                    pattern => self
                        .range_end_position(pattern.range(), node, token)
                        .map(|position| position.select_keywords(MATCH, AS)),
                }
            })
        {
            return Some(self.before_already_present(keywords));
        }

        None
    }

    /// Returns the expected type when the cursor sits inside
    /// a `raise` or `except` expression.
    ///
    /// The return value is always `None` if the cursor is not
    /// inside a `raise` or `except` context.
    fn exception_ty<'db>(&self, db: &'db dyn Db) -> Option<Type<'db>> {
        let base_exception_ty = KnownClass::BaseException.to_subclass_of(db);
        let base_exception_instance = KnownClass::BaseException.to_instance(db);
        let raise_ty = UnionType::from_elements(db, [base_exception_ty, base_exception_instance]);
        let cause_ty = UnionType::from_elements(db, [raise_ty, Type::none(db)]);
        let except_ty = UnionType::from_elements(
            db,
            [
                base_exception_ty,
                Type::homogeneous_tuple(db, base_exception_ty),
            ],
        );

        let contains = |expr: &ast::Expr| expr.range().contains_range(self.range);

        for node in self.covering_node.ancestors() {
            match node {
                ast::AnyNodeRef::StmtRaise(stmt) => {
                    if stmt.exc.as_deref().is_some_and(contains) {
                        return Some(raise_ty);
                    } else if stmt.cause.as_deref().is_some_and(contains) {
                        return Some(cause_ty);
                    }
                }
                ast::AnyNodeRef::ExceptHandlerExceptHandler(handler)
                    if handler.type_.as_deref().is_some_and(contains) =>
                {
                    return Some(except_ty);
                }
                _ => {}
            }
            if node.is_statement() {
                return None;
            }
        }
        None
    }

    /// Returns the class definition if the cursor is within its
    /// arguments node.
    ///
    /// E.g. `class Foo(Bar<CURSOR>)`
    fn enclosing_class_def(&self) -> Option<&'m ast::StmtClassDef> {
        for node in self.covering_node.ancestors() {
            if let ast::AnyNodeRef::StmtClassDef(class_def) = node {
                return class_def
                    .arguments
                    .as_ref()
                    .is_some_and(|args| args.range.contains_range(self.range))
                    .then_some(class_def);
            }
            if node.is_statement() {
                return None;
            }
        }
        None
    }

    /// Returns a set of keywords that are valid at
    /// the current cursor position.
    ///
    /// Returns None if no context-based exclusions can
    /// be identified. Meaning that all keywords are valid.
    fn valid_keywords(&self) -> Option<FxHashSet<&'static str>> {
        if self.is_in_decorator_name() {
            return Some(FxHashSet::from_iter(["lambda"]));
        }
        self.covering_node.ancestors().find_map(|node| {
            self.is_in_for_statement_iterable(node)
                .then(|| FxHashSet::from_iter(["yield", "lambda", "await"]))
                .or_else(|| {
                    self.is_expecting_expression(node).then(|| {
                        FxHashSet::from_iter([
                            "await", "lambda", "yield", "for", "if", "else", "and", "or", "not",
                            "in", "is", "True", "False", "None",
                        ])
                    })
                })
        })
    }

    /// Returns true when only an expression is valid after the cursor
    /// according to the python grammar.
    ///
    /// `node` should be the smallest AST node fully covering the
    /// typed text.
    fn is_expecting_expression(&self, node: ast::AnyNodeRef) -> bool {
        let contains = |expr: &ast::Expr| expr.range().contains_range(self.range);

        match node {
            // All checks here are intended to find cases where
            // the python grammar disallows anything but expressions.

            // if_stmt := 'if' named_expression ':' block elif_stmt
            ast::AnyNodeRef::StmtIf(stmt) => {
                contains(&stmt.test)
                    || stmt
                        .elif_else_clauses
                        .iter()
                        .any(|clause| clause.test.as_ref().is_some_and(contains))
            }
            // while_stmt := 'while' named_expression ':' block [else_block]
            ast::AnyNodeRef::StmtWhile(stmt) => contains(&stmt.test),

            // for_stmt := 'for' star_targets 'in' ~ star_expressions ':' [TYPE_COMMENT] block [else_block]
            ast::AnyNodeRef::StmtFor(stmt) => contains(&stmt.iter),
            // with_item := expression
            ast::AnyNodeRef::StmtWith(stmt) => {
                stmt.items.iter().any(|item| contains(&item.context_expr))
            }
            // match_stmt := "match" subject_expr ':' NEWLINE INDENT case_block+ DEDENT
            ast::AnyNodeRef::StmtMatch(stmt) => contains(&stmt.subject),
            // case_guard := 'if' named_expression
            ast::AnyNodeRef::MatchCase(case) => {
                case.guard.as_deref().is_some_and(contains)
                    || case.pattern.range().contains_range(self.range)
            }
            // assert_stmt := 'assert' expression [',' expression ]
            ast::AnyNodeRef::StmtAssert(stmt) => {
                contains(&stmt.test) || stmt.msg.as_deref().is_some_and(contains)
            }
            // raise_stmt := 'raise' expression ['from' expression ]
            ast::AnyNodeRef::StmtRaise(stmt) => {
                stmt.exc.as_deref().is_some_and(contains)
                    || stmt.cause.as_deref().is_some_and(contains)
            }
            // return_stmt := 'return' [star_expressions]
            ast::AnyNodeRef::StmtReturn(stmt) => stmt.value.as_deref().is_some_and(contains),

            ast::AnyNodeRef::StmtAssign(stmt) => contains(&stmt.value),
            ast::AnyNodeRef::StmtAugAssign(stmt) => contains(&stmt.value),
            ast::AnyNodeRef::StmtAnnAssign(stmt) => {
                contains(&stmt.annotation) || stmt.value.as_deref().is_some_and(contains)
            }
            // type_alias := "type" NAME [type_params] '=' expression
            ast::AnyNodeRef::StmtTypeAlias(stmt) => contains(&stmt.value),
            // except_clause := 'except' expression ':' block
            ast::AnyNodeRef::ExceptHandlerExceptHandler(handler) => {
                handler.type_.as_deref().is_some_and(contains)
            }

            ast::AnyNodeRef::ExprList(expr) => expr.elts.iter().any(contains),
            ast::AnyNodeRef::ExprTuple(expr) => expr.elts.iter().any(contains),
            ast::AnyNodeRef::ExprSet(expr) => expr.elts.iter().any(contains),
            ast::AnyNodeRef::ExprDict(expr) => expr
                .items
                .iter()
                .any(|item| item.range().contains_range(self.range)),

            // with_item := expression
            ast::AnyNodeRef::WithItem(item) => contains(&item.context_expr),

            // lambdef := 'lambda' [lambda_params] ':' expression
            ast::AnyNodeRef::ExprLambda(expr) => contains(&expr.body),

            // arguments := (positional arguments | keyword arguments | "*" expression | "**" expression)*
            ast::AnyNodeRef::Arguments(args) => {
                args.args.iter().any(contains) || args.keywords.iter().any(|kw| contains(&kw.value))
            }
            ast::AnyNodeRef::Parameter(param) => param.annotation.as_deref().is_some_and(contains),
            ast::AnyNodeRef::ParameterWithDefault(param) => {
                param.default.as_deref().is_some_and(contains)
            }
            _ => false,
        }
    }

    /// Returns true when the cursor is after the `in` keyword in a
    /// `for x in <CURSOR>` statement.
    ///
    /// `node` should be the smallest AST node fully covering the
    /// typed text.
    fn is_in_for_statement_iterable(&self, node: AnyNodeRef) -> bool {
        match node {
            ast::AnyNodeRef::StmtFor(stmt_for) => stmt_for.iter.range().contains_range(self.range),
            // Detects `for x in <CURSOR>` statements inside comprehensions.
            // E.g. `[for x in <CURSOR>]`
            ast::AnyNodeRef::Comprehension(comprehension) => {
                comprehension.target.range().contains_range(self.range)
                    || comprehension.iter.range().contains_range(self.range)
                    || comprehension
                        .ifs
                        .iter()
                        .any(|expr| expr.range().contains_range(self.range))
            }
            _ => false,
        }
    }
}

#[derive(Clone, Debug)]
struct UserQuery {
    pattern: QueryPattern,
    exact: Option<Box<str>>,
}

impl UserQuery {
    fn fuzzy(typed: Option<&str>) -> UserQuery {
        let pattern = typed
            .map(QueryPattern::fuzzy)
            .unwrap_or_else(QueryPattern::matches_all_symbols);
        let exact = typed.map(Into::into);
        UserQuery { pattern, exact }
    }

    fn exactly(name: &str) -> UserQuery {
        let pattern = QueryPattern::exactly(name);
        let exact = Some(name.into());
        UserQuery { pattern, exact }
    }

    fn will_match_everything(&self) -> bool {
        self.pattern.will_match_everything()
    }

    fn is_match(&self, name: &str) -> bool {
        self.pattern.is_match_symbol_name(name)
    }

    fn is_match_exact(&self, name: &str) -> bool {
        self.exact
            .as_ref()
            .map(|exact| &**exact == name)
            .unwrap_or(false)
    }

    fn match_quality(&self, name: &str) -> MatchQuality {
        let Some(query) = self.exact.as_deref() else {
            return MatchQuality::Fuzzy;
        };

        if query == name {
            return MatchQuality::Exact;
        }

        let query = query.to_lowercase();
        let name = name.to_lowercase();
        if name.starts_with(&query) {
            MatchQuality::Prefix
        } else if name.contains(&query) {
            MatchQuality::Substring
        } else {
            MatchQuality::Fuzzy
        }
    }
}

/// Context used to help filter completions when collecting them.
#[derive(Clone, Debug, Default)]
struct CollectionContext<'db> {
    /// A pre-computed type corresponding to what is
    /// expected in an exception expression.
    ///
    /// This is only `Some` when `is_raising_exception` is `true`.
    exception_ty: Option<Type<'db>>,
    /// Whether we're in an exception context (`raise` or `except`) or not.
    is_raising_exception: bool,
    /// Names of base classes that are already specified in the class definition,
    /// including the class being defined (unless its name was previously bound).
    /// Used to filter out duplicate and self-referential base class suggestions.
    /// This is only `Some` when we're in a class definition context.
    existing_class_bases: Option<FxHashSet<CompactString>>,
    /// When set, the context dictates that only *these* keywords
    /// are acceptable in this context.
    valid_keywords: Option<FxHashSet<&'static str>>,
    /// Whether function and method completions should insert call parentheses.
    complete_function_and_method_parentheses: bool,
    /// Whether class completions should insert constructor parentheses.
    complete_class_parentheses: bool,
    /// The supported completion capabilities
    capabilities: CompletionCapabilities,
}

impl<'db> CollectionContext<'db> {
    /// Return a collection context not derived from anything.
    ///
    /// This is useful when one wants to collect completions
    /// outside of any known or specific context. In general,
    /// the "none" context does no additional filtering.
    fn none() -> CollectionContext<'db> {
        CollectionContext::default()
    }

    /// Whether we're in a class definition context.
    fn is_in_class_def(&self) -> bool {
        self.existing_class_bases.is_some()
    }

    /// Whether the completion that would be built from the
    /// given builder should be excluded based on this context.
    ///
    /// This only returns `true` when it is definitively known that the
    /// completion would never be valid for this context.
    fn exclude(&self, db: &dyn Db, builder: &CompletionBuilder<'_>) -> bool {
        if self.is_raising_exception && builder.is_notimplemented(db) {
            return true;
        }
        if builder.kind == Some(CompletionKind::Keyword)
            && let Some(ref valid_keywords) = self.valid_keywords
        {
            return !valid_keywords.contains(builder.name.as_str());
        }
        // Exclude classes that are already listed as base classes in the class definition.
        if let Some(ref existing_class_bases) = self.existing_class_bases {
            // For in-scope completions, check if the simple name matches.
            if builder.import.is_none() && existing_class_bases.contains(&builder.name) {
                return true;
            }
            // For auto-import completions, check if the qualified name matches.
            // This handles cases like `class Foo(mod.Bar, ...)` where we want to
            // filter out auto-import suggestions for `Bar` from module `mod`.
            if let Some(ref qualified) = builder.qualified
                && existing_class_bases.contains(qualified)
            {
                return true;
            }
        }
        false
    }

    fn should_complete_callable_parentheses(&self, kind: Option<CompletionKind>) -> bool {
        match kind {
            Some(CompletionKind::Function | CompletionKind::Method) => {
                self.complete_function_and_method_parentheses
            }
            Some(CompletionKind::Class) => self.complete_class_parentheses,
            _ => false,
        }
    }
}

/// Extracts the names of base classes from a class definition.
///
/// For simple name references (e.g., `Foo`), returns the name as-is.
/// For attribute accesses (e.g., `mod.Foo`), returns the full dotted path.
fn extract_base_class_names(class_def: &ast::StmtClassDef) -> FxHashSet<CompactString> {
    class_def
        .bases()
        .iter()
        .filter_map(UnqualifiedName::from_expr)
        .map(|name| compact_str::format_compact!("{name}"))
        .collect()
}

/// Relevance information assigned to a single completion.
///
/// A "lesser" rank means the completion appears higher in the
/// completion results shown to an end user. That is, we sort
/// completions in ascending order, so the completion with the minimal
/// rank appears first.
///
/// At time of writing (2025-12-16), this type derives `PartialOrd` and
/// `Ord`. This means that the ordering of the fields on this struct
/// matter. The most important or overriding criteria should appear
/// first.
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
struct Relevance {
    /// This is set when we know that a symbol in the current context
    /// is affirmatively usable or not. That is, other symbols in the
    /// results may not be usable (we may not know for sure), but
    /// symbols that we know for sure are usable should get ranked
    /// above symbols that we're unsure about.
    definitively_usable: Sort,
    /// When there's an *exact* name match from the end user's query
    /// with the completion's name, we give strong deference to it.
    exact: Sort,
    /// At time of writing (2025-11-11), keyword completions are
    /// classified as builtins, which makes them sort after everything
    /// else. But we probably want keyword completions to sort *before*
    /// anything else since they are so common. Moreover, it seems
    /// VS Code forcefully does this sorting. By doing it ourselves,
    /// we make our natural sorting match VS Code's, and thus our
    /// completion evaluation framework should be more representative
    /// of real world conditions.
    keyword: Sort,
    /// Sorts based on the "kind" of a name. i.e., Its export status.
    /// We sort normal names the highest. Then dunder names and finally
    /// any other name that starts with a single underscore.
    name_kind: NameKind,
    /// When the symbol is already in scope, we want to rank it higher
    /// than symbols not in scope.
    ///
    /// This is redundant with `module_dependency_kind`, but lets us
    /// add extra weight to symbols in scope over, e.g., other weights
    /// like `is_module`.
    is_in_scope: Sort,
    /// If the symbol refers to a module, we give it priority over
    /// other non-module symbols, even when those symbols are in
    /// the user's project.
    is_module: Sort,
    /// Sorts based on whether this symbol is only available during
    /// type checking and not at runtime.
    type_check_only: Sort,
    /// Deprecated symbols appear lower in the completion result.
    deprecated: Sort,
    /// A broad origin ranking for completions.
    ///
    /// Regular stdlib, namespace package and third-party completions
    /// intentionally tie here so match quality and label ordering can
    /// rank different-label matches (i.e. "fuzzy" matches) before full
    /// origin breaks same-label ties.
    module_origin: Option<ModuleOriginRank>,
    /// A coarse measure of how tightly the completion label matches
    /// the user's query.
    match_quality: MatchQuality,
}

impl Relevance {
    /// Return a rank for the given completion.
    ///
    /// A smaller rank means the completion should appear higher in the
    /// results shown to end users.
    fn new(_ctx: &CollectionContext, query: &UserQuery, c: &CompletionBuilder) -> Relevance {
        Relevance {
            definitively_usable: if c.is_context_specific {
                Sort::Higher
            } else {
                Sort::Even
            },
            exact: if query.is_match_exact(&c.name) {
                Sort::Higher
            } else {
                Sort::Even
            },
            keyword: if c.kind == Some(CompletionKind::Keyword) {
                Sort::Higher
            } else {
                Sort::Even
            },
            name_kind: NameKind::classify(&c.name),
            is_in_scope: if c.module_dependency_kind == Some(ModuleDependencyKind::Current) {
                Sort::Higher
            } else {
                Sort::Even
            },
            is_module: if c.kind == Some(CompletionKind::Module)
                // We only up-rank top-level modules.
                // Doing this for sub-modules generates too
                // much noise.
                && !c
                    .qualified
                    .as_ref()
                    .map(|q| q.contains('.'))
                    .unwrap_or(true)
            {
                Sort::Higher
            } else {
                Sort::Even
            },
            type_check_only: if c.is_type_check_only {
                Sort::Lower
            } else {
                Sort::Even
            },
            deprecated: if c.deprecated {
                Sort::Lower
            } else {
                Sort::Even
            },
            module_origin: c
                .module_dependency_kind
                .map(ModuleDependencyKind::origin_rank),
            match_quality: query.match_quality(&c.name),
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
enum ModuleOriginRank {
    Current,
    Builtin,
    Project,
    StdlibSpecial,
    Other,
}

#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
enum MatchQuality {
    Exact,
    Prefix,
    Substring,
    Fuzzy,
}

/// Classifies a completion symbol by origin.
///
/// The variant order in this enum contributes to the ranking order for
/// completions after the preceding fields in `Relevance`; earlier origins in
/// this list rank higher in completions.
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
enum ModuleDependencyKind {
    /// Symbols already in scope in the user's current module.
    ///
    /// Note that this doesn't necessarily mean that the symbol is
    /// *defined* in the current module. e.g., `numpy.arra<CURSOR>`
    /// will return `array` from `numpy`, and its module dependency
    /// kind is considered `Current`.
    Current,
    /// Reserved for the Python initial basis. We want these
    /// symbols to appear high since they are used so frequently,
    /// but not higher than symbols already in scope in the
    /// current module.
    Builtin,
    /// Symbols defined somewhere in the user's project.
    Project,
    /// Symbols from "special" standard library modules that
    /// are so commonly used---but commonly have names in
    /// conflict with other stdlib modules---that we want to
    /// prioritize them above third-party re-exports and
    /// other stdlib modules.
    ///
    /// `typing` is a good example of this. It has lots of
    /// symbols that also exist in other modules. e.g.,
    /// `TypeVar` in `ast`, `cast` in `ctypes` and
    /// `Protocol` in `asyncio`.
    ///
    /// We also include `collections`
    /// for similar reasons.
    StdlibSpecial,
    /// Symbols from the standard library.
    Stdlib,
    /// A namespace package somewhat defies classification, since
    /// it can exist over multiple search paths. Since std doesn't
    /// use namespace packages, we just assume that they are roughly
    /// equivalent to third party packages.
    ///
    /// This is an erroneous assumption when the namespace
    /// package is within the user's project. Probably we
    /// could do better once we know how to navigate namespace
    /// packages better.
    Namespace,
    /// Symbols defined somewhere in a dependency, direct or
    /// indirect.
    ThirdParty,
}

impl ModuleDependencyKind {
    fn origin_rank(self) -> ModuleOriginRank {
        match self {
            ModuleDependencyKind::Current => ModuleOriginRank::Current,
            ModuleDependencyKind::Builtin => ModuleOriginRank::Builtin,
            ModuleDependencyKind::Project => ModuleOriginRank::Project,
            ModuleDependencyKind::StdlibSpecial => ModuleOriginRank::StdlibSpecial,
            ModuleDependencyKind::Stdlib
            | ModuleDependencyKind::Namespace
            | ModuleDependencyKind::ThirdParty => ModuleOriginRank::Other,
        }
    }

    /// Determines the "kind" of a symbol based on the module it is
    /// defined in.
    ///
    /// Note that this can never return `ModuleDependencyKind::Current`.
    /// Callers are expected to handle that case themselves.
    fn from_module(db: &dyn Db, module: Module<'_>) -> ModuleDependencyKind {
        if module.is_known(db, KnownModule::Builtins) {
            return ModuleDependencyKind::Builtin;
        }
        let Some(sp) = module.search_path(db) else {
            return ModuleDependencyKind::Namespace;
        };
        if sp.is_standard_library() {
            if module.is_known(db, KnownModule::Typing)
                || module.is_known(db, KnownModule::Collections)
            {
                ModuleDependencyKind::StdlibSpecial
            } else {
                ModuleDependencyKind::Stdlib
            }
        } else if sp.is_site_packages() {
            ModuleDependencyKind::ThirdParty
        } else {
            // We assume anything else, including
            // "extra" search paths and editable installs,
            // are part of the end user's code.
            ModuleDependencyKind::Project
        }
    }
}

/// An instruction to indicate an ordering preference.
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)]
enum Sort {
    /// Assign a higher rank. The suggestion will appear higher
    /// in the completion results.
    Higher,
    /// Assign an even rank. i.e., A "push" to the next
    /// criteria.
    #[default]
    Even,
    /// Assign a smaller rank. The suggestion will appear lower
    /// in the completion results.
    Lower,
}

/// Detect and add completions for unset arguments.
fn add_argument_completions<'db>(
    db: &'db dyn Db,
    model: &SemanticModel<'db>,
    cursor: &ContextCursor<'_>,
    completions: &mut Completions<'db>,
) {
    let mut in_arguments = false;
    for node in cursor.covering_node.ancestors() {
        match node {
            // Do not suggest argument completions in value positions for
            // keyword arguments
            ast::AnyNodeRef::Keyword(kw) => {
                if kw.value.range().contains_range(cursor.range) {
                    return;
                }
            }
            ast::AnyNodeRef::Arguments(_) => {
                in_arguments = true;
            }
            ast::AnyNodeRef::ExprCall(_) => {
                if in_arguments {
                    add_function_arg_completions(db, model.file(), cursor, completions);
                }
                return;
            }
            ast::AnyNodeRef::StmtClassDef(class_def) => {
                if let Some(arguments) = class_def.arguments.as_deref()
                    && arguments.range().contains_range(cursor.range)
                {
                    add_class_arg_completions(model, class_def, completions);
                }
                return;
            }
            node => {
                if node.is_statement() {
                    return;
                }
            }
        }
    }
}

/// Detect and add completions for unset class arguments.
///
/// Some arguments we know are always valid and thus they are easy
/// to provide. The `metaclass` keyword is always valid.
/// For `typing.TypedDict` subclasses, we add
/// `TypedDict` specific keywords like `total`.
fn add_class_arg_completions<'db>(
    model: &SemanticModel<'db>,
    class_def: &ast::StmtClassDef,
    completions: &mut Completions<'db>,
) {
    let is_set = |name| {
        class_def
            .arguments
            .as_ref()
            .is_some_and(|args| args.find_keyword(name).is_some())
    };

    if !is_set("metaclass") {
        let ty = KnownClass::Type.to_subclass_of(model.db());
        completions.add(CompletionBuilder::argument("metaclass").ty(ty));
    }

    let is_typed_dict = class_def
        .inferred_type(model)
        .and_then(Type::as_class_literal)
        .is_some_and(|t| t.is_typed_dict(model.db()));

    // TODO: Handle PEP 728 that adds two extra keywords,
    // closed and extra_items.
    //
    // See https://peps.python.org/pep-0728/
    if is_typed_dict && !is_set("total") {
        let ty = KnownClass::Bool.to_instance(model.db());
        completions.add(CompletionBuilder::argument("total").ty(ty));
    }
}

/// Detect and add completions for unset function arguments.
///
/// Suggestions are only provided if the cursor is currently inside a
/// function call and the function arguments have not 1) already been
/// set and 2) been defined as positional-only.
fn add_function_arg_completions<'db>(
    db: &'db dyn Db,
    file: File,
    cursor: &ContextCursor<'_>,
    completions: &mut Completions<'db>,
) {
    debug_assert!(
        cursor
            .covering_node
            .ancestors()
            .take_while(|node| !node.is_statement())
            .any(|node| node.is_arguments()),
        "Should only be called if we're already certain we're in an arguments node to avoid \
        adding completions for something like `(<CURSOR>)(arg1, arg2)`-style expressions"
    );

    let Some(sig_help) = signature_help(db, file, cursor.offset) else {
        return;
    };
    let mut set_function_args = detect_set_function_args(cursor);

    for sig in &sig_help.signatures {
        for p in &sig.parameters {
            if p.is_positional_only
                || p.is_variadic
                || p.is_keyword_variadic
                || !set_function_args.insert(p.name.as_str())
            {
                continue;
            }
            let mut builder = CompletionBuilder::argument(&p.name).ty(p.ty);
            if let Some(ref docs) = p.documentation {
                builder = builder.documentation(docs);
            }
            completions.add(builder);
        }
    }
}

/// Returns function arguments that have already been set.
///
/// If `offset` is inside an arguments node, this returns
/// the list of argument names that are already set.
///
/// For example, given:
///
/// ```python
/// def abc(foo, bar, baz): ...
/// abc(foo=1, bar=2, b<CURSOR>)
/// ```
///
/// the resulting value is `["foo", "bar"]`
///
/// This is useful to be able to exclude autocomplete suggestions
/// for arguments that have already been set to some value.
///
/// If the parent node is not an arguments node, the return value
/// is an empty Vec.
fn detect_set_function_args<'m>(cursor: &ContextCursor<'m>) -> FxHashSet<&'m str> {
    cursor
        .covering_node
        .parent()
        .and_then(|node| match node {
            ast::AnyNodeRef::Arguments(args) => Some(args),
            _ => None,
        })
        .map(|args| {
            args.keywords
                .iter()
                .filter_map(|kw| kw.arg.as_ref().map(|ident| ident.id.as_str()))
                .collect()
        })
        .unwrap_or_default()
}

pub(crate) struct ImportEdit {
    pub label: String,
    pub edit: Edit,
}

/// Get fixes that would resolve an unresolved reference
pub(crate) fn unresolved_fixes(
    db: &dyn Db,
    file: File,
    parsed: &ParsedModuleRef,
    symbol: &str,
    node: AnyNodeRef,
) -> Vec<ImportEdit> {
    let mut results = Vec::new();
    let scoped = ScopedTarget { node };
    let query = UserQuery::exactly(symbol);
    let ctx = CollectionContext::none();

    // Request imports we could add to put the symbol in scope
    let mut completions = Completions::new(db, ctx.clone(), query.clone());
    add_unimported_completions(
        db,
        file,
        parsed,
        scoped,
        |module_name: &ModuleName, symbol: &str| {
            ImportRequest::import_from(module_name.as_str(), symbol).force()
        },
        &mut completions,
    );
    results.extend(completions.into_imports());

    // Request qualifications we could apply to the symbol to make it resolve
    let mut completions = Completions::new(db, ctx, query);
    add_unimported_completions(
        db,
        file,
        parsed,
        scoped,
        |module_name: &ModuleName, symbol: &str| {
            ImportRequest::import(module_name.as_str(), symbol).force()
        },
        &mut completions,
    );
    results.extend(completions.into_qualifications(node.range()));

    results
}

/// Adds completions derived from keywords.
///
/// This should generally only be used when offering "scoped" completions.
/// This will include keywords corresponding to Python values (like `None`)
/// and general language keywords (like `raise`).
fn add_keyword_completions<'db>(db: &'db dyn Db, completions: &mut Completions<'db>) {
    let keyword_values = [
        ("None", Type::none(db)),
        ("True", Type::bool_literal(true)),
        ("False", Type::bool_literal(false)),
    ];
    for (name, ty) in keyword_values {
        completions.add(CompletionBuilder::keyword(name).ty(ty).builtin(true));
    }

    // Note that we specifically omit the `type` keyword here, since
    // it will be included via `builtins`. This does make its sorting
    // priority slightly different than other keywords, but it's not
    // clear (to me, AG) if that's an issue or not. Since the builtin
    // completion has an actual type associated with it, we use that
    // instead of a keyword completion.
    let keywords = [
        "and", "as", "assert", "async", "await", "break", "class", "continue", "def", "del",
        "elif", "else", "except", "finally", "for", "from", "global", "if", "import", "in", "is",
        "lambda", "nonlocal", "not", "or", "pass", "raise", "return", "try", "while", "with",
        "yield", "case", "match",
    ];
    for name in keywords {
        completions.add(CompletionBuilder::keyword(name));
    }
}

fn add_string_literal_completions<'db>(
    model: &SemanticModel<'db>,
    string_expr: &ast::ExprStringLiteral,
    quote_style: Option<Quote>,
    completions: &mut Completions<'db>,
) {
    fn force_escape_quote(body: &str, quote: Quote) -> String {
        let quote_char = quote.as_char();
        let mut escaped = String::with_capacity(body.len());
        let mut consecutive_backslashes = 0usize;

        for ch in body.chars() {
            if ch == '\\' {
                consecutive_backslashes += 1;
                escaped.push(ch);
                continue;
            }

            if ch == quote_char && consecutive_backslashes.is_multiple_of(2) {
                escaped.push('\\');
            }
            consecutive_backslashes = 0;
            escaped.push(ch);
        }

        escaped
    }

    // When we insert a completion for a string literal, we need to make sure
    // to properly escape any special characters in the completion value and
    // to use the appropriate quote style.
    fn escape_for_quote(value: &str, quote: Quote) -> Option<String> {
        let escaped = UnicodeEscape::with_preferred_quote(value, quote);
        let mut out = String::new();
        escaped.write_body(&mut out).ok()?;
        Some(force_escape_quote(&out, quote))
    }

    let candidates = model.expected_string_literal_completions(string_expr);
    if candidates.is_empty() {
        return;
    }

    let quote_style = quote_style.unwrap_or(Quote::Double);
    for candidate in candidates {
        let Some(insert) = escape_for_quote(&candidate.value, quote_style) else {
            continue;
        };
        completions.add_skip_query(
            Completion::builder(candidate.value.as_str())
                .insert(insert)
                .ty(candidate.ty)
                .context_specific(true),
        );
    }
}

/// Adds completions not in scope.
///
/// `scoped` should be information about the identified scope
/// in which the cursor is currently placed.
///
/// The completions returned will auto-insert import statements
/// when selected into `File`.
fn add_unimported_completions<'db>(
    db: &'db dyn Db,
    file: File,
    parsed: &ParsedModuleRef,
    scoped: ScopedTarget<'_>,
    create_import_request: impl for<'a> Fn(&'a ModuleName, &'a str) -> ImportRequest<'a>,
    completions: &mut Completions<'db>,
) {
    // This is redundant since `all_symbols` will also bail
    // when the query can match everything. But we bail here
    // to avoid building an `Importer` and other plausibly
    // costly work when we know we won't use it.
    if completions.query.will_match_everything() {
        return;
    }

    let source = source_text(db, file);
    let stylist = Stylist::from_tokens(parsed.tokens(), source.as_str());
    let importer = Importer::new(db, &stylist, file, source.as_str(), parsed);
    let members = importer.members_in_scope_at(scoped.node, scoped.node.start());

    for symbol in all_symbols(db, file, &completions.query.pattern) {
        if symbol.file() == file || symbol.module().is_known(db, KnownModule::Builtins) {
            continue;
        }

        let module_name = symbol.module().name(db);
        let (name, request) = symbol
            .name_in_file()
            .map(|name| (name, create_import_request(module_name, name)))
            .unwrap_or_else(|| {
                let name = module_name.as_str();
                (name, ImportRequest::module(name))
            });

        // Don't suggest symbols that are already imported.
        if members.satisfies(db, file, &request) {
            continue;
        }

        let import_action = importer.import(request, &members);

        // N.B. We use `add_skip_query` here because `all_symbols`
        // already takes our query into account.
        completions.add_skip_query(
            Completion::builder(name)
                .qualified(symbol.qualified())
                .insert(import_action.symbol_text())
                .kind(symbol.kind().to_completion_kind())
                .module_name(module_name)
                .import(import_action.import().cloned())
                .deprecated(symbol.deprecated())
                .module_dependency_kind(ModuleDependencyKind::from_module(db, symbol.module())),
        );
    }
}

/// The kind of tokens identified under the cursor.
#[derive(Debug)]
enum CompletionTargetTokens<'t> {
    /// A `object.attribute` token form was found, where
    /// `attribute` may be empty.
    ///
    /// This requires a name token followed by a dot token.
    ///
    /// This is "possibly" an `object.attribute` because
    /// the object token may not correspond to an object
    /// or it may correspond to *part* of an object.
    /// This is resolved when we try to find an overlapping
    /// AST `ExprAttribute` node later. If we couldn't, then
    /// this is probably not an `object.attribute`.
    PossibleObjectDot {
        /// The token preceding the dot.
        object: &'t Token,
        /// The token, if non-empty, following the dot.
        ///
        /// For right now, this is only used to determine which
        /// module in an `import` statement to return submodule
        /// completions for. But we could use it for other things,
        /// like only returning completions that start with a prefix
        /// corresponding to this token.
        #[expect(dead_code)]
        attribute: Option<&'t Token>,
    },
    /// A token was found under the cursor, but it didn't
    /// match any of our anticipated token patterns.
    Generic { token: &'t Token },
    /// No token was found. We generally treat this like
    /// `Generic` (i.e., offer scope based completions).
    Unknown,
}

impl<'t> CompletionTargetTokens<'t> {
    /// Look for the best matching token pattern at the given offset.
    fn find(cursor: &ContextCursor<'t>) -> Option<CompletionTargetTokens<'t>> {
        static OBJECT_DOT_EMPTY: [TokenKind; 1] = [TokenKind::Dot];

        let before = cursor.tokens_before;
        Some(
            // Our strategy when it comes to `object.attribute` here is
            // to look for the `.` and then take the token immediately
            // preceding it. Later, we look for an `ExprAttribute` AST
            // node that overlaps (even partially) with this token. And
            // that's the object we try to complete attributes for.
            if let Some([_dot]) = token_suffix_by_kinds(before, OBJECT_DOT_EMPTY) {
                let object = before[..before.len() - 1].last()?;
                CompletionTargetTokens::PossibleObjectDot {
                    object,
                    attribute: None,
                }
            } else if let [.., object, dot, attribute] = before
                && dot.kind() == TokenKind::Dot
                && is_name_like_token(attribute)
            {
                CompletionTargetTokens::PossibleObjectDot {
                    object,
                    attribute: Some(attribute),
                }
            } else if let Some([_]) = token_suffix_by_kinds(before, [TokenKind::Float]) {
                // If we're writing a `float`, then we should
                // specifically not offer completions. This wouldn't
                // normally be an issue, but if completions are
                // automatically triggered by a `.` (which is what we
                // request as an LSP server), then we can get here
                // in the course of just writing a decimal number.
                return None;
            } else if let Some([_]) = token_suffix_by_kinds(before, [TokenKind::Ellipsis]) {
                // Similarly as above. If we've just typed an ellipsis,
                // then we shouldn't show completions. Note that
                // this doesn't prevent `....<CURSOR>` from showing
                // completions (which would be the attributes available
                // on an `ellipsis` object).
                return None;
            } else {
                let Some(last) = before.last() else {
                    return Some(CompletionTargetTokens::Unknown);
                };
                CompletionTargetTokens::Generic { token: last }
            },
        )
    }

    /// Returns a corresponding AST node for these tokens.
    ///
    /// `offset` should be the offset of the cursor.
    ///
    /// If no plausible AST node could be found, then `None` is returned.
    fn ast(&self, cursor: &ContextCursor<'t>) -> Option<CompletionTargetAst<'t>> {
        match *self {
            CompletionTargetTokens::PossibleObjectDot { object, .. } => {
                let covering_node = cursor
                    .covering_node(object.range())
                    .find_last(|node| {
                        // We require that the end of the node range not
                        // exceed the cursor offset. This avoids selecting
                        // a node "too high" in the AST in cases where
                        // completions are requested in the middle of an
                        // expression. e.g., `foo.<CURSOR>.bar`.
                        if node.is_expr_attribute() {
                            return node.range().end() <= cursor.offset;
                        }
                        // For import statements though, they can't be
                        // nested, so we don't care as much about the
                        // cursor being strictly after the statement.
                        // And indeed, sometimes it won't be! e.g.,
                        //
                        //   import re, os.p<CURSOR>, zlib
                        //
                        // So just return once we find an import.
                        node.is_stmt_import() || node.is_stmt_import_from()
                    })
                    .ok()?;
                match covering_node.node() {
                    ast::AnyNodeRef::ExprAttribute(expr) => {
                        Some(CompletionTargetAst::ObjectDot { expr })
                    }
                    _ => None,
                }
            }
            CompletionTargetTokens::Generic { token } => {
                let node = cursor.covering_node(token.range()).node();
                Some(CompletionTargetAst::Scoped(ScopedTarget { node }))
            }
            CompletionTargetTokens::Unknown => Some(CompletionTargetAst::Scoped(ScopedTarget {
                node: cursor.covering_node.node(),
            })),
        }
    }
}

/// The AST node patterns that we support identifying under the cursor.
#[derive(Debug)]
enum CompletionTargetAst<'t> {
    /// A `object.attribute` scenario, where we want to
    /// list attributes on `object` for completions.
    ObjectDot { expr: &'t ast::ExprAttribute },
    /// A scoped scenario, where we want to list all items available in
    /// the most narrow scope containing the giving AST node.
    Scoped(ScopedTarget<'t>),
}

#[derive(Clone, Copy, Debug)]
struct ScopedTarget<'t> {
    /// The node with the smallest range that fully covers
    /// the token under the cursor.
    node: ast::AnyNodeRef<'t>,
}

/// A representation of the completion context for a possibly incomplete import
/// statement.
#[derive(Clone, Debug)]
enum ImportStatement<'a> {
    FromImport(FromImport<'a>),
    Import(Import<'a>),
    Incomplete(IncompleteImport),
}

/// A representation of the completion context for a possibly incomplete
/// `from ... import ...` statement.
#[derive(Clone, Debug)]
struct FromImport<'a> {
    ast: &'a ast::StmtImportFrom,
    kind: FromImportKind,
}

/// The kind of completions to offer for a `from import` statement.
///
/// This is either something like `from col<CURSOR>`, where we should
/// offer module completions, or `from collections.<CURSOR>`, where
/// we should offer submodule completions or
/// `from collections import default<CURSOR>` where we should offer
/// submodule/attribute completions.
#[derive(Clone, Debug)]
enum FromImportKind {
    Module,
    Submodule {
        parent: ModuleName,
    },
    Relative {
        parent: ModuleName,
        /// When `true`, an `import` keyword is allowed next.
        /// For example, `from ...<CURSOR>` should offer `import`
        /// but also submodule completions.
        import_keyword_allowed: bool,
    },
    Attribute,
}

/// A representation of the completion context for a possibly incomplete
/// `import ...` statement.
#[derive(Clone, Debug)]
struct Import<'a> {
    #[expect(dead_code)]
    ast: &'a ast::StmtImport,
    kind: ImportKind,
}

/// The kind of completions to offer for an `import` statement.
///
/// This is either something like `import col<CURSOR>`, where we should
/// offer module completions, or `import collections.<CURSOR>`, where
/// we should offer submodule completions.
#[derive(Clone, Debug)]
enum ImportKind {
    Module,
    Submodule { parent: ModuleName },
}

/// Occurs when we detect that an import statement
/// is likely incomplete by virtue of a missing or
/// in-progress `as` or `import` keyword.
#[derive(Clone, Debug)]
enum IncompleteImport {
    As,
    Import,
}

impl<'a> ImportStatement<'a> {
    /// The number of tokens we're willing to consume backwards from
    /// the cursor's position until we give up looking for an import
    /// statement. The state machine below has lots of opportunities
    /// to bail way earlier than this, but if there's, e.g., a long
    /// list of name tokens for something that isn't an import, then we
    /// could end up doing a lot of wasted work here. Probably humans
    /// aren't often working with single import statements over 1,000
    /// tokens long.
    ///
    /// The other thing to consider here is that, by the time we get to
    /// this point, ty has already done some work proportional to the
    /// length of `tokens` anyway. The unit of work we do below is very
    /// small.
    const LIMIT: usize = 1_000;

    /// Attempts to detect an import statement in reverse starting at
    /// the end of `tokens`. That is, `tokens` should correspond to the
    /// sequence of tokens up to the end user's cursor. `typed` should
    /// correspond to the text the user has typed, which is usually,
    /// but not always, the text corresponding to the last token in
    /// `tokens`.
    fn detect(
        db: &'_ dyn Db,
        file: File,
        cursor: &ContextCursor<'a>,
    ) -> Option<ImportStatement<'a>> {
        use TokenKind as TK;

        // This state machine moves backwards through the token stream,
        // starting at where the user's cursor is and ending when
        // either a `from` token is found, or a token that cannot
        // possibly appear in an import statement at a particular
        // position is found.
        //
        // To understand this state machine, it's recommended to become
        // familiar with the grammar for Python import statements:
        // https://docs.python.org/3/reference/grammar.html

        /// The current state of the parser below.
        #[derive(Clone, Copy, Debug)]
        enum S {
            /// Our initial state.
            Start,
            /// We just saw an `import` token.
            Import,
            /// We just saw a first "name" token. That is,
            /// a name-like token that appears just before
            /// the end user's cursor.
            ///
            /// This isn't just limited to `TokenKind::Name`.
            /// This also includes keywords and things like
            /// "unknown" tokens that can stand in for names
            /// at times.
            FirstName,
            /// A name seen immediately after the first name. This
            /// indicates we have an incomplete import statement like
            /// `import foo a<CURSOR>` or `from foo imp<CURSOR>`. But
            /// we mush on.
            AdjacentName,

            /// A state where we expect to see the start of or
            /// continuation of a list of names following `import`.
            /// In the [grammar], this is either `dotted_as_names`
            /// or `import_from_as_names`.
            ///
            /// [grammar]: https://docs.python.org/3/reference/grammar.html
            NameList,
            /// Occurs after seeing a name-like token at the end
            /// of a name list. This could be an alias, a dotted
            /// name or a non-dotted name.
            NameListNameOrAlias,
            /// Occurs when we've seen an `as` in a list of names.
            As,
            /// Occurs when we see a name-like token after an `as`
            /// keyword.
            AsName,

            /// Occurs when we see a `.` between name-like tokens
            /// after an `as` keyword. This implies we must parse
            /// a `from` statement, since an `as` in a `from` can
            /// never alias a dotted name.
            AsDottedNameDot,
            /// Occurs when we see a name-like token after a
            /// `.name as`.
            AsDottedName,
            /// Occurs when we see a comma right before `a.b as foo`.
            AsDottedNameComma,
            /// Occurs before `, a.b as foo`. In this state, we can
            /// see either a non-dotted alias or a dotted name.
            AsDottedNameOrAlias,
            /// Occurs before `bar, a.b as foo`. In this state, we can
            /// see a `.`, `as`, or `import`.
            AsDottedNameOrAliasName,

            /// Occurs when we've seen a dot right before the cursor
            /// or after the first name-like token. That is, `.name`.
            /// This could be from `import module.name` or `from ..name
            /// import blah`.
            InitialDot,
            /// Occurs when we see `foo.bar<CURSOR>`. When we enter
            /// this state, it means we must be in an `import`
            /// statement, since `from foo.bar` is always invalid.
            InitialDotName,
            /// Occurs when we see `.foo.bar<CURSOR>`. This lets us
            /// continue consuming a dotted name.
            InitialDottedName,

            // When the states below occur, we are locked into
            // recognizing a `from ... import ...` statement.
            /// Occurs when we've seen an ellipsis right before the
            /// cursor or after the first name-like token. That is,
            /// `...name`. This must be from a
            /// `from ...name import blah` statement.
            FromEllipsisName,
            /// A state for consuming `.` and `...` in a `from` import
            /// statement. We enter this after seeing a `.` or a `...`
            /// right after an `import` statement or a `...` right
            /// before the end user's cursor. Either way, we have to
            /// consume only dots at this point until we find a `from`
            /// token.
            FromDots,
            /// Occurs when we've seen an `import` followed by a name-like
            /// token. i.e., `from name import` or `from ...name import`.
            FromDottedName,
            /// Occurs when we've seen an `import` followed by a
            /// name-like token with a dot. i.e., `from .name import`
            /// or `from ..name import`.
            FromDottedNameDot,
            /// A `*` was just seen, which must mean the import is of
            /// the form `from module import *`.
            FromStar,
            /// A left parenthesis was just seen.
            FromLpar,

            // Below are terminal states. Once we reach one
            // of these, the state machine ends.
            /// We just saw a `from` token. We never have any
            /// outgoing transitions from this.
            From,
            /// This is like `import`, but used in a context
            /// where we know we're in an import statement and
            /// specifically *not* a `from ... import ...`
            /// statement.
            ImportFinal,
        }

        let mut state = S::Start;
        // The token immediate before (or at) the cursor.
        let last = cursor.tokens_before.last()?;
        // A token corresponding to `import`, if found.
        let mut import: Option<&Token> = None;
        // A token corresponding to `from`, if found.
        let mut from: Option<&Token> = None;
        // Whether an initial dot was found right before the cursor,
        // or right before the name at the cursor.
        let mut initial_dot = false;
        // An incomplete import statement was found.
        // Usually either `from foo imp<CURSOR>`
        // or `import foo a<CURSOR>`.
        let mut incomplete_as_or_import = false;
        for token in cursor.tokens_before.iter().rev().take(Self::LIMIT) {
            if token.kind().is_trivia() {
                continue;
            }
            state = match (state, token.kind()) {
                // These cases handle our "initial" condition.
                // Basically, this is what detects how to drop us into
                // the full state machine below for parsing any kind of
                // import statement. There are also some cases we try
                // to detect here that indicate the user is probably
                // typing an `import` or `as` token. In effect, we
                // try to pluck off the initial name-like token that
                // represents where the cursor likely is. And then
                // we move on to try and detect the type of import
                // statement that we're dealing with.
                // (S::Start, TK::Newline) => S::Start,
                (S::Start, TK::Star) => S::FromStar,
                (S::Start, TK::Name) if cursor.typed.is_none() => S::AdjacentName,
                (S::Start, TK::Name) => S::FirstName,
                (S::Start | S::FirstName | S::AdjacentName, TK::Import) => S::Import,
                (S::Start | S::FirstName | S::AdjacentName, TK::Lpar) => S::FromLpar,
                (S::Start | S::FirstName | S::AdjacentName, TK::Comma) => S::NameList,
                (S::Start | S::FirstName | S::AdjacentName, TK::Dot) => S::InitialDot,
                (S::Start | S::FirstName | S::AdjacentName, TK::Ellipsis) => S::FromEllipsisName,
                (S::Start | S::FirstName, TK::As) => S::As,
                (S::Start | S::AdjacentName, TK::From) => S::From,
                (S::FirstName, TK::From) => S::From,
                (S::FirstName, TK::Name) => S::AdjacentName,

                // This handles the case where we see `.name`. Here,
                // we could be in `from .name`, `from ..name`, `from
                // ...name`, `from foo.name`, `import foo.name`,
                // `import bar, foo.name` and so on.
                (S::InitialDot, TK::Dot | TK::Ellipsis) => S::FromDots,
                (S::InitialDot, TK::Name) => S::InitialDotName,
                (S::InitialDot, TK::From) => S::From,
                (S::InitialDotName, TK::Dot) => S::InitialDottedName,
                (S::InitialDotName, TK::Ellipsis) => S::FromDots,
                (S::InitialDotName, TK::As) => S::AsDottedNameDot,
                (S::InitialDotName, TK::Comma) => S::AsDottedNameOrAlias,
                (S::InitialDotName, TK::Import) => S::ImportFinal,
                (S::InitialDotName, TK::From) => S::From,
                (S::InitialDottedName, TK::Dot | TK::Ellipsis) => S::FromDots,
                (S::InitialDottedName, TK::Name) => S::InitialDotName,
                (S::InitialDottedName, TK::From) => S::From,

                // This state machine parses `dotted_as_names` or
                // `import_from_as_names`. It has a carve out for when
                // it finds a dot, which indicates it must parse only
                // `dotted_as_names`.
                (S::NameList, TK::Name | TK::Unknown) => S::NameListNameOrAlias,
                (S::NameList, TK::Lpar) => S::FromLpar,
                (S::NameListNameOrAlias, TK::As) => S::As,
                (S::NameListNameOrAlias, TK::Comma) => S::NameList,
                (S::NameListNameOrAlias, TK::Import) => S::Import,
                (S::NameListNameOrAlias, TK::Lpar) => S::FromLpar,
                (S::NameListNameOrAlias, TK::Unknown) => S::NameListNameOrAlias,
                // This pops us out of generic name-list parsing
                // and puts us firmly into `dotted_as_names` in
                // the grammar.
                (S::NameListNameOrAlias, TK::Dot) => S::AsDottedNameDot,

                // This identifies aliasing via `as`. The main trick
                // here is that if we see a `.`, then we move to a
                // different set of states since we know we must be in
                // an `import` statement. Without a `.` though, we
                // could be in an `import` or a `from`. For example,
                // `import numpy as np` or
                // `from collections import defaultdict as dd`.
                (S::As, TK::Name) => S::AsName,
                (S::AsName, TK::Dot) => S::AsDottedNameDot,
                (S::AsName, TK::Import) => S::Import,
                (S::AsName, TK::Comma) => S::NameList,

                // This is the mini state machine for handling
                // `dotted_as_names`. We enter it when we see
                // `foo.bar as baz`. We therefore know this must
                // be an `import` statement and not a `from import`
                // statement.
                (S::AsDottedName, TK::Dot) => S::AsDottedNameDot,
                (S::AsDottedName, TK::Comma) => S::AsDottedNameComma,
                (S::AsDottedName, TK::Import) => S::ImportFinal,
                (S::AsDottedNameDot, TK::Name) => S::AsDottedName,
                (S::AsDottedNameComma, TK::Name) => S::AsDottedNameOrAlias,
                (S::AsDottedNameOrAlias, TK::Name) => S::AsDottedNameOrAliasName,
                (S::AsDottedNameOrAlias, TK::Dot) => S::AsDottedNameDot,
                (S::AsDottedNameOrAliasName, TK::Dot | TK::As) => S::AsDottedNameDot,
                (S::AsDottedNameOrAliasName, TK::Import) => S::ImportFinal,

                // A `*` and `(` immediately constrains what we're allowed to see.
                // We can jump right to expecting an `import` keyword.
                (S::FromStar | S::FromLpar, TK::Import) => S::Import,

                // The transitions below handle everything from `from`
                // to `import`. Basically, once we see an `import`
                // token or otherwise know we're parsing the module
                // section of a `from` import statement, we end up in
                // one of the transitions below.
                (S::Import, TK::Dot | TK::Ellipsis) => S::FromDots,
                (S::Import, TK::Name | TK::Unknown) => S::FromDottedName,
                (S::FromDottedName, TK::Dot) => S::FromDottedNameDot,
                (S::FromDottedName, TK::Ellipsis) => S::FromDots,
                (S::FromDottedNameDot, TK::Name) => S::FromDottedName,
                (S::FromDottedNameDot, TK::Dot | TK::Ellipsis) => S::FromDots,
                (S::FromEllipsisName | S::FromDots, TK::Dot | TK::Ellipsis) => S::FromDots,
                (
                    S::FromEllipsisName | S::FromDots | S::FromDottedName | S::FromDottedNameDot,
                    TK::From,
                ) => S::From,

                _ => break,
            };
            // If we transition into a few different special
            // states, we record the token.
            match state {
                S::Import | S::ImportFinal => {
                    import = Some(token);
                }
                S::From => {
                    from = Some(token);
                }
                S::AdjacentName => {
                    // We've seen two adjacent name-like tokens
                    // right before the cursor. At this point,
                    // we continue on to try to recognize a nearly
                    // valid import statement, and to figure out
                    // what kinds of completions we should offer
                    // (if any).
                    incomplete_as_or_import = true;
                }
                S::InitialDot | S::FromEllipsisName => {
                    initial_dot = true;
                }
                _ => {}
            }
        }

        // Now find a possibly dotted name up to where the current
        // cursor is. This could be an item inside a module, a module
        // name, a submodule name or even a relative module. The
        // point is that it is the thing that the end user is trying
        // to complete.
        let mut to_complete = String::new();
        let end = last.range().end();
        let mut start = end;
        for token in cursor.tokens_before.iter().rev().take(Self::LIMIT) {
            match token.kind() {
                TK::Name | TK::Dot | TK::Ellipsis => {
                    start = token.range().start();
                }
                _ => break,
            }
        }
        to_complete.push_str(&cursor.source[TextRange::new(start, end)]);

        // If the typed text corresponds precisely to a keyword,
        // then as a special case, consider it "incomplete" for that
        // keyword. This occurs when the cursor is immediately at the
        // end of `import` or `as`, e.g., `import<CURSOR>`. So we
        // should provide it as a completion so that the end user can
        // confirm it as-is. We special case this because a complete
        // `import` or `as` gets special recognition as a special token
        // kind, and it isn't worth complicating the state machine
        // above to account for this.
        //
        // We also handle the more general "incomplete" cases here too.
        // Basically, `incomplete_as_or_import` is set to `true` when
        // we detect an "adjacent" name in an import statement. Some
        // examples:
        //
        //     from foo <CURSOR>
        //     from foo imp<CURSOR>
        //     from foo import bar <CURSOR>
        //     from foo import bar a<CURSOR>
        //     import foo <CURSOR>
        //     import foo a<CURSOR>
        //
        // Since there is a very limited number of cases, we can
        // suggest `import` when an `import` token isn't present. And
        // `as` when an `import` token *is* present. Notably, `as` can
        // only appear after an `import` keyword!
        if cursor.typed == Some("import") || (incomplete_as_or_import && import.is_none()) {
            return Some(ImportStatement::Incomplete(IncompleteImport::Import));
        } else if cursor.typed == Some("as") || (incomplete_as_or_import && import.is_some()) {
            return Some(ImportStatement::Incomplete(IncompleteImport::As));
        }
        match (from, import) {
            (None, None) => None,
            (None, Some(import)) => {
                let ast = find_ast_for_import(cursor.parsed, import)?;
                // If we found a dot near the cursor, then this
                // must be a request for submodule completions.
                let kind = if initial_dot {
                    let (parent, _) = to_complete.rsplit_once('.')?;
                    let module_name = ModuleName::new(parent)?;
                    ImportKind::Submodule {
                        parent: module_name,
                    }
                } else {
                    ImportKind::Module
                };
                Some(ImportStatement::Import(Import { ast, kind }))
            }
            (Some(from), import) => {
                let ast = find_ast_for_from_import(cursor.parsed, from)?;
                // If we saw an `import` keyword, then that means the
                // cursor must be *after* the `import`. And thus we
                // only ever need to offer completions for importable
                // elements from the module being imported.
                let kind = if import.is_some() {
                    FromImportKind::Attribute
                } else if !initial_dot {
                    FromImportKind::Module
                } else {
                    let to_complete_without_leading_dots = to_complete.trim_start_matches('.');

                    // When there aren't any leading dots to trim, then we
                    // have a regular absolute import. Otherwise, it's relative.
                    if to_complete == to_complete_without_leading_dots {
                        let (parent, _) = to_complete.rsplit_once('.')?;
                        let parent = ModuleName::new(parent)?;
                        FromImportKind::Submodule { parent }
                    } else {
                        let all_dots = to_complete.chars().all(|c| c == '.');
                        // We should suggest `import` in `from ...<CURSOR>`
                        // and `from ...imp<CURSOR>`.
                        let import_keyword_allowed =
                            all_dots || !to_complete_without_leading_dots.contains('.');
                        let parent = if all_dots {
                            ModuleName::from_import_statement(db, file, ast).ok()?
                        } else {
                            // We know `to_complete` is not all dots.
                            // But that it starts with a dot.
                            // So we must have one of `..foo`, `..foo.`
                            // or `..foo.bar`. We drop the leading dots,
                            // since those are captured by `ast.level`.
                            // From there, we can treat it like a normal
                            // module name. We want to list submodule
                            // completions, so we pop off the last element
                            // if there are any remaining dots.
                            let parent = to_complete_without_leading_dots
                                .rsplit_once('.')
                                .map(|(parent, _)| parent);
                            ModuleName::from_identifier_parts(db, file, parent, ast.level).ok()?
                        };
                        FromImportKind::Relative {
                            parent,
                            import_keyword_allowed,
                        }
                    }
                };
                Some(ImportStatement::FromImport(FromImport { ast, kind }))
            }
        }
    }

    /// Add completions, if any and if appropriate, based on the detected
    /// import statement.
    fn add_completions<'db>(
        &self,
        db: &'db dyn Db,
        file: File,
        completions: &mut Completions<'db>,
    ) {
        let model = SemanticModel::new(db, file);
        match *self {
            ImportStatement::Import(Import { ref kind, .. }) => match *kind {
                ImportKind::Module => {
                    add_import_completions(db, completions, model.import_completions());
                }
                ImportKind::Submodule { ref parent } => {
                    add_import_completions(
                        db,
                        completions,
                        model.import_submodule_completions_for_name(parent),
                    );
                }
            },
            ImportStatement::FromImport(FromImport { ast, ref kind }) => match *kind {
                FromImportKind::Module => {
                    add_import_completions(db, completions, model.import_completions());
                }
                FromImportKind::Submodule { ref parent } => {
                    add_import_completions(
                        db,
                        completions,
                        model.import_submodule_completions_for_name(parent),
                    );
                }
                FromImportKind::Relative {
                    ref parent,
                    import_keyword_allowed,
                } => {
                    add_import_completions(
                        db,
                        completions,
                        model.import_submodule_completions_for_name(parent),
                    );
                    if import_keyword_allowed {
                        completions.add(CompletionBuilder::keyword("import"));
                    }
                }
                FromImportKind::Attribute => {
                    let module_dependency_kind = model
                        .resolve_module(ast.module.as_ref().map(ast::Identifier::as_str), ast.level)
                        .map(|module| ModuleDependencyKind::from_module(db, module));
                    add_import_completions_with_module_dependency_kind(
                        db,
                        completions,
                        model.from_import_completions(ast),
                        module_dependency_kind,
                    );
                }
            },
            ImportStatement::Incomplete(IncompleteImport::As) => {
                completions.add(CompletionBuilder::keyword("as"));
            }
            ImportStatement::Incomplete(IncompleteImport::Import) => {
                completions.add(CompletionBuilder::keyword("import"));
            }
        }
    }
}

fn add_import_completions_with_module_dependency_kind<'db>(
    db: &'db dyn Db,
    completions: &mut Completions<'db>,
    semantic_completions: impl IntoIterator<Item = SemanticCompletion<'db>>,
    module_dependency_kind: Option<ModuleDependencyKind>,
) {
    add_import_completions_impl(db, completions, semantic_completions, |_| {
        module_dependency_kind
    });
}

fn add_import_completions<'db>(
    db: &'db dyn Db,
    completions: &mut Completions<'db>,
    semantic_completions: impl IntoIterator<Item = SemanticCompletion<'db>>,
) {
    add_import_completions_impl(db, completions, semantic_completions, |semantic| {
        if let Some(Type::ModuleLiteral(module_literal)) = semantic.ty {
            Some(ModuleDependencyKind::from_module(
                db,
                module_literal.module(db),
            ))
        } else {
            None
        }
    });
}

fn add_import_completions_impl<'db>(
    db: &'db dyn Db,
    completions: &mut Completions<'db>,
    semantic_completions: impl IntoIterator<Item = SemanticCompletion<'db>>,
    module_dependency_kind: impl Fn(&SemanticCompletion<'db>) -> Option<ModuleDependencyKind>,
) {
    for semantic in semantic_completions {
        let module_dependency_kind = module_dependency_kind(&semantic);
        let mut builder = CompletionBuilder::from_semantic_completion(db, semantic);
        if let Some(module_dependency_kind) = module_dependency_kind {
            builder = builder.module_dependency_kind(module_dependency_kind);
        }
        completions.add(builder);
    }
}

/// Finds the AST node, if available, corresponding to the given `from`
/// token.
///
/// This always returns `None` when the `token` is not a `from` token.
fn find_ast_for_from_import<'p>(
    parsed: &'p ParsedModuleRef,
    token: &Token,
) -> Option<&'p ast::StmtImportFrom> {
    let covering_node = covering_node(parsed.syntax().into(), token.range())
        .find_first(|node| node.is_stmt_import_from())
        .ok()?;
    let ast::AnyNodeRef::StmtImportFrom(from_import) = covering_node.node() else {
        return None;
    };
    Some(from_import)
}

/// Finds the AST node, if available, corresponding to the given `import`
/// token.
///
/// This always returns `None` when the `token` is not a `import` token.
fn find_ast_for_import<'p>(
    parsed: &'p ParsedModuleRef,
    token: &Token,
) -> Option<&'p ast::StmtImport> {
    let covering_node = covering_node(parsed.syntax().into(), token.range())
        .find_first(|node| node.is_stmt_import())
        .ok()?;
    let ast::AnyNodeRef::StmtImport(import) = covering_node.node() else {
        return None;
    };
    Some(import)
}

/// Returns a slice of tokens that all start before the given
/// [`TextSize`] offset.
///
/// If the given offset is between two tokens, the returned slice will end just
/// before the following token. In other words, if the offset is between the
/// end of previous token and start of next token, the returned slice will end
/// just before the next token.
///
/// Unlike `Tokens::before`, this never panics. If `offset` is within a token's
/// range (including if it's at the very beginning), then that token will be
/// included in the slice returned.
fn tokens_start_before(tokens: &Tokens, offset: TextSize) -> &[Token] {
    let partition_point = tokens.partition_point(|token| token.start() < offset);

    &tokens[..partition_point]
}

/// Returns a suffix of `tokens` corresponding to the `kinds` given.
///
/// If a suffix of `tokens` with the given `kinds` could not be found,
/// then `None` is returned.
///
/// This is useful for matching specific patterns of token sequences
/// in order to identify what kind of completions we should offer.
fn token_suffix_by_kinds<const N: usize>(
    tokens: &[Token],
    kinds: [TokenKind; N],
) -> Option<[&Token; N]> {
    if kinds.len() > tokens.len() {
        return None;
    }
    for (token, expected_kind) in tokens.iter().rev().zip(kinds.iter().rev()) {
        if &token.kind() != expected_kind {
            return None;
        }
    }
    Some(std::array::from_fn(|i| {
        &tokens[tokens.len() - (kinds.len() - i)]
    }))
}

/// Returns `true` if the token is a `Name` or a keyword.
///
/// Keywords are included because the lexer will lex a partially-typed
/// attribute name as a keyword token when it happens to match one
/// (e.g., `{1}.is` lexes `is` as `TokenKind::Is` rather than `TokenKind::Name`).
fn is_name_like_token(token: &Token) -> bool {
    matches!(token.kind(), TokenKind::Name) || token.kind().is_keyword()
}

/// Returns the "kind" of a completion using just its type information.
///
/// This is meant to be a very general classification of this completion.
/// Typically, this is communicated from the LSP server to a client, and
/// the client uses this information to help improve the UX (perhaps by
/// assigning an icon of some kind to the completion).
///
/// This is done on a best effort basis and may not return anything. In
/// general, if callers have more specific knowledge about the kind of
/// a completion, then they should use that to explicitly set its kind
/// on `CompletionBuilder`.
fn completion_kind_from_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<CompletionKind> {
    type CompletionKindVisitor<'db> =
        CycleDetector<CompletionKind, Type<'db>, Option<CompletionKind>, 3>;

    fn imp<'db>(
        db: &'db dyn Db,
        ty: Type<'db>,
        visitor: &CompletionKindVisitor<'db>,
    ) -> Option<CompletionKind> {
        Some(match ty {
            Type::FunctionLiteral(_)
            | Type::DataclassDecorator(_)
            | Type::WrapperDescriptor(_)
            | Type::DataclassTransformer(_)
            | Type::Callable(_) => CompletionKind::Function,
            Type::BoundMethod(_) | Type::KnownBoundMethod(_) => CompletionKind::Method,
            Type::ModuleLiteral(_) => CompletionKind::Module,
            Type::ClassLiteral(_) | Type::GenericAlias(_) | Type::SubclassOf(_) => {
                CompletionKind::Class
            }
            // This is a little weird for "struct." I'm mostly interpreting
            // "struct" here as a more general "object." ---AG
            Type::NominalInstance(_)
            | Type::PropertyInstance(_)
            | Type::BoundSuper(_)
            | Type::TypedDict(_)
            | Type::NewTypeInstance(_)
            | Type::EnumComplement(_) => CompletionKind::Struct,
            Type::LiteralValue(literal) if literal.is_enum() => CompletionKind::Enum,
            Type::LiteralValue(_) | Type::TypeIs(_) | Type::TypeGuard(_) | Type::TypeForm(_) => {
                CompletionKind::Value
            }
            Type::ProtocolInstance(_) => CompletionKind::Interface,
            Type::TypeVar(_) => CompletionKind::TypeParameter,
            Type::Union(union) => union
                .elements(db)
                .iter()
                .find_map(|&ty| imp(db, ty, visitor))?,
            Type::Intersection(intersection) => intersection
                .iter_positive(db)
                .find_map(|ty| imp(db, ty, visitor))?,
            Type::Dynamic(_)
            | Type::Divergent(_)
            | Type::Never
            | Type::SpecialForm(_)
            | Type::KnownInstance(_)
            | Type::AlwaysTruthy
            | Type::AlwaysFalsy => return None,
            Type::TypeAlias(alias) => {
                visitor.visit(ty, || imp(db, alias.value_type(db), visitor))?
            }
        })
    }
    imp(db, ty, &CompletionKindVisitor::default())
}

/// Defines an ordering relating the two completions for ranking purposes.
///
/// A `Ordering::Less` is returned when `c1` should be ranked above `c2`. A
/// `Ordering::Greater` is returned when `c1` should be ranked below `c2`.
/// In other words, an ascending sort used with this comparison routine will
/// yields the "best ranked" completions first. This scheme was chosen so that
/// this type works with a max-heap (such as `std::collections::BinaryHeap`).
///
/// Note that this could have been implemented via `Eq` and `Ord` impls on
/// `Completion` directly, but is instead a separate type to avoid conflating
/// relevance ranking with identity.
#[derive(Debug)]
struct CompletionRanker<'db>(Completion<'db>);

impl Eq for CompletionRanker<'_> {}

impl PartialEq for CompletionRanker<'_> {
    fn eq(&self, rhs: &CompletionRanker<'_>) -> bool {
        self.0.relevance == rhs.0.relevance
            && self.0.name == rhs.0.name
            && self.0.module_dependency_kind == rhs.0.module_dependency_kind
            && self.0.module_name == rhs.0.module_name
    }
}

impl Ord for CompletionRanker<'_> {
    fn cmp(&self, rhs: &CompletionRanker<'_>) -> Ordering {
        (
            &self.0.relevance,
            &self.0.name,
            self.0.module_dependency_kind,
            &self.0.module_name,
        )
            .cmp(&(
                &rhs.0.relevance,
                &rhs.0.name,
                rhs.0.module_dependency_kind,
                &rhs.0.module_name,
            ))
    }
}

impl PartialOrd for CompletionRanker<'_> {
    fn partial_cmp(&self, rhs: &CompletionRanker<'_>) -> Option<Ordering> {
        Some(self.cmp(rhs))
    }
}

#[cfg(test)]
mod tests {
    use insta::assert_snapshot;
    use ruff_python_ast::helpers::is_dunder;
    use ruff_python_ast::token::{TokenKind, Tokens};
    use ruff_python_parser::{Mode, ParseOptions};
    use ty_module_resolver::ModuleName;

    use crate::CompletionCapabilities;
    use crate::completion::{Completion, completion};
    use crate::tests::{CursorTest, CursorTestBuilder, SitePackagesCursorTestBuilder};

    use super::{CompletionKind, CompletionSettings, token_suffix_by_kinds};

    #[test]
    fn token_suffixes_match() {
        insta::assert_debug_snapshot!(
            token_suffix_by_kinds(&tokenize("foo.x"), [TokenKind::Newline]),
            @"
        Some(
            [
                Newline 5..5,
            ],
        )
        ",
        );

        insta::assert_debug_snapshot!(
            token_suffix_by_kinds(&tokenize("foo.x"), [TokenKind::Name, TokenKind::Newline]),
            @"
        Some(
            [
                Name 4..5,
                Newline 5..5,
            ],
        )
        ",
        );

        let all = [
            TokenKind::Name,
            TokenKind::Dot,
            TokenKind::Name,
            TokenKind::Newline,
        ];
        insta::assert_debug_snapshot!(
            token_suffix_by_kinds(&tokenize("foo.x"), all),
            @"
        Some(
            [
                Name 0..3,
                Dot 3..4,
                Name 4..5,
                Newline 5..5,
            ],
        )
        ",
        );
    }

    #[test]
    fn token_suffixes_nomatch() {
        insta::assert_debug_snapshot!(
            token_suffix_by_kinds(&tokenize("foo.x"), [TokenKind::Name]),
            @"None",
        );

        let too_many = [
            TokenKind::Dot,
            TokenKind::Name,
            TokenKind::Dot,
            TokenKind::Name,
            TokenKind::Newline,
        ];
        insta::assert_debug_snapshot!(
            token_suffix_by_kinds(&tokenize("foo.x"), too_many),
            @"None",
        );
    }

    #[test]
    fn empty() {
        let test = completion_test_builder(
            "\
<CURSOR>
",
        );

        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found after filtering out completions>",
        );
    }

    #[test]
    fn builtins() {
        let builder = completion_test_builder(
            "\
<CURSOR>
",
        );
        let test = builder.build();

        test.contains("filter");
        // Sunder items should be filtered out
        test.not_contains("_T");
        // Dunder attributes should not be stripped
        test.contains("__annotations__");
        // See `private_symbols_in_stub` for more comprehensive testing private of symbol filtering.
    }

    #[test]
    fn keywords() {
        let test = completion_test_builder(
            "\
<CURSOR>
",
        );

        assert_snapshot!(
            test.skip_builtins().build().snapshot(),
            @"
        and
        as
        assert
        async
        await
        break
        case
        class
        continue
        def
        del
        elif
        else
        except
        finally
        for
        from
        global
        if
        import
        in
        is
        lambda
        match
        nonlocal
        not
        or
        pass
        raise
        return
        try
        while
        with
        yield
        ",
        );
    }

    #[test]
    fn inside_token() {
        let test = completion_test_builder(
            "\
foo_bar_baz = 1
x = foo<CURSOR>bad
",
        );

        assert_snapshot!(
            test.skip_builtins().skip_auto_import().build().snapshot(),
            @"foo_bar_baz",
        );
    }

    #[test]
    fn type_keyword_dedup() {
        let test = completion_test_builder(
            "\
type<CURSOR>
",
        );

        assert_snapshot!(
            test.type_signatures().skip_auto_import().build().snapshot(),
            @"
        type :: <class 'type'>
        TypeError :: <class 'TypeError'>
        ",
        );
    }

    #[test]
    fn builtins_not_included_object_attr() {
        let builder = completion_test_builder(
            "\
import re

re.<CURSOR>
",
        );
        builder.build().not_contains("filter");
    }

    #[test]
    fn builtins_not_included_import() {
        let builder = completion_test_builder(
            "\
from re import <CURSOR>
",
        );
        builder.build().not_contains("filter");
    }

    #[test]
    fn imports1() {
        let builder = completion_test_builder(
            "\
import re

<CURSOR>
",
        );

        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"re");
    }

    #[test]
    fn imports2() {
        let builder = completion_test_builder(
            "\
from os import path

<CURSOR>
",
        );

        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"path");
    }

    // N.B. We don't currently explore module APIs. This
    // is still just emitting symbols from the detected scope.
    #[test]
    fn module_api() {
        let builder = completion_test_builder(
            "\
import re

re.<CURSOR>
",
        );
        builder.build().contains("findall");
    }

    #[test]
    fn private_symbols_in_stub() {
        let builder = CursorTest::builder()
            .source(
                "package/__init__.pyi",
                r#"\
from typing import TypeAlias, Literal, TypeVar, ParamSpec, TypeVarTuple, Protocol

public_name = 1
_private_name = 1
__mangled_name = 1
__dunder_name__ = 1

public_type_var = TypeVar("public_type_var")
_private_type_var = TypeVar("_private_type_var")
__mangled_type_var = TypeVar("__mangled_type_var")

public_param_spec = ParamSpec("public_param_spec")
_private_param_spec = ParamSpec("_private_param_spec")

public_type_var_tuple = TypeVarTuple("public_type_var_tuple")
_private_type_var_tuple = TypeVarTuple("_private_type_var_tuple")

public_explicit_type_alias: TypeAlias = Literal[1]
_private_explicit_type_alias: TypeAlias = Literal[1]

public_implicit_union_alias = int | str
_private_implicit_union_alias = int | str

class PublicProtocol(Protocol):
    def method(self) -> None: ...

class _PrivateProtocol(Protocol):
    def method(self) -> None: ...
"#,
            )
            .source("main.py", "import package; package.<CURSOR>")
            .completion_test_builder();

        let test = builder.build();
        test.contains("public_name");
        test.contains("_private_name");
        test.contains("__mangled_name");
        test.contains("__dunder_name__");
        test.contains("public_type_var");
        test.not_contains("_private_type_var");
        test.not_contains("__mangled_type_var");
        test.contains("public_param_spec");
        test.not_contains("_private_param_spec");
        test.contains("public_type_var_tuple");
        test.not_contains("_private_type_var_tuple");
        test.contains("public_explicit_type_alias");
        test.not_contains("_private_explicit_type_alias");
        test.contains("public_implicit_union_alias");
        test.not_contains("_private_implicit_union_alias");
        test.contains("PublicProtocol");
        test.not_contains("_PrivateProtocol");
    }

    /// Unlike [`private_symbols_in_stub`], this test doesn't use a `.pyi` file so all of the names
    /// are visible.
    #[test]
    fn private_symbols_in_module() {
        let builder = CursorTest::builder()
            .source(
                "package/__init__.py",
                r#"\
from typing import TypeAlias, Literal, TypeVar, ParamSpec, TypeVarTuple, Protocol

public_name = 1
_private_name = 1
__mangled_name = 1
__dunder_name__ = 1

public_type_var = TypeVar("public_type_var")
_private_type_var = TypeVar("_private_type_var")
__mangled_type_var = TypeVar("__mangled_type_var")

public_param_spec = ParamSpec("public_param_spec")
_private_param_spec = ParamSpec("_private_param_spec")

public_type_var_tuple = TypeVarTuple("public_type_var_tuple")
_private_type_var_tuple = TypeVarTuple("_private_type_var_tuple")

public_explicit_type_alias: TypeAlias = Literal[1]
_private_explicit_type_alias: TypeAlias = Literal[1]

class PublicProtocol(Protocol):
    def method(self) -> None: ...

class _PrivateProtocol(Protocol):
    def method(self) -> None: ...
"#,
            )
            .source("main.py", "import package; package.<CURSOR>")
            .completion_test_builder();

        let test = builder.build();
        test.contains("public_name");
        test.contains("_private_name");
        test.contains("__mangled_name");
        test.contains("__dunder_name__");
        test.contains("public_type_var");
        test.contains("_private_type_var");
        test.contains("__mangled_type_var");
        test.contains("public_param_spec");
        test.contains("_private_param_spec");
        test.contains("public_type_var_tuple");
        test.contains("_private_type_var_tuple");
        test.contains("public_explicit_type_alias");
        test.contains("_private_explicit_type_alias");
        test.contains("PublicProtocol");
        test.contains("_PrivateProtocol");
    }

    #[test]
    fn one_function_prefix() {
        let builder = completion_test_builder(
            "\
def foo(): ...

f<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"foo",
        );
    }

    #[test]
    fn one_function_not_prefix() {
        let builder = completion_test_builder(
            "\
def foo(): ...

g<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"<No completions found after filtering out completions>",
        );
    }

    #[test]
    fn one_function_blank() {
        let builder = completion_test_builder(
            "\
def foo(): ...

<CURSOR>
",
        );

        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"foo");
    }

    #[test]
    fn nested_function_prefix() {
        let builder = completion_test_builder(
            "\
def foo():
    def foofoo(): ...

f<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"foo",
        );
    }

    #[test]
    fn nested_function_blank() {
        let builder = completion_test_builder(
            "\
def foo():
    def foofoo(): ...

<CURSOR>
",
        );

        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"foo");
    }

    #[test]
    fn nested_function_not_in_global_scope_prefix() {
        let builder = completion_test_builder(
            "\
def foo():
    def foofoo(): ...
    f<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"
        foo
        foofoo
        ");
    }

    #[test]
    fn nested_function_not_in_global_scope_blank() {
        let builder = completion_test_builder(
            "\
def foo():
    def foofoo(): ...
    <CURSOR>
",
        );

        // FIXME: Should include `foofoo`.
        //
        // `foofoo` isn't included at present (2025-05-22). The problem
        // here is that the AST for `def foo():` doesn't encompass the
        // trailing indentation. So when the cursor position is in that
        // trailing indentation, we can't (easily) get a handle to the
        // right scope. And even if we could, the AST expressions for
        // `def foo():` and `def foofoo(): ...` end at precisely the
        // same point. So there is no AST we can hold after the end of
        // `foofoo` but before the end of `foo`. So at the moment, it's
        // not totally clear how to get the right scope.
        //
        // If we didn't want to change the ranges on the AST nodes,
        // another approach here would be to get the inner most scope,
        // and explore its ancestors until we get to a level that
        // matches the current cursor's indentation. This seems fraught
        // however. It's not clear to me that we can always assume a
        // correspondence between scopes and indentation level.
        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"foo");
    }

    #[test]
    fn double_nested_function_not_in_global_scope_prefix1() {
        let builder = completion_test_builder(
            "\
def foo():
    def foofoo():
        def foofoofoo(): ...
    f<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"
        foo
        foofoo
        ");
    }

    #[test]
    fn double_nested_function_not_in_global_scope_prefix2() {
        let builder = completion_test_builder(
            "\
def foo():
    def foofoo():
        def foofoofoo(): ...
    f<CURSOR>",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"
        foo
        foofoo
        ");
    }

    #[test]
    fn double_nested_function_not_in_global_scope_prefix3() {
        let builder = completion_test_builder(
            "\
def foo():
    def foofoo():
        def foofoofoo(): ...
    f<CURSOR>
def frob(): ...
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"
        foo
        foofoo
        frob
        ");
    }

    #[test]
    fn double_nested_function_not_in_global_scope_prefix4() {
        let builder = completion_test_builder(
            "\
def foo():
    def foofoo():
        def foofoofoo(): ...
f<CURSOR>
def frob(): ...
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"
        foo
        frob
        ");
    }

    #[test]
    fn double_nested_function_not_in_global_scope_prefix5() {
        let builder = completion_test_builder(
            "\
def foo():
    def foofoo():
        def foofoofoo(): ...
        f<CURSOR>
def frob(): ...
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"
        foo
        foofoo
        foofoofoo
        frob
        ");
    }

    #[test]
    fn double_nested_function_not_in_global_scope_blank1() {
        let builder = completion_test_builder(
            "\
def foo():
    def foofoo():
        def foofoofoo(): ...
    <CURSOR>
",
        );

        // FIXME: Should include `foofoo` (but not `foofoofoo`).
        //
        // The tests below fail for the same reason that
        // `nested_function_not_in_global_scope_blank` fails: there is no
        // space in the AST ranges after the end of `foofoofoo` but before
        // the end of `foofoo`. So either the AST needs to be tweaked to
        // account for the indented whitespace, or some other technique
        // needs to be used to get the scope containing `foofoo` but not
        // `foofoofoo`.
        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"foo");
    }

    #[test]
    fn double_nested_function_not_in_global_scope_blank2() {
        let builder = completion_test_builder(
            " \
def foo():
    def foofoo():
        def foofoofoo(): ...
    <CURSOR>",
        );

        // FIXME: Should include `foofoo` (but not `foofoofoo`).
        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"foo");
    }

    #[test]
    fn double_nested_function_not_in_global_scope_blank3() {
        let builder = completion_test_builder(
            "\
def foo():
    def foofoo():
        def foofoofoo(): ...
    <CURSOR>
def frob(): ...
            ",
        );

        // FIXME: Should include `foofoo` (but not `foofoofoo`).
        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"
        foo
        frob
        ");
    }

    #[test]
    fn double_nested_function_not_in_global_scope_blank4() {
        let builder = completion_test_builder(
            "\
def foo():
    def foofoo():
        def foofoofoo(): ...
    <CURSOR>

def frob(): ...
",
        );

        // FIXME: Should include `foofoo` (but not `foofoofoo`).
        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"
        foo
        frob
        ");
    }

    #[test]
    fn double_nested_function_not_in_global_scope_blank5() {
        let builder = completion_test_builder(
            "\
def foo():
    def foofoo():
        def foofoofoo(): ...

    <CURSOR>

def frob(): ...
",
        );

        // FIXME: Should include `foofoo` (but not `foofoofoo`).
        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"
        foo
        frob
        ");
    }

    /// Regression test for <https://github.com/astral-sh/ty/issues/1392>
    ///
    /// This test ensures completions work when the cursor is at the
    /// start of a zero-length token.
    #[test]
    fn completion_at_eof() {
        completion_test_builder("def f(msg: str):\n    msg.<CURSOR>")
            .build()
            .contains("upper")
            .contains("capitalize");

        completion_test_builder("def f(msg: str):\n    msg.u<CURSOR>")
            .build()
            .contains("upper")
            .not_contains("capitalize");
    }

    #[test]
    fn list_comprehension1() {
        let builder = completion_test_builder(
            "\
[<CURSOR> for bar in [1, 2, 3]]
",
        );

        // TODO: it would be good if `bar` was included here, but
        // the list comprehension is not yet valid and so we do not
        // detect this as a definition of `bar`.
        assert_snapshot!(
            builder.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found after filtering out completions>",
        );
    }

    #[test]
    fn list_comprehension2() {
        let builder = completion_test_builder(
            "\
[f<CURSOR> for foo in [1, 2, 3]]
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"foo",
        );
    }

    #[test]
    fn list_comprehension_member() {
        completion_test_builder(
            "\
items: list[str] = []
[item.<CURSOR> for item in items]
",
        )
        .build()
        .contains("upper");
    }

    #[test]
    fn set_comprehension_member() {
        completion_test_builder(
            "\
items: list[str] = []
{item.<CURSOR> for item in items}
",
        )
        .build()
        .contains("upper");
    }

    #[test]
    fn generator_expression_member() {
        completion_test_builder(
            "\
items: list[str] = []
(item.<CURSOR> for item in items)
",
        )
        .build()
        .contains("upper");
    }

    #[test]
    fn lambda_prefix1() {
        let builder = completion_test_builder(
            "\
(lambda foo: (1 + f<CURSOR> + 2))(2)
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"foo",
        );
    }

    #[test]
    fn lambda_prefix2() {
        let builder = completion_test_builder(
            "\
(lambda foo: f<CURSOR> + 1)(2)
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"foo",
        );
    }

    #[test]
    fn lambda_prefix3() {
        let builder = completion_test_builder(
            "\
(lambda foo: (f<CURSOR> + 1))(2)
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"foo",
        );
    }

    #[test]
    fn lambda_prefix4() {
        let builder = completion_test_builder(
            "\
(lambda foo: 1 + f<CURSOR>)(2)
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"foo",
        );
    }

    #[test]
    fn lambda_blank1() {
        let builder = completion_test_builder(
            "\
(lambda foo: 1 + <CURSOR> + 2)(2)
",
        );

        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"foo");
    }

    #[test]
    fn lambda_blank2() {
        let builder = completion_test_builder(
            "\
(lambda foo: <CURSOR> + 1)(2)
",
        );

        // FIXME: Should include `foo`.
        //
        // This fails for similar reasons as above: the body of the
        // lambda doesn't include the position of <CURSOR> because
        // <CURSOR> is inside leading or trailing whitespace. (Even
        // when enclosed in parentheses. Specifically, parentheses
        // aren't part of the node's range unless it's relevant e.g.,
        // tuples.)
        //
        // The `lambda_blank1` test works because there are expressions
        // on either side of <CURSOR>.
        assert_snapshot!(
            builder.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found after filtering out completions>",
        );
    }

    #[test]
    fn lambda_blank3() {
        let builder = completion_test_builder(
            "\
(lambda foo: (<CURSOR> + 1))(2)
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found after filtering out completions>",
        );
    }

    #[test]
    fn lambda_blank4() {
        let builder = completion_test_builder(
            "\
(lambda foo: 1 + <CURSOR>)(2)
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().build().snapshot(),
            @"foo",
        );
    }

    #[test]
    fn class_prefix1() {
        let builder = completion_test_builder(
            "\
class Foo:
    bar = 1
    quux = b<CURSOR>
    frob = 3
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"
        bar
        frob
        ",
        );
    }

    #[test]
    fn class_prefix2() {
        let builder = completion_test_builder(
            "\
class Foo:
    bar = 1
    quux = b<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"bar",
        );
    }

    #[test]
    fn class_blank1() {
        let builder = completion_test_builder(
            "\
class Foo:
    bar = 1
    quux = <CURSOR>
    frob = 3
",
        );

        // FIXME: Should include `bar`, `quux` and `frob`.
        // (Unclear if `Foo` should be included, but a false
        // positive isn't the end of the world.)
        //
        // These don't work for similar reasons as other
        // tests above with the <CURSOR> inside of whitespace.
        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"Foo");
    }

    #[test]
    fn class_blank2() {
        let builder = completion_test_builder(
            "\
class Foo:
    bar = 1
    quux = <CURSOR>
    frob = 3
",
        );

        // FIXME: Should include `bar`, `quux` and `frob`.
        // (Unclear if `Foo` should be included, but a false
        // positive isn't the end of the world.)
        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"Foo");
    }

    #[test]
    fn class_super1() {
        let builder = completion_test_builder(
            "\
class Bar: ...

class Foo(<CURSOR>):
    bar = 1
",
        );

        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"
        Bar
        metaclass=
        ");
    }

    #[test]
    fn class_super2() {
        let builder = completion_test_builder(
            "\
class Foo(<CURSOR>):
    bar = 1

class Bar: ...
",
        );

        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"
        Bar
        metaclass=
        ");
    }

    #[test]
    fn class_super3() {
        let builder = completion_test_builder(
            "\
class Foo(<CURSOR>
    bar = 1

class Bar: ...
",
        );

        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"
        Bar
        metaclass=
        ");
    }

    #[test]
    fn class_super4() {
        let builder = completion_test_builder(
            "\
class Bar: ...

class Foo(<CURSOR>",
        );

        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"
        Bar
        metaclass=
        ");
    }

    #[test]
    fn class_metaclass() {
        let builder = completion_test_builder(
            "\
class Foo(meta<CURSOR>",
        );

        builder
            .skip_keywords()
            .skip_builtins()
            .build()
            .contains("metaclass");
    }

    #[test]
    fn class_metaclass_set() {
        let builder = completion_test_builder(
            "\
class Foo(metaclass=x, meta<CURSOR>",
        );

        builder
            .skip_keywords()
            .skip_builtins()
            .build()
            .not_contains("metaclass");
    }

    #[test]
    fn class_base_excludes_class_being_defined() {
        let builder = completion_test_builder(
            "\
class Foo(<CURSOR>
",
        );

        builder
            .skip_keywords()
            .skip_builtins()
            .build()
            .not_contains("Foo");
    }

    #[test]
    fn class_base_excludes_class_being_defined_with_typed_text() {
        let builder = completion_test_builder(
            "\
class Foo(Fo<CURSOR>
",
        );

        builder
            .skip_keywords()
            .skip_builtins()
            .build()
            .not_contains("Foo");
    }

    #[test]
    fn class_base_includes_prior_definition_with_same_name() {
        // When a class with the same name was previously defined,
        // the bases refer to the prior definition, so it should
        // still be offered as a completion.
        let builder = completion_test_builder(
            "\
class Foo: ...

class Foo(<CURSOR>
",
        );

        builder
            .skip_keywords()
            .skip_builtins()
            .build()
            .contains("Foo");
    }

    #[test]
    fn class_base_already_specified() {
        let builder = completion_test_builder(
            "\
class Foo: ...
class Bar: ...

class Baz(Foo, <CURSOR>
",
        );

        builder
            .skip_keywords()
            .skip_builtins()
            .build()
            .not_contains("Foo")
            .contains("Bar");
    }

    #[test]
    fn class_multiple_bases_already_specified() {
        let builder = completion_test_builder(
            "\
class A: ...
class B: ...
class C: ...

class D(A, B, <CURSOR>
",
        );

        builder
            .skip_keywords()
            .skip_builtins()
            .build()
            .not_contains("A")
            .not_contains("B")
            .contains("C");
    }

    #[test]
    fn class_base_same_name_different_module() {
        // Even though `Foo` is already a base class, the auto-import suggestion
        // for `other.Foo` should still be shown since it's a different symbol.
        // Note: We need to type some characters (e.g., "Fo") to trigger auto-import
        // suggestions, as empty queries don't generate unimported completions.
        let builder = CursorTest::builder()
            .source(
                "other.py",
                "\
class Foo: ...
",
            )
            .source(
                "main.py",
                "\
class Foo: ...

class Bar(Foo, Fo<CURSOR>
",
            )
            .completion_test_builder()
            .skip_keywords()
            .skip_builtins();
        let test = builder.build();

        let foo_completions: Vec<_> = test
            .completions()
            .iter()
            .filter(|c| c.name == "Foo")
            .collect();

        // The local Foo should be filtered out (no import required)
        assert!(
            !foo_completions.iter().any(|c| c.import.is_none()),
            "Local `Foo` should be filtered out as it's already a base class"
        );

        // The auto-import Foo from `other` module should be present
        assert!(
            foo_completions.iter().any(|c| c.import.is_some()),
            "Auto-import `Foo` from other module should still be suggested"
        );
    }

    #[test]
    fn class_base_qualified_already_specified() {
        // When a qualified base class like `other.Foo` is already specified,
        // the auto-import suggestion for `Foo` from `other` should be filtered.
        let builder = CursorTest::builder()
            .source(
                "other.py",
                "\
class Foo: ...
",
            )
            .source(
                "main.py",
                "\
import other

class Bar(other.Foo, Fo<CURSOR>
",
            )
            .completion_test_builder()
            .skip_keywords()
            .skip_builtins();
        let test = builder.build();

        let foo_completions: Vec<_> = test
            .completions()
            .iter()
            .filter(|c| c.name == "Foo")
            .collect();

        // The auto-import Foo from `other` should be filtered since `other.Foo`
        // is already a base class.
        assert!(
            foo_completions.is_empty(),
            "Auto-import `Foo` from `other` should be filtered since `other.Foo` is already a base"
        );
    }

    #[test]
    fn class_base_qualified_attribute_not_filtered() {
        // TODO: This test documents a known limitation. When completing attributes
        // on an already-imported module (e.g., `other.F<CURSOR>`), we don't filter
        // out classes that are already base classes. This is because attribute
        // completions don't have `import` or `qualified` set, so we can't match
        // them against the existing base classes.
        //
        // This is a false negative (showing a duplicate that should be hidden),
        // not a false positive, so it's a minor UX issue rather than a bug.
        let builder = CursorTest::builder()
            .source(
                "other.py",
                "\
class Foo: ...
class Bar: ...
",
            )
            .source(
                "main.py",
                "\
import other

class MyClass(other.Foo, other.F<CURSOR>): pass
",
            )
            .completion_test_builder()
            .skip_keywords()
            .skip_builtins();
        let test = builder.build();

        let foo_completions: Vec<_> = test
            .completions()
            .iter()
            .filter(|c| c.name == "Foo")
            .collect();

        // Ideally this would be empty since `other.Foo` is already a base class,
        // but we don't currently filter attribute completions on imported modules.
        assert_eq!(
            foo_completions.len(),
            1,
            "Foo is not filtered out (known limitation)"
        );
    }

    #[test]
    fn class_metaclass_generic() {
        let builder = completion_test_builder(
            "\
class Foo[T](meta<CURSOR>",
        );

        builder
            .skip_keywords()
            .skip_builtins()
            .build()
            .contains("metaclass");
    }

    #[test]
    fn class_typed_dict_total() {
        let builder = completion_test_builder(
            "\
from typing import TypedDict

class Foo(TypedDict, tot<CURSOR>
",
        );

        builder
            .skip_keywords()
            .skip_builtins()
            .build()
            .contains("total");
    }

    #[test]
    fn class_typed_dict_total_alias() {
        let builder = completion_test_builder(
            "\
from typing import TypedDict as TD

class Foo(TD, tot<CURSOR>
",
        );

        builder
            .skip_keywords()
            .skip_builtins()
            .build()
            .contains("total");
    }

    #[test]
    fn class_typed_dict_total_set() {
        let builder = completion_test_builder(
            "\
from typing import TypedDict

class Foo(TypedDict, total=False, tot<CURSOR>
",
        );

        builder
            .skip_keywords()
            .skip_builtins()
            .build()
            .not_contains("total");
    }

    #[test]
    fn class_typed_dict_total_subclass() {
        let builder = completion_test_builder(
            "\
from typing import TypedDict

class Foo(TypedDict):
    x: int

class Bar(Foo, to<CURSOR>)
",
        );

        builder
            .skip_keywords()
            .skip_builtins()
            .build()
            .contains("total");
    }

    #[test]
    fn class_typed_dict_total_pep695_generic() {
        let builder = completion_test_builder(
            "\
from typing import TypedDict

class Foo[T](TypedDict, to<CURSOR>)
",
        );

        builder
            .skip_keywords()
            .skip_builtins()
            .build()
            .contains("total");
    }

    #[test]
    fn class_typed_dict_total_typevar_generic() {
        let builder = completion_test_builder(
            "\
from typing import Generic, TypeVar, TypedDict

T = TypeVar('T')

class Foo(TypedDict, Generic[T], to<CURSOR>)
",
        );

        builder
            .skip_keywords()
            .skip_builtins()
            .build()
            .contains("total");
    }

    #[test]
    fn class_init1() {
        let builder = completion_test_builder(
            "\
class Quux:
    def __init__(self):
        self.foo = 1
        self.bar = 2
        self.baz = 3

quux = Quux()
quux.<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().type_signatures().build().snapshot(), @"
        bar :: int
        baz :: int
        foo :: int
        __annotations__ :: dict[str, Any]
        __class__ :: type[Quux]
        __delattr__ :: bound method Quux.__delattr__(name: str, /) -> None
        __dict__ :: dict[str, Any]
        __dir__ :: bound method Quux.__dir__() -> Iterable[str]
        __doc__ :: str | None
        __eq__ :: bound method Quux.__eq__(value: object, /) -> bool
        __format__ :: bound method Quux.__format__(format_spec: str, /) -> str
        __getattribute__ :: bound method Quux.__getattribute__(name: str, /) -> Any
        __getstate__ :: bound method Quux.__getstate__() -> object
        __hash__ :: bound method Quux.__hash__() -> int
        __init__ :: bound method Quux.__init__() -> Unknown
        __init_subclass__ :: bound method type[Quux].__init_subclass__() -> None
        __module__ :: str
        __ne__ :: bound method Quux.__ne__(value: object, /) -> bool
        __new__ :: def __new__[Self](cls) -> Self
        __reduce__ :: bound method Quux.__reduce__() -> str | tuple[Any, ...]
        __reduce_ex__ :: bound method Quux.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
        __repr__ :: bound method Quux.__repr__() -> str
        __setattr__ :: bound method Quux.__setattr__(name: str, value: Any, /) -> None
        __sizeof__ :: bound method Quux.__sizeof__() -> int
        __str__ :: bound method Quux.__str__() -> str
        __subclasshook__ :: bound method type[Quux].__subclasshook__(subclass: type, /) -> bool
        ");
    }

    #[test]
    fn class_init2() {
        let builder = completion_test_builder(
            "\
class Quux:
    def __init__(self):
        self.foo = 1
        self.bar = 2
        self.baz = 3

quux = Quux()
quux.b<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().type_signatures().build().snapshot(), @"
        bar :: int
        baz :: int
        __getattribute__ :: bound method Quux.__getattribute__(name: str, /) -> Any
        __init_subclass__ :: bound method type[Quux].__init_subclass__() -> None
        __subclasshook__ :: bound method type[Quux].__subclasshook__(subclass: type, /) -> bool
        ");
    }

    #[test]
    fn metaclass1() {
        let builder = completion_test_builder(
            "\
class Meta(type):
    @property
    def meta_attr(self) -> int:
        return 0

class C(metaclass=Meta): ...

C.<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().type_signatures().build().snapshot(), @"
        meta_attr :: int
        mro :: bound method <class 'C'>.mro() -> list[type]
        __annotate__ :: (() -> dict[str, Any]) | None
        __annotations__ :: dict[str, Any]
        __base__ :: type | None
        __bases__ :: tuple[type, ...]
        __basicsize__ :: int
        __call__ :: bound method <class 'C'>.__call__(...) -> Any
        __class__ :: <class 'Meta'>
        __delattr__ :: def __delattr__(self, name: str, /) -> None
        __dict__ :: dict[str, Any]
        __dictoffset__ :: int
        __dir__ :: def __dir__(self) -> Iterable[str]
        __doc__ :: str | None
        __eq__ :: def __eq__(self, value: object, /) -> bool
        __flags__ :: int
        __format__ :: def __format__(self, format_spec: str, /) -> str
        __getattribute__ :: def __getattribute__(self, name: str, /) -> Any
        __getstate__ :: def __getstate__(self) -> object
        __hash__ :: def __hash__(self) -> int
        __init__ :: def __init__(self) -> None
        __init_subclass__ :: bound method <class 'C'>.__init_subclass__() -> None
        __instancecheck__ :: bound method <class 'C'>.__instancecheck__(instance: Any, /) -> bool
        __itemsize__ :: int
        __module__ :: str
        __mro__ :: tuple[type, ...]
        __name__ :: str
        __ne__ :: def __ne__(self, value: object, /) -> bool
        __new__ :: def __new__[Self](cls) -> Self
        __or__ :: bound method <class 'C'>.__or__[Self](value: Any, /) -> UnionType | Self
        __prepare__ :: bound method <class 'Meta'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
        __qualname__ :: str
        __reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
        __reduce_ex__ :: def __reduce_ex__(self, protocol: SupportsIndex, /) -> str | tuple[Any, ...]
        __repr__ :: def __repr__(self) -> str
        __ror__ :: bound method <class 'C'>.__ror__[Self](value: Any, /) -> UnionType | Self
        __setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
        __sizeof__ :: def __sizeof__(self) -> int
        __str__ :: def __str__(self) -> str
        __subclasscheck__ :: bound method <class 'C'>.__subclasscheck__(subclass: type, /) -> bool
        __subclasses__ :: bound method <class 'C'>.__subclasses__[Self]() -> list[Self]
        __subclasshook__ :: bound method <class 'C'>.__subclasshook__(subclass: type, /) -> bool
        __text_signature__ :: str | None
        __type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
        __weakrefoffset__ :: int
        ");
    }

    #[test]
    fn metaclass2() {
        let builder = completion_test_builder(
            "\
class Meta(type):
    @property
    def meta_attr(self) -> int:
        return 0

class C(metaclass=Meta): ...

Meta.<CURSOR>
",
        );

        insta::with_settings!({
            // The formatting of some types are different depending on
            // whether we're in release mode or not. These differences
            // aren't really relevant for completion tests AFAIK, so
            // just redact them. ---AG
            filters => [(r"(?m)\s*__(annotations|new|annotate)__.+$", "")]},
            {
                assert_snapshot!(
                    builder.skip_keywords().skip_builtins().type_signatures().build().snapshot(), @"
                meta_attr :: property
                mro :: def mro(self) -> list[type]
                __base__ :: type | None
                __bases__ :: tuple[type, ...]
                __basicsize__ :: int
                __call__ :: def __call__(self, *args: Any, **kwds: Any) -> Any
                __class__ :: <class 'type'>
                __delattr__ :: def __delattr__(self, name: str, /) -> None
                __dict__ :: MappingProxyType[str, Any]
                __dictoffset__ :: int
                __dir__ :: def __dir__(self) -> Iterable[str]
                __doc__ :: str | None
                __eq__ :: def __eq__(self, value: object, /) -> bool
                __flags__ :: int
                __format__ :: def __format__(self, format_spec: str, /) -> str
                __getattribute__ :: def __getattribute__(self, name: str, /) -> Any
                __getstate__ :: def __getstate__(self) -> object
                __hash__ :: def __hash__(self) -> int
                __init__ :: Overload[(self, o: object, /) -> None, (self, name: str, bases: tuple[type, ...], dict: dict[str, Any], /, **kwds: Any) -> None]
                __init_subclass__ :: bound method <class 'Meta'>.__init_subclass__() -> None
                __instancecheck__ :: def __instancecheck__(self, instance: Any, /) -> bool
                __itemsize__ :: int
                __module__ :: str
                __mro__ :: tuple[type, ...]
                __name__ :: str
                __ne__ :: def __ne__(self, value: object, /) -> bool
                __or__ :: def __or__[Self](self: Self, value: Any, /) -> UnionType | Self
                __prepare__ :: bound method <class 'Meta'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
                __qualname__ :: str
                __reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
                __reduce_ex__ :: def __reduce_ex__(self, protocol: SupportsIndex, /) -> str | tuple[Any, ...]
                __repr__ :: def __repr__(self) -> str
                __ror__ :: def __ror__[Self](self: Self, value: Any, /) -> UnionType | Self
                __setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
                __sizeof__ :: def __sizeof__(self) -> int
                __str__ :: def __str__(self) -> str
                __subclasscheck__ :: def __subclasscheck__(self, subclass: type, /) -> bool
                __subclasses__ :: def __subclasses__[Self](self: Self) -> list[Self]
                __subclasshook__ :: bound method <class 'Meta'>.__subclasshook__(subclass: type, /) -> bool
                __text_signature__ :: str | None
                __type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
                __weakrefoffset__ :: int
                ");
            }
        );
    }

    #[test]
    fn class_init3() {
        let builder = completion_test_builder(
            "\
class Quux:
    def __init__(self):
        self.foo = 1
        self.bar = 2
        self.<CURSOR>
        self.baz = 3
",
        );

        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"
        bar
        baz
        foo
        __annotations__
        __class__
        __delattr__
        __dict__
        __dir__
        __doc__
        __eq__
        __format__
        __getattribute__
        __getstate__
        __hash__
        __init__
        __init_subclass__
        __module__
        __ne__
        __new__
        __reduce__
        __reduce_ex__
        __repr__
        __setattr__
        __sizeof__
        __str__
        __subclasshook__
        ");
    }

    #[test]
    fn class_attributes1() {
        let builder = completion_test_builder(
            "\
class Quux:
    some_attribute: int = 1

    def __init__(self):
        self.foo = 1
        self.bar = 2
        self.baz = 3

    def some_method(self) -> int:
        return 1

    @property
    def some_property(self) -> int:
        return 1

    @classmethod
    def some_class_method(self) -> int:
        return 1

    @staticmethod
    def some_static_method(self) -> int:
        return 1

Quux.<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().type_signatures().build().snapshot(), @"
        mro :: bound method <class 'Quux'>.mro() -> list[type]
        some_attribute :: int
        some_class_method :: bound method <class 'Quux'>.some_class_method() -> int
        some_method :: def some_method(self) -> int
        some_property :: property
        some_static_method :: def some_static_method(self) -> int
        __annotate__ :: (() -> dict[str, Any]) | None
        __annotations__ :: dict[str, Any]
        __base__ :: type | None
        __bases__ :: tuple[type, ...]
        __basicsize__ :: int
        __call__ :: bound method <class 'Quux'>.__call__(...) -> Any
        __class__ :: <class 'type'>
        __delattr__ :: def __delattr__(self, name: str, /) -> None
        __dict__ :: dict[str, Any]
        __dictoffset__ :: int
        __dir__ :: def __dir__(self) -> Iterable[str]
        __doc__ :: str | None
        __eq__ :: def __eq__(self, value: object, /) -> bool
        __flags__ :: int
        __format__ :: def __format__(self, format_spec: str, /) -> str
        __getattribute__ :: def __getattribute__(self, name: str, /) -> Any
        __getstate__ :: def __getstate__(self) -> object
        __hash__ :: def __hash__(self) -> int
        __init__ :: def __init__(self) -> Unknown
        __init_subclass__ :: bound method <class 'Quux'>.__init_subclass__() -> None
        __instancecheck__ :: bound method <class 'Quux'>.__instancecheck__(instance: Any, /) -> bool
        __itemsize__ :: int
        __module__ :: str
        __mro__ :: tuple[type, ...]
        __name__ :: str
        __ne__ :: def __ne__(self, value: object, /) -> bool
        __new__ :: def __new__[Self](cls) -> Self
        __or__ :: bound method <class 'Quux'>.__or__[Self](value: Any, /) -> UnionType | Self
        __prepare__ :: bound method <class 'type'>.__prepare__(name: str, bases: tuple[type, ...], /, **kwds: Any) -> MutableMapping[str, object]
        __qualname__ :: str
        __reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
        __reduce_ex__ :: def __reduce_ex__(self, protocol: SupportsIndex, /) -> str | tuple[Any, ...]
        __repr__ :: def __repr__(self) -> str
        __ror__ :: bound method <class 'Quux'>.__ror__[Self](value: Any, /) -> UnionType | Self
        __setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
        __sizeof__ :: def __sizeof__(self) -> int
        __str__ :: def __str__(self) -> str
        __subclasscheck__ :: bound method <class 'Quux'>.__subclasscheck__(subclass: type, /) -> bool
        __subclasses__ :: bound method <class 'Quux'>.__subclasses__[Self]() -> list[Self]
        __subclasshook__ :: bound method <class 'Quux'>.__subclasshook__(subclass: type, /) -> bool
        __text_signature__ :: str | None
        __type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
        __weakrefoffset__ :: int
        ");
    }

    #[test]
    fn enum_attributes() {
        let builder = completion_test_builder(
            "\
from enum import Enum

class Answer(Enum):
    NO = 0
    YES = 1

Answer.<CURSOR>
",
        );

        insta::with_settings!({
            // See above: filter out some members which contain @Todo types that are
            // rendered differently in release mode.
            filters => [(r"(?m)\s*__(call|reduce_ex|annotate|signature)__.+$", "")]},
            {
                assert_snapshot!(
                    builder.skip_keywords().skip_builtins().type_signatures().build().snapshot(), @"
                NO :: Literal[Answer.NO]
                YES :: Literal[Answer.YES]
                mro :: bound method <class 'Answer'>.mro() -> list[type]
                name :: enum.property
                value :: enum.property
                __annotations__ :: dict[str, Any]
                __base__ :: type | None
                __bases__ :: tuple[type, ...]
                __basicsize__ :: int
                __bool__ :: bound method <class 'Answer'>.__bool__() -> Literal[True]
                __class__ :: <class 'EnumMeta'>
                __contains__ :: bound method <class 'Answer'>.__contains__(value: object) -> bool
                __copy__ :: def __copy__[Self](self) -> Self
                __deepcopy__ :: def __deepcopy__[Self](self, memo: Any) -> Self
                __delattr__ :: def __delattr__(self, name: str, /) -> None
                __dict__ :: dict[str, Any]
                __dictoffset__ :: int
                __dir__ :: def __dir__(self) -> list[str]
                __doc__ :: str | None
                __eq__ :: def __eq__(self, value: object, /) -> bool
                __flags__ :: int
                __format__ :: def __format__(self, format_spec: str) -> str
                __getattribute__ :: def __getattribute__(self, name: str, /) -> Any
                __getitem__ :: bound method <class 'Answer'>.__getitem__[_EnumMemberT](name: str) -> _EnumMemberT
                __getstate__ :: def __getstate__(self) -> object
                __hash__ :: def __hash__(self) -> int
                __init__ :: def __init__(self) -> None
                __init_subclass__ :: bound method <class 'Answer'>.__init_subclass__() -> None
                __instancecheck__ :: bound method <class 'Answer'>.__instancecheck__(instance: Any, /) -> bool
                __itemsize__ :: int
                __iter__ :: bound method <class 'Answer'>.__iter__[_EnumMemberT]() -> Iterator[_EnumMemberT]
                __len__ :: bound method <class 'Answer'>.__len__() -> int
                __members__ :: MappingProxyType[str, Answer]
                __module__ :: str
                __mro__ :: tuple[type, ...]
                __name__ :: str
                __ne__ :: def __ne__(self, value: object, /) -> bool
                __new__ :: def __new__[Self](cls, value: object) -> Self
                __or__ :: bound method <class 'Answer'>.__or__[Self](value: Any, /) -> UnionType | Self
                __order__ :: str
                __prepare__ :: bound method <class 'EnumMeta'>.__prepare__(cls: str, bases: tuple[type, ...], **kwds: Any) -> _EnumDict
                __qualname__ :: str
                __reduce__ :: def __reduce__(self) -> str | tuple[Any, ...]
                __repr__ :: def __repr__(self) -> str
                __reversed__ :: bound method <class 'Answer'>.__reversed__[_EnumMemberT]() -> Iterator[_EnumMemberT]
                __ror__ :: bound method <class 'Answer'>.__ror__[Self](value: Any, /) -> UnionType | Self
                __setattr__ :: def __setattr__(self, name: str, value: Any, /) -> None
                __sizeof__ :: def __sizeof__(self) -> int
                __str__ :: def __str__(self) -> str
                __subclasscheck__ :: bound method <class 'Answer'>.__subclasscheck__(subclass: type, /) -> bool
                __subclasses__ :: bound method <class 'Answer'>.__subclasses__[Self]() -> list[Self]
                __subclasshook__ :: bound method <class 'Answer'>.__subclasshook__(subclass: type, /) -> bool
                __text_signature__ :: str | None
                __type_params__ :: tuple[TypeVar | ParamSpec | TypeVarTuple, ...]
                __weakrefoffset__ :: int
                _add_alias_ :: def _add_alias_(self, name: str) -> None
                _add_value_alias_ :: def _add_value_alias_(self, value: Any) -> None
                _generate_next_value_ :: def _generate_next_value_(name: str, start: int, count: int, last_values: list[Any]) -> Any
                _ignore_ :: str | list[str]
                _member_map_ :: dict[str, Enum]
                _member_names_ :: list[str]
                _missing_ :: bound method <class 'Answer'>._missing_(value: object) -> Any
                _name_ :: str
                _order_ :: str
                _value2member_map_ :: dict[Any, Enum]
                _value_ :: Any
                ");
            }
        );
    }

    #[test]
    fn namedtuple_methods() {
        let builder = completion_test_builder(
            "\
from typing import NamedTuple

class Quux(NamedTuple):
    x: int
    y: str

quux = Quux()
quux.<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().type_signatures().build().snapshot(), @"
        count :: bound method Quux.count(value: Any, /) -> int
        index :: bound method Quux.index(value: Any, start: SupportsIndex = 0, stop: SupportsIndex = ..., /) -> int
        x :: int
        y :: str
        __add__ :: Overload[(value: tuple[int | str, ...], /) -> tuple[int | str, ...], [_T](value: tuple[_T, ...], /) -> tuple[int | str | _T, ...]]
        __annotations__ :: dict[str, Any]
        __class__ :: type[Quux]
        __class_getitem__ :: bound method type[Quux].__class_getitem__(item: Any, /) -> GenericAlias
        __contains__ :: bound method Quux.__contains__(key: object, /) -> bool
        __delattr__ :: bound method Quux.__delattr__(name: str, /) -> None
        __dict__ :: dict[str, Any]
        __dir__ :: bound method Quux.__dir__() -> Iterable[str]
        __doc__ :: str | None
        __eq__ :: bound method Quux.__eq__(value: object, /) -> bool
        __format__ :: bound method Quux.__format__(format_spec: str, /) -> str
        __ge__ :: bound method Quux.__ge__(value: tuple[int | str, ...], /) -> bool
        __getattribute__ :: bound method Quux.__getattribute__(name: str, /) -> Any
        __getitem__ :: Overload[(index: Literal[-2, 0], /) -> int, (index: Literal[-1, 1], /) -> str, (index: SupportsIndex, /) -> int | str, (index: slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> tuple[int | str, ...]]
        __getstate__ :: bound method Quux.__getstate__() -> object
        __gt__ :: bound method Quux.__gt__(value: tuple[int | str, ...], /) -> bool
        __hash__ :: bound method Quux.__hash__() -> int
        __init__ :: bound method Quux.__init__() -> None
        __init_subclass__ :: bound method type[Quux].__init_subclass__() -> None
        __iter__ :: bound method Quux.__iter__() -> Iterator[int | str]
        __le__ :: bound method Quux.__le__(value: tuple[int | str, ...], /) -> bool
        __len__ :: () -> Literal[2]
        __lt__ :: bound method Quux.__lt__(value: tuple[int | str, ...], /) -> bool
        __match_args__ :: tuple[str, ...]
        __module__ :: str
        __mul__ :: bound method Quux.__mul__(value: SupportsIndex, /) -> tuple[int | str, ...]
        __ne__ :: bound method Quux.__ne__(value: object, /) -> bool
        __new__ :: (x: int, y: str) -> Quux
        __orig_bases__ :: tuple[Any, ...]
        __reduce__ :: bound method Quux.__reduce__() -> str | tuple[Any, ...]
        __reduce_ex__ :: bound method Quux.__reduce_ex__(protocol: SupportsIndex, /) -> str | tuple[Any, ...]
        __replace__ :: bound method NamedTupleFallback.__replace__(**kwargs: Any) -> NamedTupleFallback
        __repr__ :: bound method Quux.__repr__() -> str
        __reversed__ :: bound method Quux.__reversed__() -> Iterator[int | str]
        __rmul__ :: bound method Quux.__rmul__(value: SupportsIndex, /) -> tuple[int | str, ...]
        __setattr__ :: bound method Quux.__setattr__(name: str, value: Any, /) -> None
        __sizeof__ :: bound method Quux.__sizeof__() -> int
        __str__ :: bound method Quux.__str__() -> str
        __subclasshook__ :: bound method type[Quux].__subclasshook__(subclass: type, /) -> bool
        _asdict :: bound method NamedTupleFallback._asdict() -> dict[str, Any]
        _field_defaults :: dict[str, Any]
        _fields :: tuple[str, ...]
        _make :: bound method type[NamedTupleFallback]._make(iterable: Iterable[Any]) -> NamedTupleFallback
        _replace :: bound method NamedTupleFallback._replace(**kwargs: Any) -> NamedTupleFallback
        ");
    }

    #[test]
    fn call_prefix1() {
        let builder = completion_test_builder(
            "\
def bar(okay=None): ...

foo = 1

bar(o<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"
        okay=
        foo
        "
        );
    }

    #[test]
    fn call_keyword_only_argument() {
        let builder = completion_test_builder(
            "\
def bar(*, okay): ...

foo = 1

bar(o<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"
        okay=
        foo
        "
        );
    }

    // Regression test for https://github.com/astral-sh/ty/issues/3803
    #[test]
    fn call_keyword_only_argument_between_variadic_parameters() {
        completion_test_builder(
            "\
def func(*args, special, **kw): ...

func(sp<CURSOR>
",
        )
        .skip_keywords()
        .skip_builtins()
        .skip_auto_import()
        .build()
        .contains("special");
    }

    #[test]
    fn call_keyword_only_argument_between_variadic_parameters_after_positional_argument() {
        completion_test_builder(
            "\
def func(arg1, *arg2, special, **kw): ...

func(sp<CURSOR>
",
        )
        .skip_keywords()
        .skip_builtins()
        .skip_auto_import()
        .build()
        .contains("special");
    }

    #[test]
    fn call_multiple_keyword_arguments() {
        let builder = completion_test_builder(
            "\
def foo(bar, baz, barbaz): ...

foo(b<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"
        bar=
        barbaz=
        baz=
        "
        );
    }

    #[test]
    fn call_multiple_keyword_arguments_some_set() {
        let builder = completion_test_builder(
            "\
def foo(bar, baz): ...

foo(bar=1, b<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"baz="
        );
    }

    #[test]
    fn call_arguments_multi_def() {
        let builder = completion_test_builder(
            "\
def abc(okay, x): ...
def bar(not_okay, y): ...
def baz(foobarbaz, z): ...

abc(o<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"okay="
        );
    }

    #[test]
    fn call_arguments_cursor_middle() {
        let builder = completion_test_builder(
            "\
def abc(okay, foo, bar, baz): ...

abc(okay=1, ba<CURSOR> baz=5
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"bar="
        );
    }

    #[test]
    fn call_positional_only_argument() {
        // If the parameter is positional only we don't
        // want to suggest it as specifying by name
        // is not valid.
        let builder = completion_test_builder(
            "\
def bar(okay, /): ...

foo = 1

bar(o<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"foo"
        );
    }

    #[test]
    fn call_positional_only_keyword_only_argument_mix() {
        // If the parameter is positional only we don't
        // want to suggest it as specifying by name
        // is not valid.
        let builder = completion_test_builder(
            "\
def bar(not_okay, no, /, okay, *, okay_abc, okay_okay): ...

foo = 1

bar(o<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"
        okay=
        okay_abc=
        okay_okay=
        foo
        "
        );
    }

    #[test]
    fn call_bare_paramspec_has_no_keyword_argument_completions() {
        let builder = completion_test_builder(
            "\
from typing import Callable, ParamSpec

P = ParamSpec(\"P\")
sentinel = 1

def takes(f: Callable[P, None]) -> None:
    f(<CURSOR>
",
        )
        .skip_keywords()
        .skip_builtins()
        .skip_auto_import();
        let completions = builder.build();

        completions.contains("sentinel");

        let keyword_argument_completions = completions
            .completions()
            .iter()
            .filter_map(|completion| {
                completion
                    .insert
                    .as_deref()
                    .filter(|insert| insert.ends_with('='))
            })
            .collect::<Vec<_>>();

        // Bare `ParamSpec` signatures are rendered as a synthetic parameter for
        // signature help, but they don't correspond to a valid keyword argument.
        assert!(
            keyword_argument_completions.is_empty(),
            "Unexpected keyword argument completions: {keyword_argument_completions:?}",
        );
    }

    #[test]
    fn call_blank1() {
        let builder = completion_test_builder(
            "\
def bar(okay=None): ...

foo = 1

bar(<CURSOR>
",
        );

        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"
        okay=
        bar
        foo
        ");
    }

    #[test]
    fn call_attribute_argument_no_arg_completions() {
        let builder = completion_test_builder(
            "\
class A:
    class B:
        class C: ...

def f(aaaa): ...

f(A.B.<CURSOR>)
",
        );

        builder
            .skip_keywords()
            .skip_builtins()
            .build()
            .contains("C")
            .not_contains("aaaa");
    }

    #[test]
    fn duplicate1() {
        let builder = completion_test_builder(
            "\
def foo(): ...

class C:
    def foo(self): ...
    def bar(self):
        f<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"
        foo
        self
        ");
    }

    #[test]
    fn instance_methods_are_not_regular_functions1() {
        let builder = completion_test_builder(
            "\
class C:
    def foo(self): ...

<CURSOR>
",
        );

        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"C");
    }

    #[test]
    fn instance_methods_are_not_regular_functions2() {
        let builder = completion_test_builder(
            "\
class C:
    def foo(self): ...
    def bar(self):
        f<CURSOR>
",
        );

        // FIXME: Should NOT include `foo` here, since
        // that is only a method that can be called on
        // `self`.
        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"
        foo
        self
        ");
    }

    #[test]
    fn identifier_keyword_clash1() {
        let builder = completion_test_builder(
            "\
classy_variable_name = 1

class<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"classy_variable_name",
        );
    }

    #[test]
    fn identifier_keyword_clash2() {
        let builder = completion_test_builder(
            "\
some_symbol = 1

print(f\"{some<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"some_symbol",
        );
    }

    #[test]
    fn statically_unreachable_symbols() {
        let builder = completion_test_builder(
            "\
if 1 + 2 != 3:
    hidden_symbol = 1

hidden_<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn completions_inside_unreachable_sections() {
        let builder = completion_test_builder(
            "\
import sys

if sys.platform == \"not-my-current-platform\":
    only_available_in_this_branch = 1

    on<CURSOR>
",
        );

        // TODO: ideally, `only_available_in_this_branch` should be available here, but we
        // currently make no effort to provide a good IDE experience within sections that
        // are unreachable
        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"<No completions found after filtering out completions>",
        );
    }

    #[test]
    fn star_import() {
        let builder = completion_test_builder(
            "\
from typing import *

Re<CURSOR>
",
        );

        // `ReadableBuffer` is a symbol in `typing`, but it is not re-exported
        builder
            .build()
            .contains("Reversible")
            .not_contains("ReadableBuffer");
    }

    #[test]
    fn attribute_access_empty_list() {
        let builder = completion_test_builder(
            "\
[].<CURSOR>
",
        );
        builder.build().contains("append");
    }

    #[test]
    fn attribute_access_empty_dict() {
        let builder = completion_test_builder(
            "\
{}.<CURSOR>
",
        );

        builder.build().contains("values").not_contains("add");
    }

    #[test]
    fn attribute_access_set() {
        let builder = completion_test_builder(
            "\
{1}.<CURSOR>
",
        );

        builder.build().contains("add").not_contains("values");
    }

    #[test]
    fn attribute_access_set_keyword_prefix() {
        let builder = completion_test_builder(
            "\
{1}.is<CURSOR>
",
        );

        builder
            .build()
            .contains("isdisjoint")
            .not_contains("isinstance");
    }

    #[test]
    fn attribute_parens() {
        let builder = completion_test_builder(
            "\
class A:
    x: str

a = A()
(a).<CURSOR>
",
        );

        builder.build().contains("x");
    }

    #[test]
    fn attribute_double_parens() {
        let builder = completion_test_builder(
            "\
class A:
    x: str

a = A()
((a)).<CURSOR>
",
        );

        builder.build().contains("x");
    }

    #[test]
    fn attribute_on_constructor_directly() {
        let builder = completion_test_builder(
            "\
class A:
    x: str

A().<CURSOR>
",
        );

        builder.build().contains("x");
    }

    #[test]
    fn attribute_not_on_integer() {
        let builder = completion_test_builder(
            "\
3.<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn attribute_on_integer() {
        let builder = completion_test_builder(
            "\
(3).<CURSOR>
",
        );

        builder.build().contains("bit_length");
    }

    #[test]
    fn attribute_on_float() {
        let builder = completion_test_builder(
            "\
3.14.<CURSOR>
",
        );

        builder.build().contains("conjugate");
    }

    #[test]
    fn nested_attribute_access1() {
        let builder = completion_test_builder(
            "\
class A:
    x: str

class B:
    a: A

b = B()
b.a.<CURSOR>
",
        );

        assert_snapshot!(builder.skip_dunders().build().snapshot(),
        @"x");
    }

    #[test]
    fn nested_attribute_access2() {
        let builder = completion_test_builder(
            "\
class B:
    c: int

class A:
    b: B

a = A()
([1] + [a.b.<CURSOR>] + [3]).pop()
",
        );

        builder
            .build()
            .contains("c")
            .not_contains("b")
            .not_contains("pop");
    }

    #[test]
    fn nested_attribute_access3() {
        let builder = completion_test_builder(
            "\
a = A()
([1] + [\"abc\".<CURSOR>] + [3]).pop()
",
        );

        builder
            .build()
            .contains("capitalize")
            .not_contains("append")
            .not_contains("pop");
    }

    #[test]
    fn nested_attribute_access4() {
        let builder = completion_test_builder(
            "\
class B:
    c: int

class A:
    b: B

def foo() -> A:
    return A()

foo().<CURSOR>
",
        );

        builder.build().contains("b").not_contains("c");
    }

    #[test]
    fn nested_attribute_access5() {
        let builder = completion_test_builder(
            "\
class B:
    c: int

class A:
    b: B

def foo() -> A:
    return A()

foo().b.<CURSOR>
",
        );

        builder.build().contains("c").not_contains("b");
    }

    #[test]
    fn betwixt_attribute_access1() {
        let builder = completion_test_builder(
            "\
class Foo:
    xyz: str

class Bar:
    foo: Foo

class Quux:
    bar: Bar

quux = Quux()
quux.<CURSOR>.foo.xyz
",
        );

        builder
            .build()
            .contains("bar")
            .not_contains("xyz")
            .not_contains("foo");
    }

    #[test]
    fn betwixt_attribute_access2() {
        let builder = completion_test_builder(
            "\
class Foo:
    xyz: str

class Bar:
    foo: Foo

class Quux:
    bar: Bar

quux = Quux()
quux.b<CURSOR>.foo.xyz
",
        );

        builder
            .build()
            .contains("bar")
            .not_contains("xyz")
            .not_contains("foo");
    }

    #[test]
    fn betwixt_attribute_access3() {
        let builder = completion_test_builder(
            "\
class Foo:
    xyz: str

class Bar:
    foo: Foo

class Quux:
    bar: Bar

quux = Quux()
<CURSOR>.foo.xyz
",
        );

        builder.build().contains("quux");
    }

    #[test]
    fn betwixt_attribute_access4() {
        let builder = completion_test_builder(
            "\
class Foo:
    xyz: str

class Bar:
    foo: Foo

class Quux:
    bar: Bar

quux = Quux()
q<CURSOR>.foo.xyz
",
        );

        builder.build().contains("quux");
    }

    #[test]
    fn ellipsis1() {
        let builder = completion_test_builder(
            "\
...<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn ellipsis2() {
        let builder = completion_test_builder(
            "\
....<CURSOR>
",
        );

        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"
        __annotations__
        __class__
        __delattr__
        __dict__
        __dir__
        __doc__
        __eq__
        __format__
        __getattribute__
        __getstate__
        __hash__
        __init__
        __init_subclass__
        __module__
        __ne__
        __new__
        __reduce__
        __reduce_ex__
        __repr__
        __setattr__
        __sizeof__
        __str__
        __subclasshook__
        ");
    }

    #[test]
    fn ellipsis3() {
        let builder = completion_test_builder(
            "\
class Foo: ...<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn ordering() {
        let builder = completion_test_builder(
            "\
class A:
    foo: str
    _foo: str
    __foo__: str
    __foo: str
    FOO: str
    _FOO: str
    __FOO__: str
    __FOO: str

A.<CURSOR>
",
        );

        assert_snapshot!(
            builder.filter(|c| c.name.contains("FOO") || c.name.contains("foo")).build().snapshot(),
            @"
        FOO
        foo
        __FOO__
        __foo__
        _FOO
        __FOO
        __foo
        _foo
        ",
        );
    }

    // Ref: https://github.com/astral-sh/ty/issues/572
    #[test]
    fn scope_id_missing_function_identifier1() {
        let builder = completion_test_builder(
            "\
def m<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    // Ref: https://github.com/astral-sh/ty/issues/572
    #[test]
    fn scope_id_missing_function_identifier2() {
        let builder = completion_test_builder(
            "\
def m<CURSOR>(): pass
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    // Ref: https://github.com/astral-sh/ty/issues/572
    #[test]
    fn fscope_id_missing_function_identifier3() {
        let builder = completion_test_builder(
            "\
def m(): pass
<CURSOR>
",
        );

        assert_snapshot!(builder.skip_keywords().skip_builtins().build().snapshot(), @"m");
    }

    // Ref: https://github.com/astral-sh/ty/issues/572
    #[test]
    fn scope_id_missing_class_identifier1() {
        let builder = completion_test_builder(
            "\
class M<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    // Ref: https://github.com/astral-sh/ty/issues/572
    #[test]
    fn scope_id_missing_type_alias1() {
        let builder = completion_test_builder(
            "\
Fo<CURSOR> = float
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"Fo",
        );
    }

    // Ref: https://github.com/astral-sh/ty/issues/572
    #[test]
    fn scope_id_missing_import1() {
        let builder = completion_test_builder(
            "\
import fo<CURSOR>
",
        );

        // This snapshot would generate a big list of modules,
        // which is kind of annoying. So just assert that it
        // runs without panicking and produces some non-empty
        // output.
        assert!(
            !builder
                .skip_keywords()
                .skip_builtins()
                .build()
                .completions()
                .is_empty()
        );
    }

    // Ref: https://github.com/astral-sh/ty/issues/572
    #[test]
    fn scope_id_missing_import2() {
        let builder = completion_test_builder(
            "\
import foo as ba<CURSOR>
",
        );

        // This snapshot would generate a big list of modules,
        // which is kind of annoying. So just assert that it
        // runs without panicking and produces some non-empty
        // output.
        //
        // ... some time passes ...
        //
        // Actually, this shouldn't offer any completions since
        // the context here is introducing a new name.
        assert!(
            builder
                .skip_keywords()
                .skip_builtins()
                .build()
                .completions()
                .is_empty()
        );
    }

    // Ref: https://github.com/astral-sh/ty/issues/572
    #[test]
    fn scope_id_missing_from_import1() {
        let builder = completion_test_builder(
            "\
from fo<CURSOR> import wat
",
        );

        // This snapshot would generate a big list of modules,
        // which is kind of annoying. So just assert that it
        // runs without panicking and produces some non-empty
        // output.
        assert!(
            !builder
                .skip_keywords()
                .skip_builtins()
                .build()
                .completions()
                .is_empty()
        );
    }

    // Ref: https://github.com/astral-sh/ty/issues/572
    #[test]
    fn scope_id_missing_from_import2() {
        let builder = completion_test_builder(
            "\
from foo import wa<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    // Ref: https://github.com/astral-sh/ty/issues/572
    #[test]
    fn scope_id_missing_from_import3() {
        let builder = completion_test_builder(
            "\
from foo import wat as ba<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    // Ref: https://github.com/astral-sh/ty/issues/572
    #[test]
    fn scope_id_missing_try_except1() {
        let builder = completion_test_builder(
            "\
try:
    pass
except Type<CURSOR>:
    pass
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"<No completions found after filtering out completions>",
        );
    }

    // Ref: https://github.com/astral-sh/ty/issues/2401
    #[test]
    fn no_panic_incomplete_except_handler() {
        let builder = completion_test_builder(
            "\
try:
    print()
except <CURSOR># Trigger completion/hover here
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"<No completions found after filtering out completions>",
        );
    }

    // Ref: https://github.com/astral-sh/ty/issues/2401
    #[test]
    fn incomplete_except_handler_uses_enclosing_scope() {
        completion_test_builder(
            "\
def f():
    sentinel = 1
    try:
        print()
    except <CURSOR>as err:
        pass
",
        )
        .skip_keywords()
        .skip_builtins()
        .skip_auto_import()
        .build()
        .contains("sentinel");
    }

    // Ref: https://github.com/astral-sh/ty/issues/572
    #[test]
    fn scope_id_missing_global1() {
        let builder = completion_test_builder(
            "\
def _():
    global fo<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"<No completions found after filtering out completions>",
        );
    }

    #[test]
    fn string_dot_attr1() {
        let builder = completion_test_builder(
            r#"
foo = 1
bar = 2

class Foo:
    def method(self): ...

f = Foo()

# String, this is not an attribute access
"f.<CURSOR>
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn string_dot_attr2() {
        let builder = completion_test_builder(
            r#"
foo = 1
bar = 2

class Foo:
    def method(self): ...

f = Foo()

# F-string, this is an attribute access
f"{f.<CURSOR>
"#,
        );

        builder.build().contains("method");
    }

    #[test]
    fn string_dot_attr3() {
        let builder = completion_test_builder(
            r#"
foo = 1
bar = 2

class Foo:
    def method(self): ...

f = Foo()

# T-string, this is an attribute access
t"{f.<CURSOR>
"#,
        );

        builder.build().contains("method");
    }

    #[test]
    fn no_panic_for_attribute_table_that_contains_subscript() {
        let builder = completion_test_builder(
            r#"
class Point:
    def orthogonal_direction(self):
        self[0].is_zero

def test_point(p2: Point):
    p2.<CURSOR>
"#,
        );
        builder.build().contains("orthogonal_direction");
    }

    // https://github.com/astral-sh/ty/issues/3087
    #[test]
    fn no_panic_argument_completion_before_paren() {
        let builder = completion_test_builder(
            r#"
list[int]<CURSOR>()
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"<No completions found after filtering out completions>",
        );
    }

    #[test]
    fn from_import1() {
        let builder = completion_test_builder(
            "\
from sys import <CURSOR>
",
        );
        builder.build().contains("getsizeof");
    }

    #[test]
    fn from_import2() {
        let builder = completion_test_builder(
            "\
from sys import abiflags, <CURSOR>
",
        );
        builder.build().contains("getsizeof");
    }

    #[test]
    fn from_import3() {
        let builder = completion_test_builder(
            "\
from sys import <CURSOR>, abiflags
",
        );
        builder.build().contains("getsizeof");
    }

    #[test]
    fn from_import4() {
        let builder = completion_test_builder(
            "\
from sys import abiflags, \
    <CURSOR>
",
        );
        builder.build().contains("getsizeof");
    }

    #[test]
    fn from_import5() {
        let builder = completion_test_builder(
            "\
from sys import abiflags as foo, <CURSOR>
",
        );
        builder.build().contains("getsizeof");
    }

    #[test]
    fn from_import6() {
        let builder = completion_test_builder(
            "\
from sys import abiflags as foo, g<CURSOR>
",
        );
        builder.build().contains("getsizeof");
    }

    #[test]
    fn from_import7() {
        let builder = completion_test_builder(
            "\
from sys import abiflags as foo, \
    <CURSOR>
",
        );
        builder.build().contains("getsizeof");
    }

    #[test]
    fn from_import8() {
        let builder = completion_test_builder(
            "\
from sys import abiflags as foo, \
    g<CURSOR>
",
        );
        builder.build().contains("getsizeof");
    }

    #[test]
    fn from_import9() {
        let builder = completion_test_builder(
            "\
from sys import (
    abiflags,
    <CURSOR>
",
        );
        builder.build().contains("getsizeof");
    }

    #[test]
    fn from_import10() {
        let builder = completion_test_builder(
            "\
from sys import (
    abiflags,
    <CURSOR>
)
",
        );
        builder.build().contains("getsizeof");
    }

    #[test]
    fn from_import11() {
        let builder = completion_test_builder(
            "\
from sys import (
    <CURSOR>
)
",
        );
        builder.build().contains("getsizeof");
    }

    #[test]
    fn from_import_unknown_in_module() {
        let builder = completion_test_builder(
            "\
foo = 1
from ? import <CURSOR>
",
        );
        assert_snapshot!(
            builder.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn from_import_unknown_in_import_names1() {
        let builder = completion_test_builder(
            "\
from sys import ?, <CURSOR>
",
        );
        builder.build().contains("getsizeof");
    }

    #[test]
    fn from_import_unknown_in_import_names2() {
        let builder = completion_test_builder(
            "\
from sys import ??, <CURSOR>
",
        );
        builder.build().contains("getsizeof");
    }

    #[test]
    fn from_import_unknown_in_import_names3() {
        let builder = completion_test_builder(
            "\
from sys import ??, <CURSOR>, ??
",
        );
        builder.build().contains("getsizeof");
    }

    #[test]
    fn relative_from_import1() {
        CursorTest::builder()
            .source("package/__init__.py", "")
            .source(
                "package/foo.py",
                "\
Cheetah = 1
Lion = 2
Cougar = 3
",
            )
            .source("package/sub1/sub2/bar.py", "from ...foo import <CURSOR>")
            .completion_test_builder()
            .build()
            .contains("Cheetah");
    }

    #[test]
    fn relative_from_import2() {
        CursorTest::builder()
            .source("package/__init__.py", "")
            .source(
                "package/sub1/foo.py",
                "\
Cheetah = 1
Lion = 2
Cougar = 3
",
            )
            .source("package/sub1/sub2/bar.py", "from ..foo import <CURSOR>")
            .completion_test_builder()
            .build()
            .contains("Cheetah");
    }

    #[test]
    fn relative_from_import3() {
        CursorTest::builder()
            .source("package/__init__.py", "")
            .source(
                "package/sub1/sub2/foo.py",
                "\
Cheetah = 1
Lion = 2
Cougar = 3
",
            )
            .source("package/sub1/sub2/bar.py", "from .foo import <CURSOR>")
            .completion_test_builder()
            .build()
            .contains("Cheetah");
    }

    #[test]
    fn from_import_with_submodule1() {
        CursorTest::builder()
            .source("main.py", "from package import <CURSOR>")
            .source("package/__init__.py", "")
            .source("package/foo.py", "")
            .source("package/bar.pyi", "")
            .source("package/foo-bar.py", "")
            .source("package/data.txt", "")
            .source("package/sub/__init__.py", "")
            .source("package/not-a-submodule/__init__.py", "")
            .completion_test_builder()
            .build()
            .contains("foo")
            .contains("bar")
            .contains("sub")
            .not_contains("foo-bar")
            .not_contains("data")
            .not_contains("not-a-submodule");
    }

    #[test]
    fn from_import_with_vendored_submodule1() {
        let builder = completion_test_builder(
            "\
from http import <CURSOR>
",
        );
        builder.build().contains("client");
    }

    #[test]
    fn from_import_with_vendored_submodule2() {
        let builder = completion_test_builder(
            "\
from email import <CURSOR>
",
        );
        builder.build().contains("mime").not_contains("base");
    }

    #[test]
    fn import_submodule_not_attribute1() {
        let builder = completion_test_builder(
            "\
import importlib
importlib.<CURSOR>
",
        );
        builder.build().not_contains("resources");
    }

    #[test]
    fn import_submodule_not_attribute2() {
        let builder = completion_test_builder(
            "\
import importlib.resources
importlib.<CURSOR>
",
        );
        builder.build().contains("resources");
    }

    #[test]
    fn import_submodule_not_attribute3() {
        let builder = completion_test_builder(
            "\
import importlib
import importlib.resources
importlib.<CURSOR>
",
        );
        builder.build().contains("resources");
    }

    #[test]
    fn import_with_leading_character() {
        let builder = completion_test_builder(
            "\
import c<CURSOR>
",
        );
        builder.build().contains("collections");
    }

    #[test]
    fn import_without_leading_character() {
        let builder = completion_test_builder(
            "\
import <CURSOR>
",
        );
        builder.build().contains("collections");
    }

    #[test]
    fn import_multiple_betwixt() {
        let builder = completion_test_builder(
            "\
import re, c<CURSOR>, sys
",
        );
        builder.build().contains("collections");
    }

    #[test]
    fn import_multiple_end1() {
        let builder = completion_test_builder(
            "\
import collections.abc, unico<CURSOR>
",
        );
        builder.build().contains("unicodedata");
    }

    #[test]
    fn import_multiple_end2() {
        let builder = completion_test_builder(
            "\
import collections.abc, urllib.parse, bu<CURSOR>
",
        );
        builder.build().contains("builtins");
    }

    #[test]
    fn import_with_aliases() {
        let builder = completion_test_builder(
            "\
import re as regexp, c<CURSOR>, sys as system
",
        );
        builder.build().contains("collections");
    }

    #[test]
    fn import_over_multiple_lines() {
        let builder = completion_test_builder(
            "\
import re as regexp, \\
    c<CURSOR>, \\
    sys as system
",
        );
        builder.build().contains("collections");
    }

    #[test]
    fn import_unknown_in_module() {
        let builder = completion_test_builder(
            "\
import ?, <CURSOR>
",
        );
        builder.build().contains("collections");
    }

    #[test]
    fn import_via_from_with_leading_character() {
        let builder = completion_test_builder(
            "\
from c<CURSOR>
",
        );
        builder.build().contains("collections");
    }

    #[test]
    fn import_via_from_without_leading_character() {
        let builder = completion_test_builder(
            "\
from <CURSOR>
",
        );
        builder.build().contains("collections");
    }

    #[test]
    fn import_statement_preserves_name_order_for_unrelated_third_party_module() {
        let builder = CursorTest::builder()
            .with_site_packages()
            .source("main.py", "import path<CURSOR>")
            .site_packages("patha.py", "")
            .completion_test_builder()
            .filter(|c| matches!(c.name.as_str(), "pathlib" | "patha"));

        assert_snapshot!(builder.build().snapshot(), @"
        patha
        pathlib
        ");
    }

    #[test]
    fn from_import_preserves_name_order_for_unrelated_namespace_package() {
        let builder = CursorTest::builder()
            .with_site_packages()
            .source("main.py", "from path<CURSOR>")
            .site_packages("patha/foo.py", "")
            .completion_test_builder()
            .filter(|c| matches!(c.name.as_str(), "pathlib" | "patha"));

        assert_snapshot!(builder.build().snapshot(), @"
        patha
        pathlib
        ");
    }

    #[test]
    fn import_statement_with_submodule_with_leading_character() {
        let builder = completion_test_builder(
            "\
import os.p<CURSOR>
",
        );
        builder.build().contains("path").not_contains("abspath");
    }

    #[test]
    fn import_statement_with_submodule_multiple() {
        let builder = completion_test_builder(
            "\
import re, os.p<CURSOR>, zlib
",
        );
        builder.build().contains("path").not_contains("abspath");
    }

    #[test]
    fn import_statement_with_submodule_without_leading_character() {
        let builder = completion_test_builder(
            "\
import os.<CURSOR>
",
        );
        builder.build().contains("path").not_contains("abspath");
    }

    #[test]
    fn import_via_from_with_submodule_with_leading_character() {
        let builder = completion_test_builder(
            "\
from os.p<CURSOR>
",
        );
        builder.build().contains("path").not_contains("abspath");
    }

    #[test]
    fn import_via_from_with_submodule_without_leading_character() {
        let builder = completion_test_builder(
            "\
from os.<CURSOR>
",
        );
        builder.build().contains("path").not_contains("abspath");
    }

    #[test]
    fn auto_import_with_submodule() {
        CursorTest::builder()
            .source("main.py", "Abra<CURSOR>")
            .source("package/__init__.py", "AbraKadabra = 1")
            .completion_test_builder()
            .build()
            .contains("AbraKadabra");
    }

    #[test]
    fn auto_import_should_not_include_symbols_in_current_module() {
        let snapshot = CursorTest::builder()
            .source("main.py", "Kadabra = 1\nKad<CURSOR>")
            .source("package/__init__.py", "AbraKadabra = 1")
            .completion_test_builder()
            .type_signatures()
            .module_names()
            .filter(|c| c.name.contains("Kadabra"))
            .build()
            .snapshot();
        assert_snapshot!(snapshot, @"
        Kadabra :: Literal[1] :: <no import required>
        AbraKadabra :: Unavailable :: package
        ");
    }

    #[test]
    fn import_type_check_only_lowers_ranking() {
        let builder = CursorTest::builder()
            .source(
                "main.py",
                r#"
                import foo
                foo.A<CURSOR>
                "#,
            )
            .source(
                "foo/__init__.py",
                r#"
                from typing import type_check_only

                @type_check_only
                class Apple: pass

                class Banana: pass
                class Cat: pass
                class Azorubine: pass
                "#,
            )
            .completion_test_builder();

        let test = builder.build();
        let completions = test.completions();

        let [apple_pos, banana_pos, cat_pos, azo_pos, ann_pos] =
            ["Apple", "Banana", "Cat", "Azorubine", "__annotations__"].map(|name| {
                completions
                    .iter()
                    .position(|comp| comp.name == name)
                    .unwrap()
            });

        assert!(completions[apple_pos].is_type_check_only);
        assert!(apple_pos > banana_pos.max(cat_pos).max(azo_pos));
        assert!(ann_pos > apple_pos);
    }

    #[test]
    fn type_check_only_is_type_check_only() {
        // `@typing.type_check_only` is a function that's unavailable at runtime
        // and so should rank after runtime-available completions in `typing`.
        let builder = completion_test_builder("from typing import t<CURSOR>");
        let test = builder.build();
        let completions = test.completions();
        let type_check_only_pos = completions
            .iter()
            .position(|c| c.name == "type_check_only")
            .unwrap();
        let last_runtime_nonunderscore_pos = completions
            .iter()
            .rposition(|c| !c.name.starts_with('_') && !c.is_type_check_only)
            .unwrap();

        assert!(completions[type_check_only_pos].is_type_check_only);
        assert!(type_check_only_pos > last_runtime_nonunderscore_pos);
    }

    #[test]
    fn regression_test_issue_642() {
        // Regression test for https://github.com/astral-sh/ty/issues/642

        let test = completion_test_builder(
            r#"
            match 0:
                case 1 i<CURSOR>:
                    pass
            "#,
        );

        assert_snapshot!(
            test.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"<No completions found after filtering out completions>",
        );
    }

    #[test]
    fn completion_kind_recursive_type_alias() {
        let builder = completion_test_builder(
            r#"
            type T = T | None
            def f(rec: T):
                re<CURSOR>
            "#,
        );
        let test = builder.build();

        let completion = test.completions().iter().find(|c| c.name == "rec").unwrap();
        assert_eq!(completion.kind, Some(CompletionKind::Struct));
    }

    #[test]
    fn no_completions_in_comment() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
# zqzq<CURSOR>
",
        );

        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_string_double_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(\"zqzq<CURSOR>\")
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(\"Foo.zqzq<CURSOR>\")
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_string_incomplete_double_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(\"zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(\"Foo.zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_string_single_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print('zqzq<CURSOR>')
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print('Foo.zqzq<CURSOR>')
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_string_incomplete_single_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print('zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print('Foo.zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_string_double_triple_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(\"\"\"zqzq<CURSOR>\"\"\")
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(\"\"\"Foo.zqzq<CURSOR>\"\"\")
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_string_incomplete_double_triple_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(\"\"\"zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(\"\"\"Foo.zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_string_single_triple_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print('''zqzq<CURSOR>''')
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print('''Foo.zqzq<CURSOR>''')
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_string_incomplete_single_triple_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print('''zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print('''Foo.zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_fstring_double_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(f\"zqzq<CURSOR>\")
        ",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(f\"{Foo} and Foo.zqzq<CURSOR>\")
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_fstring_incomplete_double_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(f\"zqzq<CURSOR>
        ",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(f\"{Foo} and Foo.zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_fstring_single_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(f'zqzq<CURSOR>')
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(f'{Foo} and Foo.zqzq<CURSOR>')
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_fstring_incomplete_single_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(f'zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(f'{Foo} and Foo.zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_fstring_double_triple_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(f\"\"\"zqzq<CURSOR>\"\"\")
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(f\"\"\"{Foo} and Foo.zqzq<CURSOR>\"\"\")
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_fstring_incomplete_double_triple_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(f\"\"\"zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(f\"\"\"{Foo} and Foo.zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_fstring_single_triple_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(f'''zqzq<CURSOR>''')
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(f'''{Foo} and Foo.zqzq<CURSOR>''')
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_fstring_incomplete_single_triple_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(f'''zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(f'''{Foo} and Foo.zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_tstring_double_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(t\"zqzq<CURSOR>\")
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(t\"{Foo} and Foo.zqzq<CURSOR>\")
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_tstring_incomplete_double_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(t\"zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(t\"{Foo} and Foo.zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_tstring_single_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(t'zqzq<CURSOR>')
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(t'{Foo} and Foo.zqzq<CURSOR>')
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_tstring_incomplete_single_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(t'zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(t'{Foo} and Foo.zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_tstring_double_triple_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(t\"\"\"zqzq<CURSOR>\"\"\")
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(t\"\"\"{Foo} and Foo.zqzq<CURSOR>\"\"\")
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_tstring_incomplete_double_triple_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(t\"\"\"zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(t\"\"\"{Foo} and Foo.zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_tstring_single_triple_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(t'''zqzq<CURSOR>''')
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(t'''{Foo} and Foo.zqzq<CURSOR>''')
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_tstring_incomplete_single_triple_quote() {
        let test = completion_test_builder(
            "\
zqzqzq = 1
print(t'''zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );

        let test = completion_test_builder(
            "\
class Foo:
    zqzqzq = 1
print(t'''{Foo} and Foo.zqzq<CURSOR>
",
        );
        assert_snapshot!(
            test.skip_keywords().skip_builtins().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn string_literal_completions_function_argument() {
        let builder = completion_test_builder(
            r#"
from typing import Literal

A = Literal["a", "b", "c"]

def func(a: A): ...

func("<CURSOR>")
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().type_signatures().build().snapshot(),
            @r#"
        a :: Literal["a"]
        b :: Literal["b"]
        c :: Literal["c"]
        "#,
        );
    }

    #[test]
    fn string_literal_completions_overloaded_function_argument() {
        let builder = completion_test_builder(
            r#"
from typing import Literal, overload

@overload
def func(mode: Literal["r"]) -> int: ...
@overload
def func(mode: Literal["w"]) -> str: ...
def func(mode: str) -> int | str: ...

func("<CURSOR>")
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().type_signatures().build().snapshot(),
            @r#"
        r :: Literal["r"]
        w :: Literal["w"]
        "#,
        );
    }

    #[test]
    fn string_literal_completions_annotated_assignment() {
        let builder = completion_test_builder(
            r#"
from typing import Literal

value: Literal["x", "y"] = "<CURSOR>"
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().type_signatures().build().snapshot(),
            @r#"
        x :: Literal["x"]
        y :: Literal["y"]
        "#,
        );
    }

    #[test]
    fn string_literal_completions_nested_expected_type() {
        let builder = completion_test_builder(
            r#"
from typing import Literal

type A = Literal["foo", "bar", "baz"]
xs: list[A] = ["<CURSOR>"]
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().type_signatures().build().snapshot(),
            @r#"
        bar :: Literal["bar"]
        baz :: Literal["baz"]
        foo :: Literal["foo"]
        "#,
        );
    }

    #[test]
    fn string_literal_completions_filter_non_string_literals() {
        let builder = completion_test_builder(
            r#"
from typing import Literal

Mixed = Literal["left", 1, "right"]

def consume(value: Mixed): ...

consume("<CURSOR>")
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().type_signatures().build().snapshot(),
            @r#"
        left :: Literal["left"]
        right :: Literal["right"]
        "#,
        );
    }

    #[test]
    fn string_literal_completions_typed_dict_keys() {
        let builder = completion_test_builder(
            r#"
from typing import TypedDict

class TD(TypedDict):
    left: int
    right: str

td: TD = {"left": 1, "right": "x"}

td["<CURSOR>"]
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().type_signatures().build().snapshot(),
            @r#"
        left :: Literal["left"]
        right :: Literal["right"]
        "#,
        );
    }

    #[test]
    fn string_literal_completions_typed_dict_literal_keys() {
        let builder = completion_test_builder(
            r#"
from typing import TypedDict

class Box(TypedDict):
    x: float
    y: float
    z: float

def take(box: Box): ...

take({"<CURSOR>": 1})
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().type_signatures().build().snapshot(),
            @r#"
        x :: Literal["x"]
        y :: Literal["y"]
        z :: Literal["z"]
        "#,
        );
    }

    #[test]
    fn string_literal_completions_in_incomplete_typed_dict_literal_key() {
        let builder = completion_test_builder(
            r#"
from typing import TypedDict

class Box(TypedDict):
    x: float
    y: float
    z: float

def take(box: Box): ...

take({"<CURSOR>"})
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().type_signatures().build().snapshot(),
            @r#"
        x :: Literal["x"]
        y :: Literal["y"]
        z :: Literal["z"]
        "#,
        );
    }

    #[test]
    fn string_literal_completions_in_incomplete_typed_dict_literal_key_combines_set_context() {
        let builder = completion_test_builder(
            r#"
from typing import Literal, TypedDict

class Box(TypedDict):
    x: float
    y: float
    z: float

def take(value: Box | set[Literal["item"]]): ...

take({"<CURSOR>"})
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().type_signatures().build().snapshot(),
            @r#"
        item :: Literal["item"]
        x :: Literal["x"]
        y :: Literal["y"]
        z :: Literal["z"]
        "#,
        );
    }

    #[test]
    fn string_literal_completions_typed_dict_constructor_mixed_literal_keys() {
        let builder = completion_test_builder(
            r#"
from typing import TypedDict

class Box(TypedDict):
    x: float
    y: float
    z: float

Box({"<CURSOR>": 1}, y=2)
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().type_signatures().build().snapshot(),
            @r#"
        x :: Literal["x"]
        y :: Literal["y"]
        z :: Literal["z"]
        "#,
        );
    }

    #[test]
    fn string_literal_completions_typed_dict_keys_assignment() {
        let builder = completion_test_builder(
            r#"
from typing import TypedDict

class TD(TypedDict):
    left: int
    right: str

td: TD = {"left": 1, "right": "x"}

td["<CURSOR>"] = 1
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().type_signatures().build().snapshot(),
            @r#"
        left :: Literal["left"]
        right :: Literal["right"]
        "#,
        );
    }

    #[test]
    fn string_literal_completions_typed_dict_keys_deletion() {
        let builder = completion_test_builder(
            r#"
from typing import TypedDict

class TD(TypedDict):
    left: int
    right: str

td: TD = {"left": 1, "right": "x"}

del td["<CURSOR>"]
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().type_signatures().build().snapshot(),
            @r#"
        left :: Literal["left"]
        right :: Literal["right"]
        "#,
        );
    }

    #[test]
    fn string_literal_completions_do_not_offer_typed_dict_keys_for_typed_dict_value_context() {
        let builder = completion_test_builder(
            r#"
from typing import TypedDict

class TD(TypedDict):
    left: int
    right: str

x: TD = "<CURSOR>"
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn string_literal_completions_typed_dict_union_keys() {
        let builder = completion_test_builder(
            r#"
from typing import TypedDict

class A(TypedDict):
    a: int
    both: int

class B(TypedDict):
    b: int
    both: str

x: A | B = {"both": 1}
x["<CURSOR>"]
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().type_signatures().build().snapshot(),
            @r#"
        a :: Literal["a"]
        b :: Literal["b"]
        both :: Literal["both"]
        "#,
        );
    }

    #[test]
    fn string_literal_completions_intersection_positive_union_semantics() {
        let builder = completion_test_builder(
            r#"
from typing import Literal
from ty_extensions import Intersection

x: Intersection[Literal["a", "b"], Literal["b", "c"]] = "<CURSOR>"
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().type_signatures().build().snapshot(),
            @r#"b :: Literal["b"]"#,
        );
    }

    #[test]
    fn string_literal_completions_intersection_excludes_negative_elements() {
        let builder = completion_test_builder(
            r#"
from typing import Literal
from ty_extensions import Intersection, Not

x: Intersection[Literal["a"], Not[Literal["b"]]] = "<CURSOR>"
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().type_signatures().build().snapshot(),
            @r#"a :: Literal["a"]"#,
        );
    }

    #[test]
    fn string_literal_completions_type_alias_recursion_safe() {
        let builder = completion_test_builder(
            r#"
from typing import Literal

type A = Literal["foo", "bar"]
type B = A
x: B = "<CURSOR>"
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().type_signatures().build().snapshot(),
            @r#"
        bar :: Literal["bar"]
        foo :: Literal["foo"]
        "#,
        );
    }

    #[test]
    fn string_literal_completions_single_quote_escaping() {
        let builder = completion_test_builder(
            r#"
from typing import Literal
x: Literal["can't", "won't"] = '<CURSOR>'
"#,
        );
        let builder = builder.skip_keywords().skip_builtins().skip_auto_import();
        let test = builder.build();
        let inserts = test
            .completions()
            .iter()
            .map(|completion| {
                completion
                    .insert
                    .as_deref()
                    .unwrap_or(completion.label.as_str())
                    .to_string()
            })
            .collect::<Vec<_>>();
        assert_eq!(inserts, vec![r"can\'t", r"won\'t"]);
    }

    #[test]
    fn string_literal_completions_double_quote_escaping() {
        let builder = completion_test_builder(
            r#"
from typing import Literal
x: Literal['say "hi"', 'say "bye"'] = "<CURSOR>"
"#,
        );
        let builder = builder.skip_keywords().skip_builtins().skip_auto_import();
        let test = builder.build();
        let inserts = test
            .completions()
            .iter()
            .map(|completion| {
                completion
                    .insert
                    .as_deref()
                    .unwrap_or(completion.label.as_str())
                    .to_string()
            })
            .collect::<Vec<_>>();
        assert_eq!(inserts, vec![r#"say \"bye\""#, r#"say \"hi\""#]);
    }

    #[test]
    fn string_literal_completions_backslash_escaping() {
        let builder = completion_test_builder(
            r#"
from typing import Literal
x: Literal["a\\b"] = "<CURSOR>"
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @r"a\\b",
        );
    }

    #[test]
    fn string_literal_completions_in_incomplete_string() {
        let builder = completion_test_builder(
            r#"
from typing import Literal
x: Literal["a"] = "a<CURSOR>
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @"a",
        );
    }

    #[test]
    fn string_literal_completions_in_standalone_statement() {
        let builder = completion_test_builder(
            r#"
from collections.abc import Callable
from typing import Literal

def func(callback: Callable[[], Literal["yes", "no"]]) -> None: ...

x = y = func(lambda: "<CURSOR>")
"#,
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().type_signatures().build().snapshot(),
            @r#"
        no :: Literal["no"]
        yes :: Literal["yes"]
        "#,
        );
    }

    #[test]
    fn typevar_with_upper_bound() {
        let builder = completion_test_builder(
            "\
def f[T: str](msg: T):
    msg.<CURSOR>
",
        );
        let test = builder.build();
        test.contains("upper");
        test.contains("capitalize");
    }

    #[test]
    fn typevar_with_constraints() {
        // Test TypeVar with constraints
        let builder = completion_test_builder(
            "\
from typing import TypeVar

class A:
    only_on_a: int
    on_a_and_b: str

class B:
    only_on_b: float
    on_a_and_b: str

T = TypeVar('T', A, B)

def f(x: T):
    x.<CURSOR>
",
        );
        let test = builder.build();

        test.contains("on_a_and_b");
        test.not_contains("only_on_a");
        test.not_contains("only_on_b");
    }

    #[test]
    fn typevar_without_bounds_or_constraints() {
        let test = completion_test_builder(
            "\
def f[T](x: T):
    x.<CURSOR>
",
        );
        test.build().contains("__repr__");
    }

    #[test]
    fn no_completions_in_function_def_name() {
        let builder = completion_test_builder(
            "\
foo = 1

def f<CURSOR>
    ",
        );
        assert!(builder.build().completions().is_empty());
    }

    #[test]
    fn completions_in_function_def_empty_name() {
        let builder = completion_test_builder(
            "\
def <CURSOR>
        ",
        );
        assert!(builder.build().completions().is_empty());
    }

    #[test]
    fn no_completions_in_class_def_name() {
        let builder = completion_test_builder(
            "\
foo = 1

class f<CURSOR>
    ",
        );
        assert!(builder.build().completions().is_empty());
    }

    #[test]
    fn completions_in_class_def_empty_name() {
        let builder = completion_test_builder(
            "\
class <CURSOR>
        ",
        );
        assert!(builder.build().completions().is_empty());
    }

    #[test]
    fn no_completions_in_type_def_name() {
        let builder = completion_test_builder(
            "\
foo = 1

type f<CURSOR> = int
    ",
        );

        assert!(builder.build().completions().is_empty());
    }

    #[test]
    fn no_completions_in_maybe_type_def_name() {
        let builder = completion_test_builder(
            "\
foo = 1

type f<CURSOR>
       ",
        );
        assert!(builder.build().completions().is_empty());
    }

    #[test]
    fn completions_in_type_def_empty_name() {
        let builder = completion_test_builder(
            "\
type <CURSOR>
        ",
        );
        assert!(builder.build().completions().is_empty());
    }

    #[test]
    fn no_completions_in_import_alias() {
        let builder = completion_test_builder(
            "\
foo = 1
import collections as f<CURSOR>
    ",
        );
        assert_snapshot!(
            builder.build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_from_import_alias() {
        let builder = completion_test_builder(
            "\
foo = 1
from collections import defaultdict as f<CURSOR>
    ",
        );
        assert_snapshot!(
            builder.build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn import_missing_alias_suggests_as_with_leading_char() {
        let builder = completion_test_builder(
            "\
import collections a<CURSOR>
    ",
        );
        assert_snapshot!(builder.build().snapshot(), @"as");
    }

    #[test]
    fn import_missing_alias_suggests_as() {
        let builder = completion_test_builder(
            "\
import collections <CURSOR>
    ",
        );
        assert_snapshot!(builder.build().snapshot(), @"as");
    }

    #[test]
    fn import_dotted_module_missing_alias_suggests_as() {
        let builder = completion_test_builder(
            "\
import collections.abc a<CURSOR>
    ",
        );
        assert_snapshot!(builder.build().snapshot(), @"as");
    }

    #[test]
    fn import_multiple_modules_missing_alias_suggests_as() {
        let builder = completion_test_builder(
            "\
import collections.abc as c, typing a<CURSOR>
    ",
        );
        assert_snapshot!(builder.build().snapshot(), @"as");
    }

    #[test]
    fn from_import_missing_alias_suggests_as_with_leading_char() {
        let builder = completion_test_builder(
            "\
from collections.abc import Mapping a<CURSOR>
    ",
        );
        assert_snapshot!(builder.build().snapshot(), @"as");
    }

    #[test]
    fn from_import_missing_alias_suggests_as() {
        let builder = completion_test_builder(
            "\
from collections import defaultdict <CURSOR>
    ",
        );
        assert_snapshot!(builder.build().snapshot(), @"as");
    }

    #[test]
    fn from_import_parenthesized_missing_alias_suggests_as() {
        let builder = completion_test_builder(
            "\
from typing import (
    NamedTuple a<CURSOR>
)
    ",
        );
        assert_snapshot!(builder.build().snapshot(), @"as");
    }

    #[test]
    fn from_relative_import_missing_alias_suggests_as() {
        let builder = completion_test_builder(
            "\
from ...foo import bar a<CURSOR>
    ",
        );
        assert_snapshot!(builder.build().snapshot(), @"as");
    }

    #[test]
    fn no_completions_in_with_alias() {
        let builder = completion_test_builder(
            "\
foo = 1
with open('bar') as f<CURSOR>
    ",
        );
        assert_snapshot!(
            builder.build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn with_item_suggests_as() {
        for source in [
            "with open('bar') <CURSOR>",
            "with open('bar') a<CURSOR>",
            "with (open('bar')) <CURSOR>",
            "with (open('bar') <CURSOR>)",
            "with (\n    open('bar')  # comment\n    <CURSOR>\n)",
            "with open('bar') \\\n    <CURSOR>",
        ] {
            assert_eq!(completion_test_builder(source).build().snapshot(), "as");
        }
    }

    #[test]
    fn no_completions_in_except_alias() {
        let builder = completion_test_builder(
            "\
foo = 1
try:
    [][0]
except IndexError as f<CURSOR>
    ",
        );
        assert_snapshot!(
            builder.build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn except_handler_suggests_as() {
        for source in [
            "\
try:
    pass
except ValueError <CURSOR>",
            "\
try:
    pass
except ValueError a<CURSOR>",
            "\
try:
    pass
except (ValueError) <CURSOR>",
            "\
try:
    pass
except ValueError \\
    <CURSOR>",
        ] {
            assert_eq!(completion_test_builder(source).build().snapshot(), "as");
        }
    }

    #[test]
    fn no_completions_in_match_alias() {
        let builder = completion_test_builder(
            "\
foo = 1
status = 400
match status:
    case 400 as f<CURSOR>:
        return 'Bad request'
    ",
        );
        assert_snapshot!(
            builder.build().snapshot(),
            @"<No completions found>",
        );

        // Also check that completions are suppressed
        // when nothing has been typed.
        let builder = completion_test_builder(
            "\
foo = 1
status = 400
match status:
    case 400 as <CURSOR>:
        return 'Bad request'
    ",
        );
        assert_snapshot!(
            builder.build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn match_pattern_suggests_keywords() {
        for (source, expected) in [
            (
                "\
match status:
    case 400 <CURSOR>",
                "as\nif",
            ),
            (
                "\
match status:
    case 400 a<CURSOR>",
                "as",
            ),
            (
                "\
match status:
    case 400 i<CURSOR>",
                "if",
            ),
            (
                "\
match status:
    case 400 <CURSOR> if guard",
                "as",
            ),
            (
                "\
match status:
    case (400) <CURSOR>",
                "as\nif",
            ),
            (
                "\
match status:
    case (400 <CURSOR>)",
                "as",
            ),
            (
                "\
match status:
    case ((400) <CURSOR>)",
                "as",
            ),
            (
                "\
match status:
    case (
        400  # comment
        <CURSOR>
    )",
                "as",
            ),
            (
                "\
match status:
    case 400 as value <CURSOR>",
                "if",
            ),
        ] {
            assert_eq!(completion_test_builder(source).build().snapshot(), expected);
        }
    }

    #[test]
    fn no_contextual_keywords_inside_parentheses() {
        for (source, unexpected) in [
            ("for (foo <CURSOR>) in items: pass", "in"),
            ("with ((open() <CURSOR>)): pass", "as"),
            (
                "try:\n    pass\nexcept (ValueError <CURSOR>) as error: pass",
                "as",
            ),
            ("match status:\n    case (400 <CURSOR>): pass", "if"),
            (
                "match status:\n    case (400 as value <CURSOR>): pass",
                "if",
            ),
        ] {
            let builder = completion_test_builder(source);
            assert!(
                builder
                    .build()
                    .completions()
                    .iter()
                    .all(|completion| completion.name != unexpected),
                "Expected completions for `{source}` to not include `{unexpected}`",
            );
        }
    }

    #[test]
    fn no_completions_in_empty_for_variable_binding() {
        let builder = completion_test_builder(
            "\
for <CURSOR>
",
        );
        assert_snapshot!(
            builder.build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_for_variable_binding() {
        let builder = completion_test_builder(
            "\
for foo<CURSOR>
",
        );
        assert_snapshot!(
            builder.build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn for_target_suggests_in() {
        for source in [
            "for foo <CURSOR>",
            "for foo i<CURSOR>",
            "for foo in<CURSOR>",
            "for (foo) <CURSOR>",
            "for foo \\\n    <CURSOR>",
        ] {
            assert_eq!(completion_test_builder(source).build().snapshot(), "in");
        }
    }

    #[test]
    fn comprehension_target_suggests_in() {
        for source in [
            "[x for x <CURSOR>]",
            "(x for x <CURSOR>)",
            "{x for x <CURSOR>}",
            "{x: x for x <CURSOR>}",
            "[x async for x <CURSOR>]",
        ] {
            assert_eq!(completion_test_builder(source).build().snapshot(), "in");
        }
    }

    #[test]
    fn no_completions_before_existing_contextual_keyword() {
        for source in [
            "for foo <CURSOR>in items: pass",
            "[foo for foo <CURSOR>in items]",
            "with open('bar') <CURSOR>as file: pass",
            "try:\n    pass\nexcept ValueError <CURSOR>as error: pass",
            "match status:\n    case 400 <CURSOR>as value: pass",
        ] {
            assert_eq!(
                completion_test_builder(source).build().snapshot(),
                "<No completions found>"
            );
        }
    }

    #[test]
    fn no_completions_in_for_tuple_variable_binding() {
        let builder = completion_test_builder(
            "\
for foo, bar<CURSOR>
",
        );
        assert_snapshot!(
            builder.build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_function_param() {
        let builder = completion_test_builder(
            "\
def foo(p<CURSOR>
",
        );
        assert_snapshot!(
            builder.build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_function_param_keyword() {
        let builder = completion_test_builder(
            "\
def foo(in<CURSOR>
",
        );
        assert_snapshot!(
            builder.build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_function_param_multi_keyword() {
        let builder = completion_test_builder(
            "\
def foo(param, in<CURSOR>
",
        );
        assert_snapshot!(
            builder.build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_function_param_multi_keyword_middle() {
        let builder = completion_test_builder(
            "\
def foo(param, in<CURSOR>, param_two
",
        );
        assert_snapshot!(
            builder.build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn no_completions_in_function_type_param() {
        let builder = completion_test_builder(
            "\
def foo[T<CURSOR>]
",
        );
        assert_snapshot!(
            builder.build().snapshot(),
            @"<No completions found>",
        );
    }

    #[test]
    fn completions_in_function_type_param_bound() {
        completion_test_builder(
            "\
def foo[T: s<CURSOR>]
",
        )
        .build()
        .contains("str");
    }

    #[test]
    fn completions_in_function_param_type_annotation() {
        // Ensure that completions are no longer
        // suppressed when have left the name
        // definition block.
        completion_test_builder(
            "\
def foo(param: s<CURSOR>)
",
        )
        .build()
        .contains("str");
    }

    #[test]
    fn no_statement_keywords_in_if_condition() {
        completion_test_builder(
            "\
if a<CURSOR>:
    pass
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_while() {
        completion_test_builder(
            "\
while a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn iterable_only_keywords_in_for_statement_simple1() {
        completion_test_builder(
            "\
for x in a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .not_contains("raise")
        .not_contains("False");
    }

    #[test]
    fn iterable_only_keywords_in_for_statement_simple2() {
        completion_test_builder(
            "\
for x, y, _ in a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .not_contains("raise")
        .not_contains("False");
    }

    #[test]
    fn iterable_only_keywords_in_for_statement_simple3() {
        completion_test_builder(
            "\
for i, (x, y, z) in a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .not_contains("raise")
        .not_contains("False");
    }

    #[test]
    fn iterable_only_keywords_in_for_statement_complex() {
        completion_test_builder(
            "\
for i, (obj.x, (a[0], b['k']), _), *rest in a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .not_contains("raise")
        .not_contains("False");
    }

    #[test]
    fn no_statement_keywords_in_match_subject() {
        completion_test_builder(
            "\
match a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_match_case() {
        completion_test_builder(
            "\
match m:
    case a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_match_guard() {
        completion_test_builder(
            "\
match m:
    case foo if a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_assert() {
        completion_test_builder(
            "\
assert a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_raise() {
        completion_test_builder(
            "\
raise a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_return() {
        completion_test_builder(
            "\
return a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_assign() {
        completion_test_builder(
            "\
foo = a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_augassign() {
        completion_test_builder(
            "\
foo = 'bar'
foo += a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_annotated_assign() {
        completion_test_builder(
            "\
foo: str = a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_type_alias() {
        completion_test_builder(
            "\
type foo = a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_except_handler() {
        completion_test_builder(
            "\
try:
    a + 1
except a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_list_literal() {
        completion_test_builder(
            "\
[a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_tuple_literal() {
        completion_test_builder(
            "\
(a<CURSOR>,
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_set_literal() {
        completion_test_builder(
            "\
{a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_dict_literal() {
        completion_test_builder(
            "\
{a<CURSOR>: 1
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    // TODO: Suggesting False doesn't make much sense here.
    #[test]
    fn no_statement_keywords_in_with() {
        completion_test_builder(
            "\
with a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_lambda() {
        completion_test_builder(
            "\
lambda foo: a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_arguments() {
        completion_test_builder(
            "\
def foo(bar): ...
foo(a<CURSOR>)
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_keyword_arguments() {
        completion_test_builder(
            "\
def foo(*, bar): ...
foo(bar=a<CURSOR>)
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_starred_args_parameter_annotation() {
        completion_test_builder(
            "\
def foo(*args: a<CURSOR>): ...
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn no_statement_keywords_in_parameter_default() {
        completion_test_builder(
            "\
def foo(bar: str = a<CURSOR>): ...
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    // FIXME: Should not contain False as we're expecting an iterable
    // or a container.
    #[test]
    fn no_statement_keywords_in_if_x_in() {
        completion_test_builder(
            "\
if x in a<CURSOR>:
    pass
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("False")
        .not_contains("raise");
    }

    #[test]
    fn only_lambda_keyword_in_decorator_top_of_file() {
        completion_test_builder(
            "\
@a<CURSOR>
def func(): ...
",
        )
        .build()
        .contains("lambda")
        .not_contains("raise");
    }

    #[test]
    fn only_lambda_keyword_in_decorator() {
        completion_test_builder(
            "\
foo = 123

@a<CURSOR>
def func():
    ...
",
        )
        .build()
        .contains("lambda")
        .not_contains("await")
        .not_contains("raise")
        .not_contains("False");
    }

    #[test]
    fn decorator_without_class_or_function() {
        completion_test_builder(
            "\
from dataclasses import dataclass

@dataclass(froz<CURSOR>
",
        )
        .build()
        .contains("frozen");
    }

    #[test]
    fn decorator_args_do_not_suppress_keywords() {
        completion_test_builder(
            "\
from dataclasses import dataclass

@dataclass(frozen=Tr<CURSOR>
",
        )
        .build()
        .contains("True");
    }

    #[test]
    fn decorator_chained_call_args_do_not_suppress_keywords() {
        completion_test_builder(
            "\
  @decorator(foo=False)(bar=Tr<CURSOR>
  ",
        )
        .build()
        .contains("True");
    }

    #[test]
    fn statement_keywords_in_if_body() {
        completion_test_builder(
            "\
foo = 123
if foo:
    a<CURSOR>
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .contains("raise")
        .contains("False");
    }

    #[test]
    fn iterable_only_in_list_comprehension() {
        completion_test_builder(
            "\
[x for x in a<CURSOR>]
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .not_contains("False")
        .not_contains("raise");
    }

    #[test]
    fn iterable_only_in_generator() {
        completion_test_builder(
            "\
(x for x in a<CURSOR>)
",
        )
        .build()
        .contains("lambda")
        .contains("await")
        .not_contains("False")
        .not_contains("raise");
    }

    #[test]
    fn favour_symbols_currently_imported() {
        let snapshot = CursorTest::builder()
            .source("main.py", "long_nameb = 1\nlong_name<CURSOR>")
            .source("foo.py", "def long_namea(): ...")
            .completion_test_builder()
            .type_signatures()
            .module_names()
            .filter(|c| c.name.contains("long_name"))
            .build()
            .snapshot();

        // Even though long_namea is alphabetically before long_nameb,
        // long_nameb is currently imported and should be preferred.
        assert_snapshot!(snapshot, @"
        long_nameb :: Literal[1] :: <no import required>
        long_namea :: Unavailable :: foo
        ");
    }

    #[test]
    fn favour_imported_over_builtin() {
        let snapshot =
            completion_test_builder("from typing import Protocol\nclass Foo(P<CURSOR>: ...")
                .filter(|c| c.name.starts_with('P'))
                .skip_auto_import()
                .build()
                .snapshot();

        // Here we favour `Protocol` over the other completions
        // because `Protocol` has been imported, and the other completions are builtin.
        assert_snapshot!(snapshot, @"
        Protocol
        PendingDeprecationWarning
        PermissionError
        ProcessLookupError
        PythonFinalizationError
        ");
    }

    #[test]
    fn from_import_i_suggests_import() {
        let builder = completion_test_builder("from typing i<CURSOR>");
        assert_snapshot!(builder.build().snapshot(), @"import");
    }

    #[test]
    fn from_import_import_suggests_import() {
        let builder = completion_test_builder("from typing import<CURSOR>");
        assert_snapshot!(builder.build().snapshot(), @"import");
    }

    #[test]
    fn from_import_importt_suggests_nothing() {
        let builder = completion_test_builder("from typing importt<CURSOR>");
        assert_snapshot!(builder.build().snapshot(), @"<No completions found>");
    }

    #[test]
    fn from_import_space_suggests_import() {
        let builder = completion_test_builder("from typing <CURSOR>");
        assert_snapshot!(builder.build().snapshot(), @"import");
    }

    #[test]
    fn from_import_no_space_not_suggests_import() {
        let builder = completion_test_builder("from typing<CURSOR>");
        assert_snapshot!(builder.build().snapshot(), @"typing");
    }

    #[test]
    fn from_import_two_imports_suggests_import() {
        let builder = completion_test_builder(
            "from collections.abc import Sequence
            from typing i<CURSOR>",
        );
        assert_snapshot!(builder.build().snapshot(), @"import");
    }

    #[test]
    fn from_import_random_name_suggests_nothing() {
        let builder = completion_test_builder("from typing aa<CURSOR>");
        assert_snapshot!(builder.build().snapshot(), @"<No completions found>");
    }

    #[test]
    fn from_import_dotted_name_suggests_import() {
        let builder = completion_test_builder("from collections.abc i<CURSOR>");
        assert_snapshot!(builder.build().snapshot(), @"import");
    }

    #[test]
    fn from_import_relative_import_suggests_import() {
        let builder = CursorTest::builder()
            .source("main.py", "from .foo i<CURSOR>")
            .source("foo.py", "")
            .completion_test_builder();
        assert_snapshot!(builder.build().snapshot(), @"import");
    }

    #[test]
    fn from_import_dotted_name_relative_import_suggests_import() {
        let builder = CursorTest::builder()
            .source("main.py", "from .foo.bar i<CURSOR>")
            .source("foo/bar.py", "")
            .completion_test_builder();
        assert_snapshot!(builder.build().snapshot(), @"import");
    }

    #[test]
    fn from_import_nested_dotted_name_relative_import_suggests_import() {
        let builder = CursorTest::builder()
            .source("src/main.py", "from ..foo i<CURSOR>")
            .source("foo.py", "")
            .completion_test_builder();
        assert_snapshot!(builder.build().snapshot(), @"import");
    }

    #[test]
    fn from_import_nested_very_dotted_name_relative_import_suggests_import() {
        let builder = CursorTest::builder()
            // N.B. the `...` tokenizes as `TokenKind::Ellipsis`
            .source("src/main.py", "from ...foo i<CURSOR>")
            .source("foo.py", "")
            .completion_test_builder();
        assert_snapshot!(builder.build().snapshot(), @"import");
    }

    #[test]
    fn from_import_only_dot() {
        let builder = CursorTest::builder()
            .source("package/__init__.py", "")
            .source("package/foo.py", "")
            .source(
                "package/sub1/sub2/bar.py",
                "
import_zqzqzq = 1
from .<CURSOR>
",
            )
            .completion_test_builder();
        assert_snapshot!(builder.build().snapshot(), @"import");
    }

    #[test]
    fn from_import_only_dot_incomplete() {
        let builder = CursorTest::builder()
            .source("package/__init__.py", "")
            .source("package/foo.py", "")
            .source(
                "package/sub1/sub2/bar.py",
                "
import_zqzqzq = 1
from .imp<CURSOR>
",
            )
            .completion_test_builder();
        assert_snapshot!(builder.build().snapshot(), @"import");
    }

    #[test]
    fn from_import_incomplete() {
        let builder = completion_test_builder(
            "from collections.abc i

             ZQZQZQ = 1
             ZQ<CURSOR>",
        );
        assert_snapshot!(builder.build().snapshot(), @"ZQZQZQ");
    }

    #[test]
    fn relative_import_module_after_dots1() {
        let builder = CursorTest::builder()
            .source("package/__init__.py", "")
            .source("package/foo.py", "")
            .source("package/sub1/sub2/bar.py", "from ...<CURSOR>")
            .completion_test_builder();
        assert_snapshot!(builder.build().snapshot(), @"
        import
        foo
        ");
    }

    #[test]
    fn relative_import_module_after_dots2() {
        let builder = CursorTest::builder()
            .source("package/__init__.py", "")
            .source("package/foo/__init__.py", "")
            .source("package/foo/bar.py", "")
            .source("package/foo/baz.py", "")
            .source("package/sub1/sub2/bar.py", "from ...foo.<CURSOR>")
            .completion_test_builder();
        assert_snapshot!(builder.build().snapshot(), @"
        bar
        baz
        ");
    }

    #[test]
    fn relative_import_module_after_dots3() {
        let builder = CursorTest::builder()
            .source("package/__init__.py", "")
            .source("package/foo.py", "")
            .source("package/sub1/sub2/bar.py", "from.<CURSOR>")
            .completion_test_builder();
        assert_snapshot!(builder.build().snapshot(), @"import");
    }

    #[test]
    fn relative_import_module_after_dots4() {
        let builder = CursorTest::builder()
            .source("package/__init__.py", "")
            .source("package/foo.py", "")
            .source("package/sub1/bar.py", "from ..<CURSOR>")
            .completion_test_builder();
        assert_snapshot!(builder.build().snapshot(), @"
        import
        foo
        ");
    }

    #[test]
    fn relative_import_module_after_typing1() {
        let builder = CursorTest::builder()
            .source("package/__init__.py", "")
            .source("package/foo.py", "")
            .source("package/sub1/sub2/bar.py", "from ...fo<CURSOR>")
            .completion_test_builder();
        assert_snapshot!(builder.build().snapshot(), @"foo");
    }

    #[test]
    fn relative_import_module_after_typing2() {
        let builder = CursorTest::builder()
            .source("package/__init__.py", "")
            .source("package/foo/__init__.py", "")
            .source("package/foo/bar.py", "")
            .source("package/foo/baz.py", "")
            .source("package/sub1/sub2/bar.py", "from ...foo.ba<CURSOR>")
            .completion_test_builder();
        assert_snapshot!(builder.build().snapshot(), @"
        bar
        baz
        ");
    }

    #[test]
    fn relative_import_module_after_typing3() {
        let builder = CursorTest::builder()
            .source("package/__init__.py", "")
            .source("package/foo.py", "")
            .source("package/imposition.py", "")
            .source("package/sub1/sub2/bar.py", "from ...im<CURSOR>")
            .completion_test_builder();
        assert_snapshot!(builder.build().snapshot(), @"
        import
        imposition
        ");
    }

    #[test]
    fn relative_import_module_after_typing4() {
        let builder = CursorTest::builder()
            .source("package/__init__.py", "")
            .source("package/sub1/__init__.py", "")
            .source("package/sub1/foo.py", "")
            .source("package/sub1/imposition.py", "")
            .source("package/sub1/bar.py", "from ..sub1.<CURSOR>")
            .completion_test_builder();
        assert_snapshot!(builder.build().snapshot(), @"
        bar
        foo
        imposition
        ");
    }

    #[test]
    fn typing_extensions_excluded_from_import() {
        let builder = completion_test_builder("from typing<CURSOR>").module_names();
        assert_snapshot!(builder.build().snapshot(), @"typing :: <no import required>");
    }

    #[test]
    fn typing_extensions_excluded_from_auto_import() {
        let builder = completion_test_builder("deprecated<CURSOR>").module_names();
        assert_snapshot!(builder.build().snapshot(), @"deprecated :: warnings");
    }

    #[test]
    fn typing_extensions_included_from_import() {
        let builder = CursorTest::builder()
            .source("typing_extensions.py", "deprecated = 1")
            .source("foo.py", "from typing<CURSOR>")
            .completion_test_builder()
            .module_names();
        assert_snapshot!(builder.build().snapshot(), @"
        typing :: <no import required>
        typing_extensions :: <no import required>
        ");
    }

    #[test]
    fn typing_extensions_included_from_auto_import() {
        let builder = CursorTest::builder()
            .source("typing_extensions.py", "deprecated = 1")
            .source("foo.py", "deprecated<CURSOR>")
            .completion_test_builder()
            .module_names();
        assert_snapshot!(builder.build().snapshot(), @"
        deprecated :: typing_extensions
        deprecated :: warnings
        ");
    }

    #[test]
    fn typing_extensions_included_from_import_in_stub() {
        let builder = CursorTest::builder()
            .source("foo.pyi", "from typing<CURSOR>")
            .completion_test_builder()
            .module_names();
        assert_snapshot!(builder.build().snapshot(), @"
        typing :: <no import required>
        typing_extensions :: <no import required>
        ");
    }

    #[test]
    fn typing_extensions_included_from_auto_import_in_stub() {
        let builder = CursorTest::builder()
            .source("foo.pyi", "deprecated<CURSOR>")
            .completion_test_builder()
            .module_names();
        assert_snapshot!(builder.build().snapshot(), @"
        deprecated :: typing_extensions
        deprecated :: warnings
        ");
    }

    #[test]
    fn auto_import_prefers_typing_over_third_party_reexport() {
        let builder = CursorTest::builder()
            .with_site_packages()
            .source("main.py", "Concaten<CURSOR>")
            .site_packages(
                "thirdparty/__init__.py",
                r#"
from typing import Concatenate as Concatenate
"#,
            )
            .completion_test_builder()
            .module_names()
            .filter(|c| {
                c.name == "Concatenate"
                    && matches!(
                        c.module_name.map(ModuleName::as_str),
                        Some("typing" | "thirdparty")
                    )
            });
        assert_snapshot!(builder.build().snapshot(), @"
        Concatenate :: typing
        Concatenate :: thirdparty
        ");
    }

    #[test]
    fn auto_import_prefers_collections_over_third_party_reexport() {
        let builder = CursorTest::builder()
            .with_site_packages()
            .source("main.py", "ChainM<CURSOR>")
            .site_packages(
                "thirdparty/__init__.py",
                r#"
from collections import ChainMap as ChainMap
"#,
            )
            .completion_test_builder()
            .module_names()
            .filter(|c| {
                c.name == "ChainMap"
                    && matches!(
                        c.module_name.map(ModuleName::as_str),
                        Some("collections" | "thirdparty")
                    )
            });
        assert_snapshot!(builder.build().snapshot(), @"
        ChainMap :: collections
        ChainMap :: thirdparty
        ");
    }

    #[test]
    fn auto_import_deprioritizes_deprecated_over_stdlib_special() {
        let builder = CursorTest::builder()
            .with_site_packages()
            .source("main.py", "no_type_check_dec<CURSOR>")
            .site_packages(
                "thirdparty/__init__.py",
                r#"
def no_type_check_decorator():
    pass
"#,
            )
            .completion_test_builder()
            .module_names()
            .filter(|c| {
                c.name == "no_type_check_decorator"
                    && matches!(
                        c.module_name.map(ModuleName::as_str),
                        Some("typing" | "thirdparty")
                    )
            });
        assert_snapshot!(builder.build().snapshot(), @"
        no_type_check_decorator :: thirdparty
        no_type_check_decorator :: typing
        ");
    }

    #[test]
    fn auto_import_prefers_std_lib_symbol_over_third_party_symbol_with_same_name() {
        let builder = CursorTest::builder()
            .with_site_packages()
            .source("main.py", "getpid<CURSOR>")
            .site_packages(
                "thirdparty/__init__.py",
                r#"
from os import getpid as getpid
"#,
            )
            .completion_test_builder()
            .module_names()
            .filter(|c| {
                c.name == "getpid"
                    && matches!(
                        c.module_name.map(ModuleName::as_str),
                        Some("os" | "thirdparty")
                    )
            });
        assert_snapshot!(builder.build().snapshot(), @"
        getpid :: os
        getpid :: thirdparty
        ");
    }

    #[test]
    fn auto_import_preserves_name_order_for_unrelated_third_party_symbol() {
        let builder = CursorTest::builder()
            .with_site_packages()
            .source("main.py", "av<CURSOR>")
            .site_packages("thirdparty/__init__.py", "aardvark = 1")
            .completion_test_builder()
            .module_names()
            .filter(|c| {
                matches!(
                    (c.name.as_str(), c.module_name.map(ModuleName::as_str)),
                    ("argv", Some("sys")) | ("aardvark", Some("thirdparty"))
                )
            });
        assert_snapshot!(builder.build().snapshot(), @"
        aardvark :: thirdparty
        argv :: sys
        ");
    }

    #[test]
    fn auto_import_match_quality_ranks_prefix_before_substring() {
        let builder = CursorTest::builder()
            .source("main.py", "prefix<CURSOR>")
            .source(
                "matching.py",
                r#"
def a_prefix():
    pass

def prefix_name():
    pass
"#,
            )
            .completion_test_builder()
            .module_names()
            .filter(|c| matches!(c.name.as_str(), "a_prefix" | "prefix_name"));

        assert_snapshot!(builder.build().snapshot(), @"
        prefix_name :: matching
        a_prefix :: matching
        ");
    }

    #[test]
    fn reexport_simple_import_noauto() {
        let snapshot = CursorTest::builder()
            .source(
                "main.py",
                r#"
import foo
foo.ZQ<CURSOR>
"#,
            )
            .source("foo.py", r#"from bar import ZQZQ"#)
            .source("bar.py", r#"ZQZQ = 1"#)
            .completion_test_builder()
            .module_names()
            .build()
            .snapshot();
        assert_snapshot!(snapshot, @"ZQZQ :: <no import required>");
    }

    #[test]
    fn reexport_simple_import_auto() {
        let snapshot = CursorTest::builder()
            .source(
                "main.py",
                r#"
ZQ<CURSOR>
"#,
            )
            .source("foo.py", r#"from bar import ZQZQ"#)
            .source("bar.py", r#"ZQZQ = 1"#)
            .completion_test_builder()
            .module_names()
            .build()
            .snapshot();
        // We're specifically looking for `ZQZQ` in `bar`
        // here but *not* in `foo`. Namely, in `foo`,
        // `ZQZQ` is a "regular" import that is not by
        // convention considered a re-export.
        assert_snapshot!(snapshot, @"ZQZQ :: bar");
    }

    #[test]
    fn reexport_redundant_convention_import_noauto() {
        let snapshot = CursorTest::builder()
            .source(
                "main.py",
                r#"
import foo
foo.ZQ<CURSOR>
"#,
            )
            .source("foo.py", r#"from bar import ZQZQ as ZQZQ"#)
            .source("bar.py", r#"ZQZQ = 1"#)
            .completion_test_builder()
            .module_names()
            .build()
            .snapshot();
        assert_snapshot!(snapshot, @"ZQZQ :: <no import required>");
    }

    #[test]
    fn reexport_redundant_convention_import_auto() {
        let snapshot = CursorTest::builder()
            .source(
                "main.py",
                r#"
ZQ<CURSOR>
"#,
            )
            .source("foo.py", r#"from bar import ZQZQ as ZQZQ"#)
            .source("bar.py", r#"ZQZQ = 1"#)
            .completion_test_builder()
            .module_names()
            .build()
            .snapshot();
        assert_snapshot!(snapshot, @"
        ZQZQ :: bar
        ZQZQ :: foo
        ");
    }

    #[test]
    fn auto_import_respects_all() {
        let snapshot = CursorTest::builder()
            .source(
                "main.py",
                r#"
ZQ<CURSOR>
"#,
            )
            .source(
                "bar.py",
                r#"
                ZQZQ1 = 1
                ZQZQ2 = 1
                __all__ = ['ZQZQ1']
            "#,
            )
            .completion_test_builder()
            .module_names()
            .build()
            .snapshot();
        // We specifically do not want `ZQZQ2` here, since
        // it is not part of `__all__`.
        assert_snapshot!(snapshot, @"ZQZQ1 :: bar");
    }

    // This test confirms current behavior (as of 2025-12-04), but
    // it's not consistent with auto-import. That is, it doesn't
    // strictly respect `__all__` on `bar`, but perhaps it should.
    //
    // See: https://github.com/astral-sh/ty/issues/1757
    #[test]
    fn object_attr_ignores_all() {
        let snapshot = CursorTest::builder()
            .source(
                "main.py",
                r#"
import bar
bar.ZQ<CURSOR>
"#,
            )
            .source(
                "bar.py",
                r#"
                ZQZQ1 = 1
                ZQZQ2 = 1
                __all__ = ['ZQZQ1']
            "#,
            )
            .completion_test_builder()
            .module_names()
            .build()
            .snapshot();
        // We specifically do not want `ZQZQ2` here, since
        // it is not part of `__all__`.
        assert_snapshot!(snapshot, @"
        ZQZQ1 :: <no import required>
        ZQZQ2 :: <no import required>
        ");
    }

    #[test]
    fn auto_import_ignores_modules_with_leading_underscore() {
        let snapshot = CursorTest::builder()
            .source(
                "main.py",
                r#"
Quitter<CURSOR>
"#,
            )
            .completion_test_builder()
            .module_names()
            .build()
            .snapshot();
        // There is a `Quitter` in `_sitebuiltins` in the standard
        // library. But this is skipped by auto-import because it's
        // 1) not first party and 2) starts with an `_`.
        assert_snapshot!(snapshot, @"<No completions found>");
    }

    #[test]
    fn auto_import_includes_modules_with_leading_underscore_in_first_party() {
        let snapshot = CursorTest::builder()
            .source(
                "main.py",
                r#"
ZQ<CURSOR>
"#,
            )
            .source(
                "bar.py",
                r#"
                ZQZQ1 = 1
            "#,
            )
            .source(
                "_foo.py",
                r#"
                ZQZQ1 = 1
            "#,
            )
            .completion_test_builder()
            .module_names()
            .build()
            .snapshot();
        assert_snapshot!(snapshot, @"
        ZQZQ1 :: _foo
        ZQZQ1 :: bar
        ");
    }

    #[test]
    fn auto_import_includes_stdlib_modules_as_suggestions() {
        let snapshot = CursorTest::builder()
            .source(
                "main.py",
                r#"
multiprocess<CURSOR>
"#,
            )
            .completion_test_builder()
            .build()
            .snapshot();
        assert_snapshot!(snapshot, @"
        multiprocessing
        multiprocessing.connection
        multiprocessing.context
        multiprocessing.dummy
        multiprocessing.dummy.connection
        multiprocessing.forkserver
        multiprocessing.heap
        multiprocessing.managers
        multiprocessing.pool
        multiprocessing.popen_fork
        multiprocessing.popen_forkserver
        multiprocessing.popen_spawn_posix
        multiprocessing.popen_spawn_win32
        multiprocessing.process
        multiprocessing.queues
        multiprocessing.reduction
        multiprocessing.resource_sharer
        multiprocessing.resource_tracker
        multiprocessing.shared_memory
        multiprocessing.sharedctypes
        multiprocessing.spawn
        multiprocessing.synchronize
        multiprocessing.util
        ");
    }

    #[test]
    fn auto_import_includes_first_party_modules_as_suggestions() {
        let snapshot = CursorTest::builder()
            .source(
                "main.py",
                r#"
zqzqzq<CURSOR>
"#,
            )
            .source("zqzqzqzqzq.py", "")
            .completion_test_builder()
            .build()
            .snapshot();
        assert_snapshot!(snapshot, @"zqzqzqzqzq");
    }

    #[test]
    fn auto_import_includes_sub_modules_as_suggestions() {
        let snapshot = CursorTest::builder()
            .source(
                "main.py",
                r#"
collabc<CURSOR>
"#,
            )
            .completion_test_builder()
            .build()
            .snapshot();
        assert_snapshot!(snapshot, @"collections.abc");
    }

    #[test]
    fn local_function_variable_with_return() {
        let builder = completion_test_builder(
            "\
variable_global = 1
def foo():
    variable_local = 1
    variable_<CURSOR>
    return
",
        );
        assert_snapshot!(
            builder.skip_auto_import().build().snapshot(),
            @"
        variable_global
        variable_local
        ",
        );
    }

    #[test]
    fn complete_function_parentheses() {
        let builder = completion_test_builder(
            "\
def callable():
    pass

call<CURSOR>
",
        );
        assert_snapshot!(
            &builder
                .skip_auto_import()
                .skip_builtins()
                .complete_function_parentheses()
                .snippets_support(true)
                .build()
                .snapshot(),
            @"callable($0)",
        );
    }

    #[test]
    fn complete_function_parentheses_existing_parenthesis() {
        let builder = completion_test_builder(
            "\
def callable():
    pass

call<CURSOR>(
",
        );
        assert_snapshot!(
            builder
                .skip_auto_import()
                .skip_builtins()
                .complete_function_parentheses()
                .build()
                .snapshot(),
            @"callable",
        );
    }

    #[test]
    fn complete_function_parentheses_from_import() {
        let builder = completion_test_builder("from sys import get<CURSOR>");
        assert_snapshot!(
            builder
                .complete_function_parentheses()
                .filter(|completion| {
                    completion.name == "getsizeof"
                        && completion.kind == Some(CompletionKind::Function)
                })
                .build()
                .snapshot(),
            @"getsizeof",
        );
    }

    #[test]
    fn complete_class_parentheses() {
        let builder = completion_test_builder(
            "\
class CallableType: ...

value = Call<CURSOR>
",
        );
        assert_snapshot!(
            builder
                .skip_auto_import()
                .skip_builtins()
                .complete_function_parentheses()
                .build()
                .snapshot(),
            @"CallableType()",
        );
    }

    #[test]
    fn complete_class_parentheses_disabled_by_default() {
        let builder = completion_test_builder(
            "\
class CallableType: ...

value = Call<CURSOR>
",
        );
        assert_snapshot!(
            builder.skip_auto_import().skip_builtins().build().snapshot(),
            @"CallableType",
        );
    }

    #[test]
    fn complete_function_parentheses_type_expression() {
        let builder = completion_test_builder(
            "\
class CallableType: ...

value: Call<CURSOR>
",
        );
        assert_snapshot!(
            builder
                .skip_auto_import()
                .skip_builtins()
                .complete_function_parentheses()
                .build()
                .snapshot(),
            @"CallableType",
        );
    }

    #[test]
    fn complete_function_parentheses_function_in_annotation_metadata() {
        let builder = completion_test_builder(
            "\
from typing import Annotated

def validator(): ...

value: Annotated[int, vali<CURSOR>]
",
        );
        assert_snapshot!(
            builder
                .skip_auto_import()
                .skip_builtins()
                .complete_function_parentheses()
                .build()
                .snapshot(),
            @"validator()",
        );
    }

    #[test]
    fn complete_function_parentheses_pep_613_type_alias() {
        let builder = completion_test_builder(
            "\
from typing import TypeAlias

class CallableType: ...

MyAlias: TypeAlias = Call<CURSOR>
",
        );
        assert_snapshot!(
            builder
                .skip_auto_import()
                .skip_builtins()
                .complete_function_parentheses()
                .build()
                .snapshot(),
            @"CallableType",
        );
    }

    #[test]
    fn complete_function_parentheses_except_handler() {
        let builder = completion_test_builder(
            "\
try:
    pass
except Value<CURSOR>:
    pass
",
        );
        assert_snapshot!(
            builder
                .skip_auto_import()
                .complete_function_parentheses()
                .filter(|completion| completion.name == "ValueError")
                .build()
                .snapshot(),
            @"ValueError",
        );
    }

    #[test]
    fn complete_function_parentheses_except_handler_factory() {
        let builder = completion_test_builder(
            "\
def exception_factory() -> type[Exception]:
    return ValueError

try:
    pass
except exception_f<CURSOR>:
    pass
",
        );
        assert_snapshot!(
            builder
                .skip_auto_import()
                .skip_builtins()
                .complete_function_parentheses()
                .build()
                .snapshot(),
            @"exception_factory()",
        );
    }

    #[test]
    fn complete_function_parentheses_decorator_name() {
        let builder = completion_test_builder(
            "\
def decorator(func): ...

@decor<CURSOR>
def decorated(): ...
",
        );
        assert_snapshot!(
            builder
                .skip_auto_import()
                .skip_builtins()
                .complete_function_parentheses()
                .build()
                .snapshot(),
            @"
        decorated
        decorator
        ",
        );
    }

    #[test]
    fn complete_function_parentheses_class_base() {
        let builder = completion_test_builder(
            "\
class Parent: ...

class Derived(Par<CURSOR>
",
        );
        assert_snapshot!(
            builder
                .skip_auto_import()
                .skip_builtins()
                .complete_function_parentheses()
                .build()
                .snapshot(),
            @"
        Parent
        ",
        );
    }

    #[test]
    fn nested_scopes_with_return() {
        let builder = completion_test_builder(
            "\
variable_1 = 1
def fun1():
    variable_2 = 1
    def fun2():
        variable_3 = 1
        def fun3():
            variable_4 = 1
            variable_<CURSOR>
            return
        return
    return
",
        );
        assert_snapshot!(
            builder.skip_auto_import().build().snapshot(),
            @"
        variable_1
        variable_2
        variable_3
        variable_4
        ",
        );
    }

    #[test]
    fn multiple_declarations_global_scope1() {
        let builder = completion_test_builder(
            "\
zqzqzq: int = 1
zqzqzq: str = 'foo'
zqzq<CURSOR>
",
        );
        // The type for `zqzqzq` *should* be `str`, but we consider all
        // reachable declarations and bindings, which means we get a
        // union of `int` and `str` here even though the `int` binding
        // isn't live at the cursor position.
        assert_snapshot!(
            builder.skip_auto_import().type_signatures().build().snapshot(),
            @"zqzqzq :: int | str",
        );
    }

    #[test]
    fn multiple_declarations_global_scope2() {
        let builder = completion_test_builder(
            "\
zqzqzq: int = 1
zqzq<CURSOR>
zqzqzq: str = 'foo'
",
        );
        // The type for `zqzqzq` *should* be `int`, but we consider all
        // reachable declarations and bindings, which means we get a
        // union of `int` and `str` here even though the `str` binding
        // doesn't exist at the cursor position.
        assert_snapshot!(
            builder.skip_auto_import().type_signatures().build().snapshot(),
            @"zqzqzq :: int | str",
        );
    }

    #[test]
    fn multiple_declarations_function_scope1() {
        let builder = completion_test_builder(
            "\
def foo():
    zqzqzq: int = 1
    zqzqzq: str = 'foo'
    zqzq<CURSOR>
    return
",
        );
        // The type for `zqzqzq` *should* be `str`, but we consider all
        // reachable declarations and bindings, which means we get a
        // union of `int` and `str` here even though the `int` binding
        // isn't live at the cursor position.
        assert_snapshot!(
            builder.skip_auto_import().type_signatures().build().snapshot(),
            @"zqzqzq :: int | str",
        );
    }

    #[test]
    fn multiple_declarations_function_scope2() {
        let builder = completion_test_builder(
            "\
def foo():
    zqzqzq: int = 1
    zqzq<CURSOR>
    zqzqzq: str = 'foo'
    return
",
        );
        // The type for `zqzqzq` *should* be `int`, but we consider all
        // reachable declarations and bindings, which means we get a
        // union of `int` and `str` here even though the `str` binding
        // doesn't exist at the cursor position.
        assert_snapshot!(
            builder.skip_auto_import().type_signatures().build().snapshot(),
            @"zqzqzq :: int | str",
        );
    }

    #[test]
    fn multiple_declarations_function_parameter() {
        let builder = completion_test_builder(
            "\
from pathlib import Path
def f(zqzqzq: str):
    zqzqzq: Path = Path(zqzqzq)
    zqzq<CURSOR>
    return
",
        );
        // The type for `zqzqzq` *should* be `Path`, but we consider all
        // reachable declarations and bindings, which means we get a
        // union of `str` and `Path` here even though the `str` binding
        // isn't live at the cursor position.
        assert_snapshot!(
            builder.skip_auto_import().type_signatures().build().snapshot(),
            @"zqzqzq :: str | Path",
        );
    }

    #[test]
    fn auto_import_prioritizes_reusing_import_from_statements() {
        let builder = completion_test_builder(
            "\
import typing
from typing import Callable
TypedDi<CURSOR>
",
        );
        assert_snapshot!(
            builder.imports().build().snapshot(),
            @"
        TypedDict :: , TypedDict
        is_typeddict :: , is_typeddict
        _FilterConfigurationTypedDict :: from logging.config import _FilterConfigurationTypedDict

        _FormatterConfigurationTypedDict :: from logging.config import _FormatterConfigurationTypedDict
        ",
        );
    }

    #[test]
    fn auto_import_omits_directly_imported_symbol() {
        let builder = CursorTest::builder()
            .source("main.py", "from package import Thing\nThi<CURSOR>\n")
            .source("package/__init__.py", "class Thing: ...\n")
            .completion_test_builder()
            .module_names()
            .imports()
            .filter(|c| c.name == "Thing");
        assert_snapshot!(
            builder.build().snapshot(),
            @"Thing :: <no import required> :: <no import edit>",
        );
    }

    #[test]
    fn auto_import_omits_wildcard_imported_symbol() {
        let builder = CursorTest::builder()
            .source("main.py", "from package import *\nThi<CURSOR>\n")
            .source("package/__init__.py", "class Thing: ...\n")
            .completion_test_builder()
            .module_names()
            .imports()
            .filter(|c| c.name == "Thing");
        assert_snapshot!(
            builder.build().snapshot(),
            @"Thing :: <no import required> :: <no import edit>",
        );
    }

    #[test]
    fn auto_import_omits_directly_imported_symbol_in_nested_scope() {
        let builder = CursorTest::builder()
            .source(
                "main.py",
                "\
def f():
    from package import Thing
    Thi<CURSOR>
",
            )
            .source("package/__init__.py", "class Thing: ...\n")
            .completion_test_builder()
            .module_names()
            .imports()
            .filter(|c| c.name == "Thing");
        assert_snapshot!(
            builder.build().snapshot(),
            @"Thing :: <no import required> :: <no import edit>",
        );
    }

    #[test]
    fn auto_import_omits_imported_symbol_from_parent_scope() {
        let builder = CursorTest::builder()
            .source(
                "main.py",
                "\
from package import Thing

def f():
    Thi<CURSOR>
",
            )
            .source("package/__init__.py", "class Thing: ...\n")
            .completion_test_builder()
            .module_names()
            .imports()
            .filter(|c| c.name == "Thing");
        assert_snapshot!(
            builder.build().snapshot(),
            @"Thing :: <no import required> :: <no import edit>",
        );
    }

    #[test]
    fn auto_import_omits_conditionally_imported_symbol_available_in_all_branches() {
        let builder = CursorTest::builder()
            .source(
                "main.py",
                "\
if condition:
    from package import Thing
else:
    from package import Thing

Thi<CURSOR>
",
            )
            .source("package/__init__.py", "class Thing: ...\n")
            .completion_test_builder()
            .module_names()
            .imports()
            .filter(|c| c.name == "Thing");
        assert_snapshot!(
            builder.build().snapshot(),
            @"Thing :: <no import required> :: <no import edit>",
        );
    }

    #[test]
    fn auto_import_omits_conditionally_imported_symbol_available_in_some_branches() {
        let builder = CursorTest::builder()
            .source(
                "main.py",
                "\
if condition:
    from package import Thing

Thi<CURSOR>
",
            )
            .source("package/__init__.py", "class Thing: ...\n")
            .completion_test_builder()
            .module_names()
            .imports()
            .filter(|c| c.name == "Thing");
        assert_snapshot!(
            builder.build().snapshot(),
            @"Thing :: <no import required> :: <no import edit>",
        );
    }

    #[test]
    fn auto_import_preserves_suggestion_for_import_after_cursor() {
        let builder = CursorTest::builder()
            .source("main.py", "Thi<CURSOR>\nfrom package import Thing\n")
            .source("package/__init__.py", "class Thing: ...\n")
            .completion_test_builder()
            .module_names()
            .imports()
            .filter(|c| c.name == "Thing");
        assert_snapshot!(
            builder.build().snapshot(),
            @"
        Thing :: <no import required> :: <no import edit>
        Thing :: package :: from package import Thing
        ",
        );
    }

    #[test]
    fn auto_import_preserves_qualified_suggestion_for_same_named_import() {
        let builder = CursorTest::builder()
            .source("main.py", "from other import Thing\nThi<CURSOR>\n")
            .source("other/__init__.py", "class Thing: ...\n")
            .source("package/__init__.py", "class Thing: ...\n")
            .completion_test_builder()
            .module_names()
            .imports()
            .filter(|c| c.name == "Thing");
        assert_snapshot!(
            builder.build().snapshot(),
            @"
        Thing :: <no import required> :: <no import edit>
        package.Thing :: package :: import package
        ",
        );
    }

    #[test]
    fn auto_import_preserves_qualified_suggestion_for_shadowed_import() {
        let builder = CursorTest::builder()
            .source(
                "main.py",
                "\
from package import Thing

def f():
    Thing = 1
    Thi<CURSOR>
",
            )
            .source("package/__init__.py", "class Thing: ...\n")
            .completion_test_builder()
            .module_names()
            .imports()
            .filter(|c| c.name == "Thing");
        assert_snapshot!(
            builder.build().snapshot(),
            @"
        Thing :: <no import required> :: <no import edit>
        package.Thing :: package :: import package
        ",
        );
    }

    #[test]
    fn auto_import_does_not_use_enclosing_class_scope() {
        let builder = CursorTest::builder()
            .source(
                "main.py",
                "\
class C:
    from package import Thing

    def f(self):
        Thi<CURSOR>
",
            )
            .source("package/__init__.py", "class Thing: ...\n")
            .completion_test_builder()
            .module_names()
            .imports()
            .filter(|c| c.name == "Thing" && c.import.is_some());
        assert_snapshot!(
            builder.build().snapshot(),
            @"Thing :: package :: from package import Thing",
        );
    }

    /// Tests that `xs = ["..."]; xs[0].<CURSOR>` gets completions
    /// appropriate for `str`.
    #[test]
    fn dynamic_type_list_no_type_annotation() {
        let builder = completion_test_builder(
            r#"
my_list = ["foo"]
my_list[0].remove<CURSOR>
"#,
        );
        assert_snapshot!(
            builder.build().snapshot(),
            @"
        removeprefix
        removesuffix
        ",
        );
    }

    /// Tests that when we have `Any | T` that we offer
    /// completions for `T`.
    #[test]
    fn dynamic_type_with_type_annotation() {
        let builder = completion_test_builder(
            r#"
from typing import Any

def f(x: Any | str):
    x.remove<CURSOR>
"#,
        );
        assert_snapshot!(
            builder.build().snapshot(),
            @"
        removeprefix
        removesuffix
        ",
        );
    }

    /// Tests that when we have `(U & Any) | T` that we offer
    /// completions for `T`.
    #[test]
    fn dynamic_type_with_intersection_type_annotation() {
        let builder = completion_test_builder(
            r#"
from typing import Any
from ty_extensions import Intersection

def f(x: Intersection[int, Any] | str):
    x.remove<CURSOR>
"#,
        );
        assert_snapshot!(
            builder.build().snapshot(),
            @"
        removeprefix
        removesuffix
        ",
        );
    }

    #[test]
    fn dunder_file_completion() {
        let builder = completion_test_builder("__fil<CURSOR>");

        // __file__ should be `str` when accessed within a module, not `str | None`
        assert_snapshot!(
            builder.skip_keywords().skip_auto_import().type_signatures().build().snapshot(),
            @"__file__ :: str",
        );
    }

    #[test]
    fn dunder_file_attribute_completion_non_namespace_package() {
        let builder = CursorTest::builder()
            .source("module.py", "")
            .source("main.py", "import module; module.__file<CURSOR>")
            .completion_test_builder();

        // __file__ should be `str` when accessed as an attribute on a non-namespace-package module,
        // not `str | None`
        assert_snapshot!(
            builder.skip_keywords().skip_auto_import().type_signatures().build().snapshot(),
            @"__file__ :: str",
        );
    }

    #[test]
    fn dunder_file_attribute_completion_namespace_package() {
        let builder = CursorTest::builder()
            .source("namespace_package/foo.py", "")
            .source(
                "main.py",
                "import namespace_package; namespace_package.__file<CURSOR>",
            )
            .completion_test_builder();

        // __file__ should be `None` when accessed as an attribute on a namespace-package module,
        // not `str | None`
        assert_snapshot!(
            builder.skip_keywords().skip_auto_import().type_signatures().build().snapshot(),
            @"__file__ :: None",
        );
    }

    #[test]
    fn no_duplicate_keyword_arg() {
        let builder = completion_test_builder(
            r#"
import re
re.match('', '', fla<CURSOR>
"#,
        );
        assert_snapshot!(
            builder.skip_auto_import().skip_builtins().build().snapshot(),
            @"flags=",
        );
    }

    #[test]
    fn call_keyword_argument_at_value() {
        let builder = completion_test_builder(
            "\
def bar(y_true,y_pred): ...

y_true = 1
y_pred = 2

bar(y_true=y<CURSOR>
",
        );

        assert_snapshot!(
            builder.skip_keywords().skip_builtins().skip_auto_import().build().snapshot(),
            @r###"
        y_pred
        y_true
        "###
        );
    }

    // Ideally, we should favour completions that are definitely raisable
    // here. However, doing so would require `exception_ty` to fall back to
    // token matching when AST-matching fails, making the function significantly
    // more complex. At the time of writing, this trade-off was not
    // considered worthwhile.
    //
    // Note that this only applies to bare raise/raise-from statements with no
    // characters typed after it. It does not apply to `raise A<CURSOR>`.
    #[test]
    fn empty_raise_statement() {
        let builder = completion_test_builder(
            r#"
raise <CURSOR>
"#,
        );
        assert_snapshot!(
            builder.skip_auto_import().skip_builtins().build().snapshot(),
            @"
        and
        as
        assert
        async
        await
        break
        case
        class
        continue
        def
        del
        elif
        else
        except
        finally
        for
        from
        global
        if
        import
        in
        is
        lambda
        match
        nonlocal
        not
        or
        pass
        raise
        return
        try
        while
        with
        yield
        ",
        );
    }

    /// A way to create a simple single-file (named `main.py`) completion test
    /// builder.
    ///
    /// Use cases that require multiple files with a `<CURSOR>` marker
    /// in a file other than `main.py` can use `CursorTest::builder()`
    /// and then `CursorTestBuilder::completion_test_builder()`.
    fn completion_test_builder(source: &str) -> CompletionTestBuilder {
        CursorTest::builder()
            .source("main.py", source)
            .completion_test_builder()
    }

    /// A builder for executing a completion test.
    ///
    /// This mostly owns the responsibility for generating snapshots
    /// of completions from a cursor position in source code. Most of
    /// the options involve some kind of filtering or adjustment to
    /// apply to the snapshots, depending on what one wants to test.
    #[expect(clippy::struct_excessive_bools)] // free the bools!
    struct CompletionTestBuilder {
        cursor_test: CursorTest,
        settings: CompletionSettings,
        skip_builtins: bool,
        skip_keywords: bool,
        skip_dunders: bool,
        type_signatures: bool,
        imports: bool,
        module_names: bool,
        // This doesn't seem like a "very complex" type to me... ---AG
        #[expect(clippy::type_complexity)]
        predicate: Option<Box<dyn Fn(&Completion) -> bool>>,
        capabilities: CompletionCapabilities,
    }

    impl CompletionTestBuilder {
        /// Returns completions based on this configuration.
        fn build(&self) -> CompletionTest<'_> {
            let original = completion(
                &self.cursor_test.db,
                &self.settings,
                self.capabilities,
                self.cursor_test.cursor.file,
                self.cursor_test.cursor.offset,
            );
            let filtered = original
                .iter()
                .filter(|c| !self.skip_builtins || !c.builtin)
                .filter(|c| !self.skip_keywords || c.kind != Some(CompletionKind::Keyword))
                .filter(|c| !self.skip_dunders || !is_dunder(&c.name))
                .filter(|c| {
                    self.predicate
                        .as_ref()
                        .map(|predicate| predicate(c))
                        .unwrap_or(true)
                })
                .cloned()
                .collect();
            CompletionTest {
                db: self.db(),
                original,
                filtered,
                type_signatures: self.type_signatures,
                imports: self.imports,
                module_names: self.module_names,
            }
        }

        /// Returns the underlying test DB.
        fn db(&self) -> &ty_project::TestDb {
            &self.cursor_test.db
        }

        /// When set, symbols that aren't in scope but available
        /// in the environment will NOT be included.
        ///
        /// Auto-import is enabled by default. So one must opt into
        /// skipping them with this method if one wants to test
        /// completions without auto-import enabled.
        ///
        /// It's somewhat common to want to skip auto-import
        /// completions because they can otherwise inflate the
        /// snapshot size quite a bit and obscure what is actually
        /// being tested.
        fn skip_auto_import(mut self) -> CompletionTestBuilder {
            self.settings.auto_import = false;
            self
        }

        /// When set, completions insert parentheses for callable items.
        fn complete_function_parentheses(mut self) -> CompletionTestBuilder {
            self.settings.complete_function_parentheses = true;
            self
        }

        /// When set, completions may make use of snippets in the insertion text
        /// (e.g. `$0`).
        fn snippets_support(mut self, supported: bool) -> CompletionTestBuilder {
            self.capabilities = self.capabilities.snippets(supported);
            self
        }

        /// When set, builtins from completions are skipped. This is
        /// useful in tests to reduce noise for scope based completions.
        ///
        /// Not enabled by default.
        fn skip_builtins(mut self) -> CompletionTestBuilder {
            self.skip_builtins = true;
            self
        }

        /// When set, keywords from completions are skipped. This
        /// is useful in tests to reduce noise for scope based
        /// completions.
        ///
        /// Not enabled by default.
        ///
        /// Note that, at time of writing (2025-11-11), keywords are
        /// *also* considered builtins. So `skip_builtins()` will also
        /// skip keywords. But this may not always be true. And one
        /// might want to skip keywords but *not* builtins.
        fn skip_keywords(mut self) -> CompletionTestBuilder {
            self.skip_keywords = true;
            self
        }

        /// When set, dunder completions are skipped.
        /// This is useful to reduce noise for snapshot tests
        /// when filtering on methods and attributes.
        ///
        /// Not enabled by default.
        fn skip_dunders(mut self) -> CompletionTestBuilder {
            self.skip_dunders = true;
            self
        }

        /// When set, type signatures of each completion item are
        /// included in the snapshot. This is useful when one wants
        /// to specifically test types, but it usually best to leave
        /// off as it can add lots of noise.
        ///
        /// Not enabled by default.
        fn type_signatures(mut self) -> CompletionTestBuilder {
            self.type_signatures = true;
            self
        }

        /// When set, include the import associated with the
        /// completion.
        ///
        /// Not enabled by default.
        fn imports(mut self) -> CompletionTestBuilder {
            self.imports = true;
            self
        }

        /// When set, the module name for each symbol is included
        /// in the snapshot (if available).
        fn module_names(mut self) -> CompletionTestBuilder {
            self.module_names = true;
            self
        }

        /// Apply arbitrary filtering to completions.
        fn filter(
            mut self,
            predicate: impl Fn(&Completion) -> bool + 'static,
        ) -> CompletionTestBuilder {
            self.predicate = Some(Box::new(predicate));
            self
        }
    }

    struct CompletionTest<'db> {
        db: &'db ty_project::TestDb,
        /// The original completions returned before any additional
        /// test-specific filtering. We keep this around in order to
        /// slightly modify the test snapshot generated. This
        /// lets us differentiate between "absolutely no completions
        /// were returned" and "completions were returned, but you
        /// filtered them out."
        original: Vec<Completion<'db>>,
        /// The completions that the test should act upon. These are
        /// filtered by things like `skip_builtins`.
        filtered: Vec<Completion<'db>>,
        /// Whether type signatures should be included in the snapshot
        /// generated by `CompletionTest::snapshot`.
        type_signatures: bool,
        /// Whether to show the import that will be inserted when this
        /// completion is selected.
        imports: bool,
        /// Whether module names should be included in the snapshot
        /// generated by `CompletionTest::snapshot`.
        module_names: bool,
    }

    impl<'db> CompletionTest<'db> {
        fn snapshot(&self) -> String {
            if self.original.is_empty() {
                return "<No completions found>".to_string();
            } else if self.filtered.is_empty() {
                // It'd be nice to include the actual number of
                // completions filtered out, but in practice, the
                // number is environment dependent. For example, on
                // Windows, there are 231 builtins, but on Unix, there
                // are 230. So we just leave out the number I guess.
                // ---AG
                return "<No completions found after filtering out completions>".to_string();
            }
            self.filtered
                .iter()
                .map(|c| {
                    let mut snapshot = c.insert.as_deref().unwrap_or(c.label.as_str()).to_string();
                    if self.type_signatures {
                        let ty =
                            c.ty.map(|ty| ty.display(self.db).to_string())
                                .unwrap_or_else(|| "Unavailable".to_string());
                        snapshot = format!("{snapshot} :: {ty}");
                    }
                    if self.module_names {
                        let module_name = c
                            .module_name
                            .map(ModuleName::as_str)
                            .unwrap_or("<no import required>");
                        snapshot = format!("{snapshot} :: {module_name}");
                    }
                    if self.imports {
                        if let Some(ref edit) = c.import {
                            if let Some(import) = edit.content() {
                                snapshot = format!("{snapshot} :: {import}");
                            } else {
                                snapshot = format!("{snapshot} :: <import deletion>");
                            }
                        } else {
                            snapshot = format!("{snapshot} :: <no import edit>");
                        }
                    }
                    snapshot
                })
                .collect::<Vec<String>>()
                .join("\n")
        }

        #[track_caller]
        fn contains(&self, expected: &str) -> &CompletionTest<'db> {
            assert!(
                self.filtered
                    .iter()
                    .any(|completion| completion.name == expected),
                "Expected completions to include `{expected}`"
            );
            self
        }

        #[track_caller]
        fn not_contains(&self, unexpected: &str) -> &CompletionTest<'db> {
            assert!(
                self.filtered
                    .iter()
                    .all(|completion| completion.name != unexpected),
                "Expected completions to not include `{unexpected}`",
            );
            self
        }

        /// Returns the underlying completions if the convenience assertions
        /// aren't sufficiently expressive.
        fn completions(&self) -> &[Completion<'db>] {
            &self.filtered
        }
    }

    impl CursorTestBuilder {
        fn completion_test_builder(&self) -> CompletionTestBuilder {
            CompletionTestBuilder {
                cursor_test: self.build(),
                // N.B. We very much want to use the default settings
                // here, so that our test environment matches the
                // production environment. If a default changes, the
                // tests should be fixed to accommodate that change
                // as well. ---AG
                settings: CompletionSettings::default(),
                capabilities: CompletionCapabilities::default(),
                skip_builtins: false,
                skip_keywords: false,
                skip_dunders: false,
                type_signatures: false,
                imports: false,
                module_names: false,
                predicate: None,
            }
        }
    }

    impl SitePackagesCursorTestBuilder {
        fn completion_test_builder(&self) -> CompletionTestBuilder {
            CompletionTestBuilder {
                cursor_test: self.build(),
                // Keep defaults aligned with production completion settings.
                settings: CompletionSettings::default(),
                capabilities: CompletionCapabilities::default(),
                skip_builtins: false,
                skip_keywords: false,
                skip_dunders: false,
                type_signatures: false,
                imports: false,
                module_names: false,
                predicate: None,
            }
        }
    }

    fn tokenize(src: &str) -> Tokens {
        let parsed = ruff_python_parser::parse(src, ParseOptions::from(Mode::Module))
            .expect("valid Python source for token stream");
        parsed.tokens().clone()
    }
}
