//! Param-signature validation — strict mode, positional, variadic, required.
//!
//! Builds a dialect with a "score" family (Positional + variadic last slot)
//! and a "tag" family (Strict mode with required + typed params). Decodes
//! three documents: one valid, one with a wrong type, one with a missing
//! required param. Prints Ok or Err for each.
//!
//! Run: `cargo run --example tier1_validation`

// What this shows:
//   - ParamMode::Strict rejects unknown keys and type-checks declared ones
//   - ParamMode::Positional validates positional slots by index
//   - Required params (required: true on TypedParam) trigger errors when absent
//   - Default values are filled in when a param is absent and optional

use dms::tier1::{
    decode_t1_with_registry, DialectSpec, FamilySpec, InMemoryRegistry,
    ParamMode, ParamSignature, PositionalSlot, TypeSpec, TypedParam,
    VersionStrategy,
};
use dms::{IndexMap, Value};

fn build_registry() -> InMemoryRegistry {
    // ── "tag" family: Strict, typed "class" (required string) + "id" (optional string) ──
    let mut tag_typed = IndexMap::new();
    tag_typed.insert(
        "class".into(),
        TypedParam { ty: TypeSpec::String, required: true, default: None },
    );
    tag_typed.insert(
        "id".into(),
        TypedParam {
            ty: TypeSpec::String,
            required: false,
            default: Some(Value::String("".into())),
        },
    );

    let tag_family = FamilySpec {
        name: "tag".into(),
        default_sigils: vec!["|".into()],
        empty_default: Value::Table(Default::default()),
        content_slot: None,
        params: ParamSignature {
            mode: Some(ParamMode::Strict),
            typed: tag_typed,
            required: vec!["class".into()],
            positional: vec![],
        },
    };

    // ── "score" family: Positional, two slots (value: integer, label: string variadic) ──
    let score_family = FamilySpec {
        name: "score".into(),
        default_sigils: vec!["@".into()],
        empty_default: Value::Integer(0),
        content_slot: None,
        params: ParamSignature {
            mode: Some(ParamMode::Positional),
            typed: IndexMap::new(),
            required: vec![],
            positional: vec![
                PositionalSlot {
                    name: "value".into(),
                    ty: TypeSpec::Integer,
                    required: true,
                    default: None,
                    variadic: false,
                },
                PositionalSlot {
                    name: "label".into(),
                    ty: TypeSpec::String,
                    required: false,
                    default: Some(Value::String("score".into())),
                    variadic: true,
                },
            ],
        },
    };

    let dialect = DialectSpec {
        name: "app".into(),
        version: "1.0.0".into(),
        version_strategy: VersionStrategy::Caret,
        families: vec![tag_family, score_family],
        structs: vec![],
    };

    InMemoryRegistry::new().with(dialect)
}

const FRONT_MATTER: &str = "\
+++
_dms_tier: 1
_dms_imports:
  + dialect: \"app\"
    version: \"1.0.0\"
    bind:
      \"|\": [\"tag\"]
      \"@\": [\"score\"]
+++
";

fn main() {
    let registry = build_registry();

    // ── Scenario 1: valid document ────────────────────────────────────────────
    let valid_src = format!(
        "{FRONT_MATTER}title: \"Hello\" |tag(class: \"h1\")\npoints: 95 @score(95, \"main\")\n"
    );
    let r1 = decode_t1_with_registry(&valid_src, &registry);
    match r1 {
        Ok(doc) => println!(
            "[VALID]   OK  ({} decorator entry/entries)",
            doc.decorators.len()
        ),
        Err(e)  => println!("[VALID]   UNEXPECTED ERR: {}", e.message),
    }

    // ── Scenario 2: wrong type — `class` receives an integer instead of string ──
    let wrong_type_src = format!(
        "{FRONT_MATTER}title: \"Hello\" |tag(class: 42)\n"
    );
    let r2 = decode_t1_with_registry(&wrong_type_src, &registry);
    match r2 {
        Ok(_)  => println!("[WRONG TYPE] UNEXPECTED OK"),
        Err(e) => println!("[WRONG TYPE] Err (expected): {}", e.message),
    }

    // ── Scenario 3: missing required param — `class` is absent entirely ───────
    let missing_req_src = format!(
        "{FRONT_MATTER}title: \"Hello\" |tag(id: \"x\")\n"
    );
    let r3 = decode_t1_with_registry(&missing_req_src, &registry);
    match r3 {
        Ok(_)  => println!("[MISSING REQUIRED] UNEXPECTED OK"),
        Err(e) => println!("[MISSING REQUIRED] Err (expected): {}", e.message),
    }

    // ── Scenario 4: strict mode rejects unknown key ───────────────────────────
    let unknown_key_src = format!(
        "{FRONT_MATTER}title: \"Hello\" |tag(class: \"x\", oops: true)\n"
    );
    let r4 = decode_t1_with_registry(&unknown_key_src, &registry);
    match r4 {
        Ok(_)  => println!("[UNKNOWN KEY] UNEXPECTED OK"),
        Err(e) => println!("[UNKNOWN KEY]  Err (expected): {}", e.message),
    }

    // ── Scenario 5: positional type error — first slot expects integer ─────────
    let pos_type_err_src = format!(
        "{FRONT_MATTER}points: 0 @score(\"not-an-int\")\n"
    );
    let r5 = decode_t1_with_registry(&pos_type_err_src, &registry);
    match r5 {
        Ok(_)  => println!("[POS TYPE ERR] UNEXPECTED OK"),
        Err(e) => println!("[POS TYPE ERR] Err (expected): {}", e.message),
    }
}
