use super::*;

#[derive(Debug, PartialEq)]
pub struct Module {
  pub name: TextNode,
  pub optional: bool,
  pub path: Option<TextNode>,
  pub range: lsp::Range,
}

impl Module {
  #[must_use]
  pub fn resolve(&self, base_uri: &lsp::Url) -> Option<PathBuf> {
    let base_dir = base_uri.to_file_path().ok()?.parent()?.to_path_buf();

    if let Some(path_node) = &self.path {
      let raw = path_node.value.trim_matches(|c| c == '\'' || c == '"');

      if raw.is_empty() {
        return None;
      }

      return Some(match raw.strip_prefix("~/") {
        Some(rest) => dirs::home_dir()?.join(rest),
        None => base_dir.join(raw),
      });
    }

    let name = &self.name.value;

    [
      base_dir.join(format!("{name}.just")),
      base_dir.join(name).join("mod.just"),
      base_dir.join(name).join("justfile"),
    ]
    .into_iter()
    .find(|path| path.is_file())
  }
}

#[cfg(test)]
mod tests {
  use {super::*, tempfile::Builder};

  fn module(name: &str, path: Option<&str>) -> Module {
    Module {
      name: TextNode {
        value: name.to_owned(),
        range: lsp::Range::default(),
      },
      optional: false,
      path: path.map(|p| TextNode {
        value: p.to_owned(),
        range: lsp::Range::default(),
      }),
      range: lsp::Range::default(),
    }
  }

  #[test]
  fn explicit_path() {
    let directory = Builder::new().prefix("just-lsp").tempdir().unwrap();

    let base =
      lsp::Url::from_file_path(directory.path().join("justfile")).unwrap();

    let expected = directory.path().join("bar.just");

    assert_eq!(
      module("foo", Some("'bar.just'")).resolve(&base).unwrap(),
      expected,
    );

    assert_eq!(
      module("foo", Some("\"bar.just\"")).resolve(&base).unwrap(),
      expected,
    );
  }

  #[test]
  fn explicit_path_home_directory() {
    let directory = Builder::new().prefix("just-lsp").tempdir().unwrap();

    let base =
      lsp::Url::from_file_path(directory.path().join("justfile")).unwrap();

    assert_eq!(
      module("foo", Some("'~/bar.just'")).resolve(&base).unwrap(),
      dirs::home_dir().unwrap().join("bar.just"),
    );
  }

  #[test]
  fn empty_explicit_path_returns_none() {
    let directory = Builder::new().prefix("just-lsp").tempdir().unwrap();

    let base =
      lsp::Url::from_file_path(directory.path().join("justfile")).unwrap();

    assert_eq!(module("foo", Some("''")).resolve(&base), None);
    assert_eq!(module("foo", Some("\"\"")).resolve(&base), None);
  }

  #[test]
  fn implicit_mod_just() {
    let directory = Builder::new().prefix("just-lsp").tempdir().unwrap();

    std::fs::create_dir(directory.path().join("foo")).unwrap();
    std::fs::write(directory.path().join("foo/mod.just"), "").unwrap();

    let base =
      lsp::Url::from_file_path(directory.path().join("justfile")).unwrap();

    assert_eq!(
      module("foo", None).resolve(&base).unwrap(),
      directory.path().join("foo/mod.just"),
    );
  }

  #[test]
  fn implicit_justfile() {
    let directory = Builder::new().prefix("just-lsp").tempdir().unwrap();

    std::fs::create_dir(directory.path().join("foo")).unwrap();
    std::fs::write(directory.path().join("foo/justfile"), "").unwrap();

    let base =
      lsp::Url::from_file_path(directory.path().join("justfile")).unwrap();

    assert_eq!(
      module("foo", None).resolve(&base).unwrap(),
      directory.path().join("foo/justfile"),
    );
  }

  #[test]
  fn implicit_prefers_name_dot_just() {
    let directory = Builder::new().prefix("just-lsp").tempdir().unwrap();

    std::fs::write(directory.path().join("foo.just"), "").unwrap();
    std::fs::create_dir(directory.path().join("foo")).unwrap();
    std::fs::write(directory.path().join("foo/mod.just"), "").unwrap();

    let base =
      lsp::Url::from_file_path(directory.path().join("justfile")).unwrap();

    assert_eq!(
      module("foo", None).resolve(&base).unwrap(),
      directory.path().join("foo.just"),
    );
  }

  #[test]
  fn implicit_no_file_returns_none() {
    let directory = Builder::new().prefix("just-lsp").tempdir().unwrap();

    let base =
      lsp::Url::from_file_path(directory.path().join("justfile")).unwrap();

    assert_eq!(module("foo", None).resolve(&base), None);
  }
}
