// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2025 The Gleam contributors

use std::collections::HashMap;

use lsp_types::{
    Position, PrepareRenameParams, PrepareRenamePlaceholder, Range, RenameParams,
    TextDocumentPositionParams, Uri as Url, WorkDoneProgressParams, WorkspaceEdit,
};

use crate::url_from_path;

use super::{TestProject, find_position_of, hover};

/// Returns the rename range and edit to apply if the rename is valid and can be
/// carried out.
/// However if the rename produces an error response from the language server,
/// the error message is returned.
fn rename(
    tester: &TestProject<'_>,
    new_name: &str,
    position: Position,
) -> Result<Option<(Range, WorkspaceEdit)>, String> {
    let prepare_rename_response = tester.at(position, |engine, params, _| {
        let params = PrepareRenameParams {
            text_document_position_params: TextDocumentPositionParams {
                text_document: params.text_document,
                position,
            },
            work_done_progress_params: WorkDoneProgressParams {
                work_done_token: None,
            },
        };
        engine.prepare_rename(params).result.unwrap()
    });

    let range = match prepare_rename_response {
        Some(lsp_types::PrepareRenameResult::Range(range)) => range,
        Some(lsp_types::PrepareRenameResult::PrepareRenamePlaceholder(
            PrepareRenamePlaceholder { range, .. },
        )) => range,
        _ => return Ok(None),
    };

    let outcome = tester.at(position, |engine, params, _| {
        let params = RenameParams {
            text_document_position_params: TextDocumentPositionParams {
                text_document: params.text_document,
                position,
            },
            new_name: new_name.to_string(),
            work_done_progress_params: WorkDoneProgressParams::default(),
        };
        engine.rename(params).result.unwrap()
    });

    match outcome {
        Ok(Some(edit)) => Ok(Some((range, edit))),
        Ok(None) => Ok(None),
        Err(error) => Err(error.message),
    }
}

fn rename_files(tester: &TestProject<'_>, renames: &[(&str, &str)]) -> HashMap<String, String> {
    let edit = tester.run(|engine| {
        let params = renames
            .iter()
            .map(|(old_name, new_name)| {
                let old_url =
                    url_from_path(engine.path_for_module_name(old_name).as_str()).unwrap();
                let new_url =
                    url_from_path(engine.path_for_module_name(new_name).as_str()).unwrap();
                (old_url, new_url)
            })
            .collect();
        engine.rename_files(params).result.unwrap()
    });

    let changes = edit
        .expect("No text edit found")
        .changes
        .expect("No text edit found");

    apply_code_edit(tester, changes)
}

fn apply_rename(
    tester: &TestProject<'_>,
    new_name: &str,
    position: Position,
) -> (Range, HashMap<String, String>) {
    let (range, edit) = rename(tester, new_name, position)
        .expect("Rename failed")
        .expect("No rename produced");
    let changes = edit.changes.expect("No text edit found");
    (range, apply_code_edit(tester, changes))
}

fn apply_code_edit(
    tester: &TestProject<'_>,
    changes: HashMap<Url, Vec<lsp_types::TextEdit>>,
) -> HashMap<String, String> {
    let mut modules = HashMap::new();
    for (uri, change) in changes {
        let module_name = tester.module_name_from_url(&uri).expect("Valid uri");
        let module_code = tester.src_from_module_url(&uri).expect("Module exists");

        _ = modules.insert(module_name, super::apply_code_edit(module_code, change));
    }

    modules
}

fn display_result(
    project: &TestProject<'_>,
    modules: HashMap<String, String>,
    renamed_modules: HashMap<&str, &str>,
    range: Option<Range>,
) -> String {
    let mut output = String::from("----- BEFORE RENAME\n");
    for (name, src) in project.root_package_modules.iter() {
        output.push_str(&format!("-- {name}.gleam\n{src}\n\n"));
    }

    let src = project.src;
    let app_src_before = if let Some(range) = range {
        hover::show_hover(src, range, range.start)
    } else {
        src.to_string()
    };
    output.push_str(&format!(
        "-- app.gleam\n{app_src_before}\n\n----- AFTER RENAME\n",
    ));

    for &(name, src) in project.root_package_modules.iter() {
        let used_name = if let Some(new_name) = renamed_modules.get(name) {
            new_name
        } else {
            name
        };
        output.push_str(&format!(
            "-- {used_name}.gleam\n{}\n\n",
            modules
                .get(name)
                .map(|string| string.as_str())
                .unwrap_or(src)
        ));
    }
    output.push_str(&format!(
        "-- app.gleam\n{}",
        modules
            .get("app")
            .map(|string| string.as_str())
            .unwrap_or(src)
    ));
    output
}

macro_rules! assert_rename {
    ($code:literal, $new_name:literal, $position:expr $(,)?) => {
        assert_rename!(TestProject::for_source($code), $new_name, $position);
    };

    (($module_name:literal, $module_src:literal), $code:literal, $new_name:literal, $position:expr $(,)?) => {
        assert_rename!(
            TestProject::for_source($code).add_module($module_name, $module_src),
            $new_name,
            $position
        );
    };

    ($project:expr, $new_name:literal, $position:expr $(,)?) => {
        let project = $project;
        let src = project.src;
        let position = $position.find_position(src);
        let (range, result) = apply_rename(&project, $new_name, position);

        let output = display_result(&project, result, HashMap::new(), Some(range));

        insta::assert_snapshot!(insta::internals::AutoName, output, src);
    };
}

macro_rules! assert_no_rename {
    ($code:literal, $new_name:literal, $position:expr $(,)?) => {
        let project = TestProject::for_source($code);
        assert_no_rename!(&project, $new_name, $position);
    };

    ($project:expr, $new_name:literal, $position:expr $(,)?) => {
        let src = $project.src;
        let position = $position.find_position(src);
        let result = rename($project, $new_name, position);
        assert_eq!(result, Ok(None));
    };
}

macro_rules! assert_rename_error {
    ($code:literal, $new_name:literal, $position:expr $(,)?) => {
        let project = TestProject::for_source($code);
        assert_rename_error!(&project, $new_name, $position);
    };

    ($project:expr, $new_name:literal, $position:expr $(,)?) => {
        let src = $project.src;
        let position = $position.find_position(src);
        let error = rename($project, $new_name, position).unwrap_err();
        let snapshot = format!("Error response message:\n\n{error}");
        insta::assert_snapshot!(insta::internals::AutoName, snapshot, src);
    };
}

macro_rules! assert_rename_files {
    ($(($old_name:literal, $new_name:literal, $module_src:literal)),+, $code:literal, $(,)?) => {
        let project = TestProject::for_source($code)$(.add_module($old_name, $module_src))*;

        let renames = [$(($old_name, $new_name),)*];
        let result = rename_files(&project, &renames);
        let output = display_result(&project, result, renames.into(), None);

        insta::assert_snapshot!(insta::internals::AutoName, output, project.src);
    };
}

#[test]
fn rename_local_variable() {
    assert_rename!(
        "
pub fn main() {
  let wibble = 10
  wibble
}
",
        "wobble",
        find_position_of("wibble").nth_occurrence(2),
    );
}

#[test]
fn rename_shadowed_local_variable() {
    assert_rename!(
        "
pub fn main() {
  let wibble = 10
  let wibble = wibble / 2
  wibble
}
",
        "wobble",
        find_position_of("wibble /"),
    );
}

#[test]
fn rename_shadowing_local_variable() {
    assert_rename!(
        "
pub fn main() {
  let wibble = 10
  let wibble = wibble / 2
  wibble
}
",
        "wobble",
        find_position_of("wibble").nth_occurrence(4),
    );
}

#[test]
fn rename_local_variable_record_access() {
    assert_rename!(
        "
type Wibble {
  Wibble(wibble: Int)
}

pub fn main() {
  let wibble = Wibble(wibble: 1)
  wibble.wibble
}
",
        "wobble",
        find_position_of("wibble."),
    );
}
#[test]
fn rename_local_variable_guard_clause() {
    assert_rename!(
        "
pub fn main() {
  let wibble = True
  case Nil {
    Nil if wibble -> todo
    _ -> panic
  }
  wibble || False
}
",
        "wobble",
        find_position_of("wibble ||"),
    );
}

#[test]
fn rename_local_variable_from_definition() {
    assert_rename!(
        "
pub fn main() {
  let wibble = 10
  let wobble = wibble + 1
  wobble - wibble
}
",
        "some_value",
        find_position_of("wibble =")
    );
}

#[test]
fn rename_local_variable_from_definition_nested_pattern() {
    assert_rename!(
        "
pub fn main() {
  let assert Ok([_, wibble, ..]) = Error(12)
  wibble
}
",
        "second_element",
        find_position_of("wibble,")
    );
}

