use bone_ui::frame::FrameCtx;
use bone_ui::hotkey::{ActionId, HotkeyScope};
use bone_ui::input::{KeyCode, ModifierMask, NamedKey, PointerButton};
use bone_ui::layout::{LayoutPos, LayoutPx, LayoutRect, LayoutSize};
use bone_ui::strings::StringKey;
use bone_ui::theme::{Border, Step12, StrokeWidth};
use bone_ui::widgets::{
    Button, ButtonState, ButtonVariant, LabelText, TakeKey, WidgetPaint, show_button, take_key,
};
use bone_ui::{WidgetId, WidgetKey};

use crate::hotkeys::{
    COMMANDS, DELETE_SELECTION_ACTION, ENTER_SKETCH_ACTION, EXTEND_ACTION, MIRROR_ACTION,
    SELECT_ALL_ACTION, SMART_DIMENSION_ACTION, TOGGLE_CONSTRUCTION_ACTION, TRIM_ACTION,
    ZOOM_FIT_ACTION,
};
use crate::strings as s;

const ITEM_HEIGHT_PX: f32 = 28.0;
const ITEM_GAP_PX: f32 = 2.0;
const PANEL_WIDTH_PX: f32 = 220.0;
const PANEL_PAD_PX: f32 = 6.0;
const TITLE_HEIGHT_PX: f32 = 24.0;

#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ShortcutBarState {
    pub anchor: LayoutPos,
}

#[derive(Debug, Default)]
pub struct ShortcutBarOutcome {
    pub paints: Vec<WidgetPaint>,
    pub activated: Option<ActionId>,
    pub dismissed: bool,
}

const ITEMS: &[(ActionId, StringKey)] = &[
    (ENTER_SKETCH_ACTION, s::HOTKEY_LABEL_SKETCH),
    (SMART_DIMENSION_ACTION, s::HOTKEY_LABEL_SMART_DIMENSION),
    (TRIM_ACTION, s::HOTKEY_LABEL_TRIM),
    (EXTEND_ACTION, s::HOTKEY_LABEL_EXTEND),
    (MIRROR_ACTION, s::HOTKEY_LABEL_MIRROR),
    (
        TOGGLE_CONSTRUCTION_ACTION,
        s::HOTKEY_LABEL_CONSTRUCTION_TOGGLE,
    ),
    (ZOOM_FIT_ACTION, s::HOTKEY_LABEL_ZOOM_FIT),
    (SELECT_ALL_ACTION, s::HOTKEY_LABEL_SELECT_ALL),
    (DELETE_SELECTION_ACTION, s::HOTKEY_LABEL_DELETE_SELECTION),
];

#[must_use]
#[allow(
    clippy::cast_precision_loss,
    reason = "ITEMS.len() and item indices fit in u8; f32 mantissa is fine"
)]
pub fn panel_rect(anchor: LayoutPos, viewport: LayoutSize) -> LayoutRect {
    let height =
        TITLE_HEIGHT_PX + PANEL_PAD_PX * 2.0 + ITEMS.len() as f32 * (ITEM_HEIGHT_PX + ITEM_GAP_PX)
            - ITEM_GAP_PX;
    let width = PANEL_WIDTH_PX;
    let max_x = (viewport.width.value() - width).max(0.0);
    let max_y = (viewport.height.value() - height).max(0.0);
    let x = anchor.x.value().min(max_x).max(0.0);
    let y = anchor.y.value().min(max_y).max(0.0);
    LayoutRect::new(
        LayoutPos::new(LayoutPx::new(x), LayoutPx::new(y)),
        LayoutSize::new(LayoutPx::new(width), LayoutPx::new(height)),
    )
}

#[must_use]
#[allow(
    clippy::cast_precision_loss,
    reason = "ITEMS.len() and item indices fit in u8; f32 mantissa is fine"
)]
pub fn render(
    ctx: &mut FrameCtx<'_>,
    state: ShortcutBarState,
    viewport: LayoutSize,
    is_sketch: bool,
) -> ShortcutBarOutcome {
    let rect = panel_rect(state.anchor, viewport);
    let theme = ctx.theme();
    let radius = theme.radius.sm;
    let surface_fill = theme.colors.neutral.step(Step12::APP_BG);
    let border = Border {
        width: StrokeWidth::HAIRLINE,
        color: theme.colors.neutral.step(Step12::BORDER),
    };
    let elevation = theme.elevation.level3;
    let mut paints = vec![
        WidgetPaint::Surface {
            rect,
            fill: surface_fill,
            border: Some(border),
            radius,
            elevation: Some(elevation),
        },
        WidgetPaint::Label {
            rect: LayoutRect::new(
                LayoutPos::new(
                    LayoutPx::new(rect.origin.x.value() + PANEL_PAD_PX),
                    LayoutPx::new(rect.origin.y.value() + PANEL_PAD_PX),
                ),
                LayoutSize::new(
                    LayoutPx::new(rect.size.width.value() - PANEL_PAD_PX * 2.0),
                    LayoutPx::new(TITLE_HEIGHT_PX),
                ),
            ),
            text: LabelText::Key(s::SHORTCUT_BAR_TITLE),
            color: theme.colors.text_primary(),
            role: theme.typography.label,
        },
    ];
    let root_id = WidgetId::ROOT.child(WidgetKey::new("shortcut_bar"));
    let row_origin_y = rect.origin.y.value() + PANEL_PAD_PX + TITLE_HEIGHT_PX;
    let activated = ITEMS
        .iter()
        .enumerate()
        .fold(None, |found, (i, (action, label))| {
            let y = row_origin_y + i as f32 * (ITEM_HEIGHT_PX + ITEM_GAP_PX);
            let row_rect = LayoutRect::new(
                LayoutPos::new(
                    LayoutPx::new(rect.origin.x.value() + PANEL_PAD_PX),
                    LayoutPx::new(y),
                ),
                LayoutSize::new(
                    LayoutPx::new(rect.size.width.value() - PANEL_PAD_PX * 2.0),
                    LayoutPx::new(ITEM_HEIGHT_PX),
                ),
            );
            let id = root_id.child_indexed(WidgetKey::new("item"), u64::from(action.get().get()));
            let item_state = if item_enabled(*action, is_sketch) {
                ButtonState::Idle
            } else {
                ButtonState::Disabled
            };
            let response = show_button(
                ctx,
                Button::new(id, row_rect, *label, ButtonVariant::Secondary).with_state(item_state),
            );
            paints.extend(response.paint);
            found.or(response.activated.then_some(*action))
        });
    let esc = TakeKey::new(KeyCode::Named(NamedKey::Escape), ModifierMask::NONE);
    let esc_dismiss = take_key(ctx.input, &[esc]).is_some();
    let outside_click = ctx.input.buttons_pressed.contains(PointerButton::Primary)
        && ctx
            .input
            .pointer
            .is_some_and(|sample| !rect.contains(sample.position));
    if outside_click {
        ctx.input.buttons_pressed = ctx.input.buttons_pressed.without(PointerButton::Primary);
    }
    ShortcutBarOutcome {
        paints,
        activated,
        dismissed: esc_dismiss || outside_click,
    }
}

fn item_enabled(action: ActionId, is_sketch: bool) -> bool {
    if action == ENTER_SKETCH_ACTION {
        return !is_sketch;
    }
    let Some(scope) = COMMANDS
        .iter()
        .find(|c| c.action == action)
        .map(|c| c.scope)
    else {
        return true;
    };
    !matches!(scope, HotkeyScope::Sketch) || is_sketch
}
