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

use proc_macro2::Span;

#[derive(Debug)]
pub struct ErrorDetails {
  pub span:    Span,
  pub message: String,
  pub help:    Option<String>,
  pub hints:   Option<String>,
}

impl ErrorDetails {
  pub fn new<S: AsRef<str>>(span: Span, message: S) -> Self {
    Self {
      span,
      message: message.as_ref().to_string(),
      help: None,
      hints: None,
    }
  }

  #[allow(dead_code)]
  pub fn with_help<S: AsRef<str>>(mut self, help: S) -> Self {
    self.help = Some(help.as_ref().to_string());
    self
  }

  #[allow(dead_code)]
  pub fn with_hints<S: AsRef<str>>(mut self, hints: S) -> Self {
    self.hints = Some(hints.as_ref().to_string());
    self
  }
}

#[derive(Debug)]
#[allow(dead_code)]
pub enum Error {
  Unknown(Span),
  // parse
  Parser(Span),
  ParseItemNotStruct(Span),
  MissingFieldIdentifier(Span),
  EmptyTypePath(Span),
  UnsupportedFieldType(Span, String),
  // codegen
  ReservedKeywordFieldName(Span, String, String),
  UnknownFieldType(Span, String),
  MissingRequiredSpanField(Span),
  InvalidFieldNamingConvention(Span, String, String),
  // AST
  InvalidFieldType(Span),
  InvalidFieldTypeMessage(Span, String),

  // fn_args
  InvalidPropsArgNotNamedIdent(Span),
  InvalidPropsFoundSelf(Span),

  // fn_body::parse_hook_statement
  UseStateExpectsTuple(Span),
  UseStateMalformedSetName(Span, String, String),

  // component_expr
  ComponentExprExpectedPropsStruct(Span),
  ComponentExprUnexpectedToken(Span),
}