#[test]
fn rename_local_variable_assignment_pattern() {
    assert_rename!(
        "
pub fn main() {
  let assert Error(12 as something) = Error(12)
  something
}
",
        "the_error",
        find_position_of("something").nth_occurrence(2)
    );
}

#[test]
fn rename_local_variable_from_definition_assignment_pattern() {
    assert_rename!(
        "
pub fn main() {
  let assert Error(12 as something) = Error(12)
  something
}
",
        "the_error",
        find_position_of("something)")
    );
}

#[test]
fn rename_local_variable_argument() {
    assert_rename!(
        "
pub fn add(first_number: Int, x: Int) -> Int {
  x + first_number
}
",
        "second_number",
        find_position_of("x +")
    );
}

#[test]
fn rename_local_variable_argument_from_definition() {
    assert_rename!(
        "
pub fn wibble(wibble: Float) {
  wibble /. 0.3
}
",
        "wobble",
        find_position_of("wibble:")
    );
}

#[test]
fn rename_local_variable_label_shorthand() {
    assert_rename!(
        "
type Wibble {
  Wibble(wibble: Int)
}

pub fn main() {
  let Wibble(wibble:) = todo
  wibble + 1
}
",
        "wobble",
        find_position_of("wibble +")
    );
}

#[test]
fn rename_local_variable_label_shorthand_from_definition() {
    assert_rename!(
        "
type Wibble {
  Wibble(wibble: Int)
}

pub fn main() {
  let Wibble(wibble:) = todo
  wibble + 1
}
",
        "wobble",
        find_position_of("wibble:)")
    );
}

#[test]
fn rename_local_variable_from_label_shorthand() {
    assert_rename!(
        "
type Wibble {
  Wibble(wibble: Int)
}

pub fn main() {
  let wibble = todo
  Wibble(wibble:)
}
",
        "wobble",
        find_position_of("wibble:)")
    );
}

#[test]
fn rename_local_variable_in_bit_array_pattern() {
    assert_rename!(
        "
pub fn starts_with(bits: BitArray, prefix: BitArray) -> Bool {
  let prefix_size = bit_size(prefix)

  case bits {
    <<pref:bits-size(prefix_size), _:bits>> if pref == prefix -> True
    _ -> False
  }
}
",
        "size_of_prefix",
        find_position_of("prefix_size =")
    );
}

#[test]
fn rename_local_variable_from_bit_array_pattern() {
    assert_rename!(
        "
pub fn starts_with(bits: BitArray, prefix: BitArray) -> Bool {
  let prefix_size = bit_size(prefix)

  case bits {
    <<pref:bits-size(prefix_size), _:bits>> if pref == prefix -> True
    _ -> False
  }
}
",
        "size_of_prefix",
        find_position_of("prefix_size)")
    );
}

#[test]
fn no_rename_keyword() {
    assert_no_rename!(
        "
pub fn main() {}
",
        "wibble",
        find_position_of("fn"),
    );
}

#[test]
fn no_rename_invalid_name() {
    assert_rename_error!(
        "
pub fn main() {
  let wibble = 10
  wibble
}
",
        "Not_AValid_Name",
        find_position_of("wibble").nth_occurrence(2)
    );
}

#[test]
fn rename_function_from_definition() {
    assert_rename!(
        (
            "mod",
            "
import app

fn wibble() {
  app.something()
}
"
        ),
        "
pub fn something() {
  something()
}

fn something_else() {
  something()
}
",
        "some_function",
        find_position_of("something")
    );
}

#[test]
fn rename_function_from_reference() {
    assert_rename!(
        (
            "mod",
            "
import app

fn wibble() {
  app.something()
}
"
        ),
        "
pub fn something() {
  something()
}

fn something_else() {
  something()
}
",
        "some_function",
        find_position_of("something").nth_occurrence(2)
    );
}

#[test]
fn rename_function_from_qualified_reference() {
    assert_rename!(
        (
            "mod",
            "
pub fn wibble() {
  wibble()
}
"
        ),
        "
import mod

pub fn main() {
  mod.wibble()
}
",
        "some_function",
        find_position_of("wibble")
    );
}

#[test]
fn rename_function_from_unqualified_reference() {
    assert_rename!(
        (
            "mod",
            "
pub fn wibble() {
  wibble()
}
"
        ),
        "
import mod.{wibble}

pub fn main() {
  wibble()
  mod.wibble()
}
",
        "some_function",
        find_position_of("wibble(")
    );
}

#[test]
fn rename_function_from_import() {
    assert_rename!(
        (
            "mod",
            "
pub fn wibble() {
  wibble()
}
"
        ),
        "
import mod.{wibble}

pub fn main() {
  wibble()
  mod.wibble()
}
",
        "some_function",
        find_position_of("wibble")
    );
}

#[test]
fn rename_function_from_aliased_import() {
    assert_rename!(
        (
            "mod",
            "
pub fn wibble() {
  wibble()
}
"
        ),
        "
import mod.{wibble as wobble}

pub fn main() {
  wobble()
  mod.wibble()
}
",
        "some_function",
        find_position_of("wobble")
    );
}

#[test]
fn rename_function_from_aliased_import_to_original_name() {
    assert_rename!(
        (
            "mod",
            "
pub fn wibble() {
  wibble()
}
"
        ),
        "
import mod.{wibble as wobble}

pub fn main() {
  wobble()
  mod.wibble()
}
",
        "wibble",
        find_position_of("wobble")
    );
}

#[test]
fn rename_aliased_function() {
    assert_rename!(
        (
            "mod",
            "
import app.{something as something_else}

fn wibble() {
  something_else()
}
"
        ),
        "
pub fn something() {
  something()
}

fn something_else() {
  something()
}
",
        "some_function",
        find_position_of("something")
    );
}

#[test]
fn rename_function_shadowing_module() {
    let src = "
import gleam/list

pub fn list() {
  []
}

pub fn main() {
  list.map(todo, todo)
}
    ";

    assert_rename!(
        TestProject::for_source(src).add_hex_module("gleam/list", "pub fn map(_, _) {}"),
        "empty_list",
        find_position_of("list()")
    );
}

#[test]
fn rename_function_shadowed_by_field_access() {
    assert_rename!(
        (
            "mod",
            "
import app

type App {
  App(something: Int)
}

pub fn main() {
  let app = App(10)
  app.something
}
"
        ),
        "
pub fn something() {
  todo
}
",
        "function",
        find_position_of("something")
    );
}

#[test]
fn no_rename_function_with_invalid_name() {
    assert_rename_error!(
        "
pub fn main() {
  let wibble = 10
  wibble
}
",
        "Not_AValid_Name",
        find_position_of("main")
    );
}

#[test]
fn no_rename_function_from_other_package() {
    let src = "
import wibble

pub fn main() {
  wibble.wobble()
}
";

    assert_no_rename!(
        &TestProject::for_source(src).add_hex_module("wibble", "pub fn wobble() { todo }"),
        "something",
        find_position_of("wobble")
    );
}

#[test]
fn rename_constant_from_definition() {
    assert_rename!(
        (
            "mod",
            "
import app

fn wibble() {
  app.something
}
"
        ),
        "
pub const something = 10

pub fn main() {
  something + { 4 * something }
}
",
        "ten",
        find_position_of("something")
    );
}

#[test]
fn rename_constant_from_reference() {
    assert_rename!(
        (
            "mod",
            "
import app

fn wibble() {
  app.something
}
"
        ),
        "
pub const something = 10

pub fn main() {
  something + { 4 * something }
}
",
        "ten",
        find_position_of("something").nth_occurrence(2)
    );
}

#[test]
fn rename_constant_from_qualified_reference() {
    assert_rename!(
        (
            "mod",
            "
pub const something = 10

fn wibble() {
  something
}
"
        ),
        "
import mod

pub fn main() {
  mod.something
}
",
        "ten",
        find_position_of("something")
    );
}

#[test]
fn rename_constant_from_unqualified_reference() {
    assert_rename!(
        (
            "mod",
            "
pub const something = 10

fn wibble() {
  something
}
"
        ),
        "
import mod.{something}

pub fn main() {
  something + mod.something
}
",
        "ten",
        find_position_of("something +")
    );
}

#[test]
fn rename_constant_from_import() {
    assert_rename!(
        (
            "mod",
            "
pub const something = 10

fn wibble() {
  something
}
"
        ),
        "
import mod.{something}

pub fn main() {
  something + mod.something
}
",
        "ten",
        find_position_of("something")
    );
}

#[test]
fn rename_constant_from_aliased_import() {
    assert_rename!(
        (
            "mod",
            "
pub const something = 10

fn wibble() {
  something
}
"
        ),
        "
import mod.{something as smth}

pub fn main() {
  smth + mod.something
}
",
        "ten",
        find_position_of("smth")
    );
}

