// SPDX-License-Identifier: GPL-3.0-or-later
use std::error::Error;

#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "action")]
pub enum Action {
    Update(Window),
    Clear {
        name: String,
    },
    ClearAll,
}

#[derive(Debug, Clone, Deserialize)]
pub struct Window {
    #[serde(default = "default_title")]
    pub title: String,
    pub prefix: Option<String>,
    pub root: Component,
    #[serde(default = "default_scale")]
    pub scale: f64,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "type")]
pub enum Component {
    Text {
        text: String,
        font_size: Option<u16>,
        height: Option<Length>,
        halign: Option<HorizontalAlignment>,
        valign: Option<VerticalAlignment>,
    },
    Button {
        #[serde(default)]
        label: Label,
        name: Option<String>,
        width: Option<Length>,
        height: Option<Length>,
        padding: Option<Padding>,
    },
    Row {
        elements: Vec<Component>,
        width: Option<Length>,
        height: Option<Length>,
        align: Option<Alignment>,
        spacing: Option<u16>,
        padding: Option<Padding>,
    },
    Column {
        elements: Vec<Component>,
        width: Option<Length>,
        height: Option<Length>,
        align: Option<Alignment>,
        spacing: Option<u16>,
        padding: Option<Padding>,
        max_width: Option<u32>,
    },
    VerticalRule {
        #[serde(default)]
        width: u16,
    },
    HorizontalRule {
        #[serde(default)]
        height: u16,
    },
    Checkbox {
        #[serde(default)]
        label: String,
        initial_state: bool,
        name: String,
        width: Option<Length>,
        spacing: Option<u16>,
        text_size: Option<u16>,
        override_state: Option<bool>,
    },
    Container {
        content: Box<Component>,
        padding: Option<Padding>,
        width: Option<Length>,
        height: Option<Length>,
        max_width: Option<u32>,
        max_height: Option<u32>,
        align_x: Option<HorizontalAlignment>,
        align_y: Option<VerticalAlignment>,
    },
    TextInput {
        name: String,
        #[serde(default)]
        initial_value: String,
        #[serde(default)]
        placeholder: String,
        #[serde(default)]
        subtype: TextInputKind,
        width: Option<Length>,
        padding: Option<Padding>,
        override_value: Option<String>,
    },
}

#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum Label {
    String(String),
    Component(Box<Component>),
}

#[derive(Debug, Clone, Copy, Deserialize)]
pub enum Length {
    Fill,
    Shrink,
    FillPortion(u16),
    Units(u16),
}

#[derive(Debug, Clone, Copy, Deserialize)]
pub enum Alignment {
    Start,
    End,
    Center,
    Fill,
}

#[derive(Debug, Clone, Copy, Deserialize)]
pub enum HorizontalAlignment {
    Left,
    Center,
    Right,
}

#[derive(Debug, Clone, Copy, Deserialize)]
pub enum VerticalAlignment {
    Top,
    Center,
    Bottom,
}

#[derive(Debug, Clone, Copy, Deserialize)]
#[serde(untagged)]
pub enum Padding {
    Simple(u16),
    Full {
        top: u16,
        bottom: u16,
        left: u16,
        right: u16,
    }
}

#[derive(Debug, Default, Clone, Copy, Deserialize)]
pub enum TextInputKind {
    #[default]
    Normal,
    Password,
}

fn text(s: impl Into<String>) -> Component {
    Component::Text {
        text: s.into(),
        height: None,
        valign: None,
        halign: None,
        font_size: None,
    }
}

fn default_title() -> String {
    "Unnamed Window".to_owned()
}

fn default_scale() -> f64 {
    1.0
}

impl Window {
    pub fn error(err: Box<dyn Error>) -> Self {
        Self {
            title: "Error".to_owned(),
            root: text(format!("Error: {err}")),
            ..Default::default()
        }
    }
}

impl Default for Label {
    fn default() -> Self {
        Self::String(String::new())
    }
}


impl Default for Window {
    fn default() -> Self {
        Window {
            title: default_title(),
            scale: default_scale(),
            root: text("No Content"),
            prefix: None,
        }
    }
}
