// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0

use {
  crate::core::{Ident, SymbolVisibility, Visibility},
  std::hash::Hash,
};

/// Trait for literal values that can be stored in symbols.
///
/// The Value trait represents concrete data associated with symbols, such as
/// string literals, numbers, or any domain-specific literal values.
/// Values must be cloneable for symbol table operations, comparable for
/// validation, and thread-safe for concurrent access.
///
/// # Integration with Bluegum
///
/// Values must implement `Bluegum` for visualization and debugging
/// support, allowing the symbol engine to display value contents in development
/// tools.
pub trait Value<I: Ident>:
  bluegum::Bluegum + Clone + PartialEq + Hash + Send + Sync
{
  /// Returns the primary span for this value, if it has one
  fn span(&self) -> Option<laburnum::Span>;
}

/// Built-in value types for general-purpose programming languages.
///
/// Provides common literal types that most languages need. Languages can use
/// this directly or implement their own Value type for domain-specific
/// literals.
#[derive(Debug, Clone, PartialEq)]
pub enum DefaultValue {
  /// String literal value
  String(String),
  /// Integer value
  Integer(i64),
  /// Floating-point value
  Float(f64),
  /// Boolean value
  Boolean(bool),
}

impl Hash for DefaultValue {
  fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
    core::mem::discriminant(self).hash(state);
    match self {
      | DefaultValue::String(s) => s.hash(state),
      | DefaultValue::Integer(i) => i.hash(state),
      | DefaultValue::Float(f) => f.to_bits().hash(state),
      | DefaultValue::Boolean(b) => b.hash(state),
    }
  }
}

impl<I: Ident> Value<I> for DefaultValue {
  fn span(&self) -> Option<laburnum::Span> {
    None
  }
}

impl bluegum::Bluegum for DefaultValue {
  fn node(&self, b: &mut bluegum::Builder) {
    match self {
      | DefaultValue::String(s) => b.name("String").field("value", s),
      | DefaultValue::Integer(i) => b.name("Integer").field("value", i),
      | DefaultValue::Float(f) => b.name("Float").field("value", f),
      | DefaultValue::Boolean(v) => b.name("Boolean").field("value", v),
    };
  }
}

/// Trait for symbol path types that can be used in symbol references and
/// imports.
///
/// A path is the index sort key for symbolique partitions: it is the generic,
/// user-driven `Partition::SortKey`. The required [`PartitionSortKey`] impl
/// must be structural and span-free — parent paths must be prefixes
/// of child paths (so `begins_with`/range queries work), and `resolve` is the
/// only way to render a path as text.
///
/// [`PartitionSortKey`]: laburnum::PartitionSortKey
pub trait SymbolPath: laburnum::PartitionSortKey {}

impl<T: laburnum::PartitionSortKey> SymbolPath for T {}

/// Core symbol representation covering fundamental language patterns.
///
/// The Symbol enum represents 6 structural patterns that appear across
/// programming languages, independent of their semantic meaning. This
/// separation of structure from semantics allows the same symbol table to
/// support diverse language designs while maintaining type safety and memory
/// efficiency.
///
/// # Symbol Variants
///
/// - **Keyword**: Language keywords and reserved words with no associated data
/// - **Value**: Literal values like numbers, strings, or colors
/// - **Reference**: Names that refer to other symbols (variables, types, etc.)
/// - **Definition**: Named entities that may contain values and/or other
///   symbols
/// - **Scope**: Anonymous containers for organizing symbols hierarchically
/// - **Import**: Imports that bring external symbols into scope
///
/// # Type Parameters
///
/// - `V: Value` - The type of literal values in the language
/// - `I: Ident` - The identifier type for names
/// - `P: SymbolPath` - The path type for references and imports
/// - `S: Visibility` - The visibility marker carried by definitions
///   (default: [`SymbolVisibility`])
///
/// # Note on Spans
///
/// Symbol shapes do not contain spans. Spans are stored in index entries
/// (SymbolIndexEntry) that link paths to shapes with location information.
/// This separation enables content-addressed deduplication of symbols.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub enum Symbol<
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
  S: Visibility = SymbolVisibility,