#[test]
fn rename_constant_from_aliased_import_to_original_name() {
    assert_rename!(
        (
            "mod",
            "
pub const something = 10

fn wibble() {
  something
}
"
        ),
        "
import mod.{something as smth}

pub fn main() {
  smth + mod.something
}
",
        "something",
        find_position_of("smth")
    );
}

#[test]
fn rename_aliased_constant() {
    assert_rename!(
        (
            "mod",
            "
import app.{something as some_constant}

fn wibble() {
  some_constant
}
"
        ),
        "
pub const something = 10

pub fn main() {
  something + { 4 * something }
}
",
        "ten",
        find_position_of("something")
    );
}

#[test]
fn rename_constant_shadowing_module() {
    let src = "
import gleam/list

const list = []

pub fn main() {
  list.map(todo, todo)
}
    ";

    assert_rename!(
        TestProject::for_source(src).add_hex_module("gleam/list", "pub fn map(_, _) {}"),
        "empty_list",
        find_position_of("list =")
    );
}

#[test]
fn rename_constant_shadowed_by_field_access() {
    assert_rename!(
        (
            "mod",
            "
import app

type App {
  App(something: Int)
}

pub fn main() {
  let app = App(10)
  app.something
}
"
        ),
        "
pub const something = 10
",
        "constant",
        find_position_of("something")
    );
}

#[test]
fn no_rename_constant_with_invalid_name() {
    assert_rename_error!(
        "
const value = 10
",
        "Ten",
        find_position_of("value")
    );
}

#[test]
fn no_rename_constant_from_other_package() {
    let src = "
import wibble

pub fn main() {
  wibble.wobble
}
";

    assert_no_rename!(
        &TestProject::for_source(src).add_hex_module("wibble", "pub const wobble = 2"),
        "something",
        find_position_of("wobble")
    );
}

#[test]
fn rename_type_variant_from_definition() {
    assert_rename!(
        (
            "mod",
            "
import app

fn wibble() {
  app.Constructor(4)
}
"
        ),
        "
pub type Wibble {
  Constructor(Int)
}

pub fn main() {
  Constructor(10)
}
",
        "Wibble",
        find_position_of("Constructor(Int")
    );
}

#[test]
fn rename_type_variant_from_reference() {
    assert_rename!(
        (
            "mod",
            "
import app

fn wibble() {
  app.Constructor(4)
}
"
        ),
        "
pub type Wibble {
  Constructor(Int)
}

pub fn main() {
  Constructor(10)
}
",
        "Wibble",
        find_position_of("Constructor(10")
    );
}

#[test]
fn rename_type_variant_from_qualified_reference() {
    assert_rename!(
        (
            "mod",
            "
pub type Wibble {
  Constructor(Int)
}

fn wibble() {
  Constructor(42)
}
"
        ),
        "
import mod

pub fn main() {
  mod.Constructor
}
",
        "Variant",
        find_position_of("Constructor")
    );
}

#[test]
fn rename_type_variant_from_unqualified_reference() {
    assert_rename!(
        (
            "mod",
            "
pub type Wibble {
  Constructor(Int)
}

fn wibble() {
  Constructor(81)
}
"
        ),
        "
import mod.{Constructor}

pub fn main() {
  #(Constructor(75), mod.Constructor(57))
}
",
        "Number",
        find_position_of("Constructor(75")
    );
}

#[test]
fn rename_type_variant_from_import() {
    assert_rename!(
        (
            "mod",
            "
pub type Wibble {
  Constructor(Int)
}

fn wibble() {
  Constructor(81)
}
"
        ),
        "
import mod.{Constructor}

pub fn main() {
  #(Constructor(75), mod.Constructor(57))
}
",
        "Number",
        find_position_of("Constructor")
    );
}

#[test]
fn rename_type_variant_from_aliased_import() {
    assert_rename!(
        (
            "mod",
            "
pub type Wibble {
  Constructor(Int)
}

fn wibble() {
  Constructor(81)
}
"
        ),
        "
import mod.{Constructor as C}

pub fn main() {
  #(C(75), mod.Constructor(57))
}
",
        "Number",
        find_position_of("C")
    );
}

#[test]
fn rename_type_variant_from_aliased_import_to_original_name() {
    assert_rename!(
        (
            "mod",
            "
pub type Wibble {
  Constructor(Int)
}

fn wibble() {
  Constructor(81)
}
"
        ),
        "
import mod.{Constructor as C}

pub fn main() {
  #(C(75), mod.Constructor(57))
}
",
        "Constructor",
        find_position_of("C")
    );
}

#[test]
fn rename_aliased_type_variant() {
    assert_rename!(
        (
            "mod",
            "
import app.{Constructor as ValueConstructor}

fn wibble() {
  ValueConstructor(172)
}
"
        ),
        "
pub type Wibble {
  Constructor(Int)
}

pub fn main() {
  Constructor(42)
}
",
        "MakeAWibble",
        find_position_of("Constructor")
    );
}

#[test]
fn no_rename_type_variant_with_invalid_name() {
    assert_rename_error!(
        "
pub type Wibble {
  Constructor(Int)
}
",
        "name_in_snake_case",
        find_position_of("Constructor")
    );
}

#[test]
fn rename_custom_type_variant_pattern() {
    assert_rename!(
        "
pub type Type {
  X
  Y
}

pub fn main(t) {
  case t {
    X -> 0
    Y -> 0
  }
}
",
        "Renamed",
        find_position_of("X")
    );
}

#[test]
fn rename_imported_custom_type_variant_pattern() {
    assert_rename!(
        (
            "other",
            "
import app

pub fn main(t) {
  case t {
    app.X -> 0
    app.Y -> 0
  }
}
"
        ),
        "
pub type Type {
  X
  Y
}
",
        "Renamed",
        find_position_of("X")
    );
}

#[test]
fn rename_imported_unqualified_custom_type_variant_pattern() {
    assert_rename!(
        (
            "other",
            "
import app.{X, Y}

pub fn main(t) {
  case t {
    X -> 0
    Y -> 0
  }
}
"
        ),
        "
pub type Type {
  X
  Y
}
",
        "Renamed",
        find_position_of("X")
    );
}

#[test]
fn rename_type_variant_pattern_with_arguments() {
    assert_rename!(
        "
pub type Wibble {
  Wibble(Int)
  Wobble(Float)
}

fn wibble() {
  case Wibble(10) {
    Wibble(20) -> todo
    Wibble(_) -> panic
  }
}
",
        "Variant",
        find_position_of("Wibble(10)")
    );
}

#[test]
fn rename_type_variant_from_pattern() {
    assert_rename!(
        "
pub type Type {
  X
  Y
}

pub fn main(t) {
  case t {
    X -> 0
    Y -> 0
  }
}
",
        "Renamed",
        find_position_of("X ->")
    );
}

#[test]
fn no_rename_type_variant_from_other_package() {
    let src = "
import wibble

pub fn main() {
  wibble.Wibble(10)
}
";

    assert_no_rename!(
        &TestProject::for_source(src).add_hex_module("wibble", "pub type Wibble { Wibble(Int) }"),
        "Constructor",
        find_position_of("Wibble")
    );
}

#[test]
fn rename_value_in_nested_module() {
    assert_rename!(
        (
            "sub/mod",
            "
pub fn wibble() {
  wibble()
}
"
        ),
        "
import sub/mod

pub fn main() {
  mod.wibble()
}
",
        "some_function",
        find_position_of("wibble")
    );
}

#[test]
fn rename_value_in_aliased_module() {
    assert_rename!(
        (
            "mod",
            "
pub fn wibble() {
  wibble()
}
"
        ),
        "
import mod as the_module

pub fn main() {
  the_module.wibble()
}
",
        "some_function",
        find_position_of("wibble")
    );
}

#[test]
fn rename_aliased_value() {
    assert_rename!(
        (
            "mod",
            "
import app.{Wibble as Wobble}

fn wobble() {
  Wobble
}
"
        ),
        "
pub type Wibble { Wibble }

pub fn main() {
  Wibble
}
",
        "Wubble",
        find_position_of("Wibble }")
    );
}

#[test]
fn rename_type_from_definition() {
    assert_rename!(
        (
            "mod",
            "
import app

fn wibble() -> app.Wibble { todo }
"
        ),
        "
pub type Wibble { Constructor }

pub fn main(w: Wibble) -> Wibble { todo }
",
        "SomeType",
        find_position_of("Wibble")
    );
}

#[test]
fn rename_type_from_reference() {
    assert_rename!(
        (
            "mod",
            "
import app

fn wibble() -> app.Wibble { todo }
"
        ),
        "
pub type Wibble { Constructor }

pub fn main(w: Wibble) -> Wibble { todo }
",
        "SomeType",
        find_position_of("Wibble").nth_occurrence(2)
    );
}

