use std::fmt::Debug;

use serde::{Deserialize, Serialize};

use super::{
    chat_message::ChatMessage,
    entry::{ActionDefinition, ActionResult, GameEntry},
};

pub trait Character<E: GameEntry>: CharacterSheet {
    /// types of actions that can be done on the specified entry (e.g. cast for spells and wear for armor)
    fn actions(&self, entry: &E) -> Vec<ActionDefinition>;
    /// uses an action for a specific character, some actions should be split into multiple
    ///
    /// for example, an attack sequence will be of:
    ///
    /// - `Attack` invoked on a weapon entry, which will have the `roll damage` option in the chat
    /// - `roll damage` will be done on the attacking character, which show the `damage` and `double` actions
    /// - `damage` will be used on the target character (which could apply reductions as well)
    fn use_action(&mut self, entry: &E, action: &ActionResult) -> ChatMessage;
    /// character short display, containing basic info like name(title) level and health
    fn short(&self, id: usize) -> CharacterShort;
}

pub trait CharacterSheet: Default {
    /// Character sheet inputs (stuff that are not calculated from different items), such as Name, Age, Strength
    fn inputs(&self, access: AccessLevel) -> Vec<(String, EntryType)>;
    /// All fields in the character sheet, maybe change it to only a vec of strings?
    fn fields(&self) -> Vec<(String, EntryType)>;
    /// Character sheet to display, ordered. `None`s can be used to convey a seperator (of any sort)
    fn display(&self, access: AccessLevel) -> Vec<Option<(String, EntryType)>>;
    /// Gets a character sheet entry value
    fn get(&self, entry: &str, access: AccessLevel) -> Option<EntryType>;
    /// Sets a cahrater sheet entry value
    fn set(&mut self, entry: &str, value: EntryType, access: AccessLevel);
}

#[derive(Debug, Copy, Clone)]
pub enum AccessLevel {
    Admin = 0,
    Owner = 1,
    PartyMember = 2,
    World = 3,
}
impl AccessLevel {
    pub fn has_access(self, other: Self) -> bool {
        (self as u8) <= (other as u8)
    }
}

/// Short character description to be shown in a simple small card,
/// any of the fields can be empty apart from title (as it makes no sense to omit that)
///
/// following is a diagram of what it should look like (dots are part of title), tho actual looks depend
/// on the clients themselves
///
/// ```text
///  _______________________
/// |  TITLE ...      level |
/// |  .........            |
/// |  sub_title     health |
/// |_______________________|
/// ```
pub struct CharacterShort {
    /// Character id to make sure we all know who is who
    pub id: usize,
    /// Main title, usually the name
    pub title: String,
    /// little title under the main title, probably a title or class
    pub sub_title: Option<String>,
    /// health, can be a number but is a string to allow stuff as `wounded`/`unharmed`
    pub health: Option<String>,
    pub level: Option<i32>,
}

#[derive(Debug, Serialize, Deserialize)]
pub enum EntryType {
    Number(i32),
    Text(String),
    Bool(bool),
    Array(Vec<EntryType>),
}
impl Default for EntryType {
    fn default() -> Self {
        EntryType::Number(0)
    }
}
impl EntryType {
    pub fn as_num(&self) -> i32 {
        if let EntryType::Number(n) = *self { n } else { 0 }
    }
    pub fn as_text(&self) -> String {
        if let EntryType::Text(t) = self {
            t.clone()
        } else {
            String::new()
        }
    }
    pub fn as_bool(&self) -> bool {
        if let EntryType::Bool(b) = *self { b } else { false }
    }
}
impl std::fmt::Display for EntryType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            EntryType::Number(n) => write!(f, "{}", n),
            EntryType::Text(t) => write!(f, "{}", t),
            EntryType::Bool(b) => write!(f, "{}", b),
            EntryType::Array(v) => {
                write!(f, "[ ")?;
                if v.len() > 0 {
                    for i in v.iter().take(v.len() - 1) {
                        write!(f, "{}, ", i)?;
                    }
                    write!(f, "{} ", v.iter().last().unwrap())?;
                };
                write!(f, "]")
            }
        }
    }
}
