//! `PostgreSQL` Column DDL types
//!
//! This module provides two complementary types:
//! - [`ColumnDef`] - A const-friendly definition type for compile-time schema definitions
//! - [`Column`] - A runtime type for serde serialization/deserialization

use crate::alloc_prelude::*;

#[cfg(feature = "serde")]
use crate::serde_helpers::{cow_from_string, cow_option_from_string};

// =============================================================================
// Generated Column Types
// =============================================================================

/// Generated column type
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum GeneratedType {
    /// Stored generated column
    #[default]
    Stored,
    /// Virtual generated column
    Virtual,
}

/// Generated column configuration (const-friendly)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct GeneratedDef {
    /// SQL expression for generation
    pub expression: &'static str,
    /// Generation type: stored
    pub gen_type: GeneratedType,
}

impl GeneratedDef {
    /// Create a new stored generated column
    #[must_use]
    pub const fn stored(expression: &'static str) -> Self {
        Self {
            expression,
            gen_type: GeneratedType::Stored,
        }
    }

    /// Create a new virtual generated column
    #[must_use]
    pub const fn virtual_col(expression: &'static str) -> Self {
        Self {
            expression,
            gen_type: GeneratedType::Virtual,
        }
    }

    /// Convert to runtime type
    #[must_use]
    pub const fn into_generated(self) -> Generated {
        Generated {
            expression: Cow::Borrowed(self.expression),
            gen_type: self.gen_type,
        }
    }
}

/// Generated column configuration (runtime)
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct Generated {
    /// SQL expression for generation
    #[cfg_attr(
        feature = "serde",
        serde(rename = "as", deserialize_with = "cow_from_string")
    )]
    pub expression: Cow<'static, str>,
    /// Generation type: stored
    #[cfg_attr(feature = "serde", serde(rename = "type"))]
    pub gen_type: GeneratedType,
}

// =============================================================================
// Identity Column Types
// =============================================================================

/// Identity column type (ALWAYS vs BY DEFAULT)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub enum IdentityType {
    /// GENERATED ALWAYS AS IDENTITY
    #[default]
    Always,
    /// GENERATED BY DEFAULT AS IDENTITY
    ByDefault,
}

/// Identity column configuration (const-friendly)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct IdentityDef {
    /// Sequence name
    pub name: &'static str,
    /// Schema name (optional)
    pub schema: Option<&'static str>,
    /// Identity type: always or `by_default`
    pub type_: IdentityType,
    /// Increment value (as string)
    pub increment: Option<&'static str>,
    /// Minimum value (as string)
    pub min_value: Option<&'static str>,
    /// Maximum value (as string)
    pub max_value: Option<&'static str>,
    /// Start value (as string)
    pub start_with: Option<&'static str>,
    /// Cache value (as i32)
    pub cache: Option<i32>,
    /// Cycle flag
    pub cycle: bool,
}

impl IdentityDef {
    /// Create a new identity definition
    #[must_use]
    pub const fn new(name: &'static str, type_: IdentityType) -> Self {
        Self {
            name,
            schema: None,
            type_,
            increment: None,
            min_value: None,
            max_value: None,
            start_with: None,
            cache: None,
            cycle: false,
        }
    }

    /// Set schema
    #[must_use]
    pub const fn schema(self, schema: &'static str) -> Self {
        Self {
            schema: Some(schema),
            ..self
        }
    }

    /// Set increment
    #[must_use]
    pub const fn increment(self, value: &'static str) -> Self {
        Self {
            increment: Some(value),
            ..self
        }
    }

    /// Set minimum value
    #[must_use]
    pub const fn min_value(self, value: &'static str) -> Self {
        Self {
            min_value: Some(value),
            ..self
        }
    }

    /// Set maximum value
    #[must_use]
    pub const fn max_value(self, value: &'static str) -> Self {
        Self {
            max_value: Some(value),
            ..self
        }
    }

    /// Set start value
    #[must_use]
    pub const fn start_with(self, value: &'static str) -> Self {
        Self {
            start_with: Some(value),
            ..self
        }
    }

    /// Set cache
    #[must_use]
    pub const fn cache(self, value: i32) -> Self {
        Self {
            cache: Some(value),
            ..self
        }
    }

    /// Set cycle flag
    #[must_use]
    pub const fn cycle(self) -> Self {
        Self {
            cycle: true,
            ..self
        }
    }

