use bone_types::IconId;
use bone_ui::a11y::{AccessNode, Role};
use bone_ui::frame::{FrameCtx, InteractDeclaration};
use bone_ui::hit_test::Sense;
use bone_ui::layout::{LayoutPos, LayoutPx, LayoutRect, LayoutSize};
use bone_ui::strings::StringKey;
use bone_ui::theme::{Border, Step12, StrokeWidth};
use bone_ui::widgets::{IconTint, WidgetPaint};
use bone_ui::{WidgetId, WidgetKey};

use crate::shell::MenuAction;
use crate::strings;

const BUTTON_PX: f32 = 22.0;
const ICON_PX: f32 = 16.0;
const BUTTON_GAP: f32 = 2.0;
const GROUP_GAP: f32 = 8.0;
const STRIP_PAD: f32 = 4.0;
const STRIP_TOP_INSET: f32 = 8.0;

#[derive(Copy, Clone)]
struct HeadsUpTool {
    key: &'static str,
    icon: IconId,
    label: StringKey,
    action: Option<MenuAction>,
}

const GROUPS: [&[HeadsUpTool]; 3] = [
    &[
        HeadsUpTool {
            key: "zoom_fit",
            icon: IconId::ZoomToFit,
            label: strings::HEADS_UP_ZOOM_FIT,
            action: Some(MenuAction::ZoomFit),
        },
        HeadsUpTool {
            key: "zoom_area",
            icon: IconId::ZoomToArea,
            label: strings::HEADS_UP_ZOOM_AREA,
            action: None,
        },
        HeadsUpTool {
            key: "previous_view",
            icon: IconId::PreviousView,
            label: strings::HEADS_UP_PREVIOUS_VIEW,
            action: None,
        },
        HeadsUpTool {
            key: "section_view",
            icon: IconId::SectionView,
            label: strings::HEADS_UP_SECTION_VIEW,
            action: None,
        },
    ],
    &[
        HeadsUpTool {
            key: "view_orientation",
            icon: IconId::ViewOrientation,
            label: strings::HEADS_UP_VIEW_ORIENTATION,
            action: None,
        },
        HeadsUpTool {
            key: "display_style",
            icon: IconId::DisplayStyle,
            label: strings::HEADS_UP_DISPLAY_STYLE,
            action: None,
        },
        HeadsUpTool {
            key: "hide_show",
            icon: IconId::HideShowItems,
            label: strings::HEADS_UP_HIDE_SHOW,
            action: Some(MenuAction::TogglePlanes),
        },
    ],
    &[
        HeadsUpTool {
            key: "edit_appearance",
            icon: IconId::EditAppearance,
            label: strings::HEADS_UP_EDIT_APPEARANCE,
            action: None,
        },
        HeadsUpTool {
            key: "view_settings",
            icon: IconId::ViewSettings,
            label: strings::HEADS_UP_VIEW_SETTINGS,
            action: None,
        },
    ],
];

enum StripItem {
    Separator,
    Tool(HeadsUpTool),
}

fn strip_items() -> Vec<StripItem> {
    GROUPS
        .iter()
        .enumerate()
        .flat_map(|(index, group)| {
            (index > 0)
                .then_some(StripItem::Separator)
                .into_iter()
                .chain(group.iter().copied().map(StripItem::Tool))
        })
        .collect()
}

fn strip_width() -> f32 {
    let buttons: usize = GROUPS.iter().map(|g| g.len()).sum();
    let inner_gaps: usize = GROUPS.iter().map(|g| g.len().saturating_sub(1)).sum();
    let separators = GROUPS.len().saturating_sub(1);
    #[allow(
        clippy::cast_precision_loss,
        reason = "heads-up tool counts fit the f32 mantissa"
    )]
    let body =
        buttons as f32 * BUTTON_PX + inner_gaps as f32 * BUTTON_GAP + separators as f32 * GROUP_GAP;
    2.0 * STRIP_PAD + body
}

struct StripStyle {
    hover_fill: bone_ui::theme::Color,
    separator: bone_ui::theme::Color,
    radius: bone_ui::theme::Radius,
}

