//! Module responsible for analyzing the code surrounding the cursor for completion.
use std::iter;

use hir::{ExpandResult, Semantics, Type, TypeInfo, Variant};
use ide_db::{active_parameter::ActiveParameter, RootDatabase};
use itertools::Either;
use syntax::{
    algo::{ancestors_at_offset, find_node_at_offset, non_trivia_sibling},
    ast::{
        self, AttrKind, HasArgList, HasGenericArgs, HasGenericParams, HasLoopBody, HasName,
        NameOrNameRef,
    },
    match_ast, AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode,
    SyntaxToken, TextRange, TextSize, T,
};

use crate::context::{
    AttrCtx, BreakableKind, CompletionAnalysis, DotAccess, DotAccessExprCtx, DotAccessKind,
    ItemListKind, LifetimeContext, LifetimeKind, NameContext, NameKind, NameRefContext,
    NameRefKind, ParamContext, ParamKind, PathCompletionCtx, PathExprCtx, PathKind, PatternContext,
    PatternRefutability, Qualified, QualifierCtx, TypeAscriptionTarget, TypeLocation,
    COMPLETION_MARKER,
};

#[derive(Debug)]
struct ExpansionResult {
    original_file: SyntaxNode,
    speculative_file: SyntaxNode,
    /// The offset in the original file.
    original_offset: TextSize,
    /// The offset in the speculatively expanded file.
    speculative_offset: TextSize,
    fake_ident_token: SyntaxToken,
    derive_ctx: Option<(SyntaxNode, SyntaxNode, TextSize, ast::Attr)>,
}

pub(super) struct AnalysisResult {
    pub(super) analysis: CompletionAnalysis,
    pub(super) expected: (Option<Type>, Option<ast::NameOrNameRef>),
    pub(super) qualifier_ctx: QualifierCtx,
    /// the original token of the expanded file
    pub(super) token: SyntaxToken,
    /// The offset in the original file.
    pub(super) original_offset: TextSize,
}

pub(super) fn expand_and_analyze(
    sema: &Semantics<'_, RootDatabase>,
    original_file: SyntaxNode,
    speculative_file: SyntaxNode,
    offset: TextSize,
    original_token: &SyntaxToken,
) -> Option<AnalysisResult> {
    // as we insert after the offset, right biased will *always* pick the identifier no matter
    // if there is an ident already typed or not
    let fake_ident_token = speculative_file.token_at_offset(offset).right_biased()?;
    // the relative offset between the cursor and the *identifier* token we are completing on
    let relative_offset = offset - fake_ident_token.text_range().start();
    // make the offset point to the start of the original token, as that is what the
    // intermediate offsets calculated in expansion always points to
    let offset = offset - relative_offset;
    let expansion = expand(
        sema,
        original_file.clone(),
        speculative_file.clone(),
        offset,
        fake_ident_token.clone(),
        relative_offset,
    )
    .unwrap_or(ExpansionResult {
        original_file,
        speculative_file,
        original_offset: offset,
        speculative_offset: fake_ident_token.text_range().start(),
        fake_ident_token,
        derive_ctx: None,
    });

    // add the relative offset back, so that left_biased finds the proper token
    let original_offset = expansion.original_offset + relative_offset;
    let token = expansion.original_file.token_at_offset(original_offset).left_biased()?;

    analyze(sema, expansion, original_token, &token).map(|(analysis, expected, qualifier_ctx)| {
        AnalysisResult { analysis, expected, qualifier_ctx, token, original_offset }
    })
}

/// Expand attributes and macro calls at the current cursor position for both the original file
/// and fake file repeatedly. As soon as one of the two expansions fail we stop so the original
/// and speculative states stay in sync.
///
/// We do this by recursively expanding all macros and picking the best possible match. We cannot just
/// choose the first expansion each time because macros can expand to something that does not include
/// our completion marker, e.g.:
/// ```
/// macro_rules! helper { ($v:ident) => {} }
/// macro_rules! my_macro {
///     ($v:ident) => {
///         helper!($v);
///         $v
///     };
/// }
///
/// my_macro!(complete_me_here)
/// ```
/// If we would expand the first thing we encounter only (which in fact this method used to do), we would
/// be unable to complete here, because we would be walking directly into the void. So we instead try
/// *every* possible path.
///
/// This can also creates discrepancies between the speculative and real expansions: because we insert
/// tokens, we insert characters, which means if we try the second occurrence it may not be at the same
/// position in the original and speculative file. We take an educated guess here, and for each token
/// that we check, we subtract `COMPLETION_MARKER.len()`. This may not be accurate because proc macros
/// can insert the text of the completion marker in other places while removing the span, but this is
/// the best we can do.
fn expand(
    sema: &Semantics<'_, RootDatabase>,
    original_file: SyntaxNode,
    speculative_file: SyntaxNode,
    original_offset: TextSize,
    fake_ident_token: SyntaxToken,
    relative_offset: TextSize,
) -> Option<ExpansionResult> {
    let _p = tracing::info_span!("CompletionContext::expand").entered();

    if !sema.might_be_inside_macro_call(&fake_ident_token)
        && original_file
            .token_at_offset(original_offset + relative_offset)
            .right_biased()
            .is_some_and(|original_token| !sema.might_be_inside_macro_call(&original_token))
    {
        // Recursion base case.
        return Some(ExpansionResult {
            original_file,
            speculative_file,
            original_offset,
            speculative_offset: fake_ident_token.text_range().start(),
            fake_ident_token,
            derive_ctx: None,
        });
    }

    let parent_item =
        |item: &ast::Item| item.syntax().ancestors().skip(1).find_map(ast::Item::cast);
    let ancestor_items = iter::successors(
        Option::zip(
            find_node_at_offset::<ast::Item>(&original_file, original_offset),
            find_node_at_offset::<ast::Item>(
                &speculative_file,
                fake_ident_token.text_range().start(),
            ),
        ),
        |(a, b)| parent_item(a).zip(parent_item(b)),
    );

    // first try to expand attributes as these are always the outermost macro calls
    'ancestors: for (actual_item, item_with_fake_ident) in ancestor_items {
        match (
            sema.expand_attr_macro(&actual_item),
            sema.speculative_expand_attr_macro(
                &actual_item,
                &item_with_fake_ident,
                fake_ident_token.clone(),
            ),
        ) {
            // maybe parent items have attributes, so continue walking the ancestors
            (None, None) => continue 'ancestors,
            // successful expansions
            (
                Some(ExpandResult { value: actual_expansion, err: _ }),
                Some((fake_expansion, fake_mapped_tokens)),
            ) => {
                let mut accumulated_offset_from_fake_tokens = 0;
                let actual_range = actual_expansion.text_range().end();
                let result = fake_mapped_tokens
                    .into_iter()
                    .filter_map(|(fake_mapped_token, rank)| {
                        let accumulated_offset = accumulated_offset_from_fake_tokens;
                        if !fake_mapped_token.text().contains(COMPLETION_MARKER) {
                            // Proc macros can make the same span with different text, we don't
                            // want them to participate in completion because the macro author probably
                            // didn't intend them to.
                            return None;
                        }
                        accumulated_offset_from_fake_tokens += COMPLETION_MARKER.len();

                        let new_offset = fake_mapped_token.text_range().start()
                            - TextSize::new(accumulated_offset as u32);
                        if new_offset + relative_offset > actual_range {
                            // offset outside of bounds from the original expansion,
                            // stop here to prevent problems from happening
                            return None;
                        }
                        let result = expand(
                            sema,
                            actual_expansion.clone(),
                            fake_expansion.clone(),
                            new_offset,
                            fake_mapped_token,
                            relative_offset,
                        )?;
                        Some((result, rank))
                    })
                    .min_by_key(|(_, rank)| *rank)
                    .map(|(result, _)| result);
                if result.is_some() {
                    return result;
                }
            }
            // exactly one expansion failed, inconsistent state so stop expanding completely
            _ => break 'ancestors,
        }
    }

    // No attributes have been expanded, so look for macro_call! token trees or derive token trees
    let orig_tt = ancestors_at_offset(&original_file, original_offset)
        .map_while(Either::<ast::TokenTree, ast::Meta>::cast)
        .last()?;
    let spec_tt = ancestors_at_offset(&speculative_file, fake_ident_token.text_range().start())
        .map_while(Either::<ast::TokenTree, ast::Meta>::cast)
        .last()?;

    let (tts, attrs) = match (orig_tt, spec_tt) {
        (Either::Left(orig_tt), Either::Left(spec_tt)) => {
            let attrs = orig_tt
                .syntax()
                .parent()
                .and_then(ast::Meta::cast)
                .and_then(|it| it.parent_attr())
                .zip(
                    spec_tt
                        .syntax()
                        .parent()
                        .and_then(ast::Meta::cast)
                        .and_then(|it| it.parent_attr()),
                );
            (Some((orig_tt, spec_tt)), attrs)
        }
        (Either::Right(orig_path), Either::Right(spec_path)) => {
            (None, orig_path.parent_attr().zip(spec_path.parent_attr()))
        }
        _ => return None,
    };

    // Expand pseudo-derive expansion aka `derive(Debug$0)`
    if let Some((orig_attr, spec_attr)) = attrs {
        if let (Some(actual_expansion), Some((fake_expansion, fake_mapped_tokens))) = (
            sema.expand_derive_as_pseudo_attr_macro(&orig_attr),
            sema.speculative_expand_derive_as_pseudo_attr_macro(
                &orig_attr,
                &spec_attr,
                fake_ident_token.clone(),
            ),
        ) {
            if let Some((fake_mapped_token, _)) =
                fake_mapped_tokens.into_iter().min_by_key(|(_, rank)| *rank)
            {
                return Some(ExpansionResult {
                    original_file,
                    speculative_file,
                    original_offset,
                    speculative_offset: fake_ident_token.text_range().start(),
                    fake_ident_token,
                    derive_ctx: Some((
                        actual_expansion,
                        fake_expansion,
                        fake_mapped_token.text_range().start(),
                        orig_attr,
                    )),
                });
            }
        }

        if let Some(spec_adt) =
            spec_attr.syntax().ancestors().find_map(ast::Item::cast).and_then(|it| match it {
                ast::Item::Struct(it) => Some(ast::Adt::Struct(it)),
                ast::Item::Enum(it) => Some(ast::Adt::Enum(it)),
                ast::Item::Union(it) => Some(ast::Adt::Union(it)),
                _ => None,
            })
        {
            // might be the path of derive helper or a token tree inside of one
            if let Some(helpers) = sema.derive_helper(&orig_attr) {
                for (_mac, file) in helpers {
                    if let Some((fake_expansion, fake_mapped_tokens)) = sema.speculative_expand_raw(
                        file,
                        spec_adt.syntax(),
                        fake_ident_token.clone(),
                    ) {
                        // we are inside a derive helper token tree, treat this as being inside
                        // the derive expansion
                        let actual_expansion = sema.parse_or_expand(file.into());
                        let mut accumulated_offset_from_fake_tokens = 0;
                        let actual_range = actual_expansion.text_range().end();
                        let result = fake_mapped_tokens
                            .into_iter()
                            .filter_map(|(fake_mapped_token, rank)| {
                                let accumulated_offset = accumulated_offset_from_fake_tokens;
                                if !fake_mapped_token.text().contains(COMPLETION_MARKER) {
                                    // Proc macros can make the same span with different text, we don't
                                    // want them to participate in completion because the macro author probably
                                    // didn't intend them to.
                                    return None;
                                }
                                accumulated_offset_from_fake_tokens += COMPLETION_MARKER.len();

                                let new_offset = fake_mapped_token.text_range().start()
                                    - TextSize::new(accumulated_offset as u32);
                                if new_offset + relative_offset > actual_range {
                                    // offset outside of bounds from the original expansion,
                                    // stop here to prevent problems from happening
                                    return None;
                                }
                                let result = expand(
                                    sema,
                                    actual_expansion.clone(),
                                    fake_expansion.clone(),
                                    new_offset,
                                    fake_mapped_token,
                                    relative_offset,
                                )?;
                                Some((result, rank))
                            })
                            .min_by_key(|(_, rank)| *rank)
                            .map(|(result, _)| result);
                        if result.is_some() {
                            return result;
                        }
                    }
                }
            }
        }
        // at this point we won't have any more successful expansions, so stop
        return None;
    }

    // Expand fn-like macro calls
    let (orig_tt, spec_tt) = tts?;
    let (actual_macro_call, macro_call_with_fake_ident) = (
        orig_tt.syntax().parent().and_then(ast::MacroCall::cast)?,
        spec_tt.syntax().parent().and_then(ast::MacroCall::cast)?,
    );
    let mac_call_path0 = actual_macro_call.path().as_ref().map(|s| s.syntax().text());
    let mac_call_path1 = macro_call_with_fake_ident.path().as_ref().map(|s| s.syntax().text());

    // inconsistent state, stop expanding
    if mac_call_path0 != mac_call_path1 {
        return None;
    }
    let speculative_args = macro_call_with_fake_ident.token_tree()?;

    match (
        sema.expand_macro_call(&actual_macro_call),
        sema.speculative_expand_macro_call(
            &actual_macro_call,
            &speculative_args,
            fake_ident_token.clone(),
        ),
    ) {
        // successful expansions
        (Some(actual_expansion), Some((fake_expansion, fake_mapped_tokens))) => {
            let mut accumulated_offset_from_fake_tokens = 0;
            let actual_range = actual_expansion.text_range().end();
            fake_mapped_tokens
                .into_iter()
                .filter_map(|(fake_mapped_token, rank)| {
                    let accumulated_offset = accumulated_offset_from_fake_tokens;
                    if !fake_mapped_token.text().contains(COMPLETION_MARKER) {
                        // Proc macros can make the same span with different text, we don't
                        // want them to participate in completion because the macro author probably
                        // didn't intend them to.
                        return None;
                    }
                    accumulated_offset_from_fake_tokens += COMPLETION_MARKER.len();

                    let new_offset = fake_mapped_token.text_range().start()
                        - TextSize::new(accumulated_offset as u32);
                    if new_offset + relative_offset > actual_range {
                        // offset outside of bounds from the original expansion,
                        // stop here to prevent problems from happening
                        return None;
                    }
                    let result = expand(
                        sema,
                        actual_expansion.clone(),
                        fake_expansion.clone(),
                        new_offset,
                        fake_mapped_token,
                        relative_offset,
                    )?;
                    Some((result, rank))
                })
                .min_by_key(|(_, rank)| *rank)
                .map(|(result, _)| result)
        }
        // at least one expansion failed, we won't have anything to expand from this point
        // onwards so break out
        _ => None,
    }
}

/// Fill the completion context, this is what does semantic reasoning about the surrounding context
/// of the completion location.
fn analyze(
    sema: &Semantics<'_, RootDatabase>,
    expansion_result: ExpansionResult,
    original_token: &SyntaxToken,
    self_token: &SyntaxToken,
) -> Option<(CompletionAnalysis, (Option<Type>, Option<ast::NameOrNameRef>), QualifierCtx)> {
    let _p = tracing::info_span!("CompletionContext::analyze").entered();
    let ExpansionResult {
        original_file,
        speculative_file,
        original_offset: _,
        speculative_offset,
        fake_ident_token,
        derive_ctx,
    } = expansion_result;

    // Overwrite the path kind for derives
    if let Some((original_file, file_with_fake_ident, offset, origin_attr)) = derive_ctx {
        if let Some(ast::NameLike::NameRef(name_ref)) =
            find_node_at_offset(&file_with_fake_ident, offset)
        {
            let parent = name_ref.syntax().parent()?;
            let (mut nameref_ctx, _) =
                classify_name_ref(sema, &original_file, name_ref, offset, parent)?;
            if let NameRefKind::Path(path_ctx) = &mut nameref_ctx.kind {
                path_ctx.kind = PathKind::Derive {
                    existing_derives: sema
                        .resolve_derive_macro(&origin_attr)
                        .into_iter()
                        .flatten()
                        .flatten()
                        .collect(),
                };
            }
            return Some((
                CompletionAnalysis::NameRef(nameref_ctx),
                (None, None),
                QualifierCtx::default(),
            ));
        }
        return None;
    }

    let Some(name_like) = find_node_at_offset(&speculative_file, speculative_offset) else {
        let analysis = if let Some(original) = ast::String::cast(original_token.clone()) {
            CompletionAnalysis::String { original, expanded: ast::String::cast(self_token.clone()) }
        } else {
            // Fix up trailing whitespace problem
            // #[attr(foo = $0
            let token = syntax::algo::skip_trivia_token(self_token.clone(), Direction::Prev)?;
            let p = token.parent()?;
            if p.kind() == SyntaxKind::TOKEN_TREE
                && p.ancestors().any(|it| it.kind() == SyntaxKind::META)
            {
                let colon_prefix = previous_non_trivia_token(self_token.clone())
                    .map_or(false, |it| T![:] == it.kind());

                CompletionAnalysis::UnexpandedAttrTT {
                    fake_attribute_under_caret: fake_ident_token
                        .parent_ancestors()
                        .find_map(ast::Attr::cast),
                    colon_prefix,
                    extern_crate: p.ancestors().find_map(ast::ExternCrate::cast),
                }
            } else {
                return None;
            }
        };
        return Some((analysis, (None, None), QualifierCtx::default()));
    };

    let expected = expected_type_and_name(sema, self_token, &name_like);
    let mut qual_ctx = QualifierCtx::default();
    let analysis = match name_like {
        ast::NameLike::Lifetime(lifetime) => {
            CompletionAnalysis::Lifetime(classify_lifetime(sema, &original_file, lifetime)?)
        }
        ast::NameLike::NameRef(name_ref) => {
            let parent = name_ref.syntax().parent()?;
            let (nameref_ctx, qualifier_ctx) = classify_name_ref(
                sema,
                &original_file,
                name_ref,
                expansion_result.original_offset,
                parent,
            )?;

            if let NameRefContext {
                kind:
                    NameRefKind::Path(PathCompletionCtx { kind: PathKind::Expr { .. }, path, .. }, ..),
                ..
            } = &nameref_ctx
            {
                if is_in_token_of_for_loop(path) {
                    // for pat $0
                    // there is nothing to complete here except `in` keyword
                    // don't bother populating the context
                    // Ideally this special casing wouldn't be needed, but the parser recovers
                    return None;
                }
            }

            qual_ctx = qualifier_ctx;
            CompletionAnalysis::NameRef(nameref_ctx)
        }
        ast::NameLike::Name(name) => {
            let name_ctx = classify_name(sema, &original_file, name)?;
            CompletionAnalysis::Name(name_ctx)
        }
    };
    Some((analysis, expected, qual_ctx))
}

/// Calculate the expected type and name of the cursor position.
fn expected_type_and_name(
    sema: &Semantics<'_, RootDatabase>,
    token: &SyntaxToken,
    name_like: &ast::NameLike,
) -> (Option<Type>, Option<NameOrNameRef>) {
    let mut node = match token.parent() {
        Some(it) => it,
        None => return (None, None),
    };

    let strip_refs = |mut ty: Type| match name_like {
        ast::NameLike::NameRef(n) => {
            let p = match n.syntax().parent() {
                Some(it) => it,
                None => return ty,
            };
            let top_syn = match_ast! {
                match p {
                    ast::FieldExpr(e) => e
                        .syntax()
                        .ancestors()
                        .take_while(|it| ast::FieldExpr::can_cast(it.kind()))
                        .last(),
                    ast::PathSegment(e) => e
                        .syntax()
                        .ancestors()
                        .skip(1)
                        .take_while(|it| ast::Path::can_cast(it.kind()) || ast::PathExpr::can_cast(it.kind()))
                        .find(|it| ast::PathExpr::can_cast(it.kind())),
                    _ => None
                }
            };
            let top_syn = match top_syn {
                Some(it) => it,
                None => return ty,
            };
            for _ in top_syn.ancestors().skip(1).map_while(ast::RefExpr::cast) {
                cov_mark::hit!(expected_type_fn_param_ref);
                ty = ty.strip_reference();
            }
            ty
        }
        _ => ty,
    };

    let (ty, name) = loop {
        break match_ast! {
            match node {
                ast::LetStmt(it) => {
                    cov_mark::hit!(expected_type_let_with_leading_char);
                    cov_mark::hit!(expected_type_let_without_leading_char);
                    let ty = it.pat()
                        .and_then(|pat| sema.type_of_pat(&pat))
                        .or_else(|| it.initializer().and_then(|it| sema.type_of_expr(&it)))
                        .map(TypeInfo::original)
                        .filter(|ty| {
                            // don't infer the let type if the expr is a function,
                            // preventing parenthesis from vanishing
                            it.ty().is_some() || !ty.is_fn()
                        });
                    let name = match it.pat() {
                        Some(ast::Pat::IdentPat(ident)) => ident.name().map(NameOrNameRef::Name),
                        Some(_) | None => None,
                    };

                    (ty, name)
                },
                ast::LetExpr(it) => {
                    cov_mark::hit!(expected_type_if_let_without_leading_char);
                    let ty = it.pat()
                        .and_then(|pat| sema.type_of_pat(&pat))
                        .or_else(|| it.expr().and_then(|it| sema.type_of_expr(&it)))
                        .map(TypeInfo::original);
                    (ty, None)
                },
                ast::ArgList(_) => {
                    cov_mark::hit!(expected_type_fn_param);
                    ActiveParameter::at_token(
                        sema,
                       token.clone(),
                    ).map(|ap| {
                        let name = ap.ident().map(NameOrNameRef::Name);
                        (Some(ap.ty), name)
                    })
                    .unwrap_or((None, None))
                },
                ast::RecordExprFieldList(it) => {
                    // wouldn't try {} be nice...
                    (|| {
                        if token.kind() == T![..]
                            ||token.prev_token().map(|t| t.kind()) == Some(T![..])
                        {
                            cov_mark::hit!(expected_type_struct_func_update);
                            let record_expr = it.syntax().parent().and_then(ast::RecordExpr::cast)?;
                            let ty = sema.type_of_expr(&record_expr.into())?;
                            Some((
                                Some(ty.original),
                                None
                            ))
                        } else {
                            cov_mark::hit!(expected_type_struct_field_without_leading_char);
                            let expr_field = token.prev_sibling_or_token()?
                                .into_node()
                                .and_then(ast::RecordExprField::cast)?;
                            let (_, _, ty) = sema.resolve_record_field(&expr_field)?;
                            Some((
                                Some(ty),
                                expr_field.field_name().map(NameOrNameRef::NameRef),
                            ))
                        }
                    })().unwrap_or((None, None))
                },
                ast::RecordExprField(it) => {
                    let field_ty = sema.resolve_record_field(&it).map(|(_, _, ty)| ty);
                    let field_name = it.field_name().map(NameOrNameRef::NameRef);
                    if let Some(expr) = it.expr() {
                        cov_mark::hit!(expected_type_struct_field_with_leading_char);
                        let ty = field_ty
                            .or_else(|| sema.type_of_expr(&expr).map(TypeInfo::original));
                        (ty, field_name)
                    } else {
                        cov_mark::hit!(expected_type_struct_field_followed_by_comma);
                        (field_ty, field_name)
                    }
                },
                // match foo { $0 }
                // match foo { ..., pat => $0 }
                ast::MatchExpr(it) => {
                    let on_arrow = previous_non_trivia_token(token.clone()).map_or(false, |it| T![=>] == it.kind());

                    let ty = if on_arrow {
                        // match foo { ..., pat => $0 }
                        cov_mark::hit!(expected_type_match_arm_body_without_leading_char);
                        cov_mark::hit!(expected_type_match_arm_body_with_leading_char);
                        sema.type_of_expr(&it.into())
                    } else {
                        // match foo { $0 }
                        cov_mark::hit!(expected_type_match_arm_without_leading_char);
                        it.expr().and_then(|e| sema.type_of_expr(&e))
                    }.map(TypeInfo::original);
                    (ty, None)
                },
                ast::IfExpr(it) => {
                    let ty = it.condition()
                        .and_then(|e| sema.type_of_expr(&e))
                        .map(TypeInfo::original);
                    (ty, None)
                },
                ast::IdentPat(it) => {
                    cov_mark::hit!(expected_type_if_let_with_leading_char);
                    cov_mark::hit!(expected_type_match_arm_with_leading_char);
                    let ty = sema.type_of_pat(&ast::Pat::from(it)).map(TypeInfo::original);
                    (ty, None)
                },
                ast::Fn(it) => {
                    cov_mark::hit!(expected_type_fn_ret_with_leading_char);
                    cov_mark::hit!(expected_type_fn_ret_without_leading_char);
                    let def = sema.to_def(&it);
                    (def.map(|def| def.ret_type(sema.db)), None)
                },
                ast::ClosureExpr(it) => {
                    let ty = sema.type_of_expr(&it.into());
                    ty.and_then(|ty| ty.original.as_callable(sema.db))
                        .map(|c| (Some(c.return_type()), None))
                        .unwrap_or((None, None))
                },
                ast::ParamList(_) => (None, None),
                ast::Stmt(_) => (None, None),
                ast::Item(_) => (None, None),
                _ => {
                    match node.parent() {
                        Some(n) => {
                            node = n;
                            continue;
                        },
                        None => (None, None),
                    }
                },
            }
        };
    };
    (ty.map(strip_refs), name)
}

fn classify_lifetime(
    sema: &Semantics<'_, RootDatabase>,
    original_file: &SyntaxNode,
    lifetime: ast::Lifetime,
) -> Option<LifetimeContext> {
    let parent = lifetime.syntax().parent()?;
    if parent.kind() == SyntaxKind::ERROR {
        return None;
    }

    let lifetime =
        find_node_at_offset::<ast::Lifetime>(original_file, lifetime.syntax().text_range().start());
    let kind = match_ast! {
        match parent {
            ast::LifetimeParam(_) => LifetimeKind::LifetimeParam,
            ast::BreakExpr(_) => LifetimeKind::LabelRef,
            ast::ContinueExpr(_) => LifetimeKind::LabelRef,
            ast::Label(_) => LifetimeKind::LabelDef,
            _ => {
                let def = lifetime.as_ref().and_then(|lt| sema.scope(lt.syntax())?.generic_def());
                LifetimeKind::Lifetime { in_lifetime_param_bound: ast::TypeBound::can_cast(parent.kind()), def }
            },
        }
    };

    Some(LifetimeContext { kind })
}

fn classify_name(
    sema: &Semantics<'_, RootDatabase>,
    original_file: &SyntaxNode,
    name: ast::Name,
) -> Option<NameContext> {
    let parent = name.syntax().parent()?;
    let kind = match_ast! {
        match parent {
            ast::Const(_) => NameKind::Const,
            ast::ConstParam(_) => NameKind::ConstParam,
            ast::Enum(_) => NameKind::Enum,
            ast::Fn(_) => NameKind::Function,
            ast::IdentPat(bind_pat) => {
                let mut pat_ctx = pattern_context_for(sema, original_file, bind_pat.into());
                if let Some(record_field) = ast::RecordPatField::for_field_name(&name) {
                    pat_ctx.record_pat = find_node_in_file_compensated(sema, original_file, &record_field.parent_record_pat());
                }

                NameKind::IdentPat(pat_ctx)
            },
            ast::MacroDef(_) => NameKind::MacroDef,
            ast::MacroRules(_) => NameKind::MacroRules,
            ast::Module(module) => NameKind::Module(module),
            ast::RecordField(_) => NameKind::RecordField,
            ast::Rename(_) => NameKind::Rename,
            ast::SelfParam(_) => NameKind::SelfParam,
            ast::Static(_) => NameKind::Static,
            ast::Struct(_) => NameKind::Struct,
            ast::Trait(_) => NameKind::Trait,
            ast::TypeAlias(_) => NameKind::TypeAlias,
            ast::TypeParam(_) => NameKind::TypeParam,
            ast::Union(_) => NameKind::Union,
            ast::Variant(_) => NameKind::Variant,
            _ => return None,
        }
    };
    let name = find_node_at_offset(original_file, name.syntax().text_range().start());
    Some(NameContext { name, kind })
}

fn classify_name_ref(
    sema: &Semantics<'_, RootDatabase>,
    original_file: &SyntaxNode,
    name_ref: ast::NameRef,
    original_offset: TextSize,
    parent: SyntaxNode,
) -> Option<(NameRefContext, QualifierCtx)> {
    let nameref = find_node_at_offset(original_file, original_offset);

    let make_res = |kind| (NameRefContext { nameref: nameref.clone(), kind }, Default::default());

    if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) {
        let dot_prefix = previous_non_trivia_token(name_ref.syntax().clone())
            .map_or(false, |it| T![.] == it.kind());

        return find_node_in_file_compensated(
            sema,
            original_file,
            &record_field.parent_record_lit(),
        )
        .map(|expr| NameRefKind::RecordExpr { expr, dot_prefix })
        .map(make_res);
    }
    if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) {
        let kind = NameRefKind::Pattern(PatternContext {
            param_ctx: None,
            has_type_ascription: false,
            ref_token: None,
            mut_token: None,
            record_pat: find_node_in_file_compensated(
                sema,
                original_file,
                &record_field.parent_record_pat(),
            ),
            ..pattern_context_for(sema, original_file, record_field.parent_record_pat().into())
        });
        return Some(make_res(kind));
    }

    let segment = match_ast! {
        match parent {
            ast::PathSegment(segment) => segment,
            ast::FieldExpr(field) => {
                let receiver = find_opt_node_in_file(original_file, field.expr());
                let receiver_is_ambiguous_float_literal = match &receiver {
                    Some(ast::Expr::Literal(l)) => matches! {
                        l.kind(),
                        ast::LiteralKind::FloatNumber { .. } if l.syntax().last_token().map_or(false, |it| it.text().ends_with('.'))
                    },
                    _ => false,
                };

                let receiver_is_part_of_indivisible_expression = match &receiver {
                    Some(ast::Expr::IfExpr(_)) => {
                        let next_token_kind = next_non_trivia_token(name_ref.syntax().clone()).map(|t| t.kind());
                        next_token_kind == Some(SyntaxKind::ELSE_KW)
                    },
                    _ => false
                };
                if receiver_is_part_of_indivisible_expression {
                    return None;
                }

                let kind = NameRefKind::DotAccess(DotAccess {
                    receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
                    kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal },
                    receiver,
                    ctx: DotAccessExprCtx { in_block_expr: is_in_block(field.syntax()), in_breakable: is_in_breakable(field.syntax()) }
                });
                return Some(make_res(kind));
            },
            ast::ExternCrate(_) => {
                let kind = NameRefKind::ExternCrate;
                return Some(make_res(kind));
            },
            ast::MethodCallExpr(method) => {
                let receiver = find_opt_node_in_file(original_file, method.receiver());
                let kind = NameRefKind::DotAccess(DotAccess {
                    receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
                    kind: DotAccessKind::Method { has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some()) },
                    receiver,
                    ctx: DotAccessExprCtx { in_block_expr: is_in_block(method.syntax()), in_breakable: is_in_breakable(method.syntax()) }
                });
                return Some(make_res(kind));
            },
            _ => return None,
        }
    };

    let path = segment.parent_path();
    let original_path = find_node_in_file_compensated(sema, original_file, &path);

    let mut path_ctx = PathCompletionCtx {
        has_call_parens: false,
        has_macro_bang: false,
        qualified: Qualified::No,
        parent: None,
        path: path.clone(),
        original_path,
        kind: PathKind::Item { kind: ItemListKind::SourceFile },
        has_type_args: false,
        use_tree_parent: false,
    };

    let func_update_record = |syn: &SyntaxNode| {
        if let Some(record_expr) = syn.ancestors().nth(2).and_then(ast::RecordExpr::cast) {
            find_node_in_file_compensated(sema, original_file, &record_expr)
        } else {
            None
        }
    };
    let after_if_expr = |node: SyntaxNode| {
        let prev_expr = (|| {
            let node = match node.parent().and_then(ast::ExprStmt::cast) {
                Some(stmt) => stmt.syntax().clone(),
                None => node,
            };
            let prev_sibling = non_trivia_sibling(node.into(), Direction::Prev)?.into_node()?;

            ast::ExprStmt::cast(prev_sibling.clone())
                .and_then(|it| it.expr())
                .or_else(|| ast::Expr::cast(prev_sibling))
        })();
        matches!(prev_expr, Some(ast::Expr::IfExpr(_)))
    };

    // We do not want to generate path completions when we are sandwiched between an item decl signature and its body.
    // ex. trait Foo $0 {}
    // in these cases parser recovery usually kicks in for our inserted identifier, causing it
    // to either be parsed as an ExprStmt or a ItemRecovery, depending on whether it is in a block
    // expression or an item list.
    // The following code checks if the body is missing, if it is we either cut off the body
    // from the item or it was missing in the first place
    let inbetween_body_and_decl_check = |node: SyntaxNode| {
        if let Some(NodeOrToken::Node(n)) =
            syntax::algo::non_trivia_sibling(node.into(), syntax::Direction::Prev)
        {
            if let Some(item) = ast::Item::cast(n) {
                let is_inbetween = match &item {
                    ast::Item::Const(it) => it.body().is_none() && it.semicolon_token().is_none(),
                    ast::Item::Enum(it) => it.variant_list().is_none(),
                    ast::Item::ExternBlock(it) => it.extern_item_list().is_none(),
                    ast::Item::Fn(it) => it.body().is_none() && it.semicolon_token().is_none(),
                    ast::Item::Impl(it) => it.assoc_item_list().is_none(),
                    ast::Item::Module(it) => {
                        it.item_list().is_none() && it.semicolon_token().is_none()
                    }
                    ast::Item::Static(it) => it.body().is_none(),
                    ast::Item::Struct(it) => {
                        it.field_list().is_none() && it.semicolon_token().is_none()
                    }
                    ast::Item::Trait(it) => it.assoc_item_list().is_none(),
                    ast::Item::TypeAlias(it) => it.ty().is_none() && it.semicolon_token().is_none(),
                    ast::Item::Union(it) => it.record_field_list().is_none(),
                    _ => false,
                };
                if is_inbetween {
                    return Some(item);
                }
            }
        }
        None
    };

    let generic_arg_location = |arg: ast::GenericArg| {
        let mut override_location = None;
        let location = find_opt_node_in_file_compensated(
            sema,
            original_file,
            arg.syntax().parent().and_then(ast::GenericArgList::cast),
        )
        .map(|args| {
            let mut in_trait = None;
            let param = (|| {
                let parent = args.syntax().parent()?;
                let params = match_ast! {
                    match parent {
                        ast::PathSegment(segment) => {
                            match sema.resolve_path(&segment.parent_path().top_path())? {
                                hir::PathResolution::Def(def) => match def {
                                    hir::ModuleDef::Function(func) => {
                                         sema.source(func)?.value.generic_param_list()
                                    }
                                    hir::ModuleDef::Adt(adt) => {
                                        sema.source(adt)?.value.generic_param_list()
                                    }
                                    hir::ModuleDef::Variant(variant) => {
                                        sema.source(variant.parent_enum(sema.db))?.value.generic_param_list()
                                    }
                                    hir::ModuleDef::Trait(trait_) => {
                                        if let ast::GenericArg::AssocTypeArg(arg) = &arg {
                                            let arg_name = arg.name_ref()?;
                                            let arg_name = arg_name.text();
                                            for item in trait_.items_with_supertraits(sema.db) {
                                                match item {
                                                    hir::AssocItem::TypeAlias(assoc_ty) => {
                                                        if assoc_ty.name(sema.db).as_str() == arg_name {
                                                            override_location = Some(TypeLocation::AssocTypeEq);
                                                            return None;
                                                        }
                                                    },
                                                    hir::AssocItem::Const(const_) => {
                                                        if const_.name(sema.db)?.as_str() == arg_name {
                                                            override_location =  Some(TypeLocation::AssocConstEq);
                                                            return None;
                                                        }
                                                    },
                                                    _ => (),
                                                }
                                            }
                                            return None;
                                        } else {
                                            in_trait = Some(trait_);
                                            sema.source(trait_)?.value.generic_param_list()
                                        }
                                    }
                                    hir::ModuleDef::TraitAlias(trait_) => {
                                        sema.source(trait_)?.value.generic_param_list()
                                    }
                                    hir::ModuleDef::TypeAlias(ty_) => {
                                        sema.source(ty_)?.value.generic_param_list()
                                    }
                                    _ => None,
                                },
                                _ => None,
                            }
                        },
                        ast::MethodCallExpr(call) => {
                            let func = sema.resolve_method_call(&call)?;
                            sema.source(func)?.value.generic_param_list()
                        },
                        ast::AssocTypeArg(arg) => {
                            let trait_ = ast::PathSegment::cast(arg.syntax().parent()?.parent()?)?;
                            match sema.resolve_path(&trait_.parent_path().top_path())? {
                                hir::PathResolution::Def(hir::ModuleDef::Trait(trait_)) =>  {
                                        let arg_name = arg.name_ref()?;
                                        let arg_name = arg_name.text();
                                        let trait_items = trait_.items_with_supertraits(sema.db);
                                        let assoc_ty = trait_items.iter().find_map(|item| match item {
                                            hir::AssocItem::TypeAlias(assoc_ty) => {
                                                (assoc_ty.name(sema.db).as_str() == arg_name)
                                                    .then_some(assoc_ty)
                                            },
                                            _ => None,
                                        })?;
                                        sema.source(*assoc_ty)?.value.generic_param_list()
                                    }
                                _ => None,
                            }
                        },
                        _ => None,
                    }
                }?;
                // Determine the index of the argument in the `GenericArgList` and match it with
                // the corresponding parameter in the `GenericParamList`. Since lifetime parameters
                // are often omitted, ignore them for the purposes of matching the argument with
                // its parameter unless a lifetime argument is provided explicitly. That is, for
                // `struct S<'a, 'b, T>`, match `S::<$0>` to `T` and `S::<'a, $0, _>` to `'b`.
                // FIXME: This operates on the syntax tree and will produce incorrect results when
                // generic parameters are disabled by `#[cfg]` directives. It should operate on the
                // HIR, but the functionality necessary to do so is not exposed at the moment.
                let mut explicit_lifetime_arg = false;
                let arg_idx = arg
                    .syntax()
                    .siblings(Direction::Prev)
                    // Skip the node itself
                    .skip(1)
                    .map(|arg| if ast::LifetimeArg::can_cast(arg.kind()) { explicit_lifetime_arg = true })
                    .count();
                let param_idx = if explicit_lifetime_arg {
                    arg_idx
                } else {
                    // Lifetimes parameters always precede type and generic parameters,
                    // so offset the argument index by the total number of lifetime params
                    arg_idx + params.lifetime_params().count()
                };
                params.generic_params().nth(param_idx)
            })();
            (args, in_trait, param)
        });
        let (arg_list, of_trait, corresponding_param) = match location {
            Some((arg_list, of_trait, param)) => (Some(arg_list), of_trait, param),
            _ => (None, None, None),
        };
        override_location.unwrap_or(TypeLocation::GenericArg {
            args: arg_list,
            of_trait,
            corresponding_param,
        })
    };

    let type_location = |node: &SyntaxNode| {
        let parent = node.parent()?;
        let res = match_ast! {
            match parent {
                ast::Const(it) => {
                    let name = find_opt_node_in_file(original_file, it.name())?;
                    let original = ast::Const::cast(name.syntax().parent()?)?;
                    TypeLocation::TypeAscription(TypeAscriptionTarget::Const(original.body()))
                },
                ast::RetType(it) => {
                    it.thin_arrow_token()?;
                    let parent = match ast::Fn::cast(parent.parent()?) {
                        Some(it) => it.param_list(),
                        None => ast::ClosureExpr::cast(parent.parent()?)?.param_list(),
                    };

                    let parent = find_opt_node_in_file(original_file, parent)?.syntax().parent()?;
                    TypeLocation::TypeAscription(TypeAscriptionTarget::RetType(match_ast! {
                        match parent {
                            ast::ClosureExpr(it) => {
                                it.body()
                            },
                            ast::Fn(it) => {
                                it.body().map(ast::Expr::BlockExpr)
                            },
                            _ => return None,
                        }
                    }))
                },
                ast::Param(it) => {
                    it.colon_token()?;
                    TypeLocation::TypeAscription(TypeAscriptionTarget::FnParam(find_opt_node_in_file(original_file, it.pat())))
                },
                ast::LetStmt(it) => {
                    it.colon_token()?;
                    TypeLocation::TypeAscription(TypeAscriptionTarget::Let(find_opt_node_in_file(original_file, it.pat())))
                },
                ast::Impl(it) => {
                    match it.trait_() {
                        Some(t) if t.syntax() == node => TypeLocation::ImplTrait,
                        _ => match it.self_ty() {
                            Some(t) if t.syntax() == node => TypeLocation::ImplTarget,
                            _ => return None,
                        },
                    }
                },
                ast::TypeBound(_) => TypeLocation::TypeBound,
                // is this case needed?
                ast::TypeBoundList(_) => TypeLocation::TypeBound,
                ast::GenericArg(it) => generic_arg_location(it),
                // is this case needed?
                ast::GenericArgList(it) => {
                    let args = find_opt_node_in_file_compensated(sema, original_file, Some(it));
                    TypeLocation::GenericArg { args, of_trait: None, corresponding_param: None }
                },
                ast::TupleField(_) => TypeLocation::TupleField,
                _ => return None,
            }
        };
        Some(res)
    };

    let is_in_condition = |it: &ast::Expr| {
        (|| {
            let parent = it.syntax().parent()?;
            if let Some(expr) = ast::WhileExpr::cast(parent.clone()) {
                Some(expr.condition()? == *it)
            } else if let Some(expr) = ast::IfExpr::cast(parent) {
                Some(expr.condition()? == *it)
            } else {
                None
            }
        })()
        .unwrap_or(false)
    };

    let make_path_kind_expr = |expr: ast::Expr| {
        let it = expr.syntax();
        let in_block_expr = is_in_block(it);
        let in_loop_body = is_in_breakable(it);
        let after_if_expr = after_if_expr(it.clone());
        let ref_expr_parent =
            path.as_single_name_ref().and_then(|_| it.parent()).and_then(ast::RefExpr::cast);
        let (innermost_ret_ty, self_param) = {
            let find_ret_ty = |it: SyntaxNode| {
                if let Some(item) = ast::Item::cast(it.clone()) {
                    match item {
                        ast::Item::Fn(f) => Some(sema.to_def(&f).map(|it| it.ret_type(sema.db))),
                        ast::Item::MacroCall(_) => None,
                        _ => Some(None),
                    }
                } else {
                    let expr = ast::Expr::cast(it)?;
                    let callable = match expr {
                        // FIXME
                        // ast::Expr::BlockExpr(b) if b.async_token().is_some() || b.try_token().is_some() => sema.type_of_expr(b),
                        ast::Expr::ClosureExpr(_) => sema.type_of_expr(&expr),
                        _ => return None,
                    };
                    Some(
                        callable
                            .and_then(|c| c.adjusted().as_callable(sema.db))
                            .map(|it| it.return_type()),
                    )
                }
            };
            let find_fn_self_param = |it| match it {
                ast::Item::Fn(fn_) => Some(sema.to_def(&fn_).and_then(|it| it.self_param(sema.db))),
                ast::Item::MacroCall(_) => None,
                _ => Some(None),
            };

            match find_node_in_file_compensated(sema, original_file, &expr) {
                Some(it) => {
                    // buggy
                    let innermost_ret_ty = sema
                        .ancestors_with_macros(it.syntax().clone())
                        .find_map(find_ret_ty)
                        .flatten();

                    let self_param = sema
                        .ancestors_with_macros(it.syntax().clone())
                        .filter_map(ast::Item::cast)
                        .find_map(find_fn_self_param)
                        .flatten();
                    (innermost_ret_ty, self_param)
                }
                None => (None, None),
            }
        };
        let is_func_update = func_update_record(it);
        let in_condition = is_in_condition(&expr);
        let incomplete_let = it
            .parent()
            .and_then(ast::LetStmt::cast)
            .map_or(false, |it| it.semicolon_token().is_none());
        let impl_ = fetch_immediate_impl(sema, original_file, expr.syntax());

        let in_match_guard = match it.parent().and_then(ast::MatchArm::cast) {
            Some(arm) => arm
                .fat_arrow_token()
                .map_or(true, |arrow| it.text_range().start() < arrow.text_range().start()),
            None => false,
        };

        PathKind::Expr {
            expr_ctx: PathExprCtx {
                in_block_expr,
                in_breakable: in_loop_body,
                after_if_expr,
                in_condition,
                ref_expr_parent,
                is_func_update,
                innermost_ret_ty,
                self_param,
                incomplete_let,
                impl_,
                in_match_guard,
            },
        }
    };
    let make_path_kind_type = |ty: ast::Type| {
        let location = type_location(ty.syntax());
        PathKind::Type { location: location.unwrap_or(TypeLocation::Other) }
    };

    let kind_item = |it: &SyntaxNode| {
        let parent = it.parent()?;
        let kind = match_ast! {
            match parent {
                ast::ItemList(_) => PathKind::Item { kind: ItemListKind::Module },
                ast::AssocItemList(_) => PathKind::Item { kind: match parent.parent() {
                    Some(it) => match_ast! {
                        match it {
                            ast::Trait(_) => ItemListKind::Trait,
                            ast::Impl(it) => if it.trait_().is_some() {
                                ItemListKind::TraitImpl(find_node_in_file_compensated(sema, original_file, &it))
                            } else {
                                ItemListKind::Impl
                            },
                            _ => return None
                        }
                    },
                    None => return None,
                } },
                ast::ExternItemList(it) => {
                    let exn_blk = it.syntax().parent().and_then(ast::ExternBlock::cast);
                    PathKind::Item {
                        kind: ItemListKind::ExternBlock {
                            is_unsafe: exn_blk.and_then(|it| it.unsafe_token()).is_some(),
                        }
                    }
                },
                ast::SourceFile(_) => PathKind::Item { kind: ItemListKind::SourceFile },
                _ => return None,
            }
        };
        Some(kind)
    };

    let mut kind_macro_call = |it: ast::MacroCall| {
        path_ctx.has_macro_bang = it.excl_token().is_some();
        let parent = it.syntax().parent()?;
        if let Some(kind) = kind_item(it.syntax()) {
            return Some(kind);
        }
        let kind = match_ast! {
            match parent {
                ast::MacroExpr(expr) => make_path_kind_expr(expr.into()),
                ast::MacroPat(it) => PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())},
                ast::MacroType(ty) => make_path_kind_type(ty.into()),
                _ => return None,
            }
        };
        Some(kind)
    };
    let make_path_kind_attr = |meta: ast::Meta| {
        let attr = meta.parent_attr()?;
        let kind = attr.kind();
        let attached = attr.syntax().parent()?;
        let is_trailing_outer_attr = kind != AttrKind::Inner
            && non_trivia_sibling(attr.syntax().clone().into(), syntax::Direction::Next).is_none();
        let annotated_item_kind = if is_trailing_outer_attr { None } else { Some(attached.kind()) };
        let derive_helpers = annotated_item_kind
            .filter(|kind| {
                matches!(
                    kind,
                    SyntaxKind::STRUCT
                        | SyntaxKind::ENUM
                        | SyntaxKind::UNION
                        | SyntaxKind::VARIANT
                        | SyntaxKind::TUPLE_FIELD
                        | SyntaxKind::RECORD_FIELD
                )
            })
            .and_then(|_| nameref.as_ref()?.syntax().ancestors().find_map(ast::Adt::cast))
            .and_then(|adt| sema.derive_helpers_in_scope(&adt))
            .unwrap_or_default();
        Some(PathKind::Attr { attr_ctx: AttrCtx { kind, annotated_item_kind, derive_helpers } })
    };

    // Infer the path kind
    let parent = path.syntax().parent()?;
    let kind = 'find_kind: {
        if parent.kind() == SyntaxKind::ERROR {
            if let Some(kind) = inbetween_body_and_decl_check(parent.clone()) {
                return Some(make_res(NameRefKind::Keyword(kind)));
            }

            break 'find_kind kind_item(&parent)?;
        }
        match_ast! {
            match parent {
                ast::PathType(it) => make_path_kind_type(it.into()),
                ast::PathExpr(it) => {
                    if let Some(p) = it.syntax().parent() {
                        let p_kind = p.kind();
                        // The syntax node of interest, for which we want to check whether
                        // it is sandwiched between an item decl signature and its body.
                        let probe = if ast::ExprStmt::can_cast(p_kind) {
                            Some(p)
                        } else if ast::StmtList::can_cast(p_kind) {
                            Some(it.syntax().clone())
                        } else {
                            None
                        };
                        if let Some(kind) = probe.and_then(inbetween_body_and_decl_check) {
                            return Some(make_res(NameRefKind::Keyword(kind)));
                        }
                    }

                    path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));

                    make_path_kind_expr(it.into())
                },
                ast::TupleStructPat(it) => {
                    path_ctx.has_call_parens = true;
                    PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
                },
                ast::RecordPat(it) => {
                    path_ctx.has_call_parens = true;
                    PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
                },
                ast::PathPat(it) => {
                    PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}
                },
                ast::MacroCall(it) => {
                    kind_macro_call(it)?
                },
                ast::Meta(meta) => make_path_kind_attr(meta)?,
                ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() },
                ast::UseTree(_) => PathKind::Use,
                // completing inside a qualifier
                ast::Path(parent) => {
                    path_ctx.parent = Some(parent.clone());
                    let parent = iter::successors(Some(parent), |it| it.parent_path()).last()?.syntax().parent()?;
                    match_ast! {
                        match parent {
                            ast::PathType(it) => make_path_kind_type(it.into()),
                            ast::PathExpr(it) => {
                                path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));

                                make_path_kind_expr(it.into())
                            },
                            ast::TupleStructPat(it) => {
                                path_ctx.has_call_parens = true;
                                PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
                            },
                            ast::RecordPat(it) => {
                                path_ctx.has_call_parens = true;
                                PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into()) }
                            },
                            ast::PathPat(it) => {
                                PathKind::Pat { pat_ctx: pattern_context_for(sema, original_file, it.into())}
                            },
                            ast::MacroCall(it) => {
                                kind_macro_call(it)?
                            },
                            ast::Meta(meta) => make_path_kind_attr(meta)?,
                            ast::Visibility(it) => PathKind::Vis { has_in_token: it.in_token().is_some() },
                            ast::UseTree(_) => PathKind::Use,
                            ast::RecordExpr(it) => make_path_kind_expr(it.into()),
                            _ => return None,
                        }
                    }
                },
                ast::RecordExpr(it) => {
                    // A record expression in this position is usually a result of parsing recovery, so check that
                    if let Some(kind) = inbetween_body_and_decl_check(it.syntax().clone()) {
                        return Some(make_res(NameRefKind::Keyword(kind)));
                    }
                    make_path_kind_expr(it.into())
                },
                _ => return None,
            }
        }
    };

    path_ctx.kind = kind;
    path_ctx.has_type_args = segment.generic_arg_list().is_some();

    // calculate the qualifier context
    if let Some((qualifier, use_tree_parent)) = path_or_use_tree_qualifier(&path) {
        path_ctx.use_tree_parent = use_tree_parent;
        if !use_tree_parent && segment.coloncolon_token().is_some() {
            path_ctx.qualified = Qualified::Absolute;
        } else {
            let qualifier = qualifier
                .segment()
                .and_then(|it| find_node_in_file(original_file, &it))
                .map(|it| it.parent_path());
            if let Some(qualifier) = qualifier {
                let type_anchor = match qualifier.segment().and_then(|it| it.kind()) {
                    Some(ast::PathSegmentKind::Type { type_ref: Some(type_ref), trait_ref })
                        if qualifier.qualifier().is_none() =>
                    {
                        Some((type_ref, trait_ref))
                    }
                    _ => None,
                };

                path_ctx.qualified = if let Some((ty, trait_ref)) = type_anchor {
                    let ty = match ty {
                        ast::Type::InferType(_) => None,
                        ty => sema.resolve_type(&ty),
                    };
                    let trait_ = trait_ref.and_then(|it| sema.resolve_trait(&it.path()?));
                    Qualified::TypeAnchor { ty, trait_ }
                } else {
                    let res = sema.resolve_path(&qualifier);

                    // For understanding how and why super_chain_len is calculated the way it
                    // is check the documentation at it's definition
                    let mut segment_count = 0;
                    let super_count = iter::successors(Some(qualifier.clone()), |p| p.qualifier())
                        .take_while(|p| {
                            p.segment()
                                .and_then(|s| {
                                    segment_count += 1;
                                    s.super_token()
                                })
                                .is_some()
                        })
                        .count();

                    let super_chain_len =
                        if segment_count > super_count { None } else { Some(super_count) };

                    Qualified::With { path: qualifier, resolution: res, super_chain_len }
                }
            };
        }
    } else if let Some(segment) = path.segment() {
        if segment.coloncolon_token().is_some() {
            path_ctx.qualified = Qualified::Absolute;
        }
    }

    let mut qualifier_ctx = QualifierCtx::default();
    if path_ctx.is_trivial_path() {
        // fetch the full expression that may have qualifiers attached to it
        let top_node = match path_ctx.kind {
            PathKind::Expr { expr_ctx: PathExprCtx { in_block_expr: true, .. } } => {
                parent.ancestors().find(|it| ast::PathExpr::can_cast(it.kind())).and_then(|p| {
                    let parent = p.parent()?;
                    if ast::StmtList::can_cast(parent.kind()) {
                        Some(p)
                    } else if ast::ExprStmt::can_cast(parent.kind()) {
                        Some(parent)
                    } else {
                        None
                    }
                })
            }
            PathKind::Item { .. } => parent.ancestors().find(|it| it.kind() == SyntaxKind::ERROR),
            _ => None,
        };
        if let Some(top) = top_node {
            if let Some(NodeOrToken::Node(error_node)) =
                syntax::algo::non_trivia_sibling(top.clone().into(), syntax::Direction::Prev)
            {
                if error_node.kind() == SyntaxKind::ERROR {
                    for token in
                        error_node.children_with_tokens().filter_map(NodeOrToken::into_token)
                    {
                        match token.kind() {
                            SyntaxKind::UNSAFE_KW => qualifier_ctx.unsafe_tok = Some(token),
                            SyntaxKind::ASYNC_KW => qualifier_ctx.async_tok = Some(token),
                            SyntaxKind::SAFE_KW => qualifier_ctx.safe_tok = Some(token),
                            _ => {}
                        }
                    }
                    qualifier_ctx.vis_node = error_node.children().find_map(ast::Visibility::cast);
                }
            }

            if let PathKind::Item { .. } = path_ctx.kind {
                if qualifier_ctx.none() {
                    if let Some(t) = top.first_token() {
                        if let Some(prev) = t
                            .prev_token()
                            .and_then(|t| syntax::algo::skip_trivia_token(t, Direction::Prev))
                        {
                            if ![T![;], T!['}'], T!['{']].contains(&prev.kind()) {
                                // This was inferred to be an item position path, but it seems
                                // to be part of some other broken node which leaked into an item
                                // list
                                return None;
                            }
                        }
                    }
                }
            }
        }
    }
    Some((NameRefContext { nameref, kind: NameRefKind::Path(path_ctx) }, qualifier_ctx))
}