    /// Convert to runtime type
    #[must_use]
    pub const fn into_identity(self) -> Identity {
        Identity {
            name: Cow::Borrowed(self.name),
            schema: match self.schema {
                Some(s) => Some(Cow::Borrowed(s)),
                None => None,
            },
            type_: self.type_,
            increment: match self.increment {
                Some(s) => Some(Cow::Borrowed(s)),
                None => None,
            },
            min_value: match self.min_value {
                Some(s) => Some(Cow::Borrowed(s)),
                None => None,
            },
            max_value: match self.max_value {
                Some(s) => Some(Cow::Borrowed(s)),
                None => None,
            },
            start_with: match self.start_with {
                Some(s) => Some(Cow::Borrowed(s)),
                None => None,
            },
            cache: self.cache,
            cycle: if self.cycle { Some(true) } else { None },
        }
    }
}

/// Identity column configuration (runtime)
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct Identity {
    /// Sequence name
    #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
    pub name: Cow<'static, str>,

    /// Schema name (optional)
    #[cfg_attr(
        feature = "serde",
        serde(
            skip_serializing_if = "Option::is_none",
            deserialize_with = "cow_option_from_string"
        )
    )]
    pub schema: Option<Cow<'static, str>>,

    /// Identity type: always or `by_default`
    #[cfg_attr(feature = "serde", serde(rename = "type"))]
    pub type_: IdentityType,

    /// Increment value
    #[cfg_attr(
        feature = "serde",
        serde(
            skip_serializing_if = "Option::is_none",
            deserialize_with = "cow_option_from_string"
        )
    )]
    pub increment: Option<Cow<'static, str>>,

    /// Minimum value
    #[cfg_attr(
        feature = "serde",
        serde(
            skip_serializing_if = "Option::is_none",
            deserialize_with = "cow_option_from_string"
        )
    )]
    pub min_value: Option<Cow<'static, str>>,

    /// Maximum value
    #[cfg_attr(
        feature = "serde",
        serde(
            skip_serializing_if = "Option::is_none",
            deserialize_with = "cow_option_from_string"
        )
    )]
    pub max_value: Option<Cow<'static, str>>,

    /// Start value
    #[cfg_attr(
        feature = "serde",
        serde(
            skip_serializing_if = "Option::is_none",
            deserialize_with = "cow_option_from_string"
        )
    )]
    pub start_with: Option<Cow<'static, str>>,

    /// Cache value
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub cache: Option<i32>,

    /// Cycle flag
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub cycle: Option<bool>,
}

impl Identity {
    /// Create a new identity with GENERATED ALWAYS AS IDENTITY
    #[must_use]
    pub fn always(name: impl Into<Cow<'static, str>>) -> Self {
        Self {
            name: name.into(),
            schema: None,
            type_: IdentityType::Always,
            increment: None,
            min_value: None,
            max_value: None,
            start_with: None,
            cache: None,
            cycle: None,
        }
    }

    /// Create a new identity with GENERATED BY DEFAULT AS IDENTITY
    #[must_use]
    pub fn by_default(name: impl Into<Cow<'static, str>>) -> Self {
        Self {
            name: name.into(),
            schema: None,
            type_: IdentityType::ByDefault,
            increment: None,
            min_value: None,
            max_value: None,
            start_with: None,
            cache: None,
            cycle: None,
        }
    }

    /// Set schema
    #[must_use]
    pub fn schema(mut self, schema: impl Into<Cow<'static, str>>) -> Self {
        self.schema = Some(schema.into());
        self
    }
}

// =============================================================================
// Const-friendly Definition Type
// =============================================================================

/// Const-friendly column definition for compile-time schema definitions.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ColumnDef {
    /// Schema name
    pub schema: &'static str,
    /// Parent table name
    pub table: &'static str,
    /// Column name
    pub name: &'static str,
    /// SQL type (e.g., "INTEGER", "TEXT", "VARCHAR")
    pub sql_type: &'static str,
    /// Type schema (for custom types)
    pub type_schema: Option<&'static str>,
    /// Is this column NOT NULL?
    pub not_null: bool,
    /// Default value as string (if any)
    pub default: Option<&'static str>,
    /// Generated column configuration
    pub generated: Option<GeneratedDef>,
    /// Identity column configuration
    pub identity: Option<IdentityDef>,
    /// Array dimensions (for array types)
    pub dimensions: Option<i32>,
    /// Collation name (e.g. `"en_US"`, `"C"`, `"POSIX"`, or any custom
    /// `CREATE COLLATION` value). `None` means "use the database default
    /// collation for this column type" and no `COLLATE` clause is emitted.
    pub collate: Option<&'static str>,
}

impl ColumnDef {
    /// Create a new column definition
    #[must_use]
    pub const fn new(
        schema: &'static str,
        table: &'static str,
        name: &'static str,
        sql_type: &'static str,
    ) -> Self {
        Self {
            schema,
            table,
            name,
            sql_type,
            type_schema: None,
            not_null: false,
            default: None,
            generated: None,
            identity: None,
            dimensions: None,
            collate: None,
        }
    }