#[must_use]
pub fn render_heads_up_toolbar(
    ctx: &mut FrameCtx<'_>,
    viewport: LayoutRect,
    base: WidgetId,
    paints: &mut Vec<WidgetPaint>,
) -> Option<MenuAction> {
    let width = strip_width();
    let height = 2.0 * STRIP_PAD + BUTTON_PX;
    if viewport.size.width.value() < width + 2.0 * STRIP_TOP_INSET
        || viewport.size.height.value() < height + 2.0 * STRIP_TOP_INSET
    {
        return None;
    }
    let left = viewport.min_x().value() + (viewport.size.width.value() - width) / 2.0;
    let top = viewport.min_y().value() + STRIP_TOP_INSET;
    let strip_rect = LayoutRect::new(
        LayoutPos::new(LayoutPx::new(left), LayoutPx::new(top)),
        LayoutSize::new(LayoutPx::new(width), LayoutPx::new(height)),
    );
    ctx.a11y.push(
        base,
        strip_rect,
        AccessNode::new(Role::Toolbar).with_label(strings::HEADS_UP_BAR),
    );
    let theme = ctx.theme();
    let style = StripStyle {
        hover_fill: theme.colors.neutral.step(Step12::HOVER_BG),
        separator: theme.colors.neutral.step(Step12::SUBTLE_BORDER),
        radius: theme.radius.sm,
    };
    paints.push(WidgetPaint::Surface {
        rect: strip_rect,
        fill: theme.colors.surface(theme.elevation.level2.surface),
        border: Some(Border {
            width: StrokeWidth::HAIRLINE,
            color: style.separator,
        }),
        radius: style.radius,
        elevation: Some(theme.elevation.level2),
    });
    let row_top = top + STRIP_PAD;
    let (_, clicked) = strip_items().iter().fold(
        (left + STRIP_PAD, None::<MenuAction>),
        |(x, clicked), item| match item {
            StripItem::Separator => {
                paints.push(WidgetPaint::Surface {
                    rect: LayoutRect::new(
                        LayoutPos::new(LayoutPx::new(x + GROUP_GAP / 2.0), LayoutPx::new(row_top)),
                        LayoutSize::new(
                            LayoutPx::new(StrokeWidth::HAIRLINE.value_px()),
                            LayoutPx::new(BUTTON_PX),
                        ),
                    ),
                    fill: style.separator,
                    border: None,
                    radius: ctx.theme().radius.none,
                    elevation: None,
                });
                (x + GROUP_GAP, clicked)
            }
            StripItem::Tool(tool) => {
                let next = clicked.or_else(|| {
                    draw_tool(ctx, base, tool, x, row_top, &style, paints)
                        .then_some(tool.action)
                        .flatten()
                });
                (x + BUTTON_PX + BUTTON_GAP, next)
            }
        },
    );
    clicked
}

fn draw_tool(
    ctx: &mut FrameCtx<'_>,
    base: WidgetId,
    tool: &HeadsUpTool,
    x: f32,
    row_top: f32,
    style: &StripStyle,
    paints: &mut Vec<WidgetPaint>,
) -> bool {
    let button_rect = LayoutRect::new(
        LayoutPos::new(LayoutPx::new(x), LayoutPx::new(row_top)),
        LayoutSize::new(LayoutPx::new(BUTTON_PX), LayoutPx::new(BUTTON_PX)),
    );
    let interaction = ctx.interact(
        InteractDeclaration::new(
            base.child(WidgetKey::new(tool.key)),
            button_rect,
            Sense::INTERACTIVE,
        )
        .a11y(AccessNode::new(Role::Button).with_label(tool.label)),
    );
    if interaction.hover() || interaction.pressed() {
        paints.push(WidgetPaint::Surface {
            rect: button_rect,
            fill: style.hover_fill,
            border: None,
            radius: style.radius,
            elevation: None,
        });
    }
    let inset = (BUTTON_PX - ICON_PX) / 2.0;
    paints.push(WidgetPaint::Icon {
        rect: LayoutRect::new(
            LayoutPos::new(LayoutPx::new(x + inset), LayoutPx::new(row_top + inset)),
            LayoutSize::new(LayoutPx::new(ICON_PX), LayoutPx::new(ICON_PX)),
        ),
        icon: tool.icon,
        tint: IconTint::Normal,
    });
    interaction.click()
}
