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

//! Error types for symbol table operations.
//!
//! This module defines the error types used throughout the symbol engine,
//! providing structured error reporting with rich context and suggestions
//! for recovery.
//!
//! # Error Categories
//!
//! - **Symbol Errors**: Issues with symbol creation, lookup, and manipulation
//! - **Diagnostic Errors**: Rich errors with source location and suggestions
//! - **Resolution Errors**: Problems during name resolution and reference
//!   following
//!
//! # Design Philosophy
//!
//! Errors are designed to be:
//! - **Informative**: Clear messages with context about what went wrong
//! - **Actionable**: Include suggestions for how to fix the problem
//! - **Recoverable**: Enable continued analysis even after errors occur
//! - **IDE-friendly**: Support rich error reporting in development tools

use crate::{
    core::{Ident, SymbolPath, SymbolVisibility, Value, Visibility},
    partitions::SymbolEntry,
  };

/// Severity level of a diagnostic error.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Severity {
  /// Critical issues that prevent successful compilation.
  Error,

  /// Potential problems that don't prevent compilation.
  Warning,

  /// Informational messages for user awareness.
  Info,
}

impl Severity {
  /// Check if this severity represents a compilation error.
  pub fn is_error(&self) -> bool {
    matches!(self, Severity::Error)
  }

  /// Get a string representation suitable for display.
  pub fn as_str(&self) -> &'static str {
    match self {
      | Severity::Error => "error",
      | Severity::Warning => "warning",
      | Severity::Info => "info",
    }
  }
}

/// A diagnostic error in the symbol table.
///
/// Represents a rich error with source location, context, and actionable
/// suggestions. Designed to provide excellent error messages for developers
/// using languages built with symbolique.
///
/// # Rich Error Reporting
///
/// This type supports the diagnostic error pattern where errors include:
/// - Primary error message explaining what went wrong
/// - Source location for precise error positioning
/// - Additional context explaining why it's a problem
/// - Actionable suggestions for how to fix it
///
/// # Examples
///
/// ```rust,ignore
/// let error = Error::new(
///     "Variable 'count' not found".to_string(),
///     variable_span,
/// )
/// .with_context("Variables must be declared before use".to_string())
/// .with_suggestion("Did you mean 'counter'?".to_string())
/// .with_suggestion("Add 'let count = 0;' before this line".to_string());
/// ```
#[derive(Debug, Clone)]
pub struct Error {
  /// Primary error message.
  ///
  /// Should be concise but descriptive, explaining what went wrong
  /// without assuming too much context.
  pub message: String,

  /// Source location where the error occurred.
  ///
  /// Enables precise error highlighting in IDEs and error reporting
  /// tools that can show the exact problematic code.
  pub span: Option<laburnum::Span>,

  /// Error severity level.
  ///
  /// Determines how the error should be presented to users and
  /// whether it should block compilation.
  pub severity: Severity,

  /// Additional context information.
  ///
  /// Provides background information that helps users understand
  /// why something is an error and what the rules or constraints are.
  pub context: Vec<String>,

  /// Suggested fixes or alternatives.
  ///
  /// Actionable suggestions that help users resolve the error.
  /// Should be specific and concrete when possible.
  pub suggestions: Vec<String>,
}

impl Error {
  /// Create a new error with default severity.
  ///
  /// Creates an error with `Severity::Error` and empty context/suggestions.
  /// Use the builder methods to add additional information.
  pub fn new(message: String, span: Option<laburnum::Span>) -> Self {
    Self {
      message,
      span,
      severity: Severity::Error,
      context: Vec::new(),
      suggestions: Vec::new(),
    }
  }

  /// Set the severity level.
  ///
  /// Builder method to change the error severity from the default `Error`.
  pub fn with_severity(mut self, severity: Severity) -> Self {
    self.severity = severity;
    self
  }

  /// Add contextual information.
  ///
  /// Builder method to add background information that helps explain
  /// the error. Context should explain rules, constraints, or relevant
  /// language semantics.
  pub fn with_context(mut self, context: String) -> Self {
    self.context.push(context);
    self
  }

  /// Add a suggestion for fixing the error.
  ///
  /// Builder method to add actionable suggestions. Suggestions should be
  /// specific and concrete, ideally showing exact code changes when possible.
  pub fn with_suggestion(mut self, suggestion: String) -> Self {
    self.suggestions.push(suggestion);
    self
  }

  /// Check if this error has any suggestions.
  pub fn has_suggestions(&self) -> bool {
    !self.suggestions.is_empty()
  }

  /// Check if this error has context information.
  pub fn has_context(&self) -> bool {
    !self.context.is_empty()
  }