> {
  /// Language keywords and reserved words.
  ///
  /// Represents syntactic markers that have special meaning in the language
  /// but don't store values or contain other symbols.
  /// Examples: 'if', 'while', 'class', 'import'
  Keyword {
    /// The keyword identifier
    name: I,
  },

  /// Literal values in the source code.
  ///
  /// Represents concrete data that appears directly in the program text.
  /// Examples: "hello", 42, 2.72, #FF0000
  Value {
    /// The literal value data
    value: V,
  },

  /// References to other symbols by name.
  ///
  /// Represents uses of symbols defined elsewhere. Resolution to target
  /// definitions is stored separately in the SymbolResolution partition.
  /// Examples: variable usage, type references, function calls
  Reference {
    /// The reference's own path in the index
    path: P,
    /// Optional name for the reference itself
    name: Option<I>,
    /// Path to the target symbol (user-defined path type)
    target_path: P,
    /// Whether path is absolute (::foo) or relative (foo)
    is_absolute: bool,
    /// When false, this symbol does not count toward unresolved_count() and is
    /// not visible in namespace lookups. Used for intermediate path segments
    /// (e.g., "ui" in "ui.background") that exist only for IDE features like
    /// hover and go-to-definition. Only the final full-path reference has
    /// nameable=true and participates in resolution counting.
    nameable: bool,
  },

  /// Named symbols that may have values and/or contain other symbols.
  ///
  /// The most versatile symbol type, representing variables, functions,
  /// classes, modules, and any other named entity.
  ///
  /// Can act as both a value holder and a container for nested symbols.
  /// As such it can be used as a NamedScope.
  ///
  /// Examples: variables, functions, classes, modules
  Definition {
    /// The symbol's name
    name: I,
    /// Optional associated value
    value: Option<V>,
    /// Visibility level for cross-file reference resolution
    visibility: S,
  },

  /// Anonymous scope for organizing symbols.
  ///
  /// Represents unnamed blocks or regions that contain other symbols
  /// but have no name themselves. Used for block scopes, anonymous
  /// functions, and implicit groupings.
  /// Examples: { ... } blocks, anonymous functions, implicit scopes
  Scope {
    /// Optional associated value, useful for associating kinds or types with
    /// scopes.
    value: Option<V>,
  },

  /// Imports that bring external symbols into scope.
  ///
  /// Represents import statements that reference symbols from external modules.
  /// Unlike Reference which models internal symbol usage, Import models
  /// cross-module dependencies with optional aliasing.
  /// Examples: `import foo`, `import foo::bar`, `import foo as f`
  Import {
    /// Import path (user-defined path type, e.g., "std::collections")
    path: P,
    /// Optional local alias (import foo as bar)
    alias: Option<I>,
    /// Optional domain-specific metadata
    value: Option<V>,
  },
}

impl<V: Value<I>, I: Ident, P: SymbolPath, S: Visibility> Symbol<V, I, P, S> {
  /// Returns the name identifier of the symbol if it has one.
  ///
  /// Not all symbols have names - values and scopes are anonymous.
  /// For Import, returns the alias if present (the local name).
  pub fn name(&self) -> Option<&I> {
    match self {
      | Self::Keyword { name, .. } => Some(name),
      | Self::Definition { name, .. } => Some(name),
      | Self::Value { .. } => None,
      | Self::Reference { name, .. } => name.as_ref(),
      | Self::Scope { .. } => None,
      | Self::Import { alias, .. } => alias.as_ref(),
    }
  }

  /// Returns the source span of just the symbol's name from the identifier.
  ///
  /// This is useful for rename operations where only the identifier
  /// should be modified. Note that this returns the span embedded in the
  /// Ident, not the symbol's location span (which is stored separately in
  /// index entries).
  pub fn name_span(&self) -> Option<laburnum::Span> {
    match self {
      | Self::Keyword { name, .. } => name.span(),
      | Self::Definition { name, .. } => name.span(),
      | Self::Reference { name, .. } => name.as_ref().and_then(|n| n.span()),
      | Self::Import { alias, .. } => alias.as_ref().and_then(|a| a.span()),
      // Scopes and Values are unnamed, so we MUST return none here
      | Self::Scope { .. } => None,
      | Self::Value { .. } => None,
    }
  }

  /// Checks whether this symbol contains a literal value.
  ///
  /// Returns true for Value symbols, Definition symbols with values,
  /// and Import symbols with values.
  pub fn has_value(&self) -> bool {
    matches!(
      self,
      Self::Value { .. }
        | Self::Definition { value: Some(_), .. }
        | Self::Import { value: Some(_), .. }
    )
  }

  /// Checks if this reference uses an absolute path.
  ///
  /// Absolute paths start from the root scope (e.g., ::std::string),
  /// while relative paths resolve from the current scope upward.
  pub fn is_absolute_reference(&self) -> bool {
    match self {
      | Self::Reference { is_absolute, .. } => *is_absolute,
      | _ => false,
    }
  }

