// Copyright 2018-2026 the Deno authors. MIT license.

use deno_core::ModuleSpecifier;
use deno_core::error::AnyError;
use deno_lib::util::checksum;
use indexmap::IndexMap;
use indexmap::IndexSet;
use lsp::Range;
use tower_lsp::lsp_types as lsp;

use super::lsp_custom;
use super::lsp_custom::TestData;
use crate::lsp::client::TestingNotification;
use crate::lsp::logging::lsp_warn;
use crate::lsp::urls::url_to_uri;
use crate::tools::test::TestDescription;
use crate::tools::test::TestStepDescription;

#[derive(Debug, Clone, PartialEq)]
pub struct TestDefinition {
  pub id: String,
  pub name: String,
  /// 0-based occurrence index for tests that share the same `name` and
  /// `parent_id`. The first registration with a given (parent_id, name) pair
  /// gets `0`, the next `1`, etc. Used to disambiguate duplicate test names
  /// (see https://github.com/denoland/deno/issues/20371) without changing the
  /// displayed `name`.
  pub name_index: u32,
  pub range: Option<Range>,
  pub is_dynamic: bool,
  pub parent_id: Option<String>,
  pub step_ids: IndexSet<String>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct TestModule {
  pub specifier: ModuleSpecifier,
  pub defs: IndexMap<String, TestDefinition>,
}

impl TestModule {
  pub fn new(specifier: ModuleSpecifier) -> Self {
    Self {
      specifier,
      defs: Default::default(),
    }
  }

  /// Compute the SHA-256 id for a test with the given `name`, `name_index`
  /// (occurrence index for duplicate names), and `parent_id` chain. The id is
  /// derived from the specifier + each ancestor's `name` (and `name_index` if
  /// non-zero) + the test's own `name` (and `name_index` if non-zero).
  ///
  /// `name_index == 0` preserves the original id format, so the common case
  /// of a unique test name hashes to the same value as before this method
  /// existed.
  fn compute_id(
    &self,
    name: &str,
    name_index: u32,
    parent_id: Option<&str>,
  ) -> String {
    let mut id_components: Vec<Vec<u8>> = Vec::with_capacity(8);
    id_components.push(name.as_bytes().to_vec());
    if name_index > 0 {
      id_components.push(format!("#{name_index}").into_bytes());
    }
    let mut current_parent_id = parent_id;
    while let Some(pid) = current_parent_id {
      let parent = match self.defs.get(pid) {
        Some(d) => d,
        None => {
          lsp_warn!(
            "Internal Error: parent_id \"{}\" of test \"{}\" was not registered.",
            pid,
            name
          );
          id_components.push(b"<unknown>".to_vec());
          break;
        }
      };
      id_components.push(parent.name.as_bytes().to_vec());
      if parent.name_index > 0 {
        id_components.push(format!("#{}", parent.name_index).into_bytes());
      }
      current_parent_id = parent.parent_id.as_deref();
    }
    id_components.push(self.specifier.as_str().as_bytes().to_vec());
    id_components.reverse();
    checksum::r#gen(&id_components)
  }

  /// Determine the next `name_index` to assign for a test with this `name`
  /// and `parent_id` — i.e., how many existing definitions already share
  /// those keys.
  fn next_name_index(&self, name: &str, parent_id: Option<&str>) -> u32 {
    self
      .defs
      .values()
      .filter(|d| d.name == name && d.parent_id.as_deref() == parent_id)
      .count() as u32
  }

  /// Returns `(id, is_newly_registered)`.
  ///
  /// Used by the static collector — each call inserts a fresh entry,
  /// assigning the next available `name_index` for the `(parent_id, name)`
  /// pair so duplicate-named tests in the source each get a unique id.
  pub fn register(
    &mut self,
    name: String,
    range: Option<Range>,
    is_dynamic: bool,
    parent_id: Option<String>,
  ) -> (String, bool) {
    let name_index = self.next_name_index(&name, parent_id.as_deref());
    self.register_with_index(name, name_index, range, is_dynamic, parent_id)
  }