    /// Set type schema (for custom types)
    #[must_use]
    pub const fn type_schema(self, schema: &'static str) -> Self {
        Self {
            type_schema: Some(schema),
            ..self
        }
    }

    /// Set NOT NULL constraint
    #[must_use]
    pub const fn not_null(self) -> Self {
        Self {
            not_null: true,
            ..self
        }
    }

    /// Set default value
    #[must_use]
    pub const fn default_value(self, value: &'static str) -> Self {
        Self {
            default: Some(value),
            ..self
        }
    }

    /// Set as generated stored column
    #[must_use]
    pub const fn generated_stored(self, expression: &'static str) -> Self {
        Self {
            generated: Some(GeneratedDef::stored(expression)),
            ..self
        }
    }

    /// Set as generated virtual column
    #[must_use]
    pub const fn generated_virtual(self, expression: &'static str) -> Self {
        Self {
            generated: Some(GeneratedDef::virtual_col(expression)),
            ..self
        }
    }

    /// Set as identity column
    #[must_use]
    pub const fn identity(self, identity: IdentityDef) -> Self {
        Self {
            identity: Some(identity),
            ..self
        }
    }

    /// Set array dimensions
    #[must_use]
    pub const fn dimensions(self, dims: i32) -> Self {
        Self {
            dimensions: Some(dims),
            ..self
        }
    }

    /// Set the collation for this column.
    ///
    /// PostgreSQL treats `COLLATE` identifiers as quoted names — e.g.
    /// `COLLATE "en_US"`, `COLLATE "C"`, `COLLATE "POSIX"`. Pass the bare
    /// name here; the DDL emitter wraps it in double quotes.
    #[must_use]
    pub const fn collate(self, name: &'static str) -> Self {
        Self {
            collate: Some(name),
            ..self
        }
    }

    /// Convert to runtime [`Column`] type
    ///
    /// Note: This method cannot be const because it needs to convert nested Option types
    /// (generated and identity) which require runtime method calls.
    #[must_use]
    pub const fn into_column(self) -> Column {
        Column {
            schema: Cow::Borrowed(self.schema),
            table: Cow::Borrowed(self.table),
            name: Cow::Borrowed(self.name),
            sql_type: Cow::Borrowed(self.sql_type),
            type_schema: match self.type_schema {
                Some(s) => Some(Cow::Borrowed(s)),
                None => None,
            },
            not_null: self.not_null,
            default: match self.default {
                Some(s) => Some(Cow::Borrowed(s)),
                None => None,
            },
            generated: match self.generated {
                Some(g) => Some(g.into_generated()),
                None => None,
            },
            identity: match self.identity {
                Some(i) => Some(i.into_identity()),
                None => None,
            },
            dimensions: self.dimensions,
            collate: match self.collate {
                Some(s) => Some(Cow::Borrowed(s)),
                None => None,
            },
            ordinal_position: None,
        }
    }
}

impl Default for ColumnDef {
    fn default() -> Self {
        Self::new("public", "", "", "")
    }
}

// =============================================================================
// Runtime Type for Serde
// =============================================================================

/// Runtime column entity for serde serialization.
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub struct Column {
    /// Schema name
    #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
    pub schema: Cow<'static, str>,

    /// Parent table name
    #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
    pub table: Cow<'static, str>,

    /// Column name
    #[cfg_attr(feature = "serde", serde(deserialize_with = "cow_from_string"))]
    pub name: Cow<'static, str>,

    /// SQL type (e.g., "INTEGER", "TEXT", "VARCHAR")
    #[cfg_attr(
        feature = "serde",
        serde(rename = "type", deserialize_with = "cow_from_string")
    )]
    pub sql_type: Cow<'static, str>,

    /// Type schema (for custom types)
    #[cfg_attr(
        feature = "serde",
        serde(
            default,
            skip_serializing_if = "Option::is_none",
            deserialize_with = "cow_option_from_string"
        )
    )]
    pub type_schema: Option<Cow<'static, str>>,

    /// Is this column NOT NULL?
    #[cfg_attr(feature = "serde", serde(default))]
    pub not_null: bool,

    /// Default value as string
    #[cfg_attr(
        feature = "serde",
        serde(default, deserialize_with = "cow_option_from_string")
    )]
    pub default: Option<Cow<'static, str>>,

    /// Generated column configuration
    #[cfg_attr(feature = "serde", serde(default))]
    pub generated: Option<Generated>,

    /// Identity column configuration
    #[cfg_attr(feature = "serde", serde(default))]
    pub identity: Option<Identity>,

    /// Array dimensions (for array types)
    #[cfg_attr(feature = "serde", serde(default))]
    pub dimensions: Option<i32>,

    /// Collation name (e.g. `"en_US"`, `"C"`). `None` means the database
    /// default collation and no `COLLATE` clause is emitted.
    #[cfg_attr(
        feature = "serde",
        serde(default, deserialize_with = "cow_option_from_string")
    )]
    pub collate: Option<Cow<'static, str>>,

    /// Ordinal position within the table (1-based).
    ///
    /// This is primarily populated by introspection and used for stable codegen ordering.
    #[cfg_attr(
        feature = "serde",
        serde(default, skip_serializing_if = "Option::is_none")
    )]
    pub ordinal_position: Option<i32>,
}