  /// Returns a string representation of the symbol's structural kind.
  ///
  /// Used for debugging and error messages.
  pub fn kind(&self) -> &'static str {
    match self {
      | Self::Definition { .. } => "definition",
      | Self::Value { .. } => "value",
      | Self::Reference { .. } => "reference",
      | Self::Keyword { .. } => "keyword",
      | Self::Scope { .. } => "scope",
      | Self::Import { .. } => "import",
    }
  }

  /// Checks if this symbol participates in namespace lookups and resolution
  /// counting.
  ///
  /// Returns true for most symbols. Returns false for intermediate path segment
  /// references that exist only for IDE features (hover, go-to-definition).
  /// Non-nameable symbols are skipped by `unresolved_count()`.
  /// Imports are nameable only if they have an alias.
  pub fn is_nameable(&self) -> bool {
    match self {
      | Self::Reference { nameable, .. } => *nameable,
      | Self::Import { alias, .. } => alias.is_some(),
      | _ => true,
    }
  }

  /// Checks if this symbol is a reference.
  pub fn is_reference(&self) -> bool {
    matches!(self, Self::Reference { .. })
  }

  /// Checks if this symbol is an import.
  pub fn is_import(&self) -> bool {
    matches!(self, Self::Import { .. })
  }

  /// Returns the reference's own path in the index for Reference symbols.
  pub fn reference_path(&self) -> Option<&P> {
    match self {
      | Self::Reference { path, .. } => Some(path),
      | _ => None,
    }
  }

  /// Returns the target path for Reference symbols.
  pub fn target_path(&self) -> Option<&P> {
    match self {
      | Self::Reference { target_path, .. } => Some(target_path),
      | _ => None,
    }
  }

  /// Returns the import path for Import symbols.
  pub fn import_path(&self) -> Option<&P> {
    match self {
      | Self::Import { path, .. } => Some(path),
      | _ => None,
    }
  }
}

impl<V, I, P, S> bluegum::Bluegum for Symbol<V, I, P, S>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
  S: Visibility,
{
  fn node(&self, b: &mut bluegum::Builder) {
    match self {
      | Symbol::Keyword { .. } => {
        // `I` has no Debug; the resolver-based `node_with_state`
        // renders names, the stateless view shows a placeholder.
        b.name("Keyword").field("name", "<name>");
      },
      | Symbol::Value { value } => {
        b.name("Value");
        b.add_node("value", value);
      },
      | Symbol::Definition {
        value, visibility, ..
      } => {
        b.name("Definition")
          .field("name", "<name>")
          .field("visibility", format!("{visibility:?}"));
        if let Some(v) = value {
          b.add_node("value", v);
        }
      },
      | Symbol::Reference {
        path: _,
        name,
        target_path: _,
        is_absolute,
        nameable,
      } => {
        b.name("Reference")
          .field("path", "<path>")
          .field("target_path", "<path>")
          .field("is_absolute", is_absolute)
          .field("nameable", nameable);
        if name.is_some() {
          b.field("name", "<name>");
        }
      },
      | Symbol::Scope { value } => {
        b.name("Scope");
        if let Some(v) = value {
          b.add_node("value", v);
        }
      },
      | Symbol::Import { path: _, alias, value } => {
        b.name("Import").field("path", "<path>");
        if alias.is_some() {
          b.field("alias", "<alias>");
        }
        if let Some(v) = value {
          b.add_node("value", v);
        }
      },
    }
  }
}

impl<V, I, P, S> bluegum::BluegumWithState<dyn laburnum::SourceResolver>
  for Symbol<V, I, P, S>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
  S: Visibility,
{
  fn node_with_state(
    &self,
    b: &mut bluegum::Builder,
    state: &dyn laburnum::SourceResolver,
  ) {
    let resolve_ident = |ident: &I| -> String {
      // `I` has no Debug; when the span can't be resolved to source
      // text, fall back to a placeholder rather than a derived form.
      ident
        .span()
        .and_then(|s| state.try_resolve_span(&s))
        .unwrap_or_else(|| "<unresolved>".to_string())
    };

    match self {
      | Symbol::Keyword { name } => {
        b.name("Keyword").field("name", resolve_ident(name));
      },
      | Symbol::Value { value } => {
        b.name("Value");
        b.add_node("value", value);
      },
      | Symbol::Definition {
        name,
        value,
        visibility,
      } => {
        b.name("Definition")
          .field("name", resolve_ident(name))
          .field("visibility", format!("{visibility:?}"));
        if let Some(v) = value {
          b.add_node("value", v);
        }
      },
      | Symbol::Reference {
        path,
        name,
        target_path,
        is_absolute,
        nameable,
      } => {
        b.name("Reference")
          .field("path", laburnum::PartitionSortKey::resolve(path, state))
          .field(
            "target_path",
            laburnum::PartitionSortKey::resolve(target_path, state),
          )
          .field("is_absolute", is_absolute)
          .field("nameable", nameable);
        if let Some(n) = name {
          b.field("name", resolve_ident(n));
        }
      },
      | Symbol::Scope { value } => {
        b.name("Scope");
        if let Some(v) = value {
          b.add_node("value", v);
        }
      },
      | Symbol::Import { path, alias, value } => {
        b.name("Import")
          .field("path", laburnum::PartitionSortKey::resolve(path, state));
        if let Some(a) = alias {
          b.field("alias", resolve_ident(a));
        }
        if let Some(v) = value {
          b.add_node("value", v);
        }
      },
    }
  }
}

