use serde_json::Value;
use HashSet;
use std::ptr::NonNull;

pub trait ValidationInstance<'a>: Copy + Clone {
  fn as_value(&self) -> &'a Value;
  fn child_at_key(&self, key: &str) -> Option<Self>;
  fn child_at_index(&self, idx: usize) -> Option<Self>;
  fn prune_object(&self, _keys: &HashSet<String>) {}
  fn prune_array(&self, _indices: &HashSet<usize>) {}
}

#[derive(Clone, Copy)]
pub struct ReadOnlyInstance<'a>(pub &'a Value);

impl<'a> ValidationInstance<'a> for ReadOnlyInstance<'a> {
  fn as_value(&self) -> &'a Value {
    self.0
  }

  fn child_at_key(&self, key: &str) -> Option<Self> {
    self.0.get(key).map(ReadOnlyInstance)
  }

  fn child_at_index(&self, idx: usize) -> Option<Self> {
    self.0.get(idx).map(ReadOnlyInstance)
  }
}

#[derive(Clone, Copy)]
pub struct MutableInstance {
  ptr: NonNull<Value>,
}

impl MutableInstance {
  pub fn new(val: &mut Value) -> Self {
    Self {
      ptr: NonNull::from(val),
    }
  }
}

impl<'a> ValidationInstance<'a> for MutableInstance {
  fn as_value(&self) -> &'a Value {
    unsafe { self.ptr.as_ref() }
  }

  fn child_at_key(&self, key: &str) -> Option<Self> {
    unsafe {
      if let Some(obj) = self.ptr.as_ref().as_object() {
        if obj.contains_key(key) {
          let parent_mut = &mut *self.ptr.as_ptr();
          if let Some(child_val) = parent_mut.get_mut(key) {
            return Some(MutableInstance::new(child_val));
          }
        }
      }
      None
    }
  }

  fn child_at_index(&self, idx: usize) -> Option<Self> {
    unsafe {
      if let Some(arr) = self.ptr.as_ref().as_array() {
        if idx < arr.len() {
          let parent_mut = &mut *self.ptr.as_ptr();
          if let Some(child_val) = parent_mut.get_mut(idx) {
            return Some(MutableInstance::new(child_val));
          }
        }
      }
      None
    }
  }

  fn prune_object(&self, keys: &HashSet<String>) {
    unsafe {
      let val_mut = &mut *self.ptr.as_ptr();
      if let Some(obj) = val_mut.as_object_mut() {
        obj.retain(|k, _| keys.contains(k));
      }
    }
  }

  fn prune_array(&self, indices: &HashSet<usize>) {
    unsafe {
      let val_mut = &mut *self.ptr.as_ptr();
      if let Some(arr) = val_mut.as_array_mut() {
        let mut i = 0;
        arr.retain(|_| {
          let keep = indices.contains(&i);
          i += 1;
          keep
        });
      }
    }
  }
}