impl Column {
    /// Create a new column (runtime)
    #[must_use]
    pub fn new(
        schema: impl Into<Cow<'static, str>>,
        table: impl Into<Cow<'static, str>>,
        name: impl Into<Cow<'static, str>>,
        sql_type: impl Into<Cow<'static, str>>,
    ) -> Self {
        Self {
            schema: schema.into(),
            table: table.into(),
            name: name.into(),
            sql_type: sql_type.into(),
            type_schema: None,
            not_null: false,
            default: None,
            generated: None,
            identity: None,
            dimensions: None,
            collate: None,
            ordinal_position: None,
        }
    }

    /// Set NOT NULL
    #[must_use]
    pub const fn not_null(mut self) -> Self {
        self.not_null = true;
        self
    }

    /// Set default value
    #[must_use]
    pub fn default_value(mut self, value: impl Into<Cow<'static, str>>) -> Self {
        self.default = Some(value.into());
        self
    }

    /// Set identity configuration
    #[must_use]
    pub fn identity(mut self, identity: Identity) -> Self {
        self.identity = Some(identity);
        self
    }

    /// Get the schema name
    #[inline]
    #[must_use]
    pub fn schema(&self) -> &str {
        &self.schema
    }

    /// Get the table name
    #[inline]
    #[must_use]
    pub fn table(&self) -> &str {
        &self.table
    }

    /// Get the column name
    #[inline]
    #[must_use]
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Get the SQL type
    #[inline]
    #[must_use]
    pub fn sql_type(&self) -> &str {
        &self.sql_type
    }
}

impl Default for Column {
    fn default() -> Self {
        Self::new("public", "", "", "")
    }
}

impl From<ColumnDef> for Column {
    fn from(def: ColumnDef) -> Self {
        def.into_column()
    }
}

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

    #[test]
    fn test_const_column_def() {
        const COLDEF: ColumnDef = ColumnDef::new("public", "users", "id", "INTEGER").not_null();

        assert_eq!(COLDEF.schema, "public");
        assert_eq!(COLDEF.name, "id");
        assert_eq!(COLDEF.table, "users");
        assert_eq!(COLDEF.sql_type, "INTEGER");
        const {
            assert!(COLDEF.not_null);
        }

        let col: Column = COLDEF.into_column();

        assert_eq!(col.schema, Cow::Borrowed("public"));
        assert_eq!(col.name, Cow::Borrowed("id"));
        assert_eq!(col.table, Cow::Borrowed("users"));
        assert_eq!(col.sql_type, Cow::Borrowed("INTEGER"));
        assert!(col.not_null);
    }

    #[test]
    fn test_identity_column() {
        const IDENTITY_DEF: IdentityDef = IdentityDef::new("users_id_seq", IdentityType::Always)
            .increment("1")
            .start_with("1");

        const COL: ColumnDef =
            ColumnDef::new("public", "users", "id", "INTEGER").identity(IDENTITY_DEF);

        assert!(COL.identity.is_some());
    }

    #[test]
    fn test_generated_virtual_column() {
        const COL: ColumnDef = ColumnDef::new("public", "users", "name_len", "INTEGER")
            .generated_virtual("length(name)");

        assert_eq!(
            COL.generated.expect("generated column").gen_type,
            GeneratedType::Virtual
        );

        let col = COL.into_column();
        assert_eq!(
            col.generated.expect("generated column").gen_type,
            GeneratedType::Virtual
        );
    }

    #[cfg(feature = "serde")]
    #[test]
    fn test_serde_roundtrip() {
        let col = Column::new("public", "users", "id", "INTEGER");
        let json = serde_json::to_string(&col).unwrap();
        let parsed: Column = serde_json::from_str(&json).unwrap();
        assert_eq!(parsed.name(), "id");
    }
}