/// Symbol shapes don't reference other records - they only contain data.
impl<V, I, P, S, Ps> laburnum::record::CollectReferences<Ps>
  for Symbol<V, I, P, S>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
  S: Visibility,
  Ps: laburnum::database::storage::Partitions,
{
  // Default no-op implementation is correct
}

#[cfg(test)]
mod tests {
  use super::*;
  use crate::test_helpers::*;

  // -- Definition variant ---------------------------------------------------

  #[test]
  fn definition_name() {
    let sym = make_definition("foo", None, SymbolVisibility::Private);
    let name = sym.name();
    assert!(name.is_some());
    assert_eq!(name.map(|n| n.as_str()), Some("foo"));
  }

  #[test]
  fn definition_kind() {
    let sym = make_definition("foo", None, SymbolVisibility::Private);
    assert_eq!(sym.kind(), "definition");
  }

  #[test]
  fn definition_has_value_with_value() {
    let sym =
      make_definition("foo", Some(DefaultValue::Integer(1)), SymbolVisibility::Private);
    assert!(sym.has_value());
  }

  #[test]
  fn definition_has_value_without_value() {
    let sym = make_definition("foo", None, SymbolVisibility::Private);
    assert!(!sym.has_value());
  }

  #[test]
  fn definition_is_nameable() {
    let sym = make_definition("foo", None, SymbolVisibility::Private);
    assert!(sym.is_nameable());
  }

  #[test]
  fn definition_visibility() {
    let public =
      make_definition("foo", None, SymbolVisibility::Public);
    let private =
      make_definition("bar", None, SymbolVisibility::Private);
    match &public {
      | Symbol::Definition { visibility, .. } => {
        assert_eq!(*visibility, SymbolVisibility::Public);
      },
      | _ => panic!("expected Definition"),
    }
    match &private {
      | Symbol::Definition { visibility, .. } => {
        assert_eq!(*visibility, SymbolVisibility::Private);
      },
      | _ => panic!("expected Definition"),
    }
  }

  // -- Reference variant ----------------------------------------------------

  #[test]
  fn reference_name_some() {
    let sym = make_reference("ref|r", Some("r"), "target", false, true);
    let name = sym.name();
    assert!(name.is_some());
    assert_eq!(name.map(|n| n.as_str()), Some("r"));
  }

  #[test]
  fn reference_name_none() {
    let sym = make_reference("ref|anon", None, "target", false, true);
    assert!(sym.name().is_none());
  }

  #[test]
  fn reference_kind() {
    let sym = make_reference("ref|r", Some("r"), "target", false, true);
    assert_eq!(sym.kind(), "reference");
  }

  #[test]
  fn reference_is_reference() {
    let sym = make_reference("ref|r", Some("r"), "target", false, true);
    assert!(sym.is_reference());
  }

  #[test]
  fn reference_target_path() {
    let sym = make_reference("ref|r", Some("r"), "my::path", false, true);
    let path = sym.target_path();
    assert!(path.is_some());
    assert_eq!(path, Some(&"my::path".to_string()));
  }

  #[test]
  fn reference_is_absolute() {
    let absolute = make_reference("ref|r", Some("r"), "t", true, true);
    let relative = make_reference("ref|r", Some("r"), "t", false, true);
    assert!(absolute.is_absolute_reference());
    assert!(!relative.is_absolute_reference());
  }

  #[test]
  fn reference_nameable() {
    let nameable = make_reference("ref|r", Some("r"), "t", false, true);
    let not_nameable = make_reference("ref|r", Some("r"), "t", false, false);
    assert!(nameable.is_nameable());
    assert!(!not_nameable.is_nameable());
  }

  // -- Keyword variant ------------------------------------------------------

  #[test]
  fn keyword_name() {
    let sym = make_keyword("if");
    let name = sym.name();
    assert!(name.is_some());
    assert_eq!(name.map(|n| n.as_str()), Some("if"));
  }