  /// Returns `(id, is_newly_registered)`.
  ///
  /// Used by dynamic (runtime-driven) registration. The caller is expected to
  /// pass the same `name_index` sequence (0, 1, 2, …) that the static
  /// collector assigned, so dynamic events resolve to the matching static
  /// definition.
  fn register_with_index(
    &mut self,
    name: String,
    name_index: u32,
    range: Option<Range>,
    is_dynamic: bool,
    parent_id: Option<String>,
  ) -> (String, bool) {
    let id = self.compute_id(&name, name_index, parent_id.as_deref());
    if self.defs.contains_key(&id) {
      return (id, false);
    }
    if let Some(parent_id) = &parent_id {
      let parent = self.defs.get_mut(parent_id).unwrap();
      parent.step_ids.insert(id.clone());
    }
    self.defs.insert(
      id.clone(),
      TestDefinition {
        id: id.clone(),
        name,
        name_index,
        range,
        is_dynamic,
        parent_id,
        step_ids: Default::default(),
      },
    );
    (id, true)
  }

  /// Returns `(id, was_newly_registered)`.
  pub fn register_dynamic(
    &mut self,
    desc: &TestDescription,
    name_index: u32,
  ) -> (String, bool) {
    self.register_with_index(desc.name.clone(), name_index, None, true, None)
  }

  /// Returns `(id, was_newly_registered)`.
  pub fn register_step_dynamic(
    &mut self,
    desc: &TestStepDescription,
    parent_static_id: &str,
    name_index: u32,
  ) -> (String, bool) {
    self.register_with_index(
      desc.name.clone(),
      name_index,
      None,
      true,
      Some(parent_static_id.to_string()),
    )
  }

  pub fn get(&self, id: &str) -> Option<&TestDefinition> {
    self.defs.get(id)
  }

  pub fn get_test_data(&self, id: &str) -> TestData {
    fn get_test_data_inner(tm: &TestModule, id: &str) -> TestData {
      let def = tm.defs.get(id).unwrap();
      TestData {
        id: def.id.clone(),
        label: def.name.clone(),
        steps: def
          .step_ids
          .iter()
          .map(|id| get_test_data_inner(tm, id))
          .collect(),
        range: def.range,
      }
    }
    let def = self.defs.get(id).unwrap();
    let mut current_data = get_test_data_inner(self, &def.id);
    let mut current_parent_id = &def.parent_id;
    while let Some(parent_id) = current_parent_id {
      let parent = self.defs.get(parent_id).unwrap();
      current_data = TestData {
        id: parent.id.clone(),
        label: parent.name.clone(),
        steps: vec![current_data],
        range: None,
      };
      current_parent_id = &parent.parent_id;
    }
    current_data
  }

  /// Return the test definitions as a testing module notification.
  pub fn as_replace_notification(
    &self,
    maybe_root_uri: Option<&ModuleSpecifier>,
  ) -> Result<TestingNotification, AnyError> {
    let label = self.label(maybe_root_uri);
    Ok(TestingNotification::Module(
      lsp_custom::TestModuleNotificationParams {
        text_document: lsp::TextDocumentIdentifier {
          uri: url_to_uri(&self.specifier)?,
        },
        kind: lsp_custom::TestModuleNotificationKind::Replace,
        label,
        tests: self
          .defs
          .iter()
          .filter(|(_, def)| def.parent_id.is_none())
          .map(|(id, _)| self.get_test_data(id))
          .collect(),
      },
    ))
  }

  pub fn label(&self, maybe_root_uri: Option<&ModuleSpecifier>) -> String {
    if let Some(root) = maybe_root_uri {
      self.specifier.as_str().replace(root.as_str(), "")
    } else {
      self
        .specifier
        .path_segments()
        .and_then(|mut s| s.next_back().map(|s| s.to_string()))
        .unwrap_or_else(|| "<unknown>".to_string())
    }
  }

  pub fn is_empty(&self) -> bool {
    self.defs.is_empty()
  }
}