  /// Check if this is an error-level diagnostic.
  pub fn is_error(&self) -> bool {
    self.severity.is_error()
  }
}

/// Specific error types for symbol operations.
///
/// Enumeration of the various error conditions that can occur during
/// symbol table operations. These are typically converted to `Error<S>`
/// for rich diagnostic reporting.
///
/// # Type Parameters
///
/// - `V`: Value type implementing [`Value`]
/// - `I`: Identifier type implementing [`Ident`]
/// - `P`: Symbol path type implementing [`SymbolPath`]
///
/// # Error Categories
///
/// - **Lookup Errors**: Symbol not found, wrong scope
/// - **Creation Errors**: Symbol already exists, invalid name
/// - **Type Errors**: Wrong symbol kind for operation
/// - **Reference Errors**: Circular references, invalid targets
///
/// # Usage Pattern
///
/// ```rust,ignore
/// match engine.create_symbol(symbol) {
///     Ok(id) => id,
///     Err(SymbolError::AlreadyExists { name, existing }) => {
///         // Handle duplicate symbol error
///         return Err(Error::new(
///             format!("Symbol '{}' already exists", name),
///             symbol.span,
///         ).with_suggestion("Use a different name".to_string()));
///     }
///     Err(other) => return Err(other.into()),
/// }
/// ```
pub struct SymbolError<V, I, P, S = SymbolVisibility>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
  S: Visibility,
{
  kind: SymbolErrorKind<V, I, P, S>,
}

impl<V, I, P, S> SymbolError<V, I, P, S>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
  S: Visibility,
{
  /// Create a new SymbolError from an error kind.
  pub fn new(kind: SymbolErrorKind<V, I, P, S>) -> Self {
    Self { kind }
  }

  /// Get the error kind.
  pub fn kind(&self) -> &SymbolErrorKind<V, I, P, S> {
    &self.kind
  }

  /// Get a human-readable description of the error.
  pub fn description(&self) -> String {
    self.kind.description()
  }

  /// Check if this error is recoverable.
  pub fn is_recoverable(&self) -> bool {
    self.kind.is_recoverable()
  }
}

impl<V, I, P, S> std::fmt::Debug for SymbolError<V, I, P, S>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
  S: Visibility,
{
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    // SymbolErrorKind embeds path keys which have no Debug; render
    // its own resolver-free description instead.
    f.debug_struct("SymbolError")
      .field("kind", &self.kind.description())
      .finish()
  }
}

impl<V, I, P, S> Clone for SymbolError<V, I, P, S>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
  S: Visibility,
{
  fn clone(&self) -> Self {
    Self {
      kind: self.kind.clone(),
    }
  }
}

/// The specific kind of symbol error.
pub enum SymbolErrorKind<V, I, P, S = SymbolVisibility>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
  S: Visibility,
{
  /// Symbol not found during lookup.
  ///
  /// Occurs when trying to resolve a name that doesn't exist in any
  /// accessible scope.
  NotFound {
    /// The name that couldn't be found
    name: String,
    /// The scope where lookup was attempted
    scope_name: String,
  },

  /// Symbol already exists in the same scope.
  ///
  /// Occurs when trying to create a symbol with a name that's already
  /// in use within the same scope.
  AlreadyExists {
    /// The conflicting name
    name: String,
    /// ID of the existing symbol
    existing: SymbolEntry<V, I, P, S>,
  },

  /// Symbol exists but has wrong kind for the operation.
  ///
  /// Occurs when a symbol is found but doesn't match the expected type
  /// (e.g., trying to call a variable as a function).
  WrongKind {
    /// The symbol name
    name: String,
    /// Expected symbol kinds
    expected: Vec<String>,
    /// Actual symbol kind found
    found: String,
  },

  /// Circular reference detected in symbol chain.
  ///
  /// Occurs when following references creates a cycle, which would
  /// cause infinite loops during resolution.
  CircularReference {
    /// The reference chain that forms the cycle
    chain: Vec<String>,
  },

  /// Invalid reference relationship.
  ///
  /// Occurs when trying to create a reference that violates language
  /// rules or semantic constraints.
  InvalidReference {
    /// Source symbol name
    from: String,
    /// Target symbol name
    to: String,
    /// Explanation of why the reference is invalid
    reason: String,
  },

  /// Invalid scope for symbol creation.
  ///
  /// Occurs when trying to create a symbol in a scope that doesn't exist.
  SymboldNotAllowedInScope {
    /// The invalid scope ID
    scope_kind: String,
    name: String,
    reason: String,
  },

  /// Invalid symbol fields.
  ///
  /// Occurs when a symbol doesn't have required fields or has
  /// invalid field values according to language rules.
  InvalidFields {
    name: String,
    reason: String,
  },

  InvalidSymbolId(SymbolEntry<V, I, P, S>),

  SourceKeyMismatch {
    expected: laburnum::SourceKey,
    actual: laburnum::SourceKey,
  },
}