  #[test]
  fn keyword_kind() {
    let sym = make_keyword("if");
    assert_eq!(sym.kind(), "keyword");
  }

  #[test]
  fn keyword_is_nameable() {
    let sym = make_keyword("if");
    assert!(sym.is_nameable());
  }

  // -- Value variant --------------------------------------------------------

  #[test]
  fn value_kind() {
    let sym = make_value(DefaultValue::Integer(42));
    assert_eq!(sym.kind(), "value");
  }

  #[test]
  fn value_has_value() {
    let sym = make_value(DefaultValue::Integer(42));
    assert!(sym.has_value());
  }

  #[test]
  fn value_name_is_none() {
    let sym = make_value(DefaultValue::Integer(42));
    assert!(sym.name().is_none());
  }

  // -- Scope variant --------------------------------------------------------

  #[test]
  fn scope_kind() {
    let sym = make_scope(None);
    assert_eq!(sym.kind(), "scope");
  }

  #[test]
  fn scope_name_is_none() {
    let sym = make_scope(None);
    assert!(sym.name().is_none());
  }

  #[test]
  fn scope_has_value() {
    let with_value = make_scope(Some(DefaultValue::Boolean(true)));
    let without_value = make_scope(None);
    assert!(!with_value.has_value());
    assert!(!without_value.has_value());
  }

  // -- Import variant -------------------------------------------------------

  #[test]
  fn import_kind() {
    let sym = make_import("std::io", Some("io"), None);
    assert_eq!(sym.kind(), "import");
  }

  #[test]
  fn import_is_import() {
    let sym = make_import("std::io", Some("io"), None);
    assert!(sym.is_import());
  }

  #[test]
  fn import_path() {
    let sym = make_import("std::io", Some("io"), None);
    let path = sym.import_path();
    assert!(path.is_some());
    assert_eq!(path, Some(&"std::io".to_string()));
  }

  #[test]
  fn import_name_returns_alias() {
    let sym = make_import("std::io", Some("io"), None);
    let name = sym.name();
    assert!(name.is_some());
    assert_eq!(name.map(|n| n.as_str()), Some("io"));
  }

  #[test]
  fn import_name_none_without_alias() {
    let sym = make_import("std::io", None, None);
    assert!(sym.name().is_none());
  }

  #[test]
  fn import_is_nameable_with_alias() {
    let sym = make_import("std::io", Some("io"), None);
    assert!(sym.is_nameable());
  }

  #[test]
  fn import_is_nameable_without_alias() {
    let sym = make_import("std::io", None, None);
    assert!(!sym.is_nameable());
  }

  // -- Cross-cutting --------------------------------------------------------

  #[test]
  fn non_reference_is_reference_false() {
    let sym = make_definition("foo", None, SymbolVisibility::Private);
    assert!(!sym.is_reference());
  }

  #[test]
  fn non_import_is_import_false() {
    let sym = make_definition("foo", None, SymbolVisibility::Private);
    assert!(!sym.is_import());
  }

  #[test]
  fn non_reference_target_path_none() {
    let sym = make_definition("foo", None, SymbolVisibility::Private);
    assert!(sym.target_path().is_none());
  }

  #[test]
  fn non_import_import_path_none() {
    let sym = make_definition("foo", None, SymbolVisibility::Private);
    assert!(sym.import_path().is_none());
  }

  // -- DefaultValue ---------------------------------------------------------

  #[test]
  fn default_value_string() {
    let v = DefaultValue::String("hello".to_string());
    assert_eq!(v, DefaultValue::String("hello".to_string()));
  }

  #[test]
  fn default_value_integer() {
    let v = DefaultValue::Integer(42);
    assert_eq!(v, DefaultValue::Integer(42));
  }

  #[test]
  fn default_value_float() {
    let v = DefaultValue::Float(2.72);
    assert_eq!(v, DefaultValue::Float(2.72));
  }

  #[test]
  fn default_value_boolean() {
    let v = DefaultValue::Boolean(true);
    assert_eq!(v, DefaultValue::Boolean(true));
  }

  #[test]
  fn default_value_span_is_none() {
    let values = [
      DefaultValue::String("hello".to_string()),
      DefaultValue::Integer(42),
      DefaultValue::Float(2.72),
      DefaultValue::Boolean(true),
    ];
    for v in &values {
      assert!(
        Value::<crate::StringIdent>::span(v).is_none(),
        "span() should be None for {v:?}"
      );
    }
  }

  // -- SymbolVisibility -----------------------------------------------------

  #[test]
  fn visibility_default_is_private() {
    assert_eq!(SymbolVisibility::default(), SymbolVisibility::Private);
  }
}
