use druid::{
    keyboard_types::Key,
    theme,
    widget::{Controller, CrossAxisAlignment, Flex, Label, LabelText},
    BoxConstraints, Data, Env, Event, EventCtx, LayoutCtx, LifeCycle, LifeCycleCtx, LinearGradient,
    PaintCtx, RenderContext, Size, UnitPoint, UpdateCtx, Widget,
};
const LABEL_X_PADDING: f64 = 8.0;
pub struct ListSelect<T> {
    widget: Flex<T>,
    controller: ListSelectController<T>,
}
impl<T: Data> ListSelect<T> {
    pub fn new(
        values: impl IntoIterator<Item = (impl Into<LabelText<T>> + 'static, T)>,
    ) -> ListSelect<T> {
        let mut col = Flex::column().cross_axis_alignment(CrossAxisAlignment::Fill);
        let mut variants = Vec::new();
        for (index, (label, variant)) in values.into_iter().enumerate() {
            variants.insert(index, variant.clone());
            col.add_child(ListItem::new(label, variant));
        }
        ListSelect {
            widget: col,
            controller: ListSelectController {
                variants,
                action: None,
            },
        }
    }
    pub fn on_select(self, f: impl Fn(&mut EventCtx, &mut T, &Env) + 'static) -> ListSelect<T> {
        let widget = self.widget;
        let ListSelectController { variants, .. } = self.controller;
        ListSelect {
            widget,
            controller: ListSelectController {
                variants,
                action: Some(Box::new(f)),
            },
        }
    }
}
impl<T: Data> Widget<T> for ListSelect<T> {
    fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, env: &Env) {
        self.controller
            .event(&mut self.widget, ctx, event, data, env)
    }
    fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
        self.controller
            .lifecycle(&mut self.widget, ctx, event, data, env)
    }
    fn update(&mut self, ctx: &mut UpdateCtx, old_data: &T, data: &T, env: &Env) {
        self.controller
            .update(&mut self.widget, ctx, old_data, data, env)
    }
    fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size {
        self.widget.layout(ctx, bc, data, env)
    }
    fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
        self.widget.paint(ctx, data, env)
    }
}
type ListSelectAction<T> = Box<dyn Fn(&mut EventCtx, &mut T, &Env) + 'static>;
struct ListSelectController<T> {
    variants: Vec<T>,
    action: Option<ListSelectAction<T>>,
}
impl<T: Data> ListSelectController<T> {
    fn change_index(&self, data: &mut T, next_else_previous: bool) {
        if let Some(mut index) = self.variants.iter().position(|variant| variant.same(data)) {
            if next_else_previous {
                index += 1
            } else {
                index = index.saturating_sub(1);
            }
            if let Some(new_data) = self.variants.get(index) {
                *data = (*new_data).clone();
            }
        }
    }
}
impl<T: Data> Controller<T, Flex<T>> for ListSelectController<T> {
    fn event(
        &mut self,
        child: &mut Flex<T>,
        ctx: &mut EventCtx,
        event: &Event,
        data: &mut T,
        env: &Env,
    ) {
        let mut selected = false;
        if let Event::MouseDown(_) = event {
            ctx.request_focus();
        }
        if let Event::MouseUp(_) = event {
            selected = ctx.is_hot() && ctx.has_focus();
        }
        if let Event::KeyDown(key_event) = event {
            match key_event.key {
                Key::ArrowUp => {
                    selected = true;
                    self.change_index(data, false);
                    ctx.request_update();
                }
                Key::ArrowDown => {
                    selected = true;
                    self.change_index(data, true);
                    ctx.request_update();
                }
                _ => {}
            }
        } else {
            child.event(ctx, event, data, env)
        }
        if selected {
            if let Some(cb) = &self.action {
                cb(ctx, data, env);
            }
        }
    }
}
pub struct ListItem<T> {
    variant: T,
    child_label: Label<T>,
    label_y: f64,
}
impl<T: Data> ListItem<T> {
    pub fn new(label: impl Into<LabelText<T>>, variant: T) -> ListItem<T> {
        ListItem {
            variant,
            child_label: Label::new(label),
            label_y: 0.0,
        }
    }
}
impl<T: Data> Widget<T> for ListItem<T> {
    fn event(&mut self, ctx: &mut EventCtx, event: &Event, data: &mut T, _env: &Env) {
        match event {
            Event::MouseDown(_) => {
                ctx.set_active(true);
                ctx.request_paint();
            }
            Event::MouseUp(_) => {
                if ctx.is_active() {
                    ctx.set_active(false);
                    if ctx.is_hot() {
                        *data = self.variant.clone();
                    }
                    ctx.request_paint();
                }
            }
            _ => (),
        }
    }
    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(_) = event {
            ctx.request_paint();
        }
    }
    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) {
            ctx.request_paint();
        }
    }
    fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, data: &T, env: &Env) -> Size {
        let label_size = self.child_label.layout(ctx, &bc.loosen(), data, env);
        let height = (env.get(theme::BASIC_WIDGET_HEIGHT)
            + env.get(theme::WIDGET_PADDING_VERTICAL))
        .max(label_size.height);
        self.label_y = (height - label_size.height) / 2.0;
        bc.constrain(Size::new(label_size.width + LABEL_X_PADDING * 2.0, height))
    }
    fn paint(&mut self, ctx: &mut PaintCtx, data: &T, env: &Env) {
        let border_width = 1.0;
        let rect = ctx.size().to_rect().inset(-border_width / 2.0);
        if data.same(&self.variant) {
            let background_gradient = LinearGradient::new(
                UnitPoint::TOP,
                UnitPoint::BOTTOM,
                (env.get(theme::PRIMARY_LIGHT), env.get(theme::PRIMARY_DARK)),
            );
            ctx.fill(rect, &background_gradient);
        } else if ctx.is_active() {
            let background_gradient = LinearGradient::new(
                UnitPoint::TOP,
                UnitPoint::BOTTOM,
                (
                    env.get(theme::BACKGROUND_LIGHT),
                    env.get(theme::BACKGROUND_DARK),
                ),
            );
            ctx.fill(rect, &background_gradient);
        }
        if ctx.is_hot() {
            ctx.stroke(rect, &env.get(theme::BORDER_LIGHT), 1.);
        }
        self.child_label
            .draw_at(ctx, (LABEL_X_PADDING, self.label_y));
    }
}