use std::collections::HashSet;

use serde_json::Value;

use crate::validator::context::ValidationContext;
use crate::validator::error::ValidationError;
use crate::validator::result::ValidationResult;

impl<'a> ValidationContext<'a> {
  pub(crate) fn validate_array(
    &self,
    result: &mut ValidationResult,
  ) -> Result<bool, ValidationError> {
    let current = self.instance;
    if let Some(arr) = current.as_array() {
      if let Some(min) = self.schema.min_items
        && (arr.len() as f64) < min
      {
        result.errors.push(ValidationError {
          code: "MIN_ITEMS".to_string(),
          message: "Too few items".to_string(),
          path: self.path.to_string(),
        });
      }
      if let Some(max) = self.schema.max_items
        && (arr.len() as f64) > max
      {
        result.errors.push(ValidationError {
          code: "MAX_ITEMS".to_string(),
          message: "Too many items".to_string(),
          path: self.path.to_string(),
        });
      }

      if self.schema.unique_items.unwrap_or(false) {
        let mut seen: Vec<&Value> = Vec::new();
        for item in arr {
          if seen.contains(&item) {
            result.errors.push(ValidationError {
              code: "UNIQUE_ITEMS_VIOLATED".to_string(),
              message: "Array has duplicate items".to_string(),
              path: self.path.to_string(),
            });
            break;
          }
          seen.push(item);
        }
      }

      if let Some(ref contains_schema) = self.schema.contains {
        let mut _match_count = 0;
        for (i, child_instance) in arr.iter().enumerate() {
          let derived = self.derive(
            contains_schema,
            child_instance,
            &self.path,
            HashSet::new(),
            self.extensible,
            false,
          );

          let check = derived.validate()?;
          if check.is_valid() {
            _match_count += 1;
            result.evaluated_indices.insert(i);
          }
        }

        let min = self.schema.min_contains.unwrap_or(1.0) as usize;
        if _match_count < min {
          result.errors.push(ValidationError {
            code: "CONTAINS_VIOLATED".to_string(),
            message: format!("Contains matches {} < min {}", _match_count, min),
            path: self.path.to_string(),
          });
        }
        if let Some(max) = self.schema.max_contains
          && _match_count > max as usize
        {
          result.errors.push(ValidationError {
            code: "CONTAINS_VIOLATED".to_string(),
            message: format!("Contains matches {} > max {}", _match_count, max),
            path: self.path.to_string(),
          });
        }
      }

      let len = arr.len();
      let mut validation_index = 0;

      if let Some(ref prefix) = self.schema.prefix_items {
        for (i, sub_schema) in prefix.iter().enumerate() {
          if i < len {
            let path = format!("{}/{}", self.path, i);
            if let Some(child_instance) = arr.get(i) {
              let derived = self.derive(
                sub_schema,
                child_instance,
                &path,
                HashSet::new(),
                self.extensible,
                false,
              );
              let item_res = derived.validate()?;
              result.merge(item_res);
              result.evaluated_indices.insert(i);
              validation_index += 1;
            }
          }
        }
      }

      if let Some(ref items_schema) = self.schema.items {
        for i in validation_index..len {
          let path = format!("{}/{}", self.path, i);
          if let Some(child_instance) = arr.get(i) {
            let derived = self.derive(
              items_schema,
              child_instance,
              &path,
              HashSet::new(),
              self.extensible,
              false,
            );
            let item_res = derived.validate()?;
            result.merge(item_res);
            result.evaluated_indices.insert(i);
          }
        }
      }
    }

    Ok(true)
  }
}
