//! Generate LexiconSchema trait implementations for generated types

use crate::lexicon::{
    LexInteger, LexObject, LexObjectProperty, LexRecordRecord, LexString, LexStringFormat,
    LexUserType, LexiconDoc,
};
use crate::schema::from_ast::{ConstraintCheck, ValidationCheck};

/// Extract validation checks from a LexiconDoc
///
/// Walks the lexicon structure and builds ValidationCheck structs for all
/// constraint fields (max_length, max_graphemes, minimum, maximum, etc.)
pub(crate) fn extract_validation_checks(doc: &LexiconDoc, def_name: &str) -> Vec<ValidationCheck> {
    let mut checks = Vec::new();

    // Get the specified def
    if let Some(def) = doc.defs.get(def_name) {
        match def {
            LexUserType::Record(rec) => match &rec.record {
                LexRecordRecord::Object(obj) => {
                    checks.extend(extract_object_validations(obj));
                }
            },
            LexUserType::Object(obj) => {
                checks.extend(extract_object_validations(obj));
            }
            // XRPC types, tokens, etc. don't need validation
            _ => {}
        }
    }

    checks
}

/// Extract validation checks from an object's properties
fn extract_object_validations(obj: &LexObject) -> Vec<ValidationCheck> {
    let mut checks = Vec::new();

    for (schema_name, prop) in &obj.properties {
        // Convert schema name to field name (snake_case, with r# prefix for keywords)
        let field_name = field_name_from_schema(schema_name);

        // Check if required
        let is_required = obj
            .required
            .as_ref()
            .map(|req| req.iter().any(|r| r == schema_name))
            .unwrap_or(false);

        // Extract checks from property
        checks.extend(extract_property_validations(
            &field_name,
            schema_name.as_ref(),
            prop,
            is_required,
        ));
    }

    checks
}

/// Extract validation checks from a single property
fn extract_property_validations(
    field_name: &str,
    schema_name: &str,
    prop: &LexObjectProperty,
    is_required: bool,
) -> Vec<ValidationCheck> {
    let mut checks = Vec::new();

    match prop {
        LexObjectProperty::String(s) => {
            checks.extend(extract_string_validations(
                field_name,
                schema_name,
                s,
                is_required,
            ));
        }
        LexObjectProperty::Integer(i) => {
            checks.extend(extract_integer_validations(
                field_name,
                schema_name,
                i,
                is_required,
            ));
        }
        LexObjectProperty::Array(arr) => {
            if let Some(max) = arr.max_length {
                checks.push(ValidationCheck {
                    field_name: field_name.to_string(),
                    schema_name: schema_name.to_string(),
                    field_type: "Vec<_>".to_string(),
                    is_required,
                    is_array: true,
                    check: ConstraintCheck::MaxLength { max },
                });
            }
            if let Some(min) = arr.min_length {
                checks.push(ValidationCheck {
                    field_name: field_name.to_string(),
                    schema_name: schema_name.to_string(),
                    field_type: "Vec<_>".to_string(),
                    is_required,
                    is_array: true,
                    check: ConstraintCheck::MinLength { min },
                });
            }
        }
        LexObjectProperty::Blob(b) => {
            if let Some(max) = b.max_size {
                checks.push(ValidationCheck {
                    field_name: field_name.to_string(),
                    schema_name: schema_name.to_string(),
                    field_type: "BlobRef".to_string(),
                    is_required,
                    is_array: false,
                    check: ConstraintCheck::BlobMaxSize { max },
                });
            }
            if let Some(accept) = &b.accept {
                if !accept.is_empty() {
                    checks.push(ValidationCheck {
                        field_name: field_name.to_string(),
                        schema_name: schema_name.to_string(),
                        field_type: "BlobRef".to_string(),
                        is_required,
                        is_array: false,
                        check: ConstraintCheck::BlobAccept {
                            accept: accept.iter().map(|m| m.as_str().to_string()).collect(),
                        },
                    });
                }
            }
        }
        _ => {
            // Other types don't have runtime validations in the current impl.
        }
    }

    checks
}

/// Extract validation checks from a string property
fn extract_string_validations(
    field_name: &str,
    schema_name: &str,
    string: &LexString,
    is_required: bool,
) -> Vec<ValidationCheck> {
    let mut checks = Vec::new();

    // Datetime maps to `chrono::DateTime<FixedOffset>` which does not implement
    // `AsRef<str>`, so length checks cannot be emitted for it. All other formats
    // (did, handle, at-uri, cid, nsid, tid, record-key, language, uri, etc.) use
    // string-backed wrapper types that do implement `AsRef<str>`.
    if matches!(string.format, Some(LexStringFormat::Datetime)) {
        return checks;
    }

    if let Some(max) = string.max_length {
        checks.push(ValidationCheck {
            field_name: field_name.to_string(),
            schema_name: schema_name.to_string(),
            field_type: "String".to_string(),
            is_required,
            is_array: false,
            check: ConstraintCheck::MaxLength { max },
        });
    }

    if let Some(min) = string.min_length {
        checks.push(ValidationCheck {
            field_name: field_name.to_string(),
            schema_name: schema_name.to_string(),
            field_type: "String".to_string(),
            is_required,
            is_array: false,
            check: ConstraintCheck::MinLength { min },
        });
    }

    if let Some(max) = string.max_graphemes {
        checks.push(ValidationCheck {
            field_name: field_name.to_string(),
            schema_name: schema_name.to_string(),
            field_type: "String".to_string(),
            is_required,
            is_array: false,
            check: ConstraintCheck::MaxGraphemes { max },
        });
    }

    if let Some(min) = string.min_graphemes {
        checks.push(ValidationCheck {
            field_name: field_name.to_string(),
            schema_name: schema_name.to_string(),
            field_type: "String".to_string(),
            is_required,
            is_array: false,
            check: ConstraintCheck::MinGraphemes { min },
        });
    }

    checks
}

/// Extract validation checks from an integer property
fn extract_integer_validations(
    field_name: &str,
    schema_name: &str,
    integer: &LexInteger,
    is_required: bool,
) -> Vec<ValidationCheck> {
    let mut checks = Vec::new();

    if let Some(max) = integer.maximum {
        checks.push(ValidationCheck {
            field_name: field_name.to_string(),
            schema_name: schema_name.to_string(),
            field_type: "i64".to_string(),
            is_required,
            is_array: false,
            check: ConstraintCheck::Maximum { max },
        });
    }

    if let Some(min) = integer.minimum {
        checks.push(ValidationCheck {
            field_name: field_name.to_string(),
            schema_name: schema_name.to_string(),
            field_type: "i64".to_string(),
            is_required,
            is_array: false,
            check: ConstraintCheck::Minimum { min },
        });
    }

    checks
}

/// Convert schema field name to the Rust field identifier
///
/// Returns snake_case field name without r# prefix
/// (the r# will be added by make_ident when generating tokens)
fn field_name_from_schema(schema_name: &str) -> String {
    use heck::ToSnakeCase;
    schema_name.to_snake_case()
}
