use serde::Serialize;

use crate::frame::FrameCtx;
use crate::hit_test::Interaction;
use crate::layout::LayoutRect;
use crate::strings::StringKey;
use crate::theme::{
    Border, Color, ElevationLevel, Radius, Spacing, Step12, StrokeWidth, Theme, TypographyRole,
};

use super::paint::{GlyphMark, LabelText, WidgetPaint};

#[derive(Copy, Clone, Debug, PartialEq, Serialize)]
pub struct SurfaceVisuals {
    pub fill: Color,
    pub border: Option<Border>,
    pub radius: Radius,
    pub elevation: Option<ElevationLevel>,
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize)]
pub struct TextVisuals {
    pub color: Color,
    pub role: TypographyRole,
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize)]
pub struct FieldVisuals {
    pub surface: SurfaceVisuals,
    pub text: TextVisuals,
    pub placeholder: Color,
    pub caret: Color,
    pub selection: Color,
}

pub fn push_focus_ring(
    ctx: &FrameCtx<'_>,
    paint: &mut Vec<WidgetPaint>,
    rect: LayoutRect,
    radius: Radius,
    live_focused: bool,
) {
    if live_focused && ctx.focus.focus_visible() {
        paint.push(WidgetPaint::FocusRing {
            rect,
            color: ctx.theme().colors.focus_ring(),
            radius,
            thickness: Spacing::px(StrokeWidth::HAIRLINE.value_px() * 2.0),
        });
    }
}

#[must_use]
pub fn indicator_fill(
    theme: &Theme,
    active: bool,
    disabled: bool,
    interaction: Interaction,
) -> Color {
    let neutral = theme.colors.neutral;
    let accent = theme.colors.accent;
    let pressed = interaction.pressed();
    let hovered = interaction.hover();
    if disabled {
        neutral.step(Step12::SUBTLE_BG)
    } else if active {
        if pressed || hovered {
            accent.step(Step12::HOVER_SOLID)
        } else {
            accent.step(Step12::SOLID)
        }
    } else if pressed {
        neutral.step(Step12::SELECTED_BG)
    } else if hovered {
        neutral.step(Step12::HOVER_BG)
    } else {
        neutral.step(Step12::ELEMENT_BG)
    }
}

#[must_use]
pub fn indicator_border(theme: &Theme, active: bool, hovered: bool) -> Option<Border> {
    (!active).then(|| Border {
        width: StrokeWidth::HAIRLINE,
        color: theme.colors.neutral.step(if hovered {
            Step12::HOVER_BORDER
        } else {
            Step12::BORDER
        }),
    })
}

#[must_use]
pub fn indicator_label_color(
    theme: &Theme,
    surface_fill: Color,
    active: bool,
    disabled: bool,
) -> Color {
    if disabled {
        theme.colors.text_disabled()
    } else if active {
        theme.colors.contrast_text(surface_fill)
    } else {
        theme.colors.text_primary()
    }
}

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Indicator {
    pub rect: LayoutRect,
    pub label: StringKey,
    pub mark: Option<GlyphMark>,
    pub active: bool,
    pub disabled: bool,
    pub radius: Radius,
}

pub fn push_indicator(
    ctx: &FrameCtx<'_>,
    paint: &mut Vec<WidgetPaint>,
    indicator: Indicator,
    interaction: Interaction,
    live_focused: bool,
) {
    let Indicator {
        rect,
        label,
        mark,
        active,
        disabled,
        radius,
    } = indicator;
    let fill = indicator_fill(ctx.theme(), active, disabled, interaction);
    let border = indicator_border(ctx.theme(), active, interaction.hover());
    paint.push(WidgetPaint::Surface {
        rect,
        fill,
        border,
        radius,
        elevation: None,
    });
    paint.push(WidgetPaint::Label {
        rect,
        text: LabelText::Key(label),
        color: indicator_label_color(ctx.theme(), fill, active, disabled),
        role: ctx.theme().typography.label,
    });
    if let Some(kind) = mark {
        paint.push(WidgetPaint::Mark {
            rect,
            kind,
            color: ctx.theme().colors.contrast_text(fill),
        });
    }
    push_focus_ring(ctx, paint, rect, radius, live_focused);
}