fn pattern_context_for(
    sema: &Semantics<'_, RootDatabase>,
    original_file: &SyntaxNode,
    pat: ast::Pat,
) -> PatternContext {
    let mut param_ctx = None;

    let mut missing_variants = vec![];

    let (refutability, has_type_ascription) =
    pat
        .syntax()
        .ancestors()
        .find(|it| !ast::Pat::can_cast(it.kind()))
        .map_or((PatternRefutability::Irrefutable, false), |node| {
            let refutability = match_ast! {
                match node {
                    ast::LetStmt(let_) => return (PatternRefutability::Refutable, let_.ty().is_some()),
                    ast::Param(param) => {
                        let has_type_ascription = param.ty().is_some();
                        param_ctx = (|| {
                            let fake_param_list = param.syntax().parent().and_then(ast::ParamList::cast)?;
                            let param_list = find_node_in_file_compensated(sema, original_file, &fake_param_list)?;
                            let param_list_owner = param_list.syntax().parent()?;
                            let kind = match_ast! {
                                match param_list_owner {
                                    ast::ClosureExpr(closure) => ParamKind::Closure(closure),
                                    ast::Fn(fn_) => ParamKind::Function(fn_),
                                    _ => return None,
                                }
                            };
                            Some(ParamContext {
                                param_list, param, kind
                            })
                        })();
                        return (PatternRefutability::Irrefutable, has_type_ascription)
                    },
                    ast::MatchArm(match_arm) => {
                       let missing_variants_opt = match_arm
                            .syntax()
                            .parent()
                            .and_then(ast::MatchArmList::cast)
                            .and_then(|match_arm_list| {
                                match_arm_list
                                .syntax()
                                .parent()
                                .and_then(ast::MatchExpr::cast)
                                .and_then(|match_expr| {
                                    let expr_opt = find_opt_node_in_file(original_file, match_expr.expr());

                                    expr_opt.and_then(|expr| {
                                        sema.type_of_expr(&expr)?
                                        .adjusted()
                                        .autoderef(sema.db)
                                        .find_map(|ty| match ty.as_adt() {
                                            Some(hir::Adt::Enum(e)) => Some(e),
                                            _ => None,
                                        }).map(|enum_| enum_.variants(sema.db))
                                    })
                                }).map(|variants| variants.iter().filter_map(|variant| {
                                        let variant_name = variant.name(sema.db).unescaped().display(sema.db).to_string();

                                        let variant_already_present = match_arm_list.arms().any(|arm| {
                                            arm.pat().and_then(|pat| {
                                                let pat_already_present = pat.syntax().to_string().contains(&variant_name);
                                                pat_already_present.then_some(pat_already_present)
                                            }).is_some()
                                        });

                                        (!variant_already_present).then_some(*variant)
                                    }).collect::<Vec<Variant>>())
                        });

                        if let Some(missing_variants_) = missing_variants_opt {
                            missing_variants = missing_variants_;
                        };

                        PatternRefutability::Refutable
                    },
                    ast::LetExpr(_) => PatternRefutability::Refutable,
                    ast::ForExpr(_) => PatternRefutability::Irrefutable,
                    _ => PatternRefutability::Irrefutable,
                }
            };
            (refutability, false)
        });
    let (ref_token, mut_token) = match &pat {
        ast::Pat::IdentPat(it) => (it.ref_token(), it.mut_token()),
        _ => (None, None),
    };

    // Only suggest name in let-stmt or fn param
    let should_suggest_name = matches!(
            &pat,
            ast::Pat::IdentPat(it)
                if it.syntax()
                .parent()
                .map_or(false, |node| {
                    let kind = node.kind();
                    ast::LetStmt::can_cast(kind) || ast::Param::can_cast(kind)
                })
    );

    PatternContext {
        refutability,
        param_ctx,
        has_type_ascription,
        should_suggest_name,
        parent_pat: pat.syntax().parent().and_then(ast::Pat::cast),
        mut_token,
        ref_token,
        record_pat: None,
        impl_: fetch_immediate_impl(sema, original_file, pat.syntax()),
        missing_variants,
    }
}

