use crate::goto::find_goto_target;
use crate::references::{ReferencesMode, references};
use crate::{Db, ReferenceTarget};
use ruff_db::files::File;
use ruff_text_size::{Ranged, TextSize};
use ty_python_core::ProgramFile;
use ty_python_semantic::SemanticModel;

/// Returns the range of the symbol if it can be renamed, None if not.
pub fn can_rename(
    db: &dyn Db,
    file: ProgramFile<'_>,
    offset: TextSize,
) -> Option<ruff_text_size::TextRange> {
    let parsed = ruff_db::parsed::parsed_module(db, file.python_file(db));
    let module = parsed.load(db);
    let source_file = file.file(db);
    let model = SemanticModel::new(db, file);

    // Get the definitions for the symbol at the offset
    let goto_target = find_goto_target(&model, &module, offset)?;

    // Don't allow renaming of import module components
    if matches!(
        goto_target,
        crate::goto::GotoTarget::ImportModuleComponent { .. }
    ) {
        return None;
    }

    let current_file_in_project = is_file_in_project(db, source_file);

    let declaration_targets = goto_target
        .definitions(&model, ReferencesMode::Rename.to_import_alias_resolution())?
        .goto_declaration(&model, &goto_target)?
        .into_navigation_targets(model.db());

    for target in &declaration_targets {
        let target_file = target.file();

        // If definition is outside the project, refuse rename
        if !is_file_in_project(db, target_file) {
            return None;
        }

        // If current file is not in project and any definition is outside current file, refuse rename
        if !current_file_in_project && target_file != source_file {
            return None;
        }
    }

    Some(goto_target.range())
}

/// Perform a rename operation on the symbol at the given position.
/// Returns all locations that need to be updated with the new name.
pub fn rename(
    db: &dyn Db,
    file: ProgramFile<'_>,
    offset: TextSize,
    new_name: &str,
) -> Option<Vec<ReferenceTarget>> {
    let parsed = ruff_db::parsed::parsed_module(db, file.python_file(db));
    let module = parsed.load(db);
    let model = SemanticModel::new(db, file);

    // Get the definitions for the symbol at the offset
    let goto_target = find_goto_target(&model, &module, offset)?;

    // Clients shouldn't call us with an empty new name, but just in case...
    if new_name.is_empty() {
        return None;
    }

    // Determine if we should do a multi-file rename or single-file rename
    // based on whether the current file is part of the project
    let current_file_in_project = is_file_in_project(db, file.file(db));

    // Choose the appropriate rename mode:
    // - If current file is in project, do multi-file rename
    // - If current file is not in project, limit to single-file rename
    let rename_mode = if current_file_in_project {
        ReferencesMode::RenameMultiFile
    } else {
        ReferencesMode::Rename
    };

    // Find all references that need to be renamed
    references(db, file, &goto_target, rename_mode)
}

