//! The `text` view, accepting either a static string or a reactive closure.
//!
//! `text("hi")` and `text(move || format!("{}", n.get()))` both work. Overloading
//! on "value vs closure" in Rust hits coherence limits, so we use the marker-type
//! trick (a phantom `M` distinguishes the impls) — the same approach axum and
//! Bevy use for "accept a value or a function".

use core::marker::PhantomData;

use crate::core::Color;
use crate::dom::{Attribute, Tree, WidgetId};

use super::view::View;

// ---------------------------------------------------------------------------
// RichText
// ---------------------------------------------------------------------------

/// A text view with inline spans (each span can have different color/size/weight).
/// Build via [`rich_text`].
pub struct RichText {
    spans: Vec<crate::dom::TextSpan>,
    number_of_lines: Option<u32>,
    align: Option<crate::dom::TextAlign>,
}

/// Creates a rich text view. Add spans via [`RichText::span`].
pub fn rich_text() -> RichText {
    RichText {
        spans: vec![],
        number_of_lines: None,
        align: None,
    }
}

impl RichText {
    /// Appends a [`TextSpan`](crate::dom::TextSpan) to this rich text view.
    #[must_use]
    pub fn span(mut self, span: crate::dom::TextSpan) -> Self {
        self.spans.push(span);
        self
    }

    /// Sets the maximum number of lines (0 = unlimited).
    #[must_use]
    pub fn lines(mut self, n: u32) -> Self {
        self.number_of_lines = Some(n);
        self
    }

    /// Sets the horizontal text alignment.
    #[must_use]
    pub fn align(mut self, a: crate::dom::TextAlign) -> Self {
        self.align = Some(a);
        self
    }
}

impl View for RichText {
    fn build(self, tree: &mut Tree) -> WidgetId {
        let id = tree.create_text();
        tree.set(id, Attribute::RichText(self.spans));
        if let Some(n) = self.number_of_lines {
            tree.set(id, Attribute::NumberOfLines(n));
        }
        if let Some(align) = self.align {
            tree.set(id, Attribute::TextAlign(align));
        }
        id
    }
}

/// Marker: the text is a fixed string set once.
pub struct StaticText;
/// Marker: the text is a reactive closure bound to signals.
pub struct DynamicText;

/// Converts an argument to `text` into a binding on a text widget.
pub trait IntoText<M> {
    /// Applies the value to `id` in `tree` (static set or reactive bind).
    fn apply(self, tree: &mut Tree, id: WidgetId);
}

impl IntoText<StaticText> for &str {
    fn apply(self, tree: &mut Tree, id: WidgetId) {
        tree.set(id, Attribute::Text(self.to_string()));
    }
}

impl IntoText<StaticText> for String {
    fn apply(self, tree: &mut Tree, id: WidgetId) {
        tree.set(id, Attribute::Text(self));
    }
}

impl<F: FnMut() -> String + 'static> IntoText<DynamicText> for F {
    fn apply(self, tree: &mut Tree, id: WidgetId) {
        let mut f = self;
        tree.bind(id, move || Attribute::Text(f()));
    }
}

/// A text label view. Build via [`text`].
pub struct Text<M, T: IntoText<M>> {
    value: T,
    font_size: Option<f32>,
    color: Option<Color>,
    weight: Option<f32>,
    italic: bool,
    align: Option<crate::dom::TextAlign>,
    number_of_lines: Option<u32>,
    font_family: Option<String>,
    text_style: Option<crate::dom::TextStyle>,
    letter_spacing: Option<f32>,
    line_height: Option<f32>,
    text_decoration: Option<crate::dom::TextDecoration>,
    text_shadow: Option<(Color, f32, f32, f32)>,
    font_style: Option<crate::dom::FontStyle>,
    selectable: Option<bool>,
    paragraph_spacing: Option<f32>,
    _marker: PhantomData<fn() -> M>,
}

/// Creates a text view from a static string or a reactive `FnMut() -> String`.
pub fn text<M, T: IntoText<M>>(value: T) -> Text<M, T> {
    Text {
        value,
        font_size: None,
        color: None,
        weight: None,
        italic: false,
        align: None,
        number_of_lines: None,
        font_family: None,
        text_style: None,
        letter_spacing: None,
        line_height: None,
        text_decoration: None,
        text_shadow: None,
        font_style: None,
        selectable: None,
        paragraph_spacing: None,
        _marker: PhantomData,
    }
}

impl<M, T: IntoText<M>> Text<M, T> {
    /// Sets the font size in logical pixels.
    #[must_use]
    pub fn font_size(mut self, size: f32) -> Self {
        self.font_size = Some(size);
        self
    }

    /// Sets the text color.
    #[must_use]
    pub fn color(mut self, color: Color) -> Self {
        self.color = Some(color);
        self
    }

    /// Sets a font weight (100–900).
    #[must_use]
    pub fn weight(mut self, weight: f32) -> Self {
        self.weight = Some(weight);
        self
    }

    /// Bold (weight 700).
    #[must_use]
    pub fn bold(mut self) -> Self {
        self.weight = Some(700.0);
        self
    }

    /// Italic style.
    #[must_use]
    pub fn italic(mut self) -> Self {
        self.italic = true;
        self
    }

