use ruin_ui::{Color, Element, TextFontFamily, TextSpan, TextSpanWeight, TextStyle, TextWrap};

use crate::colors;
use crate::context::allocate_element_id;
use crate::text_style::current_text_style;
use crate::view::{TextChildren, View};

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum TextRole {
    Body,
    Heading(u8),
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FontWeight {
    Normal,
    Medium,
    Semibold,
    Bold,
}

impl FontWeight {
    pub(crate) const fn to_text_span_weight(self) -> TextSpanWeight {
        match self {
            Self::Normal => TextSpanWeight::Normal,
            Self::Medium => TextSpanWeight::Medium,
            Self::Semibold => TextSpanWeight::Semibold,
            Self::Bold => TextSpanWeight::Bold,
        }
    }
}

#[derive(Default)]
pub struct TextBuilder {
    pub(crate) role: Option<TextRole>,
    pub(crate) size: Option<f32>,
    pub(crate) weight: Option<FontWeight>,
    pub(crate) color: Option<Color>,
    pub(crate) font_family: Option<TextFontFamily>,
    pub(crate) wrap: Option<TextWrap>,
    pub(crate) selectable: Option<bool>,
    pub(crate) pointer_events: Option<bool>,
}

pub fn text() -> TextBuilder {
    TextBuilder::default()
}

impl TextBuilder {
    pub fn role(mut self, role: TextRole) -> Self {
        self.role = Some(role);
        self
    }

    pub fn size(mut self, size: f32) -> Self {
        self.size = Some(size);
        self
    }

    pub fn weight(mut self, weight: FontWeight) -> Self {
        self.weight = Some(weight);
        self
    }

    pub fn color(mut self, color: Color) -> Self {
        self.color = Some(color);
        self
    }

    pub fn font_family(mut self, family: TextFontFamily) -> Self {
        self.font_family = Some(family);
        self
    }

    pub fn wrap(mut self, wrap: TextWrap) -> Self {
        self.wrap = Some(wrap);
        self
    }

    pub fn selectable(mut self, selectable: bool) -> Self {
        self.selectable = Some(selectable);
        self
    }

    pub fn pointer_events(mut self, pointer_events: bool) -> Self {
        self.pointer_events = Some(pointer_events);
        self
    }

    pub fn children(self, children: impl TextChildren) -> View {
        let inherited = current_text_style();

        let role = self.role.unwrap_or(TextRole::Body);
        let size = self.size.or(inherited.size).unwrap_or(match role {
            TextRole::Body => 18.0,
            TextRole::Heading(1) => 32.0,
            TextRole::Heading(2) => 28.0,
            TextRole::Heading(_) => 24.0,
        });
        let color = self.color.or(inherited.color).unwrap_or(colors::text());
        let weight = self.weight.or(inherited.weight).unwrap_or(match role {
            TextRole::Body => FontWeight::Normal,
            TextRole::Heading(_) => FontWeight::Semibold,
        });
        let wrap = self.wrap.or(inherited.wrap).unwrap_or(TextWrap::None);
        let line_height = inherited.line_height.unwrap_or(size * 1.2);

        let mut style = TextStyle::new(size, color)
            .with_line_height(line_height)
            .with_wrap(wrap);

        let font_family = self.font_family.or(inherited.font_family);
        if let Some(font_family) = font_family {
            style = style.with_font_family(font_family);
        }

        let span = TextSpan::new(children.into_text())
            .color(color)
            .weight(weight.to_text_span_weight());
        View::from_element(
            Element::spans(
                [span],
                style.with_selectable(self.selectable.unwrap_or(true)),
            )
            .pointer_events(self.pointer_events.unwrap_or(true))
            .id(allocate_element_id()),
        )
    }
}
