use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum Schema {
    String,
    Number,
    Boolean,
    Null,
    Any,
    Array(Box<Schema>),
    Object(Vec<(String, Schema)>),
    Enum(Vec<String>), // Simple string enum
    Union(Vec<Schema>),
    Tuple(Vec<Schema>),
    Ref(String), // Reference to another type
                 // For complex enums, we might need more structure, but let's start simple
                 // or maybe just use a custom "Type" definition
}

pub trait SchemaBridge {
    fn to_ts() -> String;
    fn to_schema() -> Schema;
}

// Implement for basic types
impl SchemaBridge for String {
    fn to_ts() -> String {
        "string".to_string()
    }
    fn to_schema() -> Schema {
        Schema::String
    }
}

impl SchemaBridge for i32 {
    fn to_ts() -> String {
        "number".to_string()
    }
    fn to_schema() -> Schema {
        Schema::Number
    }
}

impl SchemaBridge for f64 {
    fn to_ts() -> String {
        "number".to_string()
    }
    fn to_schema() -> Schema {
        Schema::Number
    }
}

impl SchemaBridge for bool {
    fn to_ts() -> String {
        "boolean".to_string()
    }
    fn to_schema() -> Schema {
        Schema::Boolean
    }
}

impl<T: SchemaBridge> SchemaBridge for Option<T> {
    fn to_ts() -> String {
        format!("{} | null", T::to_ts())
    }
    fn to_schema() -> Schema {
        Schema::Union(vec![T::to_schema(), Schema::Null])
    }
}

impl<T: SchemaBridge> SchemaBridge for Vec<T> {
    fn to_ts() -> String {
        format!("{}[]", T::to_ts())
    }
    fn to_schema() -> Schema {
        Schema::Array(Box::new(T::to_schema()))
    }
}

// Helper to generate the full TS file content
pub fn generate_ts_file(types: Vec<(&str, String)>) -> String {
    let mut content = String::new();
    content.push_str("// This file is auto-generated by schema-bridge\n\n");

    for (name, ts_def) in types {
        content.push_str(&format!("export type {} = {};\n\n", name, ts_def));
    }

    content
}

/// Export types to a TypeScript file
pub fn export_to_file(types: Vec<(&str, String)>, path: &str) -> std::io::Result<()> {
    let content = generate_ts_file(types);
    std::fs::write(path, content)
}

/// Macro to easily export types to a file
#[macro_export]
macro_rules! export_types {
    ($path:expr, $($name:ident),+ $(,)?) => {{
        let types = vec![
            $((stringify!($name), $name::to_ts()),)+
        ];
        $crate::export_to_file(types, $path)
    }};
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_string_to_ts() {
        assert_eq!(String::to_ts(), "string");
    }

    #[test]
    fn test_i32_to_ts() {
        assert_eq!(i32::to_ts(), "number");
    }

    #[test]
    fn test_f64_to_ts() {
        assert_eq!(f64::to_ts(), "number");
    }

    #[test]
    fn test_bool_to_ts() {
        assert_eq!(bool::to_ts(), "boolean");
    }

    #[test]
    fn test_option_to_ts() {
        assert_eq!(Option::<String>::to_ts(), "string | null");
        assert_eq!(Option::<i32>::to_ts(), "number | null");
    }

    #[test]
    fn test_vec_to_ts() {
        assert_eq!(Vec::<String>::to_ts(), "string[]");
        assert_eq!(Vec::<i32>::to_ts(), "number[]");
    }

    #[test]
    fn test_nested_vec() {
        assert_eq!(Vec::<Vec::<String>>::to_ts(), "string[][]");
    }

    #[test]
    fn test_optional_vec() {
        assert_eq!(Option::<Vec::<String>>::to_ts(), "string[] | null");
    }

    #[test]
    fn test_generate_ts_file() {
        let types = vec![
            ("User", "{ name: string; age: number; }".to_string()),
            ("Status", "'Active' | 'Inactive'".to_string()),
        ];

        let result = generate_ts_file(types);

        assert!(result.contains("// This file is auto-generated by schema-bridge"));
        assert!(result.contains("export type User = { name: string; age: number; };"));
        assert!(result.contains("export type Status = 'Active' | 'Inactive';"));
    }

    #[test]
    fn test_schema_enum() {
        let schema = Schema::String;
        assert_eq!(schema, Schema::String);

        let schema = Schema::Array(Box::new(Schema::Number));
        assert!(matches!(schema, Schema::Array(_)));
    }
}
