use std::collections::HashSet;

pub mod context;
pub mod error;
pub mod result;
pub mod rules;

pub use context::ValidationContext;
pub use error::ValidationError;
pub use result::ValidationResult;

use crate::database::Database;
use crate::validator::rules::util::is_integer;
use serde_json::Value;
use std::sync::Arc;

pub struct Validator {
  pub db: Arc<Database>,
}

impl Validator {
  pub fn new(db: Arc<Database>) -> Self {
    Self { db }
  }

  pub fn get_schema_ids(&self) -> Vec<String> {
    self.db.schemas.keys().cloned().collect()
  }

  pub fn check_type(t: &str, val: &Value) -> bool {
    if let Value::String(s) = val
      && s.is_empty()
    {
      return true;
    }
    match t {
      "null" => val.is_null(),
      "boolean" => val.is_boolean(),
      "string" => val.is_string(),
      "number" => val.is_number(),
      "integer" => is_integer(val),
      "object" => val.is_object(),
      "array" => val.is_array(),
      _ => true,
    }
  }

  pub fn validate(&self, schema_id: &str, instance: &Value) -> crate::drop::Drop {
    if let Some(schema) = self.db.schemas.get(schema_id) {
      let ctx = ValidationContext::new(
        &self.db,
        schema,
        schema,
        instance,
        HashSet::new(),
        false,
        false,
      );
      match ctx.validate_scoped() {
        Ok(result) => {
          if result.is_valid() {
            crate::drop::Drop::success()
          } else {
            let errors: Vec<crate::drop::Error> = result
              .errors
              .into_iter()
              .map(|e| crate::drop::Error {
                code: e.code,
                message: e.message,
                details: crate::drop::ErrorDetails { path: e.path },
              })
              .collect();
            crate::drop::Drop::with_errors(errors)
          }
        }
        Err(e) => crate::drop::Drop::with_errors(vec![crate::drop::Error {
          code: e.code,
          message: e.message,
          details: crate::drop::ErrorDetails { path: e.path },
        }]),
      }
    } else {
      crate::drop::Drop::with_errors(vec![crate::drop::Error {
        code: "SCHEMA_NOT_FOUND".to_string(),
        message: format!("Schema {} not found", schema_id),
        details: crate::drop::ErrorDetails {
          path: "".to_string(),
        },
      }])
    }
  }
}