#[test]
fn rename_type_from_qualified_reference() {
    assert_rename!(
        (
            "mod",
            "
pub type Wibble { Constructor }

fn wibble(w: Wibble) -> Wibble { todo }
"
        ),
        "
import mod

pub fn main(w: mod.Wibble) -> mod.Wibble { todo }
",
        "SomeType",
        find_position_of("Wibble")
    );
}

#[test]
fn rename_type_from_unqualified_reference() {
    assert_rename!(
        (
            "mod",
            "
pub type Wibble { Constructor }

fn wibble(w: Wibble) -> Wibble { todo }
"
        ),
        "
import mod.{type Wibble}

pub fn main(w: Wibble) -> mod.Wibble { todo }
",
        "SomeType",
        find_position_of("Wibble)")
    );
}

#[test]
fn rename_type_from_import() {
    assert_rename!(
        (
            "mod",
            "
pub type Wibble { Constructor }

fn wibble(w: Wibble) -> Wibble { todo }
"
        ),
        "
import mod.{type Wibble}

pub fn main(w: Wibble) -> mod.Wibble { todo }
",
        "SomeType",
        find_position_of("Wibble")
    );
}

#[test]
fn rename_type_from_aliased_import() {
    assert_rename!(
        (
            "mod",
            "
pub type Wibble { Constructor }

fn wibble(w: Wibble) -> Wibble { todo }
"
        ),
        "
import mod.{type Wibble as Wobble}

pub fn main(w: Wobble) -> mod.Wibble { todo }
",
        "SomeType",
        find_position_of("Wobble")
    );
}

#[test]
fn rename_type_from_aliased_import_to_original_name() {
    assert_rename!(
        (
            "mod",
            "
pub type Wibble { Constructor }

fn wibble(w: Wibble) -> Wibble { todo }
"
        ),
        "
import mod.{type Wibble as Wobble}

pub fn main(w: Wobble) -> mod.Wibble { todo }
",
        "Wibble",
        find_position_of("Wobble")
    );
}

#[test]
fn rename_aliased_type() {
    assert_rename!(
        (
            "mod",
            "
import app.{type Wibble as Wobble}

fn wibble() -> Wobble { todo }
"
        ),
        "
pub type Wibble { Constructor }

pub fn main(w: Wibble) -> Wibble { todo }
",
        "SomeType",
        find_position_of("Wibble")
    );
}

#[test]
fn no_rename_type_with_invalid_name() {
    assert_rename_error!(
        "
type Wibble { Wobble }
",
        "a_type_name",
        find_position_of("Wibble")
    );
}

#[test]
fn no_rename_type_from_other_package() {
    let src = "
import wibble

pub fn main() -> wibble.Wibble { todo }
";

    assert_no_rename!(
        &TestProject::for_source(src).add_hex_module("wibble", "pub type Wibble { Wibble }"),
        "SomeType",
        find_position_of("Wibble")
    );
}

// https://github.com/gleam-lang/gleam/issues/4372
#[test]
fn rename_type_referenced_in_variant_constructor_argument() {
    assert_rename!(
        (
            "mod",
            "
import app

pub type Wobble {
  Wobble(w: app.Wibble)
}
"
        ),
        "
pub type Wibble {
  Wibble
}

pub fn main() {
  let wibble = Wibble
}
",
        "SomeType",
        find_position_of("Wibble")
    );
}

// https://github.com/gleam-lang/gleam/issues/4372
#[test]
fn rename_type_from_variant_constructor_argument() {
    assert_rename!(
        (
            "mod",
            "
pub type Wibble {
  Wibble
}

pub fn main() {
  let wibble = Wibble
}
"
        ),
        "
import mod

pub type Wobble {
  Wobble(w: mod.Wibble)
}
",
        "SomeType",
        find_position_of("Wibble")
    );
}

// https://github.com/gleam-lang/gleam/issues/4553
#[test]
fn rename_local_variable_with_label_shorthand() {
    assert_rename!(
        "
pub type Wibble {
  Wibble(first: Int, second: Int)
}

pub fn main() {
  let second = 2
  Wibble(first: 1, second:)
}
",
        "something",
        find_position_of("second =")
    );
}

// https://github.com/gleam-lang/gleam/issues/4748
#[test]
fn rename_alternative_pattern() {
    assert_rename!(
        "
pub fn main(x) {
  case x {
    #(wibble, [wobble]) | #(wobble, [wibble, _]) | #(_, [wibble, wobble, ..]) ->
      wibble + wobble
    _ -> 0
  }
}
",
        "new_name",
        find_position_of("wibble")
    );
}

// https://github.com/gleam-lang/gleam/issues/5091
#[test]
fn rename_alternative_pattern_aliases() {
    assert_rename!(
        "
pub fn main(x) {
  case x {
    [] as list | [_] as list -> list
    _ -> []
  }
}
",
        "new_name",
        find_position_of("list")
    );
}

#[test]
fn rename_alternative_pattern_aliases_from_alternative() {
    assert_rename!(
        "
pub fn main(x) {
  case x {
    [] as list | [_] as list -> list
    _ -> []
  }
}
",
        "new_name",
        find_position_of("list").nth_occurrence(2)
    );
}

#[test]
fn rename_alternative_pattern_aliases_from_usage() {
    assert_rename!(
        "
pub fn main(x) {
  case x {
    [] as list | [_] as list -> list
    _ -> []
  }
}
",
        "new_name",
        find_position_of("list").nth_occurrence(3)
    );
}

#[test]
fn rename_alternative_pattern_alias_and_variable_1() {
    assert_rename!(
        "
pub fn main(x) {
  case x {
    [] as list | [_, ..list] -> list
    _ -> []
  }
}
",
        "new_name",
        find_position_of("list").nth_occurrence(1)
    );
}

#[test]
fn rename_alternative_pattern_alias_and_variable_2() {
    assert_rename!(
        "
pub fn main(x) {
  case x {
    [] as list | [_, ..list] -> list
    _ -> []
  }
}
",
        "new_name",
        find_position_of("list").nth_occurrence(2)
    );
}

#[test]
fn rename_alternative_pattern_alias_and_variable_3() {
    assert_rename!(
        "
pub fn main(x) {
  case x {
    [_, ..list] | [] as list -> list
    _ -> []
  }
}
",
        "new_name",
        find_position_of("list").nth_occurrence(1)
    );
}

#[test]
fn rename_alternative_pattern_alias_and_variable_4() {
    assert_rename!(
        "
pub fn main(x) {
  case x {
    [_, ..list] | [] as list -> list
    _ -> []
  }
}
",
        "new_name",
        find_position_of("list").nth_occurrence(2)
    );
}

#[test]
fn rename_alternative_pattern_from_usage() {
    assert_rename!(
        "
pub fn main(x) {
  case x {
    #(wibble, [wobble]) | #(wobble, [wibble, _]) | #(_, [wibble, wobble, ..]) ->
      wibble + wobble
    _ -> 0
  }
}
",
        "new_name",
        find_position_of("wibble +")
    );
}

// https://github.com/gleam-lang/gleam/issues/4605
#[test]
fn rename_prelude_value() {
    assert_rename!(
        "
pub fn main() {
  Ok(10)
}
",
        "Success",
        find_position_of("Ok")
    );
}
#[test]
fn rename_prelude_type() {
    assert_rename!(
        "
pub fn main() -> Result(Int, Nil) {
  Ok(10)
}
",
        "SuccessOrFailure",
        find_position_of("Result")
    );
}

#[test]
fn rename_variable_with_alternative_pattern_with_same_name() {
    assert_rename!(
        "
pub fn main(x) {
  let some_var = 10

  case x {
    #(some_var, []) | #(_, [some_var]) ->
      some_var
    _ -> 0
  }

  some_var
}
",
        "new_name",
        find_position_of("some_var")
    );
}

#[test]
fn rename_prelude_value_with_prelude_already_imported() {
    assert_rename!(
        "
import gleam

pub fn main() {
  Ok(gleam.Error(10))
}
",
        "Success",
        find_position_of("Ok")
    );
}

#[test]
fn rename_prelude_value_with_prelude_import_with_empty_braces() {
    assert_rename!(
        "
import gleam.{}

pub fn main() {
  Ok(gleam.Error(10))
}
",
        "Success",
        find_position_of("Ok")
    );
}

#[test]
fn rename_prelude_value_with_other_prelude_value_imported() {
    assert_rename!(
        "
import gleam.{Error}

pub fn main() {
  Ok(Error(10))
}
",
        "Success",
        find_position_of("Ok")
    );
}

#[test]
fn rename_prelude_type_with_prelude_value_imported_with_trailing_comma() {
    assert_rename!(
        "
import gleam.{Error,}

pub fn main() -> Result(Int, Nil) {
  Error(10)
}
",
        "OkOrError",
        find_position_of("Result")
    );
}