fn fetch_immediate_impl(
    sema: &Semantics<'_, RootDatabase>,
    original_file: &SyntaxNode,
    node: &SyntaxNode,
) -> Option<ast::Impl> {
    let mut ancestors = ancestors_in_file_compensated(sema, original_file, node)?
        .filter_map(ast::Item::cast)
        .filter(|it| !matches!(it, ast::Item::MacroCall(_)));

    match ancestors.next()? {
        ast::Item::Const(_) | ast::Item::Fn(_) | ast::Item::TypeAlias(_) => (),
        ast::Item::Impl(it) => return Some(it),
        _ => return None,
    }
    match ancestors.next()? {
        ast::Item::Impl(it) => Some(it),
        _ => None,
    }
}

/// Attempts to find `node` inside `syntax` via `node`'s text range.
/// If the fake identifier has been inserted after this node or inside of this node use the `_compensated` version instead.
fn find_opt_node_in_file<N: AstNode>(syntax: &SyntaxNode, node: Option<N>) -> Option<N> {
    find_node_in_file(syntax, &node?)
}

/// Attempts to find `node` inside `syntax` via `node`'s text range.
/// If the fake identifier has been inserted after this node or inside of this node use the `_compensated` version instead.
fn find_node_in_file<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> {
    let syntax_range = syntax.text_range();
    let range = node.syntax().text_range();
    let intersection = range.intersect(syntax_range)?;
    syntax.covering_element(intersection).ancestors().find_map(N::cast)
}

