//! Constellation-memory GraphQL output types with single-source cross-surface
//! equivalence.
//!
//! Re-exports from [`crate::memory_types`] — the canonical single definition
//! that uses `#[cfg_attr(feature = "graphql", derive(SimpleObject))]` so the
//! types compile on every surface (HTTP, MCP, GraphQL) without duplication. G-4
//! coherence tests live here where they can reference the `async_graphql` SDL
//! generation logic.

pub use crate::memory_types::{ChronicleEntry, Criterion, PanelSpec, RoadmapItem};

#[cfg(test)]
mod tests {
    use async_graphql::{EmptyMutation, EmptySubscription, Object, Schema};

    use super::*;

    fn serde_keys<T: serde::Serialize>(value: &T) -> Vec<String> {
        let v = serde_json::to_value(value).expect("entity must serialize");
        let mut keys: Vec<String> = v
            .as_object()
            .expect("entity serializes to a JSON object")
            .keys()
            .cloned()
            .collect();
        keys.sort();
        keys
    }

    fn sorted(list: &[&str]) -> Vec<String> {
        let mut v: Vec<String> = list.iter().map(|s| s.to_string()).collect();
        v.sort();
        v
    }

    #[test]
    fn serde_field_sets_match_canonical_source() {
        assert_eq!(
            serde_keys(&ChronicleEntry {
                id: "e1".into(),
                date: "2026-06-08".into(),
                kind: "adr".into(),
                title: "t".into(),
                summary: Some("s".into()),
                domain: Some("coding".into()),
                refs: vec!["adr-050".into()],
            }),
            sorted(&["id", "date", "kind", "title", "summary", "domain", "refs"]),
        );
        assert_eq!(
            serde_keys(&RoadmapItem {
                id: "r1".into(),
                title: "t".into(),
                status: "open".into(),
                horizon: "next".into(),
                graduates_to: Some("Adr".into()),
                mechanism: None,
                target_date: None,
            }),
            sorted(&[
                "id",
                "title",
                "status",
                "horizon",
                "graduates_to",
                "mechanism",
                "target_date"
            ]),
        );
        assert_eq!(
            serde_keys(&Criterion {
                id: "c1".into(),
                description: "d".into(),
                kind: "normative".into(),
                provenance: None,
                validator_ref: Some("adr-accepted-has-proof".into()),
                related_adr: Some("adr-049".into()),
            }),
            sorted(&[
                "id",
                "description",
                "kind",
                "provenance",
                "validator_ref",
                "related_adr"
            ]),
        );
        assert_eq!(
            serde_keys(&PanelSpec {
                id: "chronicle".into(),
                title: "Chronicle".into(),
                kind: "Timeline".into(),
                data_source_op: "chronicle_query".into(),
                surfaces: vec!["Ui".into()],
                actors: vec!["Developer".into()],
            }),
            sorted(&[
                "id",
                "title",
                "kind",
                "data_source_op",
                "surfaces",
                "actors"
            ]),
        );
    }

    struct TestQuery;

    #[Object]
    impl TestQuery {
        async fn chronicle(&self) -> ChronicleEntry {
            ChronicleEntry {
                id: String::new(),
                date: String::new(),
                kind: String::new(),
                title: String::new(),
                summary: None,
                domain: None,
                refs: vec![],
            }
        }
        async fn roadmap(&self) -> RoadmapItem {
            RoadmapItem {
                id: String::new(),
                title: String::new(),
                status: String::new(),
                horizon: String::new(),
                graduates_to: None,
                mechanism: None,
                target_date: None,
            }
        }
        async fn criterion(&self) -> Criterion {
            Criterion {
                id: String::new(),
                description: String::new(),
                kind: String::new(),
                provenance: None,
                validator_ref: None,
                related_adr: None,
            }
        }
        async fn panel(&self) -> PanelSpec {
            PanelSpec {
                id: String::new(),
                title: String::new(),
                kind: String::new(),
                data_source_op: String::new(),
                surfaces: vec![],
                actors: vec![],
            }
        }
    }

    #[test]
    fn graphql_sdl_exposes_camelcase_image_of_canonical_fields() {
        let schema = Schema::build(TestQuery, EmptyMutation, EmptySubscription).finish();
        let sdl = schema.sdl();
        for camel in [
            "graduatesTo",
            "targetDate",
            "validatorRef",
            "relatedAdr",
            "dataSourceOp",
        ] {
            assert!(
                sdl.contains(camel),
                "GraphQL SDL must expose '{camel}'; SDL was:\n{sdl}"
            );
        }
        for plain in ["date", "horizon", "provenance", "summary"] {
            assert!(sdl.contains(plain), "GraphQL SDL must expose '{plain}'");
        }
    }
}