#[test]
fn rename_prelude_value_with_other_module_imported() {
    assert_rename!(
        ("something", "pub type Something"),
        "
import something

pub fn main() {
  Ok(10)
}
",
        "Success",
        find_position_of("Ok")
    );
}

#[test]
fn rename_module_access_in_clause_guard() {
    assert_rename!(
        (
            "wibble",
            "
import app

pub fn main() {
  case app.something {
    thing if thing == app.something -> True
    _ -> False
  }
}
"
        ),
        "
pub const something = 10
",
        "new_name",
        find_position_of("something")
    );
}

#[test]
fn rename_variable_used_in_record_update() {
    assert_rename!(
        "
type Wibble {
  Wibble(a: Int, b: Int, c: Int)
}

fn wibble(wibble: Wibble) {
  Wibble(..wibble, c: 1)
}
",
        "value",
        find_position_of("wibble:")
    );
}

//https://github.com/gleam-lang/gleam/issues/4941
#[test]
fn rename_external_function() {
    assert_rename!(
        r#"
pub fn main() { wibble() }

@external(erlang, "a", "a")
fn wibble() -> Nil
"#,
        "new_name",
        find_position_of("wibble").nth_occurrence(2)
    );
}

#[test]
fn rename_external_javascript_function_with_pure_gleam_fallback() {
    assert_rename!(
        r#"
pub fn main() { wibble() }

@external(javascript, "a", "a")
fn wibble() -> Nil {
  Nil
}
"#,
        "new_name",
        find_position_of("wibble").nth_occurrence(2)
    );
}

#[test]
fn rename_nested_aliased_pattern() {
    assert_rename!(
        r#"
pub fn go(x) {
  case x {
    [[nested, ..] as wibble, ..] -> todo
    _ -> todo
  }
}
        "#,
        "new_name",
        find_position_of("nested")
    );
}

#[test]
fn rename_module_from_import() {
    assert_rename!(
        TestProject::for_source("import      option")
            .add_module("option", "pub type Option(a) { Some(a) None }"),
        "opt",
        find_position_of("option"),
    );
}

#[test]
fn rename_works_when_error_is_present() {
    assert_rename!(
        r#"
fn wibble() {
  "test string"
}

pub fn main() {
  1 + "1"
  echo wibble()
}
  "#,
        "wobble",
        find_position_of("wibble")
    );
}

#[test]
fn rename_module_from_import_with_alias() {
    assert_rename!(
        TestProject::for_source("import   option    as      opt")
            .add_module("option", "pub type Option(a) { Some(a) None }"),
        "o",
        find_position_of("opt"),
    );
}

#[test]
fn reanem_module_from_import_with_unqualified_values() {
    assert_rename!(
        TestProject::for_source("import   option   .   {    Some, None }")
            .add_module("option", "pub type Option(a) { Some(a) None }"),
        "opt",
        find_position_of("option"),
    );
}

#[test]
fn rename_module_from_import_with_unqualified_value_and_alias() {
    assert_rename!(
        TestProject::for_source("import   option  .     {Some, None}   as     opt")
            .add_module("option", "pub type Option(a) { Some(a) None }"),
        "o",
        find_position_of("opt"),
    );
}

#[test]
fn rename_module_from_import_namespaced() {
    assert_rename!(
        TestProject::for_source("import   std /   option  ")
            .add_module("std/option", "pub type Option(a) { Some(a) None }"),
        "opt",
        find_position_of("option"),
    );
}

#[test]
fn rename_module_from_import_namespaced_with_alias() {
    assert_rename!(
        TestProject::for_source("import   std /   option   as     opt")
            .add_module("std/option", "pub type Option(a) { Some(a) None }"),
        "o",
        find_position_of("opt"),
    );
}

#[test]
fn rename_module_from_import_namespaced_with_unqualified_values() {
    assert_rename!(
        TestProject::for_source("import   std /   option   .   {    Some, None }")
            .add_module("std/option", "pub type Option(a) { Some(a) None }"),
        "opt",
        find_position_of("option"),
    );
}

#[test]
fn rename_module_from_import_namespaced_with_unqualified_value_and_alias() {
    assert_rename!(
        TestProject::for_source("import   std /   option   .   {    Some, None }   as     opt")
            .add_module("std/option", "pub type Option(a) { Some(a) None }"),
        "o",
        find_position_of("opt"),
    );
}

#[test]
fn rename_module_from_import_with_alias_to_original_name() {
    assert_rename!(
        TestProject::for_source("import   option   as     opt")
            .add_module("option", "pub type Option(a) { Some(a) None }"),
        "option",
        find_position_of("opt"),
    );
}

#[test]
fn rename_module_from_variant_in_expression() {
    let src = r#"
import option

pub fn main() {
  echo option     . None
}
"#;
    assert_rename!(
        TestProject::for_source(src).add_module("option", "pub type Option(a) { Some(a) None }"),
        "opt",
        find_position_of("option").nth_occurrence(2)
    );
}

#[test]
fn rename_prefix_string_suffix_variable_in_case() {
    assert_rename!(
        "
fn main() -> String {
  let wibble = \"1-wibble\"
  case wibble {
    \"1\" <> rest -> rest
    other -> other
  }
}
",
        "new_name",
        find_position_of("rest").nth_occurrence(1)
    );
}

#[test]
fn rename_module_from_constant_in_expression() {
    let src = r#"
import maths

pub fn main() {
  echo maths  .    pi
}
"#;

    assert_rename!(
        TestProject::for_source(src).add_module("maths", "pub const pi = 3.14"),
        "m",
        find_position_of("maths").nth_occurrence(2),
    );
}

#[test]
fn rename_module_from_variant_in_const() {
    let src = r#"
import option

const x = option   .None
"#;

    assert_rename!(
        TestProject::for_source(src).add_module("option", "pub type Option(a) { Some(a) None }"),
        "opt",
        find_position_of("option").nth_occurrence(2),
    );
}

#[test]
fn rename_module_from_constant_in_const() {
    let src = r#"
import maths

const x = maths   .  pi
"#;

    assert_rename!(
        TestProject::for_source(src).add_module("maths", "pub const pi = 3.14"),
        "m",
        find_position_of("maths").nth_occurrence(2),
    );
}

#[test]
fn rename_module_from_variant_in_pattern() {
    let src = r#"
import option

pub fn is_some(option) {
  case option {
    option.  Some(_) -> True
    option  .None -> False
  }
}
"#;

    assert_rename!(
        TestProject::for_source(src).add_module("option", "pub type Option(a) { Some(a) None }"),
        "opt",
        find_position_of("option").nth_occurrence(4),
    );
}

#[test]
fn rename_prefix_string_suffix_variable_in_case_triggered_from_usage() {
    assert_rename!(
        "
fn main() -> String {
  let wibble = \"1-wibble\"
  case wibble {
    \"1\" <> rest -> rest
    other -> other
  }
}
",
        "new_name",
        find_position_of("rest").nth_occurrence(2)
    );
}

#[test]
fn rename_module_from_variant_in_clause_guard() {
    let src = r#"
import option

pub fn count_none(list) {
  case list {
    [option, ..rest] if option == option  . None -> 1 + count_none(rest)
    [_, ..rest] -> count_none(rest)
    [] -> 0
  }
}
"#;

    assert_rename!(
        TestProject::for_source(src).add_module("option", "pub type Option(a) { Some(a) None }"),
        "opt",
        find_position_of("option").nth_occurrence(4),
    );
}

#[test]
fn rename_prefix_string_suffix_variable_with_alternative_definition_in_case() {
    assert_rename!(
        "
fn main() -> String {
  let wibble = \"1-wibble\"
  case wibble {
    \"1\" <> rest | \"2\" <> rest -> rest
    other -> other
  }
}
",
        "new_name",
        find_position_of("rest").nth_occurrence(1),
    );
}

#[test]
fn rename_module_from_constant_in_clause_guard() {
    let src = r#"
import maths

pub fn count_pi(list) {
  case list {
    [number, ..rest] if number == maths  . pi -> 1 + count_pi(rest)
    [_, ..rest] -> count_pi(rest)
    [] -> 0
  }
}
"#;

    assert_rename!(
        TestProject::for_source(src).add_module("maths", "pub const pi = 3.14"),
        "m",
        find_position_of("maths").nth_occurrence(2),
    );
}

#[test]
fn rename_prefix_string_suffix_variable_with_alternative_definition_triggered_from_second_pattern()
{
    assert_rename!(
        "
fn main() -> String {
  let wibble = \"1-wibble\"
  case wibble {
    \"1\" <> rest | \"2\" <> rest -> rest
    other -> other
  }
}
",
        "new_name",
        find_position_of("rest").nth_occurrence(2),
    );
}

#[test]
fn rename_module_from_type_in_custom_type() {
    let src = r#"
import option

type Value(a) {
  Value(option.Option(a))
}
"#;

    assert_rename!(
        TestProject::for_source(src).add_module("option", "pub type Option(a) { Some(a) None }"),
        "opt",
        find_position_of("option").nth_occurrence(2),
    );
}

