//! The `jsonb` (SteVec) encrypted-JSONB payload types — HAND-WRITTEN.
//!
//! Unlike the scalar families, the SteVec struct bodies (fields, `#[serde(flatten)]`,
//! the `Option<bool>` array marker, per-struct serde strictness) are not derivable
//! from `eql-domains::CATALOG`, so they live here by hand — symmetric with the
//! hand-written SQL under `src/v3/jsonb/`. The generated `inventory.rs` still lists
//! these three domains (in CATALOG order) via its `Shape` branch. See the SteVec
//! caveat in `mod.rs` for why the entry/query structs are necessarily lax.

use schemars::{schema_for, JsonSchema, Schema};
use serde::{Deserialize, Serialize};
use ts_rs::TS;

use crate::v3::terms::{Ciphertext, Hmac256, OreCllw, Selector};
use crate::v3::DomainType;
use crate::{Identifier, SchemaVersion};

/// The `k` envelope key — the EQL payload **form discriminator**, always the
/// literal `"sv"` for an encrypted-JSONB document.
///
/// `k` distinguishes the payload forms in the canonical wire contract (`"ct"` =
/// scalar ciphertext, `"sv"` = STE-vec). `eql_v3` itself does not consume `k`
/// (the typed domain + the structural `c`-vs-`sv` shape already discriminate,
/// and no `eql_v3` SQL reads it), but the canonical `SteVecPayload`
/// (`eql-payload-v2.3.schema.json`, `required: [v,k,i,sv]`) mandates it and
/// cipherstash-client emits it on every SteVec document — so the strict document
/// struct must model it or it rejects the real wire.
///
/// Pinned exactly like [`SchemaVersion`] pins `v`: deserialization rejects any
/// value other than `"sv"`, so a scalar (`k:"ct"`) payload cannot be read back
/// as a document. The inner value is private; the only constructible instance is
/// [`SteVecForm::SV`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, TS)]
#[ts(export, export_to = "v3/")]
pub struct SteVecForm(#[ts(type = "\"sv\"")] &'static str);

impl SteVecForm {
    /// The only valid form for a document: the STE-vec discriminator `"sv"`.
    pub const SV: Self = Self("sv");
}

impl<'de> Deserialize<'de> for SteVecForm {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let k = String::deserialize(deserializer)?;
        if k == "sv" {
            Ok(Self::SV)
        } else {
            Err(serde::de::Error::custom(format!(
                "unsupported SteVec form discriminator {k:?} (expected \"sv\")"
            )))
        }
    }
}

/// Manual schema: pins `k` to the literal `"sv"` (`const`), mirroring the
/// `SchemaVersion` schema — the derive would emit an unconstrained string.
impl JsonSchema for SteVecForm {
    fn schema_name() -> std::borrow::Cow<'static, str> {
        "SteVecForm".into()
    }

    fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
        schemars::json_schema!({
            "type": "string",
            "const": "sv",
            "description": "The `k` envelope form discriminator — always `\"sv\"` for a SteVec document.",
        })
    }
}

/// `eql_v3.json` — a SteVec encrypted-JSONB document (`{v, k, i, sv:[entry]}`,
/// no root ciphertext). Strict. `k` is the `"sv"` form discriminator (see
/// [`SteVecForm`]) — carried on the real wire, so the strict struct models it.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TS, JsonSchema)]
#[ts(export, export_to = "v3/")]
#[serde(deny_unknown_fields)]
pub struct SteVecDocument {
    pub v: SchemaVersion,
    pub k: SteVecForm,
    pub i: Identifier,
    pub sv: Vec<SteVecEntry>,
}

/// `eql_v3.jsonb_entry` — one sv element (returned by `->`). Carries a selector
/// `s`, ciphertext `c`, optional array-membership marker `a`, and exactly one of
/// `hm` XOR `oc`. LAX (flatten precludes `deny_unknown_fields`): tolerates the
/// root `i`/`v` merged in by `->`. XOR of the term is enforced by the SQL CHECK.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TS, JsonSchema)]
#[ts(export, export_to = "v3/")]
pub struct SteVecEntry {
    pub s: Selector,
    pub c: Ciphertext,
    // `#[ts(optional = nullable)]` emits `a?: boolean | null` — ts-rs does NOT
    // infer optionality from serde `default`/`skip_serializing_if` (10.1: only an
    // explicit `#[ts(optional)]`/`optional = nullable` does), so without this the
    // TS binding would render `a` REQUIRED and drift from the JSON Schema, which
    // excludes `a` from `required`. `= nullable` keeps the `| null`.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    #[ts(optional = nullable)]
    pub a: Option<bool>,
    #[serde(flatten)]
    pub term: SteVecTerm,
}

/// `eql_v3.jsonb_query` — a containment needle (`{sv:[query-entry]}`). Strict.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TS, JsonSchema)]
#[ts(export, export_to = "v3/")]
#[serde(deny_unknown_fields)]
pub struct SteVecQuery {
    pub sv: Vec<SteVecQueryEntry>,
}

/// One element of a SteVec containment needle: a selector plus one term, and
/// (per the SQL CHECK) no ciphertext. LAX for the same flatten reason as
/// `SteVecEntry`; the "no `c`" contract is enforced by `is_valid_ste_vec_query_payload`.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TS, JsonSchema)]
#[ts(export, export_to = "v3/")]
pub struct SteVecQueryEntry {
    pub s: Selector,
    #[serde(flatten)]
    pub term: SteVecTerm,
}

/// The per-entry deterministic term: exactly one of `hm` (HMAC equality) or `oc`
/// (CLLW-ORE ordering). Untagged — a document mixes both across its `sv` array.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, TS, JsonSchema)]
#[ts(export, export_to = "v3/")]
#[serde(untagged)]
pub enum SteVecTerm {
    Hmac { hm: Hmac256 },
    OreCllw { oc: OreCllw },
}

macro_rules! ste_vec_domain_type {
    ($ty:ident, $sql:literal) => {
        impl DomainType for $ty {
            fn sql_domain_static() -> &'static str {
                $sql
            }
            fn sql_domain(&self) -> &'static str {
                Self::sql_domain_static()
            }
            // `term_json_keys` keeps the trait default (`None`): SteVec index
            // terms live per sv leaf (`hm` XOR `oc`), not as flat payload keys.
            fn parse_value(&self, value: &serde_json::Value) -> Result<(), serde_json::Error> {
                <$ty as Deserialize>::deserialize(value).map(|_| ())
            }
            fn schema(&self) -> Schema {
                schema_for!($ty)
            }
        }
    };
}

ste_vec_domain_type!(SteVecDocument, "eql_v3.json");
ste_vec_domain_type!(SteVecEntry, "eql_v3.jsonb_entry");
ste_vec_domain_type!(SteVecQuery, "eql_v3.jsonb_query");