/// Attempts to find `node` inside `syntax` via `node`'s text range while compensating
/// for the offset introduced by the fake ident.
/// This is wrong if `node` comes before the insertion point! Use `find_node_in_file` instead.
fn find_node_in_file_compensated<N: AstNode>(
    sema: &Semantics<'_, RootDatabase>,
    in_file: &SyntaxNode,
    node: &N,
) -> Option<N> {
    ancestors_in_file_compensated(sema, in_file, node.syntax())?.find_map(N::cast)
}

fn ancestors_in_file_compensated<'sema>(
    sema: &'sema Semantics<'_, RootDatabase>,
    in_file: &SyntaxNode,
    node: &SyntaxNode,
) -> Option<impl Iterator<Item = SyntaxNode> + 'sema> {
    let syntax_range = in_file.text_range();
    let range = node.text_range();
    let end = range.end().checked_sub(TextSize::try_from(COMPLETION_MARKER.len()).ok()?)?;
    if end < range.start() {
        return None;
    }
    let range = TextRange::new(range.start(), end);
    // our inserted ident could cause `range` to go outside of the original syntax, so cap it
    let intersection = range.intersect(syntax_range)?;
    let node = match in_file.covering_element(intersection) {
        NodeOrToken::Node(node) => node,
        NodeOrToken::Token(tok) => tok.parent()?,
    };
    Some(sema.ancestors_with_macros(node))
}

/// Attempts to find `node` inside `syntax` via `node`'s text range while compensating
/// for the offset introduced by the fake ident..
/// This is wrong if `node` comes before the insertion point! Use `find_node_in_file` instead.
fn find_opt_node_in_file_compensated<N: AstNode>(
    sema: &Semantics<'_, RootDatabase>,
    syntax: &SyntaxNode,
    node: Option<N>,
) -> Option<N> {
    find_node_in_file_compensated(sema, syntax, &node?)
}

fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<(ast::Path, bool)> {
    if let Some(qual) = path.qualifier() {
        return Some((qual, false));
    }
    let use_tree_list = path.syntax().ancestors().find_map(ast::UseTreeList::cast)?;
    let use_tree = use_tree_list.syntax().parent().and_then(ast::UseTree::cast)?;
    Some((use_tree.path()?, true))
}

fn is_in_token_of_for_loop(path: &ast::Path) -> bool {
    // oh my ...
    (|| {
        let expr = path.syntax().parent().and_then(ast::PathExpr::cast)?;
        let for_expr = expr.syntax().parent().and_then(ast::ForExpr::cast)?;
        if for_expr.in_token().is_some() {
            return Some(false);
        }
        let pat = for_expr.pat()?;
        let next_sibl = next_non_trivia_sibling(pat.syntax().clone().into())?;
        Some(match next_sibl {
            syntax::NodeOrToken::Node(n) => {
                n.text_range().start() == path.syntax().text_range().start()
            }
            syntax::NodeOrToken::Token(t) => {
                t.text_range().start() == path.syntax().text_range().start()
            }
        })
    })()
    .unwrap_or(false)
}

fn is_in_breakable(node: &SyntaxNode) -> BreakableKind {
    node.ancestors()
        .take_while(|it| it.kind() != SyntaxKind::FN && it.kind() != SyntaxKind::CLOSURE_EXPR)
        .find_map(|it| {
            let (breakable, loop_body) = match_ast! {
                match it {
                    ast::ForExpr(it) => (BreakableKind::For, it.loop_body()),
                    ast::WhileExpr(it) => (BreakableKind::While, it.loop_body()),
                    ast::LoopExpr(it) => (BreakableKind::Loop, it.loop_body()),
                    ast::BlockExpr(it) => return it.label().map(|_| BreakableKind::Block),
                    _ => return None,
                }
            };
            loop_body
                .filter(|it| it.syntax().text_range().contains_range(node.text_range()))
                .map(|_| breakable)
        })
        .unwrap_or(BreakableKind::None)
}

fn is_in_block(node: &SyntaxNode) -> bool {
    node.parent()
        .map(|node| ast::ExprStmt::can_cast(node.kind()) || ast::StmtList::can_cast(node.kind()))
        .unwrap_or(false)
}

fn previous_non_trivia_token(e: impl Into<SyntaxElement>) -> Option<SyntaxToken> {
    let mut token = match e.into() {
        SyntaxElement::Node(n) => n.first_token()?,
        SyntaxElement::Token(t) => t,
    }
    .prev_token();
    while let Some(inner) = token {
        if !inner.kind().is_trivia() {
            return Some(inner);
        } else {
            token = inner.prev_token();
        }
    }
    None
}

fn next_non_trivia_token(e: impl Into<SyntaxElement>) -> Option<SyntaxToken> {
    let mut token = match e.into() {
        SyntaxElement::Node(n) => n.last_token()?,
        SyntaxElement::Token(t) => t,
    }
    .next_token();
    while let Some(inner) = token {
        if !inner.kind().is_trivia() {
            return Some(inner);
        } else {
            token = inner.next_token();
        }
    }
    None
}

fn next_non_trivia_sibling(ele: SyntaxElement) -> Option<SyntaxElement> {
    let mut e = ele.next_sibling_or_token();
    while let Some(inner) = e {
        if !inner.kind().is_trivia() {
            return Some(inner);
        } else {
            e = inner.next_sibling_or_token();
        }
    }
    None
}