impl Error {
  pub fn get(&self) -> ErrorDetails {
    match self {
      | Self::Unknown(span) => ErrorDetails::new(*span, "unknown error"),
      // parse
      | Self::Parser(span) => {
        ErrorDetails {
          span:    *span,
          message: "failed to parse the syntax tree".to_string(),
          help:    Some(
            "Check that your Rust syntax is valid and the struct is well-formed".to_string(),
          ),
          hints:   None,
        }
      },
      | Self::ParseItemNotStruct(span) => {
        ErrorDetails {
          span:    *span,
          message: "#[laburnum_syntax] can only be applied to structs".to_string(),
          help:    Some(
            "This attribute is designed to work with struct definitions only".to_string(),
          ),
          hints:   Some(
            "Try using: #[laburnum_syntax(AST)] pub struct MyStruct { ... }".to_string(),
          ),
        }
      },
      | Self::MissingFieldIdentifier(span) => {
        ErrorDetails {
          span:    *span,
          message: "struct field must have a name".to_string(),
          help:    Some(
            "All fields in structs used with #[laburnum_syntax] must be named fields".to_string(),
          ),
          hints:   Some(
            "Example: pub struct MyStruct { field_name: NodeId<crate::Type> }".to_string(),
          ),
        }
      },
      | Self::EmptyTypePath(span) => {
        ErrorDetails {
          span:    *span,
          message: "type path cannot be empty".to_string(),
          help:    Some(
            "Type paths must contain at least one segment (e.g., `NodeId`, `Option`, `Vec`)".to_string(),
          ),
          hints:   Some(
            "Check that your field type is properly specified".to_string(),
          ),
        }
      },
      | Self::UnsupportedFieldType(span, type_name) => {
        ErrorDetails {
          span:    *span,
          message: format!("unsupported field type: {type_name}"),
          help:    Some(
            "Fields must use supported container types like NodeId, Field, Option, Vec, or EnumNodeId".to_string(),
          ),
          hints:   Some(
            "Supported types: NodeId<T>, Field<T>, Option<NodeId<T>>, Vec<T>, EnumNodeId<T>".to_string(),
          ),
        }
      },
      // codegen
      | Self::ReservedKeywordFieldName(span, field_name, keyword) => {
        ErrorDetails {
          span:    *span,
          message: format!(
            "field name '{field_name}' converts to reserved keyword '{keyword}' when converted to UpperCamelCase"
          ),
          help:    Some(
            "Choose a different field name that doesn't conflict with Rust keywords when converted to UpperCamelCase".to_string(),
          ),
          hints:   Some(
            "Try using a different name like 'my_field' instead of 'self_'".to_string(),
          ),
        }
      },
      | Self::UnknownFieldType(span, field_type) => {
        ErrorDetails {
          span:    *span,
          message: format!("unknown field type: {field_type}"),
          help:    Some(
            "This field type is not supported by the laburnum_syntax macro".to_string(),
          ),
          hints:   Some(
            "Supported field types: NodeId<T>, Field<T>, Option<T>, Vec<T>, EnumNodeId<T>, String".to_string(),
          ),
        }
      },
      | Self::MissingRequiredSpanField(span) => {
        ErrorDetails {
          span:    *span,
          message: "CST structs must have a 'span' field of type 'Span'".to_string(),
          help:    Some(
            "Add a 'span: Span' field to your CST struct".to_string(),
          ),
          hints:   Some(
            "Example: pub struct MyCst { span: Span, /* other fields */ }".to_string(),
          ),
        }
      },
      | Self::InvalidFieldNamingConvention(span, field_name, expected_suffix) => {
        ErrorDetails {
          span:    *span,
          message: format!(
            "field '{field_name}' must end with '{expected_suffix}' for this field type"
          ),
          help:    Some(
            "CST field naming conventions require specific suffixes based on field type".to_string(),
          ),
          hints:   Some(
            format!("Rename the field to '{field_name}{expected_suffix}' or similar"),
          ),
        }
      },
      // AST
      | Self::InvalidFieldType(span) => {
        ErrorDetails {
          span:    *span,
          message: "invalid field type - fields must use NodeId, Field, Vec, Option, or other supported containers".to_string(),
          help:    Some(
            "Supported field types include:\n  - NodeId<T>\n  - Field<T>\n  - Option<NodeId<T>>\n  - Vec<T>\n  - EnumNodeId<T>".to_string(),
          ),
          hints:   Some(
            "Example: field: NodeId<crate::Type> or field: Option<NodeId<crate::Type>>".to_string(),
          ),
        }
      },
      | Self::InvalidFieldTypeMessage(span, message) => {
        ErrorDetails {
          span:    *span,
          message: message.to_string(),
          help:    None,
          hints:   None,
        }
      },
      // fn_args
      | Self::InvalidPropsArgNotNamedIdent(span) => {
        ErrorDetails {
          span:    *span,
          message: "argument is not a named identifier".to_string(),
          help:    Some("Arguments must be simple named identifiers".to_string()),
          hints:   Some("Example: fn my_function(arg_name: Type) { ... }".to_string()),
        }
      },
      | Self::InvalidPropsFoundSelf(span) => {
        ErrorDetails {
          span:    *span,
          message: "Found self argument which is not allowed".to_string(),
          help:    Some(
            "The #[laburnum_syntax] macro generates methods that already have self. Remove any self parameter from your struct definition.".to_string(),
          ),
          hints:   None,
        }
      },
      // fn_body::parse_hook_statement
      | Self::UseStateExpectsTuple(span) => {
        ErrorDetails {
          span:    *span,
          message: "use_state expects a tuple pattern for destructuring".to_string(),
          help:    Some(
            "Use tuple destructuring to get both the state value and setter function".to_string(),
          ),
          hints:   Some(
            "Example: let (state, set_state) = use_state(initial_value);".to_string(),
          ),
        }
      },
      | Self::UseStateMalformedSetName(span, expected, got) => {
        ErrorDetails {
          span:    *span,
          message: format!(
            "use_state setter function should be named `{expected}` but found `{got}`"
          ),
          help:    Some(format!("Rename the second tuple element to `{expected}`")),
          hints:   Some(
            "The setter follows the convention set_<state_name>".to_string(),
          ),
        }
      },
      // component_expr
      | Self::ComponentExprExpectedPropsStruct(span) => {
        ErrorDetails {
          span:    *span,
          message: "expected a props struct but found something else".to_string(),
          help:    Some(
            "Component expressions require a props struct to pass data".to_string(),
          ),
          hints:   Some(
            "Example: MyComponent { prop1: value1, prop2: value2 }".to_string(),
          ),
        }
      },
      | Self::ComponentExprUnexpectedToken(span) => {
        ErrorDetails {
          span:    *span,
          message: "unexpected token in component expression".to_string(),
          help:    Some(
            "Check the syntax of your component expression".to_string(),
          ),
          hints:   Some(
            "Component expressions should follow the pattern: ComponentName { props }".to_string(),
          ),
        }
      },
    }
  }
}

