use std::env;
use std::fs;
use std::path::PathBuf;

struct SchemaEntry {
    impl_generics: &'static str,
    ty: &'static str,
    descriptor: &'static str,
}

fn main() {
    let entries = [
        SchemaEntry {
            impl_generics: "",
            ty: "Principal",
            descriptor: "Principal{claims:Box<[Claim]>,id:Box<str>,kind:Box<str>}",
        },
        SchemaEntry {
            impl_generics: "",
            ty: "Claim",
            descriptor: "Claim{key:Box<str>,value:Box<[u8]>}",
        },
        SchemaEntry {
            impl_generics: "<'a>",
            ty: "PrincipalRef<'a>",
            descriptor: "PrincipalRef{claims:&[ClaimRef],id:&str,kind:&str}",
        },
        SchemaEntry {
            impl_generics: "<'a>",
            ty: "ClaimRef<'a>",
            descriptor: "ClaimRef{key:&str,value:&[u8]}",
        },
        SchemaEntry {
            impl_generics: "",
            ty: "UpstreamCandidate",
            descriptor: "UpstreamCandidate{kind:Box<str>,name:Box<str>,observed_at_unix_secs:u64,organization_type:Box<str>,plan_capacity_ratio:f64,predicted_cache_read_tokens:u32,rate_limit_tier:Box<str>,seat_tier:Box<str>,upstream_id:Box<str>}",
        },
        SchemaEntry {
            impl_generics: "<'a>",
            ty: "UpstreamCandidateRef<'a>",
            descriptor: "UpstreamCandidateRef{kind:&str,name:&str,observed_at_unix_secs:u64,organization_type:&str,plan_capacity_ratio:f64,predicted_cache_read_tokens:u32,rate_limit_tier:&str,seat_tier:&str,upstream_id:&str}",
        },
        SchemaEntry {
            impl_generics: "",
            ty: "Header",
            descriptor: "Header{name:Box<str>,value:Box<[u8]>}",
        },
        SchemaEntry {
            impl_generics: "<'a>",
            ty: "HeaderRef<'a>",
            descriptor: "HeaderRef{name:&str,value:&[u8]}",
        },
        SchemaEntry {
            impl_generics: "",
            ty: "FilterRequest",
            descriptor: "FilterRequest{body:Box<[u8]>,candidates:Box<[UpstreamCandidate]>,headers:Box<[Header]>,method:Box<str>,path:Box<str>,principal:Principal,query:Option<Box<str>>,request_id:Box<str>,thread_id:Option<Box<str>>}",
        },
        SchemaEntry {
            impl_generics: "<'a>",
            ty: "FilterRequestRef<'a>",
            descriptor: "FilterRequestRef{body:&[u8],candidates:&[UpstreamCandidateRef],headers:&[HeaderRef],method:&str,path:&str,principal:PrincipalRef,query:Option<QueryRef>,request_id:&str,thread_id:Option<QueryRef>}",
        },
        SchemaEntry {
            impl_generics: "<'a>",
            ty: "QueryRef<'a>",
            descriptor: "QueryRef{value:&str}",
        },
        SchemaEntry {
            impl_generics: "",
            ty: "PerCandidateReason",
            descriptor: "PerCandidateReason{decision:Box<str>,reason:Box<str>,upstream_id:Box<str>}",
        },
        SchemaEntry {
            impl_generics: "",
            ty: "FilterResponse",
            descriptor: "FilterResponse{results:Box<[PerCandidateReason]>}",
        },
        SchemaEntry {
            impl_generics: "",
            ty: "Upstream",
            descriptor: "Upstream[AnthropicDirect{base_url:Option<Box<str>>}]",
        },
        SchemaEntry {
            impl_generics: "<'a>",
            ty: "UpstreamRef<'a>",
            descriptor: "UpstreamRef[AnthropicDirect{base_url:Option<QueryRef>}]",
        },
        SchemaEntry {
            impl_generics: "",
            ty: "ShapeRequest",
            descriptor: "ShapeRequest{body:Box<[u8]>,headers:Box<[Header]>,method:Box<str>,path:Box<str>,principal:Principal,query:Option<Box<str>>,request_id:Box<str>,upstream:Upstream}",
        },
        SchemaEntry {
            impl_generics: "<'a>",
            ty: "ShapeRequestRef<'a>",
            descriptor: "ShapeRequestRef{body:&[u8],headers:&[HeaderRef],method:&str,path:&str,principal:PrincipalRef,query:Option<QueryRef>,request_id:&str,upstream:UpstreamRef}",
        },
        SchemaEntry {
            impl_generics: "",
            ty: "ShapeResponse",
            descriptor: "ShapeResponse{body:Box<[u8]>,headers:Box<[Header]>,method:Box<str>,url:Box<str>}",
        },
        SchemaEntry {
            impl_generics: "",
            ty: "ObserveEvent",
            descriptor: "ObserveEvent[RequestStarted{request_id:Box<str>,downstream_user_agent:Option<Box<str>>},AuthnComplete{principal_id:Box<str>,principal_kind:Box<str>},UpstreamChosen{upstream:Upstream},Chunk{batch_index:u64,event_count:u64,total_bytes:u64},RequestFinished{status:u16,input_tokens:Option<u64>,output_tokens:Option<u64>,cache_creation_input_tokens:Option<u64>,cache_read_input_tokens:Option<u64>,duration_ms:u64},Error{code:Box<str>,message:Box<str>,source:Box<str>}]",
        },
    ];

    let mut output = String::new();
    for entry in entries {
        let digest = blake3::hash(entry.descriptor.as_bytes());
        let bytes = digest
            .as_bytes()
            .iter()
            .map(u8::to_string)
            .collect::<Vec<_>>()
            .join(", ");
        output.push_str(&format!(
            "impl{} crate::schema::WireSchema for {} {{ const FINGERPRINT: [u8; 32] = [{}]; const DESCRIPTOR: &'static str = {:?}; }}\n",
            entry.impl_generics, entry.ty, bytes, entry.descriptor
        ));
    }

    let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR set by Cargo"));
    fs::write(out_dir.join("wire_schema_impls.rs"), output)
        .expect("write generated WireSchema impls");
}