#[test]
fn rename_module_from_type_in_type_alias() {
    let src = r#"
import option

type Option(a) =
  option.Option(a)
"#;

    assert_rename!(
        TestProject::for_source(src).add_module("option", "pub type Option(a) { Some(a) None }"),
        "opt",
        find_position_of("option").nth_occurrence(2),
    );
}

#[test]
fn rename_module_from_type_in_annotation() {
    let src = r#"
import option

const x: option.Option(Int) = option.Some(1)
"#;

    assert_rename!(
        TestProject::for_source(src).add_module("option", "pub type Option(a) { Some(a) None }"),
        "opt",
        find_position_of("option").nth_occurrence(2),
    );
}

#[test]
fn rename_module_from_function_call() {
    let src = r#"
import option

pub fn main() {
  option.is_some(option.Some(1))
}
"#;

    let option_module = r#"
pub type Option(a) {
  Some(a)
  None
}

pub fn is_some(option: Option(a)) -> Bool {
  case option {
    Some(_) -> True
    None -> False
  }
}
"#;

    assert_rename!(
        TestProject::for_source(src).add_module("option", option_module),
        "opt",
        find_position_of("option").nth_occurrence(2),
    );
}

#[test]
fn rename_prefix_string_suffix_variable_in_let_assert() {
    assert_rename!(
        "
fn main() -> String {
  let assert \"1\" <> rest = \"1-wibble\"
  rest
}
",
        "new_name",
        find_position_of("rest").nth_occurrence(1)
    );
}

#[test]
fn rename_prefix_string_suffix_variable_in_let_assert_triggered_from_usage() {
    assert_rename!(
        "
fn main() -> String {
  let assert \"1\" <> rest = \"1-wibble\"
  rest
}
",
        "new_name",
        find_position_of("rest").nth_occurrence(2)
    );
}

#[test]
fn rename_prefix_string_alias_in_case() {
    assert_rename!(
        "
fn main() -> String {
  let wibble = \"1-wibble\"
  case wibble {
    \"1\" as digit <> rest -> digit <> rest
    other -> other
  }
}
",
        "new_name",
        find_position_of("digit").nth_occurrence(1)
    );
}

#[test]
fn rename_prefix_string_alias_in_case_triggered_from_usage() {
    assert_rename!(
        "
fn main() -> String {
  let wibble = \"1-wibble\"
  case wibble {
    \"1\" as digit <> rest -> digit <> rest
    other -> other
  }
}
",
        "new_name",
        find_position_of("digit").nth_occurrence(2)
    );
}

#[test]
fn rename_prefix_string_alias_with_alternative_definitions_in_case() {
    assert_rename!(
        "
fn main() -> String {
  let wibble = \"1-wibble\"
  case wibble {
    \"1\" as digit <> rest | \"2\" as digit <> rest -> digit <> rest
    other -> other
  }
}
",
        "new_name",
        find_position_of("digit").nth_occurrence(1)
    );
}

#[test]
fn rename_prefix_string_alias_with_alternative_definitions_triggered_from_second_pattern() {
    assert_rename!(
        "
fn main() -> String {
  let wibble = \"1-wibble\"
  case wibble {
    \"1\" as digit <> rest | \"2\" as digit <> rest -> digit <> rest
    other -> other
  }
}
",
        "new_name",
        find_position_of("digit").nth_occurrence(2)
    );
}

#[test]
fn rename_prefix_string_alias_in_let_assert() {
    assert_rename!(
        "
fn main() -> String {
  let assert \"1\" as digit <> rest = \"1-wibble\"
  digit
}
",
        "new_name",
        find_position_of("digit").nth_occurrence(1)
    );
}

#[test]
fn rename_prefix_string_alias_in_let_assert_triggered_from_usage() {
    assert_rename!(
        "
fn main() -> String {
  let assert \"1\" as digit <> rest = \"1-wibble\"
  digit
}
",
        "new_name",
        find_position_of("digit").nth_occurrence(2)
    );
}

#[test]
fn rename_prefix_string_suffix_variable_nested_in_tuple() {
    assert_rename!(
        "
fn main() {
  case #(\"1-wibble\", 0) {
    #(\"1\" <> rest, _) -> rest
    _ -> \"\"
  }
}
",
        "new_name",
        find_position_of("rest").nth_occurrence(1)
    );
}

#[test]
fn rename_prefix_string_alias_used_in_guard() {
    assert_rename!(
        "
fn main() {
  case \"1-wibble\" {
    \"1\" as digit <> _rest if digit == \"1\" -> digit
    _ -> \"\"
  }
}
",
        "new_name",
        find_position_of("digit").nth_occurrence(1)
    );
}

#[test]
fn rename_prefix_string_suffix_used_in_guard() {
    assert_rename!(
        "
fn main() {
  case \"1-wibble\" {
    \"1\" <> rest if rest == \"-wibble\" -> rest
    _ -> \"\"
  }
}
",
        "new_name",
        find_position_of("rest").nth_occurrence(1)
    );
}

#[test]
fn rename_prefix_string_suffix_shadowing_outer_variable() {
    assert_rename!(
        "
fn main() {
  let rest = \"outer\"
  case \"1-wibble\" {
    \"1\" <> rest -> rest
    _ -> rest
  }
}
",
        "new_name",
        find_position_of("rest").nth_occurrence(2)
    );
}

#[test]
fn rename_prefix_string_alias_and_suffix_complex_guard() {
    assert_rename!(
        "
fn main() {
  case \"1-wibble\" {
    \"1\" as digit <> rest if digit == \"1\" && rest == \"-wibble\" -> #(digit, rest)
    _ -> #(\"\", \"\")
  }
}
",
        "new_name",
        find_position_of("digit").nth_occurrence(1)
    );
}

#[test]
fn rename_module_from_alias_use() {
    let src = r#"
import maths  as     m

pub fn main() {
  echo m      .pi
}
"#;

    assert_rename!(
        TestProject::for_source(src).add_module("maths", "pub const pi = 3.14"),
        "mth",
        find_position_of("m").nth_occurrence(5)
    );
}

#[test]
fn rename_local_variable_from_guard() {
    assert_rename!(
        "
pub fn main() {
  let wibble = True
  let wobble = False
  case wibble {
    True if wobble -> !wibble
    False if !wobble -> wibble
    _ -> wobble
  }
}
",
        "something_else",
        find_position_of("wobble").nth_occurrence(2).under_char('o')
    );
}

#[test]
fn alias_imported_module_from_guard() {
    assert_rename!(
        ("mod", "pub const wibble = 10"),
        "
import mod

pub fn main() {
  let wibble = True
  case wibble {
    True if mod.wibble < 5 -> !wibble
    False if mod.wibble != 10 -> wibble
    _ -> mod.wibble + 1
  }
}
",
        "module",
        find_position_of("mod.wibble").under_char('m')
    );
}

#[test]
fn rename_module_select_from_guard() {
    assert_rename!(
        ("mod", "pub const wibble = 10"),
        "
import mod

pub fn main() {
  let wibble = True
  case wibble {
    True if mod.wibble < 5 -> !wibble
    False if mod.wibble != 10 -> wibble
    _ -> mod.wibble + 1
  }
}
",
        "ten",
        find_position_of("mod.wibble").under_char('w')
    );
}

#[test]
fn rename_alias_with_weird_spacing() {
    assert_rename!(
        (
            "mod",
            "
pub fn wibble() {
  wibble()
}
"
        ),
        "
import mod.{wibble     as         wobble}

pub fn main() {
  wobble()
  mod.wibble()
}
",
        "some_function",
        find_position_of("wibble")
    );
}

#[test]
fn rename_alias_with_weird_spacing_to_original_name() {
    assert_rename!(
        (
            "mod",
            "
pub fn wibble() {
  wibble()
}
"
        ),
        "
import mod.{wibble     as         wobble}

pub fn main() {
  wobble()
  mod.wibble()
}
",
        "wibble",
        find_position_of("wibble")
    );
}

#[test]
fn rename_type_variable_in_function() {
    assert_rename!(
        "
pub fn first(a: wibble, b: wobble) -> wibble {
  a
}
",
        "value",
        find_position_of("wibble")
    );
}

#[test]
fn rename_type_variable_in_function_from_return() {
    assert_rename!(
        "
pub fn first(a: wibble, b: wobble) -> wibble {
  a
}
",
        "value",
        find_position_of("-> wibble").under_char('l')
    );
}

#[test]
fn rename_type_variable_in_function_body_not_used_in_head() {
    assert_rename!(
        "
pub fn something() {
  let x: Result(a, b) = todo
  let y: a = case x {
    Ok(a) -> a
    Error(_) -> panic
  }
}
",
        "ok",
        find_position_of(": a").under_char('a')
    );
}

