#[cfg(not(test))]
use pgrx::*;

#[cfg(not(test))]
pg_module_magic!();

#[cfg(test)]
pub struct JsonB(pub serde_json::Value);

pub mod database;
pub mod drop;
pub mod jspg;
pub mod merger;
pub mod queryer;
pub mod validator;

use std::sync::{Arc, RwLock};

lazy_static::lazy_static! {
  // Global Atomic Swap Container:
  // - RwLock: To protect the SWAP of the Option.
  // - Option: Because it starts empty.
  // - Arc: Because multiple running threads might hold the OLD engine while we swap.
  // - Jspg: The root semantic engine encapsulating the database metadata, validator, queryer, and merger.
  static ref GLOBAL_JSPG: RwLock<Option<Arc<jspg::Jspg>>> = RwLock::new(None);
}

fn jspg_failure() -> JsonB {
  let error = crate::drop::Error {
    code: "ENGINE_NOT_INITIALIZED".to_string(),
    message: "JSPG extension has not been initialized via jspg_setup".to_string(),
    details: crate::drop::ErrorDetails {
      path: "".to_string(),
    },
  };
  let drop = crate::drop::Drop::with_errors(vec![error]);
  JsonB(serde_json::to_value(drop).unwrap())
}

#[cfg_attr(not(test), pg_extern(strict))]
pub fn jspg_setup(database: JsonB) -> JsonB {
  match crate::jspg::Jspg::new(&database.0) {
    Ok(new_jspg) => {
      let new_arc = Arc::new(new_jspg);

      // 3. ATOMIC SWAP
      {
        let mut lock = GLOBAL_JSPG.write().unwrap();
        *lock = Some(new_arc);
      }

      let drop = crate::drop::Drop::success();
      JsonB(serde_json::to_value(drop).unwrap())
    }
    Err(drop) => JsonB(serde_json::to_value(drop).unwrap()),
  }
}

#[cfg_attr(not(test), pg_extern)]
pub fn jspg_merge(data: JsonB) -> JsonB {
  // Try to acquire a read lock to get a clone of the Engine Arc
  let engine_opt = {
    let lock = GLOBAL_JSPG.read().unwrap();
    lock.clone()
  };

  match engine_opt {
    Some(engine) => {
      let drop = engine.merger.merge(data.0);
      JsonB(serde_json::to_value(drop).unwrap())
    }
    None => jspg_failure(),
  }
}

#[cfg_attr(not(test), pg_extern)]
pub fn jspg_query(schema_id: &str, stem: Option<&str>, filters: Option<JsonB>) -> JsonB {
  let engine_opt = {
    let lock = GLOBAL_JSPG.read().unwrap();
    lock.clone()
  };

  match engine_opt {
    Some(engine) => {
      let drop = engine
        .queryer
        .query(schema_id, stem, filters.as_ref().map(|f| &f.0));
      JsonB(serde_json::to_value(drop).unwrap())
    }
    None => jspg_failure(),
  }
}

// `mask_json_schema` has been removed as the mask architecture is fully replaced by Spi string queries during DB interactions.

#[cfg_attr(not(test), pg_extern(strict, parallel_safe))]
pub fn jspg_validate(schema_id: &str, instance: JsonB) -> JsonB {
  // 1. Acquire Snapshot
  let jspg_arc = {
    let lock = GLOBAL_JSPG.read().unwrap();
    lock.clone()
  };

  // 2. Validate (Lock-Free)
  if let Some(engine) = jspg_arc {
    let drop = engine.validator.validate(schema_id, &instance.0);
    JsonB(serde_json::to_value(drop).unwrap())
  } else {
    jspg_failure()
  }
}

#[cfg_attr(not(test), pg_extern)]
pub fn jspg_stems() -> JsonB {
  use serde_json::{Map, Value};

  let engine_opt = {
    let lock = GLOBAL_JSPG.read().unwrap();
    lock.clone()
  };

  match engine_opt {
    Some(engine) => {
      JsonB(serde_json::to_value(&engine.database.stems).unwrap_or(Value::Object(Map::new())))
    }
    None => JsonB(Value::Object(Map::new())),
  }
}

#[cfg_attr(not(test), pg_extern(strict))]
pub fn jspg_teardown() -> JsonB {
  let mut lock = GLOBAL_JSPG.write().unwrap();
  *lock = None;
  let drop = crate::drop::Drop::success();
  JsonB(serde_json::to_value(drop).unwrap())
}

#[cfg(test)]
pub mod tests;
