use crate::ui::{
    component::{base::ComponentExt, Child, Component, Event, EventCtx},
    constant::screen,
    event::TouchEvent,
    geometry::{Grid, Insets, Point, Rect},
    shape::{self, Renderer},
};

use super::{theme, Button, ButtonMsg};

pub enum NumberInputSliderDialogMsg {
    Changed(u8),
    Confirmed,
    Cancelled,
}

pub struct NumberInputSliderDialog {
    area: Rect,
    input: Child<NumberInputSlider>,
    cancel_button: Child<Button>,
    confirm_button: Child<Button>,
}

impl NumberInputSliderDialog {
    pub fn new(min: u8, max: u8, init_value: u8) -> Self {
        debug_assert!(min < max);
        Self {
            area: Rect::zero(),
            input: NumberInputSlider::new(min, max, init_value).into_child(),
            cancel_button: Button::with_text("CANCEL".into())
                .styled(theme::button_cancel())
                .into_child(),
            confirm_button: Button::with_text("CONFIRM".into())
                .styled(theme::button_confirm())
                .into_child(),
        }
    }

    pub fn value(&self) -> u8 {
        self.input.inner().value
    }
}

impl Component for NumberInputSliderDialog {
    type Msg = NumberInputSliderDialogMsg;

    fn place(&mut self, bounds: Rect) -> Rect {
        self.area = bounds;
        let button_height = theme::BUTTON_HEIGHT;
        let content_area = self.area.inset(Insets::top(2 * theme::BUTTON_SPACING));
        let (_, content_area) = content_area.split_top(30);
        let (input_area, _) = content_area.split_top(15);
        let (_, button_area) = content_area.split_bottom(button_height);

        let grid = Grid::new(button_area, 1, 2).with_spacing(theme::KEYBOARD_SPACING);
        self.input.place(input_area.inset(Insets::sides(20)));
        self.cancel_button.place(grid.row_col(0, 0));
        self.confirm_button.place(grid.row_col(0, 1));
        bounds
    }

    fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
        if let Some(value) = self.input.event(ctx, event) {
            return Some(Self::Msg::Changed(value));
        }
        if let Some(ButtonMsg::Clicked) = self.cancel_button.event(ctx, event) {
            return Some(Self::Msg::Cancelled);
        }
        if let Some(ButtonMsg::Clicked) = self.confirm_button.event(ctx, event) {
            return Some(Self::Msg::Confirmed);
        };
        None
    }

    fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
        self.input.render(target);
        self.cancel_button.render(target);
        self.confirm_button.render(target);
    }
}

#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for NumberInputSliderDialog {
    fn trace(&self, t: &mut dyn crate::trace::Tracer) {
        t.component("NumberInputSliderDialog");
        t.child("input", &self.input);
        t.child("cancel_button", &self.cancel_button);
        t.child("confirm_button", &self.confirm_button);
    }
}

pub struct NumberInputSlider {
    area: Rect,
    touch_area: Rect,
    min: u8,
    max: u8,
    value: u8,
}

impl NumberInputSlider {
    pub fn new(min: u8, max: u8, value: u8) -> Self {
        let value = value.clamp(min, max);
        Self {
            area: Rect::zero(),
            touch_area: Rect::zero(),
            min,
            max,
            value,
        }
    }

    pub fn slider_eval(&mut self, pos: Point, ctx: &mut EventCtx) -> Option<u8> {
        if self.touch_area.contains(pos) {
            let filled = pos.x - self.area.x0;
            let filled = filled.clamp(0, self.area.width());
            let val_pct = (filled as u16 * 100) / self.area.width() as u16;
            let val = ((val_pct * (self.max - self.min) as u16) / 100) as u8 + self.min;

            if val != self.value {
                self.value = val;
                ctx.request_paint();
                return Some(self.value);
            }
        }
        None
    }
}

impl Component for NumberInputSlider {
    type Msg = u8;

    fn place(&mut self, bounds: Rect) -> Rect {
        self.area = bounds;
        self.touch_area = bounds.outset(Insets::new(40, 20, 40, 20)).clamp(screen());
        bounds
    }

    fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
        if let Event::Touch(touch_event) = event {
            return match touch_event {
                TouchEvent::TouchStart(pos) => self.slider_eval(pos, ctx),
                TouchEvent::TouchMove(pos) => self.slider_eval(pos, ctx),
                TouchEvent::TouchEnd(pos) => self.slider_eval(pos, ctx),
            };
        }
        None
    }

    fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
        let val_pct = (100 * (self.value - self.min) as u16) / (self.max - self.min) as u16;

        shape::Bar::new(self.area)
            .with_radius(2)
            .with_thickness(2)
            .with_bg(theme::BG)
            .with_fg(theme::FG)
            .render(target);

        let inner = self.area.inset(Insets::uniform(1));

        let fill_to = (val_pct as i16 * inner.width()) / 100;

        let inner = inner.with_width(fill_to as _);

        shape::Bar::new(inner)
            .with_radius(1)
            .with_bg(theme::FG)
            .render(target);
    }
}

#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for NumberInputSlider {
    fn trace(&self, t: &mut dyn crate::trace::Tracer) {
        t.component("NumberInputSlider");
        t.int("value", self.value as i64);
    }
}