impl<V, I, P, S> Clone for SymbolErrorKind<V, I, P, S>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
  S: Visibility,
{
  fn clone(&self) -> Self {
    match self {
      | Self::NotFound { name, scope_name } => Self::NotFound {
        name: name.clone(),
        scope_name: scope_name.clone(),
      },
      | Self::AlreadyExists { name, existing } => Self::AlreadyExists {
        name: name.clone(),
        existing: existing.clone(),
      },
      | Self::WrongKind {
        name,
        expected,
        found,
      } => Self::WrongKind {
        name: name.clone(),
        expected: expected.clone(),
        found: found.clone(),
      },
      | Self::CircularReference { chain } => Self::CircularReference {
        chain: chain.clone(),
      },
      | Self::InvalidReference { from, to, reason } => Self::InvalidReference {
        from: from.clone(),
        to: to.clone(),
        reason: reason.clone(),
      },
      | Self::SymboldNotAllowedInScope {
        scope_kind,
        name,
        reason,
      } => Self::SymboldNotAllowedInScope {
        scope_kind: scope_kind.clone(),
        name: name.clone(),
        reason: reason.clone(),
      },
      | Self::InvalidFields { name, reason } => Self::InvalidFields {
        name: name.clone(),
        reason: reason.clone(),
      },
      | Self::InvalidSymbolId(id) => Self::InvalidSymbolId(id.clone()),
      | Self::SourceKeyMismatch { expected, actual } => {
        Self::SourceKeyMismatch {
          expected: *expected,
          actual: *actual,
        }
      },
    }
  }
}

impl<V, I, P, S> SymbolErrorKind<V, I, P, S>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
  S: Visibility,
{
  /// Get a human-readable description of the error.
  pub fn description(&self) -> String {
    match self {
      | SymbolErrorKind::NotFound { name, scope_name } => {
        format!("Symbol '{}' not found in scope '{}'", name, scope_name)
      },
      | SymbolErrorKind::AlreadyExists { name, .. } => {
        format!("Symbol '{}' already exists", name)
      },
      | SymbolErrorKind::WrongKind {
        name,
        expected,
        found,
      } => {
        if expected.len() == 1 {
          format!(
            "Symbol '{}' is {} but expected {}",
            name, found, expected[0]
          )
        } else {
          format!(
            "Symbol '{}' is {} but expected one of: {}",
            name,
            found,
            expected.join(", ")
          )
        }
      },
      | SymbolErrorKind::CircularReference { chain } => {
        format!("Circular reference detected: {}", chain.join(" -> "))
      },
      | SymbolErrorKind::InvalidReference { from, to, reason } => {
        format!("Invalid reference from '{}' to '{}': {}", from, to, reason)
      },
      | SymbolErrorKind::SymboldNotAllowedInScope {
        scope_kind,
        name,
        reason,
      } => {
        format!(
          "Symbol '{}' is not allowed in scope '{}': {}",
          name, scope_kind, reason
        )
      },
      | SymbolErrorKind::InvalidFields { name, reason } => {
        format!("Invalid fields for symbol '{}': {}", name, reason)
      },
      | SymbolErrorKind::InvalidSymbolId(id) => {
        // No resolver here and the path key has no string form; the
        // record's content hash is its stable identity.
        format!("Invalid symbol ID: {:?}", id.symbol.content_hash())
      },
      | SymbolErrorKind::SourceKeyMismatch { expected, actual } => {
        format!("Source key mismatch: expected {}, got {}", expected, actual)
      },
    }
  }

  /// Check if this error is recoverable.
  ///
  /// Some errors allow continued processing while others require stopping.
  pub fn is_recoverable(&self) -> bool {
    matches!(
      self,
      SymbolErrorKind::NotFound { .. }
        | SymbolErrorKind::WrongKind { .. }
        | SymbolErrorKind::InvalidReference { .. }
    )
  }
}