/// Utility for accumulating multiple validation errors before returning them
/// This allows users to see all validation issues at once instead of fixing
/// them one by one
#[derive(Debug, Default)]
pub struct ErrorAccumulator {
  errors: Vec<syn::Error>,
}

#[allow(dead_code)]
impl ErrorAccumulator {
  /// Create a new empty error accumulator
  pub fn new() -> Self {
    Self { errors: Vec::new() }
  }

  /// Add an error using our custom Error type
  pub fn add_error(&mut self, error: Error) {
    let details = error.get();
    let mut syn_error = syn::Error::new(details.span, &details.message);

    // Add help message if available
    if let Some(help) = &details.help {
      let help_error = syn::Error::new(details.span, format!("help: {help}"));
      syn_error.combine(help_error);
    }

    // Add hints if available
    if let Some(hints) = &details.hints {
      let hints_error = syn::Error::new(details.span, format!("hint: {hints}"));
      syn_error.combine(hints_error);
    }

    self.errors.push(syn_error);
  }

  /// Add an error directly from syn::Error
  pub fn add_syn_error(&mut self, error: syn::Error) {
    self.errors.push(error);
  }

  /// Add an error with a custom message and span
  pub fn add_spanned_error<T>(
    &mut self,
    tokens: T,
    message: impl std::fmt::Display,
  ) where
    T: quote::ToTokens,
  {
    let error = syn::Error::new_spanned(tokens, message);
    self.errors.push(error);
  }

  /// Add an error with just a span and message
  pub fn add_simple_error(
    &mut self,
    span: proc_macro2::Span,
    message: impl std::fmt::Display,
  ) {
    let error = syn::Error::new(span, message);
    self.errors.push(error);
  }

  /// Check if any errors have been accumulated
  pub fn has_errors(&self) -> bool {
    !self.errors.is_empty()
  }

  /// Get the number of accumulated errors
  pub fn error_count(&self) -> usize {
    self.errors.len()
  }

  /// Convert to a Result, returning Ok(value) if no errors, or
  /// Err(combined_errors) if any
  pub fn into_result<T>(self, ok_value: T) -> Result<T, syn::Error> {
    if self.errors.is_empty() {
      Ok(ok_value)
    } else {
      Err(self.combine_errors())
    }
  }

  /// Convert to a Result that fails if any errors exist
  pub fn into_error_result(self) -> Result<(), syn::Error> {
    if self.errors.is_empty() {
      Ok(())
    } else {
      Err(self.combine_errors())
    }
  }

  /// Combine all accumulated errors into a single syn::Error
  fn combine_errors(self) -> syn::Error {
    let mut errors = self.errors.into_iter();

    // Start with the first error
    let mut combined = errors.next().expect("Should have at least one error");

    // Combine all remaining errors
    for error in errors {
      combined.combine(error);
    }

    combined
  }

  /// Add errors from another accumulator
  pub fn extend(&mut self, other: ErrorAccumulator) {
    self.errors.extend(other.errors);
  }

  /// Create an accumulator with a single error
  pub fn with_error(error: Error) -> Self {
    let mut acc = Self::new();
    acc.add_error(error);
    acc
  }

  /// Create a detailed error message that includes help and hints
  /// This provides better error messages that are compatible with both
  /// syn::Error and proc-macro-error workflows
  pub fn create_detailed_error(error: Error) -> syn::Error {
    let details = error.get();
    let mut message = details.message.clone();

    if let Some(help) = &details.help {
      message.push_str(&format!("\nhelp: {help}"));
    }

    if let Some(hints) = &details.hints {
      message.push_str(&format!("\nnote: {hints}"));
    }

    syn::Error::new(details.span, message)
  }

  /// Add an error with enhanced formatting that includes help and hints
  /// This provides better error messages while remaining test-compatible
  pub fn add_enhanced_error(&mut self, error: Error) {
    let enhanced_error = Self::create_detailed_error(error);
    self.errors.push(enhanced_error);
  }
}