    /// Horizontal text alignment.
    #[must_use]
    pub fn align(mut self, align: crate::dom::TextAlign) -> Self {
        self.align = Some(align);
        self
    }

    /// Sets the maximum number of lines (0 = unlimited).
    #[must_use]
    pub fn lines(mut self, n: u32) -> Self {
        self.number_of_lines = Some(n);
        self
    }

    /// Sets a custom font family by PostScript name (e.g. `"Georgia-Bold"`).
    /// Falls back to the system font if the name is not registered on device.
    #[must_use]
    pub fn font_family(mut self, name: impl Into<String>) -> Self {
        self.font_family = Some(name.into());
        self
    }

    /// Applies a Dynamic Type semantic text style that scales with the user's
    /// preferred reading size. Overrides any fixed `font_size` set earlier.
    #[must_use]
    pub fn text_style(mut self, style: crate::dom::TextStyle) -> Self {
        self.text_style = Some(style);
        self
    }

    /// Sets letter spacing (tracking) in points.
    #[must_use]
    pub fn letter_spacing(mut self, kern: f32) -> Self {
        self.letter_spacing = Some(kern);
        self
    }

    /// Sets the line height multiplier.
    #[must_use]
    pub fn line_height(mut self, h: f32) -> Self {
        self.line_height = Some(h);
        self
    }

    /// Applies a single underline decoration.
    #[must_use]
    pub fn underline(mut self) -> Self {
        self.text_decoration = Some(crate::dom::TextDecoration::Underline);
        self
    }

    /// Applies a strikethrough decoration.
    #[must_use]
    pub fn strikethrough(mut self) -> Self {
        self.text_decoration = Some(crate::dom::TextDecoration::Strikethrough);
        self
    }

    /// Applies a text shadow.
    ///
    /// - `color` — shadow color (alpha controls opacity).
    /// - `offset_x` / `offset_y` — shadow offset in points.
    /// - `blur` — blur radius in points.
    #[must_use]
    pub fn text_shadow(mut self, color: Color, offset_x: f32, offset_y: f32, blur: f32) -> Self {
        self.text_shadow = Some((color, offset_x, offset_y, blur));
        self
    }

    /// Sets the font style (normal, italic, or oblique).
    ///
    /// On iOS, italic maps to `UIFont.italicSystemFont(ofSize:)` or a named
    /// italic variant; oblique falls back to italic.
    #[must_use]
    pub fn font_style(mut self, style: crate::dom::FontStyle) -> Self {
        self.font_style = Some(style);
        self
    }

    /// Controls whether this text label's content is user-selectable
    /// (copy / selection handles).
    ///
    /// On iOS, `true` is a hint to the backend to use `UITextView` instead of
    /// `UILabel`, which supports selection. `false` (the default) uses `UILabel`.
    #[must_use]
    pub fn selectable(mut self, enabled: bool) -> Self {
        self.selectable = Some(enabled);
        self
    }

    /// Sets paragraph spacing in points — extra vertical space added after
    /// each paragraph break (`\n`).
    ///
    /// Maps to `NSParagraphStyle.paragraphSpacing` on iOS via
    /// `NSMutableParagraphStyle`.
    #[must_use]
    pub fn paragraph_spacing(mut self, spacing: f32) -> Self {
        self.paragraph_spacing = Some(spacing);
        self
    }
}

impl<M, T: IntoText<M>> View for Text<M, T> {
    fn build(self, tree: &mut Tree) -> WidgetId {
        let id = tree.create_text();
        // Size first, so the backend can compose weight/italic onto it.
        if let Some(size) = self.font_size {
            tree.set(id, Attribute::FontSize(size));
        }
        if let Some(weight) = self.weight {
            tree.set(id, Attribute::FontWeight(weight));
        }
        if self.italic {
            tree.set(id, Attribute::Italic(true));
        }
        if let Some(color) = self.color {
            tree.set(id, Attribute::TextColor(color));
        }
        if let Some(align) = self.align {
            tree.set(id, Attribute::TextAlign(align));
        }
        if let Some(n) = self.number_of_lines {
            tree.set(id, Attribute::NumberOfLines(n));
        }
        if let Some(family) = self.font_family {
            tree.set(id, Attribute::FontFamily(family));
        }
        if let Some(style) = self.text_style {
            tree.set(id, Attribute::TextStyle(style));
        }
        if let Some(kern) = self.letter_spacing {
            tree.set(id, Attribute::LetterSpacing(kern));
        }
        if let Some(h) = self.line_height {
            tree.set(id, Attribute::LineHeight(h));
        }
        if let Some(decoration) = self.text_decoration {
            tree.set(id, Attribute::TextDecoration(decoration));
        }
        if let Some((color, offset_x, offset_y, blur)) = self.text_shadow {
            tree.set(id, Attribute::TextShadow { color, offset_x, offset_y, blur });
        }
        if let Some(style) = self.font_style {
            tree.set(id, Attribute::FontStyle(style));
        }
        if let Some(enabled) = self.selectable {
            tree.set(id, Attribute::UserSelectText(enabled));
        }
        if let Some(spacing) = self.paragraph_spacing {
            tree.set(id, Attribute::ParagraphSpacing(spacing));
        }
        self.value.apply(tree, id);
        id
    }
}