#[test]
fn rename_type_variable_in_function_used_in_body() {
    assert_rename!(
        "
pub fn first(a: wibble, b: wobble) -> wibble {
  let x: wibble = a
  let f = fn(a: wibble) -> wibble { a }
  f(x)
}
",
        "value",
        find_position_of("wibble")
    );
}

#[test]
fn rename_type_variable_in_function_from_body() {
    assert_rename!(
        "
pub fn first(a: wibble, b: wobble) -> wibble {
  let x: wibble = a
  let f = fn(a: wibble) -> wibble { a }
  f(x)
}
",
        "value",
        find_position_of("x: wibble").under_char('i')
    );
}

#[test]
fn type_variable_in_separate_function_is_not_renamed() {
    assert_rename!(
        "
pub fn first(a: wibble, b: wobble) -> wibble {
  a
}

pub fn an_unrelated_function(a: wibble, b: wibble) -> wibble {
  a
}
",
        "value",
        find_position_of("wibble")
    );
}

#[test]
fn rename_type_variable_in_constant() {
    assert_rename!(
        "
pub const empty: List(#(anything, anything)) = []
",
        "a",
        find_position_of("anything")
    );
}

#[test]
fn rename_type_variable_in_type_alias() {
    assert_rename!(
        "
pub type Option(a) {
  Some(a)
  None
}

pub type Maybe(some_type) =
  Option(some_type)
",
        "something",
        find_position_of("Option(some_type").under_char('_')
    );
}

#[test]
fn rename_type_variable_in_type_alias_from_head() {
    assert_rename!(
        "
pub type Option(a) {
  Some(a)
  None
}

pub type Maybe(some_type) =
  Option(some_type)
",
        "something",
        find_position_of("some_type")
    );
}

#[test]
fn rename_type_variable_in_custom_type_from_head() {
    assert_rename!(
        "
pub type Option(anything) {
  Some(anything)
  None
}
",
        "something",
        find_position_of("anything")
    );
}

#[test]
fn rename_type_variable_in_custom_type_from_constructor() {
    assert_rename!(
        "
pub type Option(anything) {
  Some(anything)
  None
}
",
        "something",
        find_position_of("Some(anything").under_char('i')
    );
}

#[test]
fn invalid_type_variable_name() {
    assert_rename_error!(
        "
pub type Option(anything) {
  Some(anything)
  None
}
",
        "SomeType",
        find_position_of("anything")
    );
}
#[test]
fn renaming_file_modifies_imports_and_references() {
    assert_rename_files!(
        (
            "wibble/wobble",
            "wibble/wubble",
            "
pub type Wibble {
  Wibble
  Wobble
}

pub const wibble = Wibble
"
        ),
        "
import wibble/wobble.{Wibble}

pub fn main() -> wobble.Wibble {
  assert wobble.wibble == Wibble
  wobble.Wobble
}
",
    );
}

#[test]
fn change_directory_of_file() {
    assert_rename_files!(
        (
            "wobble",
            "wibble/wobble",
            "
pub type Wibble {
  Wibble
  Wobble
}

pub const wibble = Wibble
"
        ),
        "
import wobble.{Wibble}

pub fn main() -> wobble.Wibble {
  assert wobble.wibble == Wibble
  wobble.Wobble
}
",
    );
}

#[test]
fn rename_file_does_not_modify_aliased_imports() {
    assert_rename_files!(
        (
            "wibble/wobble",
            "wibble/wubble",
            "
pub type Wibble {
  Wibble
  Wobble
}

pub const wibble = Wibble
"
        ),
        "
import wibble/wobble.{Wibble} as wibble

pub fn main() -> wibble.Wibble {
  assert wibble.wibble == Wibble
  wibble.Wobble
}
",
    );
}

#[test]
fn rename_file_removes_unnecessary_alias() {
    assert_rename_files!(
        (
            "wibble/wobble",
            "wibble/wibble",
            "
pub type Wibble {
  Wibble
  Wobble
}

pub const wibble = Wibble
"
        ),
        "
import wibble/wobble.{Wibble} as wibble

pub fn main() -> wibble.Wibble {
  assert wibble.wibble == Wibble
  wibble.Wobble
}
",
    );
}

#[test]
fn rename_file_changes_all_correct_ast_nodes() {
    assert_rename_files!(
        (
            "wibble",
            "wobble",
            "
pub type Wibble {
  Wibble
  Wobble
}

pub const wibble = Wibble
"
        ),
        "
import wibble

pub const one = wibble.Wibble

pub const two = wibble.wibble

pub fn main() -> wibble.Wibble {
  case wibble.Wobble {
    x if x == wibble.Wibble -> x
    x if x == wibble.wibble -> x
    wibble.Wobble -> wibble.wibble
  }
}
",
    );
}

#[test]
fn rename_multiple_files() {
    assert_rename_files!(
        (
            "wibble",
            "wibble/wibble",
            "pub type Wibble { Wibble Wobble }"
        ),
        (
            "wobble",
            "wibble/wobble",
            "import wibble
pub const wibble = wibble.Wobble"
        ),
        "
import wibble
import wobble

pub fn main() -> wibble.Wibble {
  wobble.wibble
}
",
    );
}

#[test]
fn rename_record_field_from_access() {
    assert_rename!(
        "
type Wibble {
  Wibble(wibble: Int)
}

pub fn main() {
  let value = Wibble(wibble: 1)
  value.wibble
}
",
        "wobble",
        find_position_of("value.wibble").under_char('i')
    );
}

#[test]
fn rename_record_field_from_constructor_label() {
    assert_rename!(
        "
type Wibble {
  Wibble(wibble: Int)
}

pub fn main() {
  let value = Wibble(wibble: 1)
  value.wibble
}
",
        "wobble",
        find_position_of("wibble: 1").under_char('w')
    );
}

#[test]
fn rename_record_field_from_pattern_label() {
    assert_rename!(
        "
type Wibble {
  Wibble(wibble: Int)
}

pub fn main(w: Wibble) {
  case w {
    Wibble(wibble: value) -> value
  }
}
",
        "wobble",
        find_position_of("wibble: value").under_char('w')
    );
}

#[test]
fn rename_record_field_expands_label_shorthand() {
    assert_rename!(
        "
type Wibble {
  Wibble(wibble: Int)
}

pub fn main() {
  let wibble = 1
  let value = Wibble(wibble:)
  value.wibble
}
",
        "wobble",
        find_position_of("value.wibble").under_char('i')
    );
}

#[test]
fn rename_record_field_in_record_update() {
    assert_rename!(
        "
type Wibble {
  Wibble(wibble: Int, wobble: Int)
}

pub fn main(w: Wibble) {
  Wibble(..w, wibble: 2)
}
",
        "wabble",
        find_position_of("wibble: 2").under_char('w')
    );
}

#[test]
fn rename_record_field_across_modules() {
    assert_rename!(
        ("wibble", "pub type Wibble {\n  Wibble(wibble: Int)\n}"),
        "
import wibble

pub fn main() {
  let value = wibble.Wibble(wibble: 1)
  value.wibble
}
",
        "wobble",
        find_position_of("value.wibble").under_char('i')
    );
}

#[test]
fn no_rename_record_field_from_other_package() {
    let src = "
import wibble

pub fn main() {
  let value = wibble.Wibble(wibble: 1)
  value.wibble
}
";

    assert_no_rename!(
        &TestProject::for_source(src)
            .add_hex_module("wibble", "pub type Wibble {\n  Wibble(wibble: Int)\n}"),
        "wobble",
        find_position_of("value.wibble").under_char('i')
    );
}

#[test]
fn rename_record_field_in_module_not_importing_the_type_module() {
    assert_rename!(
        TestProject::for_source(
            "
import wobble

pub fn main() {
  wobble.make().wibble
}
"
        )
        .add_module("wibble", "pub type Wibble {\n  Wibble(wibble: Int)\n}")
        .add_module(
            "wobble",
            "import wibble\n\npub fn make() -> wibble.Wibble {\n  wibble.Wibble(wibble: 1)\n}"
        ),
        "wabble",
        find_position_of("().wibble").under_char('b')
    );
}

#[test]
fn rename_record_field_with_invalid_name() {
    assert_rename_error!(
        "
type Wibble {
  Wibble(wibble: Int)
}

pub fn main() {
  let value = Wibble(wibble: 1)
  value.wibble
}
",
        "Wobble",
        find_position_of("value.wibble").under_char('i')
    );
}

#[test]
fn rename_record_field_from_definition() {
    assert_rename!(
        "
type Wibble {
  Wibble(wibble: Int)
}

pub fn main() {
  let value = Wibble(wibble: 1)
  value.wibble
}
",
        "wobble",
        find_position_of("wibble: Int").under_char('w')
    );
}

#[test]
fn rename_record_field_renames_labelled_arguments_of_call_with_incorrect_arity() {
    assert_rename!(
        "
type Wibble {
  Wibble(wibble: Int, wobble: Int)
}

pub fn main() {
  Wibble(wibble: 1)
}
",
        "wabble",
        find_position_of("wibble: Int").under_char('w')
    );
}

#[test]
fn rename_record_field_ignored_by_record_update_spread() {
    // https://github.com/gleam-lang/gleam/pull/5533#pullrequestreview-4475156690
    assert_rename!(
        "
type Wibble {
  Wibble(wibble: Int, wobble: Int)
}

pub fn main(w: Wibble) {
  Wibble(..w, wibble: 2)
}
",
        "wabble",
        find_position_of("wobble: Int").under_char('w')
    );
}

#[test]
fn rename_record_field_ignored_by_record_update_spread_with_expression() {
    assert_rename!(
        "
type Wibble {
  Wibble(wibble: Int, wobble: Int)
}

pub fn make() {
  Wibble(wibble: 1, wobble: 2)
}

pub fn main() {
  Wibble(..make(), wibble: 2)
}
",
        "wabble",
        find_position_of("wobble: Int").under_char('w')
    );
}

#[test]
fn rename_record_field_ignored_by_pattern_spread() {
    assert_rename!(
        "
type Wibble {
  Wibble(wibble: Int, wobble: Int)
}

pub fn main(w: Wibble) {
  case w {
    Wibble(wibble: a, ..) -> a
  }
}
",
        "wabble",
        find_position_of("wobble: Int").under_char('w')
    );
}

#[test]
fn rename_record_field_ignored_by_bare_pattern_spread() {
    assert_rename!(
        "
type Wibble {
  Wibble(wibble: Int, wobble: Int)
}

pub fn main(w: Wibble) {
  case w {
    Wibble(..) -> 1
  }
}
",
        "wabble",
        find_position_of("wibble: Int").under_char('w')
    );
}

#[test]
fn rename_record_field_shared_between_variants() {
    assert_rename!(
        "
pub type Shape {
  Square(colour: Int, width: Int)
  Circle(colour: Int, radius: Int)
}

pub fn main() {
  let shape = Square(colour: 1, width: 10)
  case shape {
    Circle(colour:, ..) -> colour
    Square(..) -> shape.colour
  }
}
",
        "shade",
        find_position_of("colour: Int, width").under_char('c')
    );
}

// https://github.com/gleam-lang/gleam/issues/5861
#[test]
fn rename_item_with_import_alias() {
    assert_rename!(
        TestProject::for_source(
            "
import wibble.{Wibble as Wobble}

pub fn main() {
  let _ = Wobble
}
"
        )
        .add_module(
            "wibble",
            "
pub type Wibble {
  Wibble
}
"
        ),
        "Wubble",
        find_position_of("= Wobble").under_char('b')
    );
}

// https://github.com/gleam-lang/gleam/issues/5861
#[test]
fn rename_type_with_import_alias() {
    assert_rename!(
        TestProject::for_source(
            "
import wibble.{type Wibble as Wobble}

pub fn main() -> Wobble {
  todo
}
"
        )
        .add_module(
            "wibble",
            "
pub type Wibble {
  Wibble
}
"
        ),
        "Wubble",
        find_position_of("-> Wobble").under_char('b')
    );
}

// https://github.com/gleam-lang/gleam/issues/5861
#[test]
fn rename_type_with_import_alias_and_other_imported_items() {
    assert_rename!(
        TestProject::for_source(
            "
import wibble.{Wibble as Wobble, type Wibble as Wobble}

pub fn main() -> Wobble {
  Wobble
}
"
        )
        .add_module(
            "wibble",
            "
pub type Wibble {
  Wibble
}
"
        ),
        "Wubble",
        find_position_of("-> Wobble").under_char('b')
    );
}

// https://github.com/gleam-lang/gleam/issues/5861
#[test]
fn rename_value_with_import_alias_and_other_imported_items() {
    assert_rename!(
        TestProject::for_source(
            "
import wibble.{Wibble as Wobble, type Wibble as Wobble}

pub fn main() -> Wobble {
  Wobble
}
"
        )
        .add_module(
            "wibble",
            "
pub type Wibble {
  Wibble
}
"
        ),
        "Wubble",
        find_position_of("Wobble").nth_occurrence(4).under_char('b')
    );
}

#[test]
fn rename_item_with_import_alias_to_original_name() {
    assert_rename!(
        TestProject::for_source(
            "
import wibble.{Wibble as Wobble}

pub fn main() {
  let _ = Wobble
}
"
        )
        .add_module(
            "wibble",
            "
pub type Wibble {
  Wibble
}
"
        ),
        "Wibble",
        find_position_of("= Wobble").under_char('b')
    );
}

#[test]
fn rename_type_with_import_alias_to_original_name() {
    assert_rename!(
        TestProject::for_source(
            "
import wibble.{type Wibble as Wobble}

pub fn main() -> Wobble {
  todo
}
"
        )
        .add_module(
            "wibble",
            "
pub type Wibble {
  Wibble
}
"
        ),
        "Wibble",
        find_position_of("-> Wobble").under_char('b')
    );
}

#[test]
fn rename_type_with_import_alias_to_original_name_with_other_items() {
    assert_rename!(
        TestProject::for_source(
            "
import wibble.{type Wibble as Wobble, Wibble as Wobble}

pub fn main() -> Wobble {
  Wobble
}
"
        )
        .add_module(
            "wibble",
            "
pub type Wibble {
  Wibble
}
"
        ),
        "Wibble",
        find_position_of("-> Wobble").under_char('b')
    );
}

#[test]
fn rename_item_with_import_alias_to_original_name_with_other_items() {
    assert_rename!(
        TestProject::for_source(
            "
import wibble.{type Wibble as Wobble, Wibble as Wobble}

pub fn main() -> Wobble {
  Wobble
}
"
        )
        .add_module(
            "wibble",
            "
pub type Wibble {
  Wibble
}
"
        ),
        "Wibble",
        find_position_of("Wobble").nth_occurrence(4).under_char('b')
    );
}

#[test]
fn rename_value_from_aliased_import_imported_name() {
    assert_rename!(
        (
            "mod",
            "
pub fn wibble() {
  wibble()
}
"
        ),
        "
import mod.{wibble as wobble}

pub fn main() {
  wobble()
  mod.wibble()
}
",
        "some_function",
        find_position_of("wibble")
    );
}

#[test]
fn rename_value_from_aliased_import_as() {
    assert_rename!(
        (
            "mod",
            "
pub fn wibble() {
  wibble()
}
"
        ),
        "
import mod.{wibble as wobble}

pub fn main() {
  wobble()
  mod.wibble()
}
",
        "some_function",
        find_position_of("as")
    );
}

#[test]
fn rename_value_from_aliased_import_alias_name() {
    assert_rename!(
        (
            "mod",
            "
pub fn wibble() {
  wibble()
}
"
        ),
        "
import mod.{wibble as wobble}

pub fn main() {
  wobble()
  mod.wibble()
}
",
        "some_function",
        find_position_of("wobble")
    );
}

#[test]
fn rename_type_from_aliased_import_type() {
    assert_rename!(
        (
            "mod",
            "
pub type Wibble { Constructor }

fn wibble(w: Wibble) -> Wibble { todo }
"
        ),
        "
import mod.{type Wibble as Wobble}

pub fn main(w: Wobble) -> mod.Wibble { todo }
",
        "SomeType",
        find_position_of("type")
    );
}

#[test]
fn rename_type_from_aliased_import_imported_name() {
    assert_rename!(
        (
            "mod",
            "
pub type Wibble { Constructor }

fn wibble(w: Wibble) -> Wibble { todo }
"
        ),
        "
import mod.{type Wibble as Wobble}

pub fn main(w: Wobble) -> mod.Wibble { todo }
",
        "SomeType",
        find_position_of("Wibble")
    );
}

#[test]
fn rename_type_from_aliased_import_as() {
    assert_rename!(
        (
            "mod",
            "
pub type Wibble { Constructor }

fn wibble(w: Wibble) -> Wibble { todo }
"
        ),
        "
import mod.{type Wibble as Wobble}

pub fn main(w: Wobble) -> mod.Wibble { todo }
",
        "SomeType",
        find_position_of("as")
    );
}

#[test]
fn rename_type_from_aliased_import_aliased_name() {
    assert_rename!(
        (
            "mod",
            "
pub type Wibble { Constructor }

fn wibble(w: Wibble) -> Wibble { todo }
"
        ),
        "
import mod.{type Wibble as Wobble}

pub fn main(w: Wobble) -> mod.Wibble { todo }
",
        "SomeType",
        find_position_of("Wobble")
    );
}
