//! Shared generator types.

use std::path::PathBuf;

/// Top-level generator flow selected by the user.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GeneratorFlow {
    /// Generate one mock route or route directory.
    Route,
    /// Generate the root `rs-mock-server.toml`.
    MainConfig,
}

/// Route family supported by the generator.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RouteKind {
    /// Static method or static file route.
    Basic,
    /// In-memory REST collection route.
    Rest,
    /// Authentication route.
    Auth,
    /// Upload directory route.
    Upload,
    /// Public static directory route.
    Public,
    /// GraphQL route directory.
    Graphql,
    /// SQL-backed GET route.
    Sql,
}

/// File content format generated by a route flow.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GeneratedFileType {
    /// JSON content.
    Json,
    /// JGD dynamic generation definition.
    Jgd,
    /// Plain text content.
    Text,
    /// SQL query content.
    Sql,
    /// Directory-only route with optional seed files.
    Directory,
}

/// Supported generated field value kinds.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum FieldKind {
    /// String value.
    #[default]
    String,
    /// Integer value.
    Integer,
    /// Boolean value.
    Boolean,
    /// ISO-ish date/time string.
    DateTime,
    /// UUID string.
    Uuid,
}

/// Identifier behavior for object-like generated data.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IdType {
    /// No automatic ID handling.
    None,
    /// UUID ID.
    Uuid,
    /// Integer ID.
    Int,
}

/// One flat generated object field.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FieldSpec {
    /// Field name.
    pub name: String,
    /// Field value kind.
    pub kind: FieldKind,
}

impl FieldSpec {
    /// Creates a field spec.
    pub fn new(name: impl Into<String>, kind: FieldKind) -> Self {
        Self {
            name: name.into(),
            kind,
        }
    }
}

/// Route generation choices independent of terminal UI.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RouteSelection {
    /// Route kind.
    pub kind: RouteKind,
    /// Route path, for example `/api/users`.
    pub route: String,
    /// HTTP method for basic routes.
    pub method: String,
    /// Generated file type.
    pub file_type: GeneratedFileType,
    /// Whether the route should be protected when supported.
    pub protected: bool,
    /// Whether upload directories are temporary.
    pub temporary_upload: bool,
    /// Identifier field name.
    pub id_key: String,
    /// Identifier type.
    pub id_type: IdType,
    /// Object fields for generated sample content.
    pub fields: Vec<FieldSpec>,
    /// Optional collection name used by SQL templates.
    pub collection_name: Option<String>,
}

impl Default for RouteSelection {
    fn default() -> Self {
        Self {
            kind: RouteKind::Basic,
            route: "/api/example".to_string(),
            method: "get".to_string(),
            file_type: GeneratedFileType::Json,
            protected: false,
            temporary_upload: false,
            id_key: "id".to_string(),
            id_type: IdType::Uuid,
            fields: default_fields(),
            collection_name: None,
        }
    }
}

/// Root config generation choices.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MainConfigSelection {
    /// Server port.
    pub port: u16,
    /// Mock folder.
    pub folder: String,
    /// Enable CORS.
    pub enable_cors: bool,
    /// Optional allowed origin.
    pub allowed_origin: Option<String>,
    /// Include global route defaults.
    pub include_route_defaults: bool,
    /// Optional global delay in milliseconds.
    pub delay: Option<u16>,
    /// Optional global route remap/prefix.
    pub remap: Option<String>,
    /// Optional global protection default.
    pub protect: Option<bool>,
}

impl Default for MainConfigSelection {
    fn default() -> Self {
        Self {
            port: crate::DEFAULT_PORT,
            folder: crate::DEFAULT_FOLDER.to_string(),
            enable_cors: true,
            allowed_origin: None,
            include_route_defaults: false,
            delay: None,
            remap: None,
            protect: None,
        }
    }
}

/// One planned filesystem write.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WriteOperation {
    /// Target path.
    pub path: PathBuf,
    /// Content to write. `None` means create directory.
    pub content: Option<String>,
}

impl WriteOperation {
    /// Creates a file write operation.
    pub fn file(path: impl Into<PathBuf>, content: impl Into<String>) -> Self {
        Self {
            path: path.into(),
            content: Some(content.into()),
        }
    }

    /// Creates a directory creation operation.
    pub fn directory(path: impl Into<PathBuf>) -> Self {
        Self {
            path: path.into(),
            content: None,
        }
    }
}

/// Fully rendered write plan.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct WritePlan {
    /// Planned write operations.
    pub operations: Vec<WriteOperation>,
}

impl WritePlan {
    /// Adds an operation to this plan.
    pub fn push(&mut self, operation: WriteOperation) {
        self.operations.push(operation);
    }
}

/// Default flat audit-oriented fields.
pub fn default_fields() -> Vec<FieldSpec> {
    vec![
        FieldSpec::new("id", FieldKind::Uuid),
        FieldSpec::new("description", FieldKind::String),
        FieldSpec::new("created_at", FieldKind::DateTime),
        FieldSpec::new("created_by", FieldKind::String),
        FieldSpec::new("updated_at", FieldKind::DateTime),
        FieldSpec::new("updated_by", FieldKind::String),
    ]
}

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

    #[test]
    fn default_fields_include_audit_shape() {
        let names = default_fields()
            .into_iter()
            .map(|field| field.name)
            .collect::<Vec<_>>();
        assert_eq!(
            names,
            vec![
                "id",
                "description",
                "created_at",
                "created_by",
                "updated_at",
                "updated_by"
            ]
        );
    }

    #[test]
    fn route_selection_defaults_are_route_generator_safe() {
        let selection = RouteSelection::default();
        assert_eq!(selection.kind, RouteKind::Basic);
        assert_eq!(selection.route, "/api/example");
        assert_eq!(selection.method, "get");
        assert_eq!(selection.file_type, GeneratedFileType::Json);
        assert_eq!(selection.id_key, "id");
        assert_eq!(selection.id_type, IdType::Uuid);
        assert!(!selection.protected);
        assert!(!selection.temporary_upload);
        assert!(selection.collection_name.is_none());
    }

    #[test]
    fn main_config_defaults_match_cli_defaults() {
        let selection = MainConfigSelection::default();
        assert_eq!(selection.port, crate::DEFAULT_PORT);
        assert_eq!(selection.folder, crate::DEFAULT_FOLDER);
        assert!(selection.enable_cors);
        assert!(selection.allowed_origin.is_none());
        assert!(!selection.include_route_defaults);
        assert!(selection.delay.is_none());
        assert!(selection.remap.is_none());
        assert!(selection.protect.is_none());
    }

    #[test]
    fn write_operations_preserve_file_and_directory_intent() {
        let file = WriteOperation::file("api/get.json", "{}");
        let directory = WriteOperation::directory("api/uploads");
        assert_eq!(file.path, PathBuf::from("api/get.json"));
        assert_eq!(file.content.as_deref(), Some("{}"));
        assert_eq!(directory.path, PathBuf::from("api/uploads"));
        assert!(directory.content.is_none());
    }

    #[test]
    fn write_plan_push_preserves_operation_order() {
        let mut plan = WritePlan::default();
        plan.push(WriteOperation::directory("api"));
        plan.push(WriteOperation::file("api/get.json", "{}"));
        assert!(plan.operations[0].content.is_none());
        assert!(plan.operations[1].content.is_some());
    }
}
