use strum::Display;

use crate::generator::{
  ast::{OperationInfo, OperationKind, RustType},
  operation_registry::OperationEntry,
};

#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct GenerationStats {
  pub types_generated: usize,
  pub structs_generated: usize,
  pub enums_generated: usize,
  pub enums_with_helpers_generated: usize,
  pub type_aliases_generated: usize,
  pub operations_converted: usize,
  pub webhooks_converted: usize,
  pub cycles_detected: usize,
  pub cycle_details: Vec<Vec<String>>,
  pub warnings: Vec<GenerationWarning>,
  pub orphaned_schemas_count: usize,
  pub client_methods_generated: usize,
  pub client_headers_generated: usize,
}

impl GenerationStats {
  pub fn record_struct(&mut self) {
    self.structs_generated += 1;
    self.types_generated += 1;
  }

  pub fn record_enum(&mut self, has_helpers: bool) {
    self.enums_generated += 1;
    self.types_generated += 1;
    if has_helpers {
      self.enums_with_helpers_generated += 1;
    }
  }

  pub fn record_type_alias(&mut self) {
    self.type_aliases_generated += 1;
    self.types_generated += 1;
  }

  pub fn record_rust_type(&mut self, rust_type: &RustType) {
    match rust_type {
      RustType::Struct(_) => self.record_struct(),
      RustType::Enum(def) => self.record_enum(!def.methods.is_empty()),
      RustType::DiscriminatedEnum(_) | RustType::ResponseEnum(_) => self.record_enum(false),
      RustType::TypeAlias(_) => self.record_type_alias(),
    }
  }

  pub fn record_rust_types(&mut self, types: &[RustType]) {
    for rust_type in types {
      self.record_rust_type(rust_type);
    }
  }

  pub fn record_operation(&mut self, operation: &OperationInfo) {
    self.operations_converted += 1;
    if matches!(operation.kind, OperationKind::Webhook) {
      self.webhooks_converted += 1;
    }
  }

  pub fn record_operations(&mut self, operations: &[OperationInfo]) {
    for operation in operations {
      self.record_operation(operation);
    }
  }

  pub fn record_cycle(&mut self, cycle: Vec<String>) {
    self.cycles_detected += 1;
    self.cycle_details.push(cycle);
  }

  pub fn record_cycles(&mut self, cycles: Vec<Vec<String>>) {
    for cycle in cycles {
      self.record_cycle(cycle);
    }
  }

  pub fn record_orphaned_schemas(&mut self, count: usize) {
    self.orphaned_schemas_count += count;
  }

  pub fn record_client_methods(&mut self, count: usize) {
    self.client_methods_generated += count;
  }

  pub fn record_client_headers(&mut self, count: usize) {
    self.client_headers_generated += count;
  }

  pub fn record_warning(&mut self, warning: GenerationWarning) {
    self.warnings.push(warning);
  }

  pub fn record_warnings(&mut self, warnings: impl IntoIterator<Item = GenerationWarning>) {
    self.warnings.extend(warnings);
  }
}

#[derive(Debug, Clone, PartialEq, Eq, Display)]
pub enum GenerationWarning {
  #[strum(to_string = "Failed to convert schema '{schema_name}': {error}")]
  SchemaConversionFailed { schema_name: String, error: String },
  #[strum(to_string = "Failed to convert operation '{method} {path}': {error}")]
  OperationConversionFailed {
    method: String,
    path: String,
    error: String,
  },
  #[strum(to_string = "[{operation_id}] {message}")]
  OperationSpecific { operation_id: String, message: String },
  #[strum(to_string = "Schema '{schema_name}': {message}")]
  DiscriminatorMappingFailed { schema_name: String, message: String },
}

impl GenerationWarning {
  pub fn conversion_failure(entry: &OperationEntry, error: &impl ToString) -> Self {
    Self::OperationConversionFailed {
      method: entry.method.to_string(),
      path: entry.path.clone(),
      error: error.to_string(),
    }
  }

  pub fn operation_specific(operation_id: &impl ToString, message: &impl ToString) -> Self {
    Self::OperationSpecific {
      operation_id: operation_id.to_string(),
      message: message.to_string(),
    }
  }

  pub fn is_skipped_item(&self) -> bool {
    matches!(
      self,
      Self::SchemaConversionFailed { .. } | Self::OperationConversionFailed { .. }
    )
  }
}