/// Helper function to check if a file is included in the project.
fn is_file_in_project(db: &dyn Db, file: File) -> bool {
    file.path(db).is_system_virtual_path() || db.project().files(db).contains(&file)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::tests::{CursorTest, IntoDiagnostic, cursor_test};
    use insta::assert_snapshot;
    use ruff_db::diagnostic::{Annotation, Diagnostic, DiagnosticId, LintName, Severity, Span};
    use ruff_db::files::FileRange;
    use ruff_text_size::Ranged;

    impl CursorTest {
        fn prepare_rename(&self) -> String {
            let Some(range) = salsa::attach(&self.db, || {
                can_rename(
                    &self.db,
                    self.program_file(self.cursor.file),
                    self.cursor.offset,
                )
            }) else {
                return "Cannot rename".to_string();
            };

            format!("Can rename symbol at range {range:?}")
        }

        fn rename(&self, new_name: &str) -> String {
            let rename_results = salsa::attach(&self.db, || {
                can_rename(
                    &self.db,
                    self.program_file(self.cursor.file),
                    self.cursor.offset,
                )?;

                rename(
                    &self.db,
                    self.program_file(self.cursor.file),
                    self.cursor.offset,
                    new_name,
                )
            });

            let Some(rename_results) = rename_results else {
                return "Cannot rename".to_string();
            };

            if rename_results.is_empty() {
                return "No locations to rename".to_string();
            }

            // Create a single diagnostic with multiple annotations
            let rename_diagnostic = RenameResultSet {
                locations: rename_results
                    .into_iter()
                    .map(|ref_item| FileRange::new(ref_item.file(), ref_item.range()))
                    .collect(),
            };

            self.render_diagnostics([rename_diagnostic])
        }
    }

    struct RenameResultSet {
        locations: Vec<FileRange>,
    }

    impl IntoDiagnostic for RenameResultSet {
        fn into_diagnostic(self) -> Diagnostic {
            let mut main = Diagnostic::new(
                DiagnosticId::Lint(LintName::of("rename")),
                Severity::Info,
                format!("Rename symbol (found {} locations)", self.locations.len()),
            );

            // Add the first location as primary annotation (the symbol being renamed)
            if let Some(first_location) = self.locations.first() {
                main.annotate(Annotation::primary(
                    Span::from(first_location.file()).with_range(first_location.range()),
                ));

                // Add remaining locations as secondary annotations
                for location in &self.locations[1..] {
                    main.annotate(Annotation::secondary(
                        Span::from(location.file()).with_range(location.range()),
                    ));
                }
            }

            main
        }
    }

    #[test]
    fn rename_does_not_mix_global_and_nonlocal_comprehension_walruses() {
        let test = cursor_test(
            "
last = 0

def outer():
    last = 1

    def write_global():
        global last
        [(last := global_item) for global_item in [2]]

    def write_nonlocal():
        nonlocal last
        [(last := nonlocal_item) for nonlocal_item in [3]]

    write_global()
    write_nonlocal()
    return la<CURSOR>st
",
        );

        assert_snapshot!(test.rename("result"), @"
        info[rename]: Rename symbol (found 4 locations)
          --> main.py:5:5
           |
         5 |     last = 1
           |     ^^^^
           |
          ::: main.py:12:18
           |
        12 |         nonlocal last
           |                  ----
        13 |         [(last := nonlocal_item) for nonlocal_item in [3]]
           |           ----
        14 |
        15 |     write_global()
        16 |     write_nonlocal()
        17 |     return last
           |            ----
        ");
    }

    #[test]
    fn rename_comprehension_walrus_in_function() {
        let test = cursor_test(
            "
def f(items):
    [(la<CURSOR>st := item) for item in items]
    return last
",
        );

        assert_snapshot!(test.rename("result"), @"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:3:7
          |
        3 |     [(last := item) for item in items]
          |       ^^^^
        4 |     return last
          |            ----
        ");
    }

    #[test]
    fn rename_comprehension_walrus_across_files() {
        let test = CursorTest::builder()
            .source("lib.py", "[(la<CURSOR>st := item) for item in [1]]\n")
            .source("main.py", "from lib import last\nprint(last)\n")
            .build();

        assert_snapshot!(test.rename("result"), @"
        info[rename]: Rename symbol (found 3 locations)
         --> lib.py:1:3
          |
        1 | [(last := item) for item in [1]]
          |   ^^^^
          |
         ::: main.py:1:17
          |
        1 | from lib import last
          |                 ----
        2 | print(last)
          |       ----
        ");
    }

    #[test]
    fn prepare_rename_parameter() {
        let test = cursor_test(
            "
def func(<CURSOR>value: int) -> int:
    value *= 2
    return value

value = 0
",
        );

        assert_snapshot!(test.prepare_rename(), @"Can rename symbol at range 10..15");
    }

    #[test]
    fn rename_parameter() {
        let test = cursor_test(
            "
def func(<CURSOR>value: int) -> int:
    value *= 2
    return value

func(value=42)
",
        );

        assert_snapshot!(test.rename("number"), @"
        info[rename]: Rename symbol (found 4 locations)
         --> main.py:2:10
          |
        2 | def func(value: int) -> int:
          |          ^^^^^
        3 |     value *= 2
          |     -----
        4 |     return value
          |            -----
        5 |
        6 | func(value=42)
          |      -----
        ");
    }

    #[test]
    fn rename_function() {
        let test = cursor_test(
            "
def fu<CURSOR>nc():
    pass

result1 = func()
x = func
",
        );

        assert_snapshot!(test.rename("calculate"), @"
        info[rename]: Rename symbol (found 3 locations)
         --> main.py:2:5
          |
        2 | def func():
          |     ^^^^
        3 |     pass
        4 |
        5 | result1 = func()
          |           ----
        6 | x = func
          |     ----
        ");
    }

    #[test]
    fn rename_class() {
        let test = cursor_test(
            "
class My<CURSOR>Class:
    def __init__(self):
        pass

obj1 = MyClass()
cls = MyClass
",
        );

        assert_snapshot!(test.rename("MyNewClass"), @"
        info[rename]: Rename symbol (found 3 locations)
         --> main.py:2:7
          |
        2 | class MyClass:
          |       ^^^^^^^
        3 |     def __init__(self):
        4 |         pass
        5 |
        6 | obj1 = MyClass()
          |        -------
        7 | cls = MyClass
          |       -------
        ");
    }

    #[test]
    fn rename_invalid_name() {
        let test = cursor_test(
            "
def fu<CURSOR>nc():
    pass
",
        );

        assert_snapshot!(test.rename(""), @"Cannot rename");
        assert_snapshot!(test.rename("valid_name"), @"
        info[rename]: Rename symbol (found 1 locations)
         --> main.py:2:5
          |
        2 | def func():
          |     ^^^^
        ");
    }

    #[test]
    fn multi_file_function_rename() {
        let test = CursorTest::builder()
            .source(
                "utils.py",
                "
def fu<CURSOR>nc(x):
    return x * 2
",
            )
            .source(
                "module.py",
                "
from utils import func

def test(data):
    return func(data)
",
            )
            .source(
                "app.py",
                "
from utils import helper_function

class DataProcessor:
    def __init__(self):
        self.multiplier = helper_function

    def process(self, value):
        return helper_function(value)
",
            )
            .build();

        assert_snapshot!(test.rename("utility_function"), @"
        info[rename]: Rename symbol (found 3 locations)
         --> utils.py:2:5
          |
        2 | def func(x):
          |     ^^^^
          |
         ::: module.py:2:19
          |
        2 | from utils import func
          |                   ----
        3 |
        4 | def test(data):
        5 |     return func(data)
          |            ----
        ");
    }

    #[test]
    fn multi_file_parameter_rename_updates_keyword_argument_labels() {
        let test = CursorTest::builder()
            .source(
                "example_rename_2.py",
                "
class ExampleClass:
    def __init__(self, <CURSOR>old_name: str) -> None:
        self.old_name = old_name
",
            )
            .source(
                "example_rename.py",
                r#"
from example_rename_2 import ExampleClass

instance = ExampleClass(old_name="test")
"#,
            )
            .build();

        assert_snapshot!(test.rename("new_name"), @r#"
        info[rename]: Rename symbol (found 3 locations)
         --> example_rename_2.py:3:24
          |
        3 |     def __init__(self, old_name: str) -> None:
          |                        ^^^^^^^^
        4 |         self.old_name = old_name
          |                         --------
          |
         ::: example_rename.py:4:25
          |
        4 | instance = ExampleClass(old_name="test")
          |                         --------
        "#);
    }

    #[test]
    fn rename_string_annotation1() {
        let test = cursor_test(
            r#"
        a: "MyCla<CURSOR>ss" = 1

        class MyClass:
            """some docs"""
        "#,
        );

        assert_snapshot!(test.rename("MyNewClass"), @r#"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:2:5
          |
        2 | a: "MyClass" = 1
          |     ^^^^^^^
        3 |
        4 | class MyClass:
          |       -------
        "#);
    }

    #[test]
    fn rename_string_annotation2() {
        let test = cursor_test(
            r#"
        a: "None | MyCl<CURSOR>ass" = 1

        class MyClass:
            """some docs"""
        "#,
        );

        assert_snapshot!(test.rename("MyNewClass"), @r#"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:2:12
          |
        2 | a: "None | MyClass" = 1
          |            ^^^^^^^
        3 |
        4 | class MyClass:
          |       -------
        "#);
    }

    #[test]
    fn rename_string_annotation3() {
        let test = cursor_test(
            r#"
        a: "None |<CURSOR> MyClass" = 1

        class MyClass:
            """some docs"""
        "#,
        );

        assert_snapshot!(test.rename("MyNewClass"), @"Cannot rename");
    }

    #[test]
    fn rename_string_annotation4() {
        let test = cursor_test(
            r#"
        a: "None | MyClass<CURSOR>" = 1

        class MyClass:
            """some docs"""
        "#,
        );

        assert_snapshot!(test.rename("MyNewClass"), @r#"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:2:12
          |
        2 | a: "None | MyClass" = 1
          |            ^^^^^^^
        3 |
        4 | class MyClass:
          |       -------
        "#);
    }

    #[test]
    fn rename_string_annotation5() {
        let test = cursor_test(
            r#"
        a: "None | MyClass"<CURSOR> = 1

        class MyClass:
            """some docs"""
        "#,
        );

        assert_snapshot!(test.rename("MyNewClass"), @"Cannot rename");
    }

    #[test]
    fn rename_string_annotation_dangling1() {
        let test = cursor_test(
            r#"
        a: "MyCl<CURSOR>ass |" = 1

        class MyClass:
            """some docs"""
        "#,
        );

        assert_snapshot!(test.rename("MyNewClass"), @"Cannot rename");
    }

    #[test]
    fn rename_string_annotation_dangling2() {
        let test = cursor_test(
            r#"
        a: "MyCl<CURSOR>ass | No" = 1

        class MyClass:
            """some docs"""
        "#,
        );

        assert_snapshot!(test.rename("MyNewClass"), @r#"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:2:5
          |
        2 | a: "MyClass | No" = 1
          |     ^^^^^^^
        3 |
        4 | class MyClass:
          |       -------
        "#);
    }

    #[test]
    fn rename_string_annotation_dangling3() {
        let test = cursor_test(
            r#"
        a: "MyClass | N<CURSOR>o" = 1

        class MyClass:
            """some docs"""
        "#,
        );

        assert_snapshot!(test.rename("MyNewClass"), @"Cannot rename");
    }

    #[test]
    fn rename_match_name_stmt() {
        let test = cursor_test(
            r#"
            def my_func(command: str):
                match command.split():
                    case ["get", a<CURSOR>b]:
                        x = ab
            "#,
        );

        assert_snapshot!(test.rename("XY"), @r#"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:4:22
          |
        4 |         case ["get", ab]:
          |                      ^^
        5 |             x = ab
          |                 --
        "#);
    }

    #[test]
    fn rename_match_name_binding() {
        let test = cursor_test(
            r#"
            def my_func(command: str):
                match command.split():
                    case ["get", ab]:
                        x = a<CURSOR>b
            "#,
        );

        assert_snapshot!(test.rename("XY"), @r#"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:4:22
          |
        4 |         case ["get", ab]:
          |                      ^^
        5 |             x = ab
          |                 --
        "#);
    }

    #[test]
    fn rename_match_rest_stmt() {
        let test = cursor_test(
            r#"
            def my_func(command: str):
                match command.split():
                    case ["get", *a<CURSOR>b]:
                        x = ab
            "#,
        );

        assert_snapshot!(test.rename("XY"), @r#"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:4:23
          |
        4 |         case ["get", *ab]:
          |                       ^^
        5 |             x = ab
          |                 --
        "#);
    }

    #[test]
    fn rename_match_rest_binding() {
        let test = cursor_test(
            r#"
            def my_func(command: str):
                match command.split():
                    case ["get", *ab]:
                        x = a<CURSOR>b
            "#,
        );

        assert_snapshot!(test.rename("XY"), @r#"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:4:23
          |
        4 |         case ["get", *ab]:
          |                       ^^
        5 |             x = ab
          |                 --
        "#);
    }

    #[test]
    fn rename_match_as_stmt() {
        let test = cursor_test(
            r#"
            def my_func(command: str):
                match command.split():
                    case ["get", ("a" | "b") as a<CURSOR>b]:
                        x = ab
            "#,
        );

        assert_snapshot!(test.rename("XY"), @r#"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:4:37
          |
        4 |         case ["get", ("a" | "b") as ab]:
          |                                     ^^
        5 |             x = ab
          |                 --
        "#);
    }

    #[test]
    fn rename_match_as_binding() {
        let test = cursor_test(
            r#"
            def my_func(command: str):
                match command.split():
                    case ["get", ("a" | "b") as ab]:
                        x = a<CURSOR>b
            "#,
        );

        assert_snapshot!(test.rename("XY"), @r#"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:4:37
          |
        4 |         case ["get", ("a" | "b") as ab]:
          |                                     ^^
        5 |             x = ab
          |                 --
        "#);
    }

    #[test]
    fn rename_match_keyword_stmt() {
        let test = cursor_test(
            r#"
            class Click:
                __match_args__ = ("position", "button")
                def __init__(self, pos, btn):
                    self.position: int = pos
                    self.button: str = btn

            def my_func(event: Click):
                match event:
                    case Click(x, button=a<CURSOR>b):
                        x = ab
            "#,
        );

        assert_snapshot!(test.rename("XY"), @"
        info[rename]: Rename symbol (found 2 locations)
          --> main.py:10:30
           |
        10 |         case Click(x, button=ab):
           |                              ^^
        11 |             x = ab
           |                 --
        ");
    }

    #[test]
    fn rename_match_keyword_binding() {
        let test = cursor_test(
            r#"
            class Click:
                __match_args__ = ("position", "button")
                def __init__(self, pos, btn):
                    self.position: int = pos
                    self.button: str = btn

            def my_func(event: Click):
                match event:
                    case Click(x, button=ab):
                        x = a<CURSOR>b
            "#,
        );

        assert_snapshot!(test.rename("XY"), @"
        info[rename]: Rename symbol (found 2 locations)
          --> main.py:10:30
           |
        10 |         case Click(x, button=ab):
           |                              ^^
        11 |             x = ab
           |                 --
        ");
    }

    #[test]
    fn rename_match_class_name() {
        let test = cursor_test(
            r#"
            class Click:
                __match_args__ = ("position", "button")
                def __init__(self, pos, btn):
                    self.position: int = pos
                    self.button: str = btn

            def my_func(event: Click):
                match event:
                    case Cl<CURSOR>ick(x, button=ab):
                        x = ab
            "#,
        );

        assert_snapshot!(test.rename("XY"), @"
        info[rename]: Rename symbol (found 3 locations)
          --> main.py:2:7
           |
         2 | class Click:
           |       ^^^^^
           |
          ::: main.py:8:20
           |
         8 | def my_func(event: Click):
           |                    -----
         9 |     match event:
        10 |         case Click(x, button=ab):
           |              -----
        ");
    }

    #[test]
    fn rename_match_class_field_name() {
        let test = cursor_test(
            r#"
            class Click:
                __match_args__ = ("position", "button")
                def __init__(self, pos, btn):
                    self.position: int = pos
                    self.button: str = btn

            def my_func(event: Click):
                match event:
                    case Click(x, but<CURSOR>ton=ab):
                        x = ab
            "#,
        );

        assert_snapshot!(test.rename("XY"), @"Cannot rename");
    }

    #[test]
    fn rename_typevar_name_stmt() {
        let test = cursor_test(
            r#"
            type Alias1[A<CURSOR>B: int = bool] = tuple[AB, list[AB]]
            "#,
        );

        assert_snapshot!(test.rename("XY"), @"
        info[rename]: Rename symbol (found 3 locations)
         --> main.py:2:13
          |
        2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
          |             ^^                      --       --
        ");
    }

    #[test]
    fn rename_typevar_name_binding() {
        let test = cursor_test(
            r#"
            type Alias1[AB: int = bool] = tuple[A<CURSOR>B, list[AB]]
            "#,
        );

        assert_snapshot!(test.rename("XY"), @"
        info[rename]: Rename symbol (found 3 locations)
         --> main.py:2:13
          |
        2 | type Alias1[AB: int = bool] = tuple[AB, list[AB]]
          |             ^^                      --       --
        ");
    }

    #[test]
    fn rename_typevar_spec_stmt() {
        let test = cursor_test(
            r#"
            from typing import Callable
            type Alias2[**A<CURSOR>B = [int, str]] = Callable[AB, tuple[AB]]
            "#,
        );

        assert_snapshot!(test.rename("XY"), @"
        info[rename]: Rename symbol (found 3 locations)
         --> main.py:3:15
          |
        3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
          |               ^^                          --        --
        ");
    }

    #[test]
    fn rename_typevar_spec_binding() {
        let test = cursor_test(
            r#"
            from typing import Callable
            type Alias2[**AB = [int, str]] = Callable[A<CURSOR>B, tuple[AB]]
            "#,
        );

        assert_snapshot!(test.rename("XY"), @"
        info[rename]: Rename symbol (found 3 locations)
         --> main.py:3:15
          |
        3 | type Alias2[**AB = [int, str]] = Callable[AB, tuple[AB]]
          |               ^^                          --        --
        ");
    }

    #[test]
    fn rename_typevar_tuple_stmt() {
        let test = cursor_test(
            r#"
            type Alias3[*A<CURSOR>B = ()] = tuple[tuple[*AB], tuple[*AB]]
            "#,
        );

        assert_snapshot!(test.rename("XY"), @"
        info[rename]: Rename symbol (found 3 locations)
         --> main.py:2:14
          |
        2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
          |              ^^                      --          --
        ");
    }

    #[test]
    fn rename_typevar_tuple_binding() {
        let test = cursor_test(
            r#"
            type Alias3[*AB = ()] = tuple[tuple[*A<CURSOR>B], tuple[*AB]]
            "#,
        );

        assert_snapshot!(test.rename("XY"), @"
        info[rename]: Rename symbol (found 3 locations)
         --> main.py:2:14
          |
        2 | type Alias3[*AB = ()] = tuple[tuple[*AB], tuple[*AB]]
          |              ^^                      --          --
        ");
    }

    #[test]
    fn cannot_rename_import_module_component() {
        // Test that we cannot rename parts of module names in import statements
        let test = cursor_test(
            "
import <CURSOR>os.path
x = os.path.join('a', 'b')
",
        );

        assert_snapshot!(test.prepare_rename(), @"Cannot rename");
    }

    #[test]
    fn cannot_rename_from_import_module_component() {
        // Test that we cannot rename parts of module names in from import statements
        let test = cursor_test(
            "
from os.<CURSOR>path import join
result = join('a', 'b')
",
        );

        assert_snapshot!(test.prepare_rename(), @"Cannot rename");
    }

    #[test]
    fn cannot_rename_external_file() {
        // This test verifies that we cannot rename a symbol when it's defined in a file
        // that's outside the project (like a standard library function)
        let test = cursor_test(
            "
import os
x = <CURSOR>os.path.join('a', 'b')
",
        );

        assert_snapshot!(test.prepare_rename(), @"Cannot rename");
    }

    #[test]
    fn rename_alias_at_import_statement() {
        let test = CursorTest::builder()
            .source(
                "utils.py",
                "
def test(): pass
",
            )
            .source(
                "main.py",
                "
from utils import test as <CURSOR>alias
result = alias()
",
            )
            .build();

        assert_snapshot!(test.rename("new_alias"), @"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:2:27
          |
        2 | from utils import test as alias
          |                           ^^^^^
        3 | result = alias()
          |          -----
        ");
    }

    #[test]
    fn rename_alias_at_usage_site() {
        // Test renaming an alias when the cursor is on the alias in the usage statement
        let test = CursorTest::builder()
            .source(
                "utils.py",
                "
def test(): pass
",
            )
            .source(
                "main.py",
                "
from utils import test as alias
result = <CURSOR>alias()
",
            )
            .build();

        assert_snapshot!(test.rename("new_alias"), @"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:2:27
          |
        2 | from utils import test as alias
          |                           ^^^^^
        3 | result = alias()
          |          -----
        ");
    }

    #[test]
    fn rename_across_import_chain_with_mixed_aliases() {
        // Test renaming a symbol that's imported across multiple files with mixed alias patterns
        // File 1 (source.py): defines the original function
        // File 2 (middle.py): imports without alias from source.py
        // File 3 (consumer.py): imports with alias from middle.py
        let test = CursorTest::builder()
            .source(
                "source.py",
                "
def original_func<CURSOR>tion():
    return 'Hello from source'
",
            )
            .source(
                "middle.py",
                "
from source import original_function

def wrapper():
    return original_function()

result = original_function()
",
            )
            .source(
                "consumer.py",
                "
from middle import original_function as func_alias

def process():
    return func_alias()

value1 = func_alias()
",
            )
            .build();

        assert_snapshot!(test.rename("renamed_function"), @"
        info[rename]: Rename symbol (found 5 locations)
         --> source.py:2:5
          |
        2 | def original_function():
          |     ^^^^^^^^^^^^^^^^^
          |
         ::: consumer.py:2:20
          |
        2 | from middle import original_function as func_alias
          |                    -----------------
          |
         ::: middle.py:2:20
          |
        2 | from source import original_function
          |                    -----------------
        3 |
        4 | def wrapper():
        5 |     return original_function()
          |            -----------------
        6 |
        7 | result = original_function()
          |          -----------------
        ");
    }

    #[test]
    fn rename_alias_in_import_chain() {
        let test = CursorTest::builder()
            .source(
                "file1.py",
                "
def func1(): pass
",
            )
            .source(
                "file2.py",
                "
from file1 import func1 as func2

func2()
",
            )
            .source(
                "file3.py",
                "
from file2 import func2

class App:
    def run(self):
        return fu<CURSOR>nc2()
",
            )
            .build();

        assert_snapshot!(test.rename("new_util_name"), @"
        info[rename]: Rename symbol (found 4 locations)
         --> file3.py:2:19
          |
        2 | from file2 import func2
          |                   ^^^^^
        3 |
        4 | class App:
        5 |     def run(self):
        6 |         return func2()
          |                -----
          |
         ::: file2.py:2:28
          |
        2 | from file1 import func1 as func2
          |                            -----
        3 |
        4 | func2()
          | -----
        ");
    }

    #[test]
    fn cannot_rename_keyword() {
        // Test that we cannot rename Python keywords like "None"
        let test = cursor_test(
            "
def process_value(value):
    if value is <CURSOR>None:
        return 'empty'
    return str(value)
",
        );

        assert_snapshot!(test.prepare_rename(), @"Cannot rename");
    }

    #[test]
    fn cannot_rename_builtin_type() {
        // Test that we cannot rename Python builtin types like "int"
        let test = cursor_test(
            "
def convert_to_number(value):
    return <CURSOR>int(value)
",
        );

        assert_snapshot!(test.prepare_rename(), @"Cannot rename");
    }

    #[test]
    fn cannot_rename_private_builtin_helper() {
        // Unresolved references must not resolve to a private typeshed helper that likely does not
        // exist at runtime or rename matching unresolved references in other files.
        let test = CursorTest::builder()
            .source("other.py", "_T_co\n")
            .source("main.py", "<CURSOR>_T_co\n")
            .build();

        assert_snapshot!(test.prepare_rename(), @"Cannot rename");
    }

    #[test]
    fn rename_keyword_argument() {
        // Test renaming a keyword argument and its corresponding parameter
        let test = cursor_test(
            "
def func(x, y=5):
    return x + y

result = func(10, <CURSOR>y=20)
",
        );

        assert_snapshot!(test.rename("z"), @"
        info[rename]: Rename symbol (found 3 locations)
         --> main.py:2:13
          |
        2 | def func(x, y=5):
          |             ^
        3 |     return x + y
          |                -
        4 |
        5 | result = func(10, y=20)
          |                   -
        ");
    }

    #[test]
    fn rename_parameter_with_keyword_argument() {
        // Test renaming a parameter and its corresponding keyword argument
        let test = cursor_test(
            "
def func(x, <CURSOR>y=5):
    return x + y

result = func(10, y=20)
",
        );

        assert_snapshot!(test.rename("z"), @"
        info[rename]: Rename symbol (found 3 locations)
         --> main.py:2:13
          |
        2 | def func(x, y=5):
          |             ^
        3 |     return x + y
          |                -
        4 |
        5 | result = func(10, y=20)
          |                   -
        ");
    }

    #[test]
    fn rename_keyword_argument_typeddict_field() {
        let test = cursor_test(
            "
from typing import TypedDict

class TD(TypedDict):
    f<CURSOR>: int
    g: str

TD(f=1)
",
        );

        assert_snapshot!(test.rename("z"), @"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:5:5
          |
        5 |     f: int
          |     ^
        6 |     g: str
        7 |
        8 | TD(f=1)
          |    -
        ");
    }

    #[test]
    fn rename_typeddict_field_from_keyword_argument() {
        let test = cursor_test(
            "
from typing import TypedDict

class TD(TypedDict):
    f: int
    g: str

TD(f<CURSOR>=1)
",
        );

        assert_snapshot!(test.rename("z"), @"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:5:5
          |
        5 |     f: int
          |     ^
        6 |     g: str
        7 |
        8 | TD(f=1)
          |    -
        ");
    }

    #[test]
    fn rename_keyword_argument_namedtuple_field() {
        let test = cursor_test(
            "
from typing import NamedTuple

class NT(NamedTuple):
    f<CURSOR>: int
    g: str

NT(f=1)
",
        );

        assert_snapshot!(test.rename("z"), @"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:5:5
          |
        5 |     f: int
          |     ^
        6 |     g: str
        7 |
        8 | NT(f=1)
          |    -
        ");
    }

    #[test]
    fn rename_keyword_argument_dataclass_field() {
        let test = cursor_test(
            "
from dataclasses import dataclass

@dataclass
class DC:
    f<CURSOR>: int
    g: str

DC(f=1)
",
        );

        assert_snapshot!(test.rename("z"), @"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:6:5
          |
        6 |     f: int
          |     ^
        7 |     g: str
        8 |
        9 | DC(f=1)
          |    -
        ");
    }

    #[test]
    fn import_alias() {
        let test = CursorTest::builder()
            .source(
                "main.py",
                r#"
                import warnings
                import warnings as <CURSOR>abc

                x = abc
                y = warnings
            "#,
            )
            .build();

        assert_snapshot!(test.rename("z"), @"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:3:20
          |
        3 | import warnings as abc
          |                    ^^^
        4 |
        5 | x = abc
          |     ---
        ");
    }

    #[test]
    fn import_alias_to_first_party_definition() {
        let test = CursorTest::builder()
            .source("lib.py", "def deprecated(): pass")
            .source(
                "main.py",
                r#"
                import lib as lib2<CURSOR>

                x = lib2
            "#,
            )
            .build();

        assert_snapshot!(test.rename("z"), @"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:2:15
          |
        2 | import lib as lib2
          |               ^^^^
        3 |
        4 | x = lib2
          |     ----
        ");
    }

    #[test]
    fn imported_first_party_definition() {
        let test = CursorTest::builder()
            .source("lib.py", "def deprecated(): pass")
            .source(
                "main.py",
                r#"
                from lib import deprecated<CURSOR>

                x = deprecated
            "#,
            )
            .build();

        assert_snapshot!(test.rename("z"), @"
        info[rename]: Rename symbol (found 3 locations)
         --> main.py:2:17
          |
        2 | from lib import deprecated
          |                 ^^^^^^^^^^
        3 |
        4 | x = deprecated
          |     ----------
          |
         ::: lib.py:1:5
          |
        1 | def deprecated(): pass
          |     ----------
        ");
    }

    #[test]
    fn import_alias_use() {
        let test = CursorTest::builder()
            .source(
                "main.py",
                r#"
                import warnings
                import warnings as abc

                x = abc<CURSOR>
                y = warnings
            "#,
            )
            .build();

        assert_snapshot!(test.rename("z"), @"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:3:20
          |
        3 | import warnings as abc
          |                    ^^^
        4 |
        5 | x = abc
          |     ---
        ");
    }

    #[test]
    fn rename_submodule_import_from_use() {
        let test = CursorTest::builder()
            .source(
                "mypackage/__init__.py",
                r#"
                from .subpkg.submod import val

                x = sub<CURSOR>pkg
                "#,
            )
            .source("mypackage/subpkg/__init__.py", r#""#)
            .source(
                "mypackage/subpkg/submod.py",
                r#"
                val: int = 0
                "#,
            )
            .build();

        // TODO(submodule-imports): we should refuse to rename this (it's the name of a module)
        assert_snapshot!(test.rename("mypkg"), @"
        info[rename]: Rename symbol (found 1 locations)
         --> mypackage/__init__.py:4:5
          |
        4 | x = subpkg
          |     ^^^^^^
        ");
    }

    #[test]
    fn rename_submodule_import_from_def() {
        let test = CursorTest::builder()
            .source(
                "mypackage/__init__.py",
                r#"
                from .sub<CURSOR>pkg.submod import val

                x = subpkg
                "#,
            )
            .source("mypackage/subpkg/__init__.py", r#""#)
            .source(
                "mypackage/subpkg/submod.py",
                r#"
                val: int = 0
                "#,
            )
            .build();

        // Refusing to rename is correct
        assert_snapshot!(test.rename("mypkg"), @"Cannot rename");
    }

    #[test]
    fn rename_submodule_import_from_wrong_use() {
        let test = CursorTest::builder()
            .source(
                "mypackage/__init__.py",
                r#"
                from .subpkg.submod import val

                x = sub<CURSOR>mod
                "#,
            )
            .source("mypackage/subpkg/__init__.py", r#""#)
            .source(
                "mypackage/subpkg/submod.py",
                r#"
                val: int = 0
                "#,
            )
            .build();

        // Refusing to rename is good/fine here, it's an undefined reference
        assert_snapshot!(test.rename("mypkg"), @"Cannot rename");
    }

    #[test]
    fn rename_submodule_import_from_wrong_def() {
        let test = CursorTest::builder()
            .source(
                "mypackage/__init__.py",
                r#"
                from .subpkg.sub<CURSOR>mod import val

                x = submod
                "#,
            )
            .source("mypackage/subpkg/__init__.py", r#""#)
            .source(
                "mypackage/subpkg/submod.py",
                r#"
                val: int = 0
                "#,
            )
            .build();

        // Refusing to rename is good here, it's a module name
        assert_snapshot!(test.rename("mypkg"), @"Cannot rename");
    }

    #[test]
    fn rename_submodule_import_from_confusing_shadowed_def() {
        let test = CursorTest::builder()
            .source(
                "mypackage/__init__.py",
                r#"
                from .sub<CURSOR>pkg import subpkg

                x = subpkg
                "#,
            )
            .source(
                "mypackage/subpkg/__init__.py",
                r#"
                subpkg: int = 10
                "#,
            )
            .build();

        // Refusing to rename is good here, it's the name of a module
        assert_snapshot!(test.rename("mypkg"), @"Cannot rename");
    }

    #[test]
    fn rename_submodule_import_from_confusing_real_def() {
        let test = CursorTest::builder()
            .source(
                "mypackage/__init__.py",
                r#"
                from .subpkg import sub<CURSOR>pkg

                x = subpkg
                "#,
            )
            .source(
                "mypackage/subpkg/__init__.py",
                r#"
                subpkg: int = 10
                "#,
            )
            .build();

        // Renaming the integer is correct
        assert_snapshot!(test.rename("mypkg"), @"
        info[rename]: Rename symbol (found 3 locations)
         --> mypackage/__init__.py:2:21
          |
        2 | from .subpkg import subpkg
          |                     ^^^^^^
        3 |
        4 | x = subpkg
          |     ------
          |
         ::: mypackage/subpkg/__init__.py:2:1
          |
        2 | subpkg: int = 10
          | ------
        ");
    }

    #[test]
    fn rename_submodule_import_from_confusing_use() {
        let test = CursorTest::builder()
            .source(
                "mypackage/__init__.py",
                r#"
                from .subpkg import subpkg

                x = sub<CURSOR>pkg
                "#,
            )
            .source(
                "mypackage/subpkg/__init__.py",
                r#"
                subpkg: int = 10
                "#,
            )
            .build();

        // Includes import binding and underlying definition in one rename set.
        assert_snapshot!(test.rename("mypkg"), @"
        info[rename]: Rename symbol (found 3 locations)
         --> mypackage/__init__.py:2:21
          |
        2 | from .subpkg import subpkg
          |                     ^^^^^^
        3 |
        4 | x = subpkg
          |     ------
          |
         ::: mypackage/subpkg/__init__.py:2:1
          |
        2 | subpkg: int = 10
          | ------
        ");
    }

    #[test]
    fn rename_overloaded_function() {
        let test = CursorTest::builder()
            .source(
                "lib.py",
                r#"
                from typing import overload, Any

                @overload
                def test<CURSOR>() -> None: ...
                @overload
                def test(a: str) -> str: ...
                @overload
                def test(a: int) -> int: ...

                def test(a: Any) -> Any:
                    return a
                "#,
            )
            .source(
                "main.py",
                r#"
                from lib import test

                test("test")
                "#,
            )
            .build();

        assert_snapshot!(test.rename("better_name"), @r#"
        info[rename]: Rename symbol (found 6 locations)
          --> lib.py:5:5
           |
         5 | def test() -> None: ...
           |     ^^^^
         6 | @overload
         7 | def test(a: str) -> str: ...
           |     ----
         8 | @overload
         9 | def test(a: int) -> int: ...
           |     ----
        10 |
        11 | def test(a: Any) -> Any:
           |     ----
           |
          ::: main.py:2:17
           |
         2 | from lib import test
           |                 ----
         3 |
         4 | test("test")
           | ----
        "#);
    }

    #[test]
    fn rename_overloaded_method() {
        let test = CursorTest::builder()
            .source(
                "lib.py",
                r#"
                from typing import overload, Any

                class Test:
                    @overload
                    def test<CURSOR>() -> None: ...
                    @overload
                    def test(a: str) -> str: ...
                    @overload
                    def test(a: int) -> int: ...

                    def test(a: Any) -> Any:
                        return a

                "#,
            )
            .source(
                "main.py",
                r#"
                from lib import Test

                Test().test("test")
                "#,
            )
            .build();

        assert_snapshot!(test.rename("better_name"), @r#"
        info[rename]: Rename symbol (found 5 locations)
          --> lib.py:6:9
           |
         6 |     def test() -> None: ...
           |         ^^^^
         7 |     @overload
         8 |     def test(a: str) -> str: ...
           |         ----
         9 |     @overload
        10 |     def test(a: int) -> int: ...
           |         ----
        11 |
        12 |     def test(a: Any) -> Any:
           |         ----
           |
          ::: main.py:4:8
           |
         4 | Test().test("test")
           |        ----
        "#);
    }

    #[test]
    fn rename_overloaded_function_usage() {
        let test = CursorTest::builder()
            .source(
                "lib.py",
                r#"
                from typing import overload, Any

                @overload
                def test() -> None: ...
                @overload
                def test(a: str) -> str: ...
                @overload
                def test(a: int) -> int: ...

                def test(a: Any) -> Any:
                    return a
                "#,
            )
            .source(
                "main.py",
                r#"
                from lib import test

                test<CURSOR>("test")
                "#,
            )
            .build();

        // Includes all overload declarations plus implementation from usage-site rename.
        assert_snapshot!(test.rename("better_name"), @r#"
        info[rename]: Rename symbol (found 6 locations)
          --> main.py:2:17
           |
         2 | from lib import test
           |                 ^^^^
         3 |
         4 | test("test")
           | ----
           |
          ::: lib.py:5:5
           |
         5 | def test() -> None: ...
           |     ----
         6 | @overload
         7 | def test(a: str) -> str: ...
           |     ----
         8 | @overload
         9 | def test(a: int) -> int: ...
           |     ----
        10 |
        11 | def test(a: Any) -> Any:
           |     ----
        "#);
    }

    #[test]
    fn rename_property() {
        let test = CursorTest::builder()
            .source(
                "lib.py",
                r#"
                class Foo:
                    @property
                    def my_property<CURSOR>(self) -> int:
                        return 42
                "#,
            )
            .source(
                "main.py",
                r#"
                from lib import Foo

                print(Foo().my_property)
                "#,
            )
            .build();

        assert_snapshot!(test.rename("better_name"), @"
        info[rename]: Rename symbol (found 2 locations)
         --> lib.py:4:9
          |
        4 |     def my_property(self) -> int:
          |         ^^^^^^^^^^^
          |
         ::: main.py:4:13
          |
        4 | print(Foo().my_property)
          |             -----------
        ");
    }

    #[test]
    fn rename_property_with_setter() {
        let test = CursorTest::builder()
            .source(
                "lib.py",
                r#"
                class Foo:
                    @property
                    def my_property<CURSOR>(self) -> int:
                        return 42

                    @my_property.setter
                    def my_property(self, value: int) -> None:
                        pass
                "#,
            )
            .source(
                "main.py",
                r#"
                from lib import Foo

                print(Foo().my_property)
                Foo().my_property = 56
                "#,
            )
            .build();

        assert_snapshot!(test.rename("better_name"), @"
        info[rename]: Rename symbol (found 5 locations)
         --> lib.py:4:9
          |
        4 |     def my_property(self) -> int:
          |         ^^^^^^^^^^^
        5 |         return 42
        6 |
        7 |     @my_property.setter
          |      -----------
        8 |     def my_property(self, value: int) -> None:
          |         -----------
          |
         ::: main.py:4:13
          |
        4 | print(Foo().my_property)
          |             -----------
        5 | Foo().my_property = 56
          |       -----------
        ");
    }

    #[test]
    fn rename_property_with_deleter() {
        let test = CursorTest::builder()
            .source(
                "lib.py",
                r#"
                class Foo:
                    @property
                    def my_property<CURSOR>(self) -> int:
                        return 42

                    @my_property.deleter
                    def my_property(self) -> None:
                        pass
                "#,
            )
            .source(
                "main.py",
                r#"
                from lib import Foo

                print(Foo().my_property)
                del Foo().my_property
                "#,
            )
            .build();

        assert_snapshot!(test.rename("better_name"), @"
        info[rename]: Rename symbol (found 5 locations)
         --> lib.py:4:9
          |
        4 |     def my_property(self) -> int:
          |         ^^^^^^^^^^^
        5 |         return 42
        6 |
        7 |     @my_property.deleter
          |      -----------
        8 |     def my_property(self) -> None:
          |         -----------
          |
         ::: main.py:4:13
          |
        4 | print(Foo().my_property)
          |             -----------
        5 | del Foo().my_property
          |           -----------
        ");
    }

    #[test]
    fn rename_property_with_setter_and_deleter() {
        let test = CursorTest::builder()
            .source(
                "lib.py",
                r#"
                class Foo:
                    @property
                    def my_property<CURSOR>(self) -> int:
                        return 42

                    @my_property.setter
                    def my_property(self, value: int) -> None:
                        pass

                    @my_property.deleter
                    def my_property(self) -> None:
                        pass
                "#,
            )
            .source(
                "main.py",
                r#"
                from lib import Foo

                print(Foo().my_property)
                Foo().my_property = 56
                del Foo().my_property
                "#,
            )
            .build();

        assert_snapshot!(test.rename("better_name"), @"
        info[rename]: Rename symbol (found 8 locations)
          --> lib.py:4:9
           |
         4 |     def my_property(self) -> int:
           |         ^^^^^^^^^^^
         5 |         return 42
         6 |
         7 |     @my_property.setter
           |      -----------
         8 |     def my_property(self, value: int) -> None:
           |         -----------
         9 |         pass
        10 |
        11 |     @my_property.deleter
           |      -----------
        12 |     def my_property(self) -> None:
           |         -----------
           |
          ::: main.py:4:13
           |
         4 | print(Foo().my_property)
           |             -----------
         5 | Foo().my_property = 56
           |       -----------
         6 | del Foo().my_property
           |           -----------
        ");
    }

    /// Renaming from the setter's function name should rename the getter,
    /// the decorator reference, and the setter function name itself.
    #[test]
    fn rename_property_from_setter() {
        let test = CursorTest::builder()
            .source(
                "lib.py",
                r#"
                class Foo:
                    @property
                    def my_property(self) -> int:
                        return 42

                    @my_property.setter
                    def my_property<CURSOR>(self, value: int) -> None:
                        pass
                "#,
            )
            .source(
                "main.py",
                r#"
                from lib import Foo

                print(Foo().my_property)
                Foo().my_property = 56
                "#,
            )
            .build();

        assert_snapshot!(test.rename("better_name"), @"
        info[rename]: Rename symbol (found 5 locations)
         --> lib.py:4:9
          |
        4 |     def my_property(self) -> int:
          |         ^^^^^^^^^^^
        5 |         return 42
        6 |
        7 |     @my_property.setter
          |      -----------
        8 |     def my_property(self, value: int) -> None:
          |         -----------
          |
         ::: main.py:4:13
          |
        4 | print(Foo().my_property)
          |             -----------
        5 | Foo().my_property = 56
          |       -----------
        ");
    }

    /// Renaming from the decorator reference (`@my_property.setter`)
    /// should rename everything.
    #[test]
    fn rename_property_from_decorator() {
        let test = CursorTest::builder()
            .source(
                "lib.py",
                r#"
                class Foo:
                    @property
                    def my_property(self) -> int:
                        return 42

                    @my_<CURSOR>property.setter
                    def my_property(self, value: int) -> None:
                        pass
                "#,
            )
            .source(
                "main.py",
                r#"
                from lib import Foo

                print(Foo().my_property)
                Foo().my_property = 56
                "#,
            )
            .build();

        assert_snapshot!(test.rename("better_name"), @"
        info[rename]: Rename symbol (found 5 locations)
         --> lib.py:4:9
          |
        4 |     def my_property(self) -> int:
          |         ^^^^^^^^^^^
        5 |         return 42
        6 |
        7 |     @my_property.setter
          |      -----------
        8 |     def my_property(self, value: int) -> None:
          |         -----------
          |
         ::: main.py:4:13
          |
        4 | print(Foo().my_property)
          |             -----------
        5 | Foo().my_property = 56
          |       -----------
        ");
    }

    /// Renaming from a usage site should rename the property getter,
    /// setter, and decorator.
    #[test]
    fn rename_property_from_usage() {
        let test = CursorTest::builder()
            .source(
                "lib.py",
                r#"
                class Foo:
                    @property
                    def my_property(self) -> int:
                        return 42

                    @my_property.setter
                    def my_property(self, value: int) -> None:
                        pass
                "#,
            )
            .source(
                "main.py",
                r#"
                from lib import Foo

                print(Foo().my_<CURSOR>property)
                Foo().my_property = 56
                "#,
            )
            .build();

        assert_snapshot!(test.rename("better_name"), @"
        info[rename]: Rename symbol (found 5 locations)
         --> main.py:4:13
          |
        4 | print(Foo().my_property)
          |             ^^^^^^^^^^^
        5 | Foo().my_property = 56
          |       -----------
          |
         ::: lib.py:4:9
          |
        4 |     def my_property(self) -> int:
          |         -----------
        5 |         return 42
        6 |
        7 |     @my_property.setter
          |      -----------
        8 |     def my_property(self, value: int) -> None:
          |         -----------
        ");
    }

    /// Two different properties in the same class: renaming one should
    /// not affect the other.
    #[test]
    fn rename_property_no_cross_contamination() {
        let test = CursorTest::builder()
            .source(
                "lib.py",
                r#"
                class Foo:
                    @property
                    def alpha<CURSOR>(self) -> int:
                        return 1

                    @alpha.setter
                    def alpha(self, value: int) -> None:
                        pass

                    @property
                    def beta(self) -> str:
                        return "hello"

                    @beta.setter
                    def beta(self, value: str) -> None:
                        pass
                "#,
            )
            .build();

        assert_snapshot!(test.rename("gamma"), @"
        info[rename]: Rename symbol (found 3 locations)
         --> lib.py:4:9
          |
        4 |     def alpha(self) -> int:
          |         ^^^^^
        5 |         return 1
        6 |
        7 |     @alpha.setter
          |      -----
        8 |     def alpha(self, value: int) -> None:
          |         -----
        ");
    }

    /// A non-property decorator that happens to have a `.setter` attribute
    /// should not trigger property rename behavior.
    #[test]
    fn rename_non_property_setter_decorator() {
        let test = CursorTest::builder()
            .source(
                "lib.py",
                r#"
                class Registry:
                    def setter(self, func):
                        return func

                registry = Registry()

                def my_func<CURSOR>():
                    pass

                @my_func.setter
                def my_func():
                    pass
                "#,
            )
            .build();

        // Only 2 locations should be found (the definition and the decorator reference),
        // not the second `my_func` function definition, because `my_func` is not a property.
        assert_snapshot!(test.rename("better_name"), @"
        info[rename]: Rename symbol (found 2 locations)
          --> lib.py:8:5
           |
         8 | def my_func():
           |     ^^^^^^^
         9 |     pass
        10 |
        11 | @my_func.setter
           |  -------
        ");
    }

    /// When renaming the decorated function (not the one referenced in the decorator),
    /// we should only rename that function, not the one in the decorator.
    #[test]
    fn rename_non_property_setter_decorator_from_decorated() {
        let test = CursorTest::builder()
            .source(
                "lib.py",
                r#"
                class Registry:
                    def setter(self, func):
                        return func

                registry = Registry()

                def my_func():
                    pass

                @my_func.setter
                def my_func<CURSOR>():
                    pass
                "#,
            )
            .build();

        // The second `my_func` definition should be renamed. The decorator reference
        // is incorrectly included because `definitions_for_name` returns all bindings
        // for a symbol rather than using position-aware lookup (`bindings_at_use`).
        // Note: The first `def my_func` is correctly excluded since it has a distinct
        // `Definition` from the second. The decorator should also be excluded since it
        // semantically refers to the first definition, but fixing this requires using
        // position-aware binding resolution in `definitions_for_name`.
        assert_snapshot!(test.rename("better_name"), @"
        info[rename]: Rename symbol (found 2 locations)
          --> lib.py:12:5
           |
        11 | @my_func.setter
           |  -------
        12 | def my_func():
           |     ^^^^^^^
        ");
    }

    /// A property with differently-named getter and setter should not link them
    /// together during rename. This is an unusual pattern but valid Python.
    #[test]
    fn rename_property_with_different_setter_name() {
        let test = CursorTest::builder()
            .source(
                "lib.py",
                r#"
                class Foo:
                    @property
                    def my_getter<CURSOR>(self) -> int:
                        return 42

                    @my_getter.setter
                    def my_setter(self, value: int) -> None:
                        pass
                "#,
            )
            .build();

        // Only the getter should be renamed, not the setter (they have different names).
        assert_snapshot!(test.rename("new_name"), @"
        info[rename]: Rename symbol (found 2 locations)
         --> lib.py:4:9
          |
        4 |     def my_getter(self) -> int:
          |         ^^^^^^^^^
        5 |         return 42
        6 |
        7 |     @my_getter.setter
          |      ---------
        ");
    }

    #[test]
    fn rename_single_dispatch_function() {
        let test = CursorTest::builder()
            .source(
                "foo.py",
                r#"
                from functools import singledispatch

                @singledispatch
                def f<CURSOR>(x: object):
                    raise NotImplementedError

                @f.register
                def _(x: int) -> str:
                    return "int"

                @f.register
                def _(x: str) -> int:
                    return int(x)
                "#,
            )
            .build();

        assert_snapshot!(test.rename("better_name"), @r#"
        info[rename]: Rename symbol (found 3 locations)
          --> foo.py:5:5
           |
         5 | def f(x: object):
           |     ^
         6 |     raise NotImplementedError
         7 |
         8 | @f.register
           |  -
         9 | def _(x: int) -> str:
        10 |     return "int"
        11 |
        12 | @f.register
           |  -
        "#);
    }

    #[test]
    fn rename_single_dispatch_function_stacked_register() {
        let test = CursorTest::builder()
            .source(
                "foo.py",
                r#"
                from functools import singledispatch

                @singledispatch
                def f<CURSOR>(x):
                    raise NotImplementedError

                @f.register(int)
                @f.register(float)
                def _(x) -> float:
                    return "int"

                @f.register(str)
                def _(x) -> int:
                    return int(x)
                "#,
            )
            .build();

        assert_snapshot!(test.rename("better_name"), @r#"
        info[rename]: Rename symbol (found 4 locations)
          --> foo.py:5:5
           |
         5 | def f(x):
           |     ^
         6 |     raise NotImplementedError
         7 |
         8 | @f.register(int)
           |  -
         9 | @f.register(float)
           |  -
        10 | def _(x) -> float:
        11 |     return "int"
        12 |
        13 | @f.register(str)
           |  -
        "#);
    }

    #[test]
    fn rename_single_dispatchmethod() {
        let test = CursorTest::builder()
            .source(
                "foo.py",
                r#"
                from functools import singledispatchmethod

                class Foo:
                    @singledispatchmethod
                    def f<CURSOR>(self, x: object):
                        raise NotImplementedError

                    @f.register
                    def _(self, x: str) -> float:
                        return "int"

                    @f.register
                    def _(self, x: str) -> int:
                        return int(x)
                "#,
            )
            .build();

        assert_snapshot!(test.rename("better_name"), @r#"
        info[rename]: Rename symbol (found 3 locations)
          --> foo.py:6:9
           |
         6 |     def f(self, x: object):
           |         ^
         7 |         raise NotImplementedError
         8 |
         9 |     @f.register
           |      -
        10 |     def _(self, x: str) -> float:
        11 |         return "int"
        12 |
        13 |     @f.register
           |      -
        "#);
    }

    #[test]
    fn rename_single_dispatchmethod_staticmethod() {
        let test = CursorTest::builder()
            .source(
                "foo.py",
                r#"
                from functools import singledispatchmethod

                class Foo:
                    @singledispatchmethod
                    @staticmethod
                    def f<CURSOR>(self, x):
                        raise NotImplementedError

                    @f.register(str)
                    @staticmethod
                    def _(x: int) -> str:
                        return "int"

                    @f.register
                    @staticmethod
                    def _(x: str) -> int:
                        return int(x)
                "#,
            )
            .build();

        assert_snapshot!(test.rename("better_name"), @r#"
        info[rename]: Rename symbol (found 3 locations)
          --> foo.py:7:9
           |
         7 |     def f(self, x):
           |         ^
         8 |         raise NotImplementedError
         9 |
        10 |     @f.register(str)
           |      -
        11 |     @staticmethod
        12 |     def _(x: int) -> str:
        13 |         return "int"
        14 |
        15 |     @f.register
           |      -
        "#);
    }

    #[test]
    fn rename_single_dispatchmethod_classmethod() {
        let test = CursorTest::builder()
            .source(
                "foo.py",
                r#"
                from functools import singledispatchmethod

                class Foo:
                    @singledispatchmethod
                    @classmethod
                    def f<CURSOR>(cls, x):
                        raise NotImplementedError

                    @f.register(str)
                    @classmethod
                    def _(cls, x) -> str:
                        return "int"

                    @f.register(int)
                    @f.register(float)
                    @staticmethod
                    def _(cls, x) -> int:
                        return int(x)
                "#,
            )
            .build();

        assert_snapshot!(test.rename("better_name"), @r#"
        info[rename]: Rename symbol (found 4 locations)
          --> foo.py:7:9
           |
         7 |     def f(cls, x):
           |         ^
         8 |         raise NotImplementedError
         9 |
        10 |     @f.register(str)
           |      -
        11 |     @classmethod
        12 |     def _(cls, x) -> str:
        13 |         return "int"
        14 |
        15 |     @f.register(int)
           |      -
        16 |     @f.register(float)
           |      -
        "#);
    }

    #[test]
    fn rename_attribute() {
        let test = CursorTest::builder()
            .source(
                "foo.py",
                r#"
                class Test:
                    attribute<CURSOR>: str

                    def __init__(self, value: str):
                        self.attribute = value

                class Child(Test):
                    def test(self):
                        return self.attribute


                c = Child("test")

                print(c.attribute)
                c.attribute = "new_value"
                "#,
            )
            .build();

        assert_snapshot!(test.rename("better_name"), @r#"
        info[rename]: Rename symbol (found 5 locations)
          --> foo.py:3:5
           |
         3 |     attribute: str
           |     ^^^^^^^^^
         4 |
         5 |     def __init__(self, value: str):
         6 |         self.attribute = value
           |              ---------
         7 |
         8 | class Child(Test):
         9 |     def test(self):
        10 |         return self.attribute
           |                     ---------
           |
          ::: foo.py:15:9
           |
        15 | print(c.attribute)
           |         ---------
        16 | c.attribute = "new_value"
           |   ---------
        "#);
    }

    #[test]
    fn rename_property_from_assignment_usage() {
        let test = CursorTest::builder()
            .source(
                "lib.py",
                r#"
                class Foo:
                    @property
                    def my_property(self) -> int:
                        return 42

                    @my_property.setter
                    def my_property(self, value: int) -> None:
                        pass
                "#,
            )
            .source(
                "main.py",
                r#"
                from lib import Foo

                print(Foo().my_property)
                Foo().my_<CURSOR>property = 56
                "#,
            )
            .build();

        assert_snapshot!(test.rename("better_name"), @"
        info[rename]: Rename symbol (found 5 locations)
         --> main.py:4:13
          |
        4 | print(Foo().my_property)
          |             ^^^^^^^^^^^
        5 | Foo().my_property = 56
          |       -----------
          |
         ::: lib.py:4:9
          |
        4 |     def my_property(self) -> int:
          |         -----------
        5 |         return 42
        6 |
        7 |     @my_property.setter
          |      -----------
        8 |     def my_property(self, value: int) -> None:
          |         -----------
        ");
    }

    // TODO: This should rename all attribute usages
    // Note: Pylance only renames the assignment in `__init__`.
    #[test]
    fn rename_implicit_attribute() {
        let test = CursorTest::builder()
            .source(
                "main.py",
                r#"
                class Test:
                    def __init__(self, value: str):
                        self.<CURSOR>attribute = value

                class Child(Test):
                    def __init__(self, value: str):
                        super().__init__(value)
                        self.attribute = value + "child"

                    def test(self):
                        return self.attribute


                c = Child("test")

                print(c.attribute)
                c.attribute = "new_value"
                "#,
            )
            .build();

        assert_snapshot!(test.rename("better_name"), @"
        info[rename]: Rename symbol (found 1 locations)
         --> main.py:4:14
          |
        4 |         self.attribute = value
          |              ^^^^^^^^^
        ");
    }

    // TODO: Should not rename the first declaration
    #[test]
    fn rename_redeclarations() {
        let test = CursorTest::builder()
            .source(
                "main.py",
                r#"
                a: str = "test"

                a: int = 10

                print(a<CURSOR>)
                "#,
            )
            .build();

        assert_snapshot!(test.rename("better_name"), @r#"
        info[rename]: Rename symbol (found 3 locations)
         --> main.py:2:1
          |
        2 | a: str = "test"
          | ^
        3 |
        4 | a: int = 10
          | -
        5 |
        6 | print(a)
          |       -
        "#);
    }

    #[test]
    fn rename_attribute_updates_slots_tuple() {
        let test = cursor_test(
            r#"
class C:
    __slots__ = ("value", "other")

    def __init__(self):
        self.va<CURSOR>lue = 1
"#,
        );

        assert_snapshot!(test.rename("amount"), @r#"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:3:19
          |
        3 |     __slots__ = ("value", "other")
          |                   ^^^^^
        4 |
        5 |     def __init__(self):
        6 |         self.value = 1
          |              -----
        "#);
    }

    #[test]
    fn rename_attribute_updates_slots_list() {
        let test = cursor_test(
            r#"
class C:
    __slots__ = ["value", "other"]

    def __init__(self):
        self.va<CURSOR>lue = 1
"#,
        );

        assert_snapshot!(test.rename("amount"), @r#"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:3:19
          |
        3 |     __slots__ = ["value", "other"]
          |                   ^^^^^
        4 |
        5 |     def __init__(self):
        6 |         self.value = 1
          |              -----
        "#);
    }

    #[test]
    fn rename_attribute_updates_slots_set() {
        let test = cursor_test(
            r#"
class C:
    __slots__ = {"value", "other"}

    def __init__(self):
        self.va<CURSOR>lue = 1
"#,
        );

        assert_snapshot!(test.rename("amount"), @r#"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:3:19
          |
        3 |     __slots__ = {"value", "other"}
          |                   ^^^^^
        4 |
        5 |     def __init__(self):
        6 |         self.value = 1
          |              -----
        "#);
    }

    #[test]
    fn rename_attribute_updates_slots_dict_key() {
        let test = cursor_test(
            r#"
class C:
    __slots__ = {"value": "doc", "other": "doc"}

    def __init__(self):
        self.va<CURSOR>lue = 1
"#,
        );

        assert_snapshot!(test.rename("amount"), @r#"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:3:19
          |
        3 |     __slots__ = {"value": "doc", "other": "doc"}
          |                   ^^^^^
        4 |
        5 |     def __init__(self):
        6 |         self.value = 1
          |              -----
        "#);
    }

    #[test]
    fn rename_cannot_start_from_slot_string() {
        // Renaming is driven from the attribute; the `__slots__` string itself
        // is not a renameable symbol, so starting a rename on it is rejected.
        let test = cursor_test(
            r#"
class C:
    __slots__ = ("va<CURSOR>lue",)

    def __init__(self):
        self.value = 1
"#,
        );

        assert_snapshot!(test.rename("amount"), @"Cannot rename");
    }

    #[test]
    fn rename_attribute_does_not_touch_other_class_slots() {
        let test = cursor_test(
            r#"
class C:
    __slots__ = ("value",)

    def __init__(self):
        self.va<CURSOR>lue = 1

class D:
    __slots__ = ("value",)
"#,
        );

        assert_snapshot!(test.rename("amount"), @r#"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:3:19
          |
        3 |     __slots__ = ("value",)
          |                   ^^^^^
        4 |
        5 |     def __init__(self):
        6 |         self.value = 1
          |              -----
        "#);
    }

    #[test]
    fn rename_attribute_does_not_touch_slots_dict_value() {
        let test = cursor_test(
            r#"
class C:
    __slots__ = {"value": "value"}

    def __init__(self):
        self.va<CURSOR>lue = 1
"#,
        );

        assert_snapshot!(test.rename("amount"), @r#"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:3:19
          |
        3 |     __slots__ = {"value": "value"}
          |                   ^^^^^
        4 |
        5 |     def __init__(self):
        6 |         self.value = 1
          |              -----
        "#);
    }

    #[test]
    fn rename_class_body_annotation_updates_slots() {
        let test = cursor_test(
            r#"
class C:
    __slots__ = ("value",)
    va<CURSOR>lue: int
"#,
        );

        assert_snapshot!(test.rename("amount"), @r#"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:3:19
          |
        3 |     __slots__ = ("value",)
          |                   ^^^^^
        4 |     value: int
          |     -----
        "#);
    }

    #[test]
    fn rename_attribute_ignores_slots_annotation_container() {
        let test = cursor_test(
            r#"
class C:
    __slots__: ("value",) = ("other",)

    def __init__(self):
        self.va<CURSOR>lue = 1
"#,
        );

        assert_snapshot!(test.rename("amount"), @"
        info[rename]: Rename symbol (found 1 locations)
         --> main.py:6:14
          |
        6 |         self.value = 1
          |              ^^^^^
        ");
    }

    #[test]
    fn rename_stub_ellipsis_valued_attribute_updates_slots() {
        let test = CursorTest::builder()
            .source(
                "main.pyi",
                r#"
class C:
    __slots__ = ("value",)
    va<CURSOR>lue: int = ...
"#,
            )
            .build();

        assert_snapshot!(test.rename("amount"), @r#"
        info[rename]: Rename symbol (found 2 locations)
         --> main.pyi:3:19
          |
        3 |     __slots__ = ("value",)
          |                   ^^^^^
        4 |     value: int = ...
          |     -----
        "#);
    }

    #[test]
    fn rename_parameter_does_not_touch_slots() {
        // The parameter `value` is a local symbol, not the instance attribute, so renaming it must
        // leave the `__slots__` string untouched even though it shares the name.
        let test = cursor_test(
            r#"
class C:
    __slots__ = ("value",)

    def __init__(self, va<CURSOR>lue):
        self.value = value
"#,
        );

        assert_snapshot!(test.rename("amount"), @r#"
        info[rename]: Rename symbol (found 2 locations)
         --> main.py:5:24
          |
        5 |     def __init__(self, value):
          |                        ^^^^^
        6 |         self.value = value
          |                      -----
        "#);
    }

    #[test]
    fn rename_nested_class_attribute_does_not_touch_outer_slots() {
        // The instance attribute belongs to `Inner`, whose nearest enclosing class has no matching
        // slot, so renaming it must not touch `Outer.__slots__`.
        let test = cursor_test(
            r#"
class Outer:
    __slots__ = ("value",)

    class Inner:
        def __init__(self):
            self.va<CURSOR>lue = 1
"#,
        );

        assert_snapshot!(test.rename("amount"), @r#"
        info[rename]: Rename symbol (found 1 locations)
         --> main.py:7:18
          |
        7 |             self.value = 1
          |                  ^^^^^
        "#);
    }
}
