//! Character-creation form schema, exchanged between server and client so
//! the character builder UI can render the right form for the active plugin.

use std::collections::HashMap;

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct CharacterCreationSchema {
    pub fields: Vec<FieldSpec>,
}

impl CharacterCreationSchema {
    pub fn name_only() -> Self {
        Self {
            fields: vec![FieldSpec::Text {
                key:       "name".into(),
                label:     "Character name".into(),
                max_chars: 80,
            }],
        }
    }
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, PartialEq)]
pub enum FieldSpec {
    Text {
        key:       String,
        label:     String,
        max_chars: usize,
    },
    Dropdown {
        key:     String,
        label:   String,
        options: Vec<String>,
    },
    MultiSelect {
        key:         String,
        label:       String,
        options:     Vec<String>,
        min_choices: usize,
        max_choices: usize,
    },
    AbilityScoreArray {
        keys:   [String; 6],
        labels: [String; 6],
    },
}

#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "schema", derive(utoipa::ToSchema))]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct CharacterFields {
    pub values: HashMap<String, String>,
    #[cfg_attr(feature = "serde", serde(default))]
    pub ability_scores: Option<[i32; 6]>,
    #[cfg_attr(feature = "serde", serde(default))]
    pub multi_values: HashMap<String, Vec<String>>,
}

impl CharacterFields {
    pub fn name_only(name: impl Into<String>) -> Self {
        let mut values = HashMap::new();
        values.insert("name".to_string(), name.into());
        Self { values, ability_scores: None, multi_values: HashMap::new() }
    }

    pub fn get(&self, key: &str) -> Option<&str> {
        self.values.get(key).map(|s| s.as_str())
    }

    pub fn get_multi(&self, key: &str) -> Option<&[String]> {
        self.multi_values.get(key).map(|v| v.as_slice())
    }
}

#[cfg(all(test, feature = "serde"))]
mod tests {
    use super::*;

    #[test]
    fn default_schema_is_name_only() {
        let s = CharacterCreationSchema::name_only();
        assert_eq!(s.fields.len(), 1);
        match &s.fields[0] {
            FieldSpec::Text { key, max_chars, .. } => {
                assert_eq!(key, "name");
                assert_eq!(*max_chars, 80);
            }
            other => panic!("expected Text, got {other:?}"),
        }
    }

    #[test]
    fn fields_name_only_helper() {
        let f = CharacterFields::name_only("Aerin");
        assert_eq!(f.get("name"), Some("Aerin"));
        assert!(f.ability_scores.is_none());
    }
}