// Convenience constructors for SymbolError
impl<V, I, P, S> SymbolError<V, I, P, S>
where
  V: Value<I>,
  I: Ident,
  P: SymbolPath,
  S: Visibility,
{
  /// Create a NotFound error.
  pub fn not_found(name: String, scope_name: String) -> Self {
    Self::new(SymbolErrorKind::NotFound { name, scope_name })
  }

  /// Create an AlreadyExists error.
  pub fn already_exists(
    name: String,
    existing: SymbolEntry<V, I, P, S>,
  ) -> Self {
    Self::new(SymbolErrorKind::AlreadyExists { name, existing })
  }

  /// Create a WrongKind error.
  pub fn wrong_kind(
    name: String,
    expected: Vec<String>,
    found: String,
  ) -> Self {
    Self::new(SymbolErrorKind::WrongKind {
      name,
      expected,
      found,
    })
  }

  /// Create a CircularReference error.
  pub fn circular_reference(chain: Vec<String>) -> Self {
    Self::new(SymbolErrorKind::CircularReference { chain })
  }

  /// Create an InvalidReference error.
  pub fn invalid_reference(from: String, to: String, reason: String) -> Self {
    Self::new(SymbolErrorKind::InvalidReference { from, to, reason })
  }

  /// Create a SymbolNotAllowedInScope error.
  pub fn symbol_not_allowed_in_scope(
    scope_kind: String,
    name: String,
    reason: String,
  ) -> Self {
    Self::new(SymbolErrorKind::SymboldNotAllowedInScope {
      scope_kind,
      name,
      reason,
    })
  }

  /// Create an InvalidFields error.
  pub fn invalid_fields(name: String, reason: String) -> Self {
    Self::new(SymbolErrorKind::InvalidFields { name, reason })
  }

  /// Create an InvalidSymbolId error.
  pub fn invalid_symbol_id(id: SymbolEntry<V, I, P, S>) -> Self {
    Self::new(SymbolErrorKind::InvalidSymbolId(id))
  }

  /// Create a SourceKeyMismatch error.
  pub fn source_key_mismatch(
    expected: laburnum::SourceKey,
    actual: laburnum::SourceKey,
  ) -> Self {
    Self::new(SymbolErrorKind::SourceKeyMismatch { expected, actual })
  }
}

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

  // -- Severity tests ------------------------------------------------------

  #[test]
  fn severity_is_error() {
    assert!(Severity::Error.is_error());
    assert!(!Severity::Warning.is_error());
    assert!(!Severity::Info.is_error());
  }

  #[test]
  fn severity_as_str() {
    assert_eq!(Severity::Error.as_str(), "error");
    assert_eq!(Severity::Warning.as_str(), "warning");
    assert_eq!(Severity::Info.as_str(), "info");
  }

  // -- Error builder tests -------------------------------------------------

  #[test]
  fn error_new_defaults_to_error_severity() {
    let err = Error::new("test".to_string(), None);
    assert_eq!(err.severity, Severity::Error);
  }

  #[test]
  fn error_with_severity() {
    let err =
      Error::new("test".to_string(), None).with_severity(Severity::Warning);
    assert_eq!(err.severity, Severity::Warning);
  }

  #[test]
  fn error_with_context() {
    let err = Error::new("test".to_string(), None)
      .with_context("ctx1".to_string())
      .with_context("ctx2".to_string());
    assert_eq!(err.context.len(), 2);
    assert_eq!(err.context[0], "ctx1");
    assert_eq!(err.context[1], "ctx2");
  }

  #[test]
  fn error_with_suggestion() {
    let err = Error::new("test".to_string(), None)
      .with_suggestion("fix1".to_string())
      .with_suggestion("fix2".to_string());
    assert_eq!(err.suggestions.len(), 2);
    assert_eq!(err.suggestions[0], "fix1");
    assert_eq!(err.suggestions[1], "fix2");
  }

  #[test]
  fn error_has_suggestions() {
    let err = Error::new("test".to_string(), None);
    assert!(!err.has_suggestions());
    let err = err.with_suggestion("fix".to_string());
    assert!(err.has_suggestions());
  }

  #[test]
  fn error_has_context() {
    let err = Error::new("test".to_string(), None);
    assert!(!err.has_context());
    let err = err.with_context("ctx".to_string());
    assert!(err.has_context());
  }

  // -- SymbolErrorKind description tests -----------------------------------

  #[test]
  fn not_found_description() {
    let err = SymbolError::<DV, SI, TP>::not_found(
      "myVar".to_string(),
      "global".to_string(),
    );
    let desc = err.description();
    assert!(desc.contains("myVar"));
    assert!(desc.contains("global"));
  }

  #[test]
  fn already_exists_description() {
    let mut cache = test_span_cache();
    let entry = dummy_symbol_entry(&mut cache);
    let err = SymbolError::<DV, SI, TP>::already_exists(
      "duplicate".to_string(),
      entry,
    );
    let desc = err.description();
    assert!(desc.contains("duplicate"));
  }

  #[test]
  fn wrong_kind_description() {
    let err = SymbolError::<DV, SI, TP>::wrong_kind(
      "foo".to_string(),
      vec!["function".to_string()],
      "variable".to_string(),
    );
    let desc = err.description();
    assert!(desc.contains("foo"));
    assert!(desc.contains("variable"));
    assert!(desc.contains("function"));
  }

  #[test]
  fn circular_reference_description() {
    let err = SymbolError::<DV, SI, TP>::circular_reference(vec![
      "a".to_string(),
      "b".to_string(),
      "c".to_string(),
    ]);
    let desc = err.description();
    assert!(desc.contains("a -> b -> c"));
  }

  #[test]
  fn invalid_reference_description() {
    let err = SymbolError::<DV, SI, TP>::invalid_reference(
      "src".to_string(),
      "dst".to_string(),
      "type mismatch".to_string(),
    );
    let desc = err.description();
    assert!(desc.contains("src"));
    assert!(desc.contains("dst"));
    assert!(desc.contains("type mismatch"));
  }

  #[test]
  fn symbol_not_allowed_in_scope_description() {
    let err = SymbolError::<DV, SI, TP>::symbol_not_allowed_in_scope(
      "block".to_string(),
      "myFn".to_string(),
      "functions cannot be defined here".to_string(),
    );
    let desc = err.description();
    assert!(desc.contains("myFn"));
    assert!(desc.contains("block"));
    assert!(desc.contains("functions cannot be defined here"));
  }

  #[test]
  fn invalid_fields_description() {
    let err = SymbolError::<DV, SI, TP>::invalid_fields(
      "widget".to_string(),
      "missing required field".to_string(),
    );
    let desc = err.description();
    assert!(desc.contains("widget"));
    assert!(desc.contains("missing required field"));
  }

  #[test]
  fn invalid_symbol_id_description() {
    let mut cache = test_span_cache();
    let entry = dummy_symbol_entry(&mut cache);
    let err = SymbolError::<DV, SI, TP>::invalid_symbol_id(entry);
    let desc = err.description();
    assert!(desc.contains("Invalid symbol ID"));
  }

  #[test]
  fn source_key_mismatch_description() {
    let expected = laburnum::SourceKey::new(1, 0);
    let actual = laburnum::SourceKey::new(2, 0);
    let err =
      SymbolError::<DV, SI, TP>::source_key_mismatch(expected, actual);
    let desc = err.description();
    let expected_str = format!("{}", expected);
    let actual_str = format!("{}", actual);
    assert!(desc.contains(&expected_str));
    assert!(desc.contains(&actual_str));
  }

  // -- Recoverability tests ------------------------------------------------

  #[test]
  fn recoverable_errors() {
    let not_found = SymbolError::<DV, SI, TP>::not_found(
      "x".to_string(),
      "s".to_string(),
    );
    assert!(not_found.is_recoverable());

    let wrong_kind = SymbolError::<DV, SI, TP>::wrong_kind(
      "x".to_string(),
      vec!["a".to_string()],
      "b".to_string(),
    );
    assert!(wrong_kind.is_recoverable());

    let invalid_ref = SymbolError::<DV, SI, TP>::invalid_reference(
      "a".to_string(),
      "b".to_string(),
      "r".to_string(),
    );
    assert!(invalid_ref.is_recoverable());
  }

  #[test]
  fn non_recoverable_errors() {
    let mut cache = test_span_cache();
    let entry = dummy_symbol_entry(&mut cache);

    let already_exists = SymbolError::<DV, SI, TP>::already_exists(
      "x".to_string(),
      entry.clone(),
    );
    assert!(!already_exists.is_recoverable());

    let circular = SymbolError::<DV, SI, TP>::circular_reference(vec![
      "a".to_string(),
    ]);
    assert!(!circular.is_recoverable());

    let invalid_fields = SymbolError::<DV, SI, TP>::invalid_fields(
      "x".to_string(),
      "r".to_string(),
    );
    assert!(!invalid_fields.is_recoverable());

    let invalid_id =
      SymbolError::<DV, SI, TP>::invalid_symbol_id(entry);
    assert!(!invalid_id.is_recoverable());

    let source_mismatch = SymbolError::<DV, SI, TP>::source_key_mismatch(
      laburnum::SourceKey::new(1, 0),
      laburnum::SourceKey::new(2, 0),
    );
    assert!(!source_mismatch.is_recoverable());

    let not_allowed =
      SymbolError::<DV, SI, TP>::symbol_not_allowed_in_scope(
        "s".to_string(),
        "n".to_string(),
        "r".to_string(),
      );
    assert!(!not_allowed.is_recoverable());
  }
}
