use phono_backends::pg_attribute::PgAttribute;
use serde::{Deserialize, Serialize};
use strum::{EnumDiscriminants, EnumIter, EnumString};

pub const RFC_3339_S: &str = "yyyy-MM-dd'T'HH:mm:ssXXX";

/// Struct defining how a field's is displayed and how it accepts input in UI.
#[derive(
    Clone,
    Debug,
    Deserialize,
    EnumDiscriminants,
    EnumIter,
    EnumString,
    PartialEq,
    Serialize,
    strum::Display,
)]
#[strum_discriminants(derive(EnumString))]
#[serde(tag = "t", content = "c")]
pub enum Presentation {
    Dropdown {
        allow_custom: bool,
        options: Vec<DropdownOption>,
    },
    Numeric {},
    Text {
        input_mode: TextInputMode,
    },
    Timestamp {
        format: String,
        utc: bool,
    },
    Uuid {},
}

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct DropdownOption {
    pub color: String,
    pub value: String,
}

impl Presentation {
    /// Returns a SQL fragment for the default data type for creating or
    /// altering a backing column, such as "integer", or "timestamptz".
    pub fn attr_data_type_fragment(&self) -> &'static str {
        match self {
            Self::Dropdown { .. } | Self::Text { .. } => "text",
            Self::Numeric { .. } => "numeric",
            Self::Timestamp { .. } => "timestamptz",
            Self::Uuid { .. } => "uuid",
        }
    }

    /// Generate the default presentation based on an existing column's type.
    /// Returns None if no default presentation exists.
    pub fn default_from_attr(attr: &PgAttribute) -> Option<Self> {
        match attr.regtype.to_lowercase().as_str() {
            "numeric" => Some(Self::Numeric {}),
            "text" => Some(Self::Text {
                input_mode: TextInputMode::MultiLine {},
            }),
            "timestamp" => Some(Self::Timestamp {
                format: RFC_3339_S.to_owned(),
                utc: true,
            }),
            "uuid" => Some(Self::Uuid {}),
            _ => None,
        }
    }
}

impl Default for Presentation {
    /// Defaults to [`Self::Text`] as a reasonable fallback. The [`Default`]
    /// trait is implemented for convenience, but in the vast majority of cases
    /// the presentation value should be well defined and this should not be
    /// called directly.
    fn default() -> Self {
        Self::Text {
            input_mode: Default::default(),
        }
    }
}

#[derive(Clone, Debug, Deserialize, EnumString, EnumIter, PartialEq, Serialize, strum::Display)]
#[serde(tag = "t", content = "c")]
pub enum TextInputMode {
    SingleLine {},
    MultiLine {},
}

impl Default for TextInputMode {
    fn default() -> Self {
        Self::MultiLine {}
    }
}
