use std::collections::BTreeSet;

use indexmap::{IndexMap, map::Entry};

use crate::generator::{
  ast::{MethodKind, RustType, SerdeImpl, ValidationAttribute},
  converter::GenerationTarget,
};

pub(crate) struct RustTypeDeduplication {
  types: Vec<RustType>,
}

impl RustTypeDeduplication {
  pub(crate) fn new(types: Vec<RustType>) -> Self {
    Self { types }
  }

  pub(crate) fn process(self) -> Vec<RustType> {
    let mut map = IndexMap::new();

    for t in &self.types {
      let name = t.type_name().to_string();
      let priority = t.type_priority();

      match map.entry(name) {
        Entry::Vacant(e) => {
          e.insert(t.clone());
        }
        Entry::Occupied(mut e) => {
          if priority > e.get().type_priority() {
            e.insert(t.clone());
          }
        }
      }
    }

    map.into_values().collect::<Vec<_>>()
  }
}

pub(crate) struct ModuleImports {
  types: Vec<RustType>,
  target: GenerationTarget,
}

impl ModuleImports {
  pub(crate) fn new(types: Vec<RustType>, target: GenerationTarget) -> Self {
    Self { types, target }
  }

  pub(crate) fn process(self) -> BTreeSet<String> {
    let mut uses = BTreeSet::new();

    let mut needs_serialize = false;
    let mut needs_deserialize = false;
    let mut needs_validate = false;

    for ty in &self.types {
      needs_serialize |= ty.is_serializable() == SerdeImpl::Derive;
      needs_deserialize |= ty.is_deserializable() == SerdeImpl::Derive;

      if let RustType::Struct(def) = ty {
        needs_validate |= def
          .fields
          .iter()
          .any(|f| f.validation_attrs.contains(&ValidationAttribute::Nested));
        needs_validate |= def.methods.iter().any(|m| matches!(m.kind, MethodKind::Builder { .. }));
      }
    }

    if needs_serialize {
      uses.insert("serde::Serialize".to_string());
    }
    if needs_deserialize {
      uses.insert("serde::Deserialize".to_string());
    }
    if needs_validate {
      uses.insert("validator::Validate".to_string());
    }
    if self.target == GenerationTarget::Server {
      uses.insert("axum::response::IntoResponse".to_string());
    }

    uses
  }
}
