use druid::{
    debug_state::DebugState,
    kurbo::Circle,
    theme,
    widget::{prelude::*, Axis, CrossAxisAlignment, Flex, Label, LabelText},
    Data,
};
use tracing::{instrument, trace};
const DEFAULT_RADIO_RADIUS: f64 = (20.0 - 1.) / 2.;
const INNER_CIRCLE_RADIUS: f64 = 6.0;
#[derive(Debug, Clone)]
pub struct RadioGroup;
impl RadioGroup {
    pub fn column<T: Data + PartialEq>(
        variants: impl IntoIterator<Item = (impl Into<LabelText<T>> + 'static, T)>,
    ) -> impl Widget<T> {
        RadioGroup::for_axis(Axis::Vertical, variants)
    }
    pub fn row<T: Data + PartialEq>(
        variants: impl IntoIterator<Item = (impl Into<LabelText<T>> + 'static, T)>,
    ) -> impl Widget<T> {
        RadioGroup::for_axis(Axis::Horizontal, variants)
    }
    pub fn for_axis<T: Data + PartialEq>(
        axis: Axis,
        variants: impl IntoIterator<Item = (impl Into<LabelText<T>> + 'static, T)>,
    ) -> impl Widget<T> {
        let mut col = Flex::for_axis(axis).cross_axis_alignment(CrossAxisAlignment::Start);
        for (label, variant) in variants.into_iter() {
            let radio = Radio::new(label, variant);
            col.add_child(radio);
        }
        col
    }
}
pub struct Radio<T> {
    variant: T,
    inner_circle_target_radius: f64,
    inner_circle_current_radius: f64,
    label_height: f64,
    child_label: Label<T>,
}
impl<T: Data> Radio<T> {
    pub fn new(label: impl Into<LabelText<T>>, variant: T) -> Radio<T> {
        Radio {
            variant,
            label_height: 0.,
            inner_circle_target_radius: 0.,
            inner_circle_current_radius: 0.,
            child_label: crate::widgets::label::new(label),
        }
    }
}
impl<T: Data + PartialEq> Widget<T> for Radio<T> {
    #[instrument(name = "Radio", level = "trace", skip(self, ctx, event, data, _env))]
    fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, _env: &Env) {
        match event {
            Event::MouseDown(_) => {
                if !ctx.is_disabled() {
                    if data == &self.variant {
                        self.inner_circle_target_radius = INNER_CIRCLE_RADIUS - 1.;
                    } else {
                        self.inner_circle_target_radius = INNER_CIRCLE_RADIUS;
                    }
                    ctx.set_active(true);
                    ctx.request_anim_frame();
                    ctx.request_paint();
                    trace!("Radio button {:?} pressed", ctx.widget_id());
                }
            }
            Event::MouseUp(_) => {
                if ctx.is_active() && !ctx.is_disabled() && ctx.is_hot() {
                    if ctx.is_active() {
                        *data = self.variant.clone();
                    }
                    if ctx.is_hot() {
                        self.inner_circle_target_radius = INNER_CIRCLE_RADIUS;
                    }
                    ctx.request_anim_frame();
                    ctx.request_paint();
                    trace!("Radio button {:?} released", ctx.widget_id());
                }
                ctx.set_active(false);
            }
            Event::AnimFrame(_) => {
                self.inner_circle_current_radius +=
                    (self.inner_circle_target_radius - self.inner_circle_current_radius) / 3.;
                if (self.inner_circle_target_radius - self.inner_circle_current_radius).abs()
                    > f64::EPSILON
                {
                    ctx.request_anim_frame();
                }
                ctx.request_paint();
            }
            _ => (),
        }
    }
    #[instrument(name = "Radio", level = "trace", skip(self, ctx, event, data, env))]
    fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
        self.child_label.lifecycle(ctx, event, data, env);
        if let LifeCycle::HotChanged(_) | LifeCycle::DisabledChanged(_) = event {
            let target_radius = if data == &self.variant {
                if ctx.is_active() {
                    INNER_CIRCLE_RADIUS + 0.5
                } else {
                    INNER_CIRCLE_RADIUS
                }
            } else if ctx.is_active() {
                INNER_CIRCLE_RADIUS
            } else {
                0.
            };
            if (self.inner_circle_target_radius - target_radius).abs() > f64::EPSILON {
                self.inner_circle_target_radius = target_radius;
                ctx.request_anim_frame();
            }
            ctx.request_paint();
        } else if let LifeCycle::WidgetAdded = event {
            self.inner_circle_target_radius = if data == &self.variant {
                INNER_CIRCLE_RADIUS
            } else {
                0.
            };
            self.inner_circle_current_radius = self.inner_circle_target_radius;
        }
    }
    #[instrument(name = "Radio", level = "trace", skip(self, ctx, old_data, data, env))]
    fn update(&mut self, ctx: &mut UpdateCtx, old_data: &T, data: &T, env: &Env) {
        self.child_label.update(ctx, old_data, data, env);
        if !old_data.same(data) {
            let target_radius = if data == &self.variant {
                if ctx.is_active() {
                    INNER_CIRCLE_RADIUS + 0.5
                } else {
                    INNER_CIRCLE_RADIUS
                }
            } else if ctx.is_active() {
                INNER_CIRCLE_RADIUS
            } else {
                0.
            };
            if (self.inner_circle_target_radius - target_radius).abs() > f64::EPSILON {
                self.inner_circle_target_radius = target_radius;
                ctx.request_anim_frame();
            }
            ctx.request_paint();
        }
    }
    #[instrument(name = "Radio", level = "trace", skip(self, ctx, bc, data, env))]
    fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size {
        bc.debug_check("Radio");
        let radio_diam = env.get(theme::BASIC_WIDGET_HEIGHT);
        let x_padding = env.get(theme::WIDGET_CONTROL_COMPONENT_PADDING);
        let label_size = self.child_label.layout(
            ctx,
            &bc.shrink((
                ((DEFAULT_RADIO_RADIUS + x_padding) * 2. + x_padding).max(32.),
                0.,
            )),
            data,
            env,
        );
        self.label_height = label_size.height;
        let desired_size = Size::new(
            label_size.width + radio_diam + x_padding,
            radio_diam.max(label_size.height).max(32.),
        );
        let size = bc.constrain(desired_size);
        trace!("Computed size: {}", size);
        size
    }
    #[instrument(name = "Radio", level = "trace", skip(self, ctx, data, env))]
    fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
        let size = ctx.size();
        let is_dark = env.get(crate::theme::color::main::IS_DARK);
        let x_padding = env.get(theme::WIDGET_CONTROL_COMPONENT_PADDING);
        let circle_x = (size.height / 2.).min(DEFAULT_RADIO_RADIUS) + x_padding;
        let circle_y = size.height / 2.;
        let circle = Circle::new((circle_x, circle_y), DEFAULT_RADIO_RADIUS);
        let is_matched = data == &self.variant;
        let background_color = if is_matched {
            env.get(crate::theme::color::accent::ACCENT)
        } else if ctx.is_active() {
            env.get(crate::theme::color::base::MEDIUM_LOW)
        } else if ctx.is_hot() {
            env.get(crate::theme::color::base::LOW)
        } else {
            env.get(crate::theme::color::alt::LOW)
        };
        ctx.fill(circle, &background_color);
        let border_color = if is_dark {
            if is_matched {
                env.get(crate::theme::color::accent::ACCENT)
            } else if ctx.is_active() {
                env.get(crate::theme::color::base::MEDIUM)
            } else {
                env.get(crate::theme::color::base::LOW)
            }
        } else if is_matched {
            env.get(crate::theme::color::accent::ACCENT)
        } else if ctx.is_active() {
            env.get(crate::theme::color::base::MEDIUM_HIGH)
        } else {
            env.get(crate::theme::color::base::MEDIUM_LOW)
        };
        ctx.stroke(circle, &border_color, 1.);
        let inner_circle = Circle::new((circle_x, circle_y), self.inner_circle_current_radius);
        ctx.fill(inner_circle, &env.get(crate::theme::color::alt::HIGH));
        self.child_label.draw_at(
            ctx,
            (
                (circle_x + INNER_CIRCLE_RADIUS + x_padding * 2.).max(32.),
                (size.height - self.label_height) / 2.,
            ),
        );
    }
    fn debug_state(&self, data: &T) -> DebugState {
        let value_text = if *data == self.variant {
            format!("[X] {}", self.child_label.text())
        } else {
            self.child_label.text().to_string()
        };
        DebugState {
            display_name: self.short_type_name().to_string(),
            main_value: value_text,
            ..Default::default()
        }
    }
}