use super::axis::Axis;
use super::dock::PanelId;
use super::engine::{NodeKind, SolvedLayout, SolvedNode};
use super::geometry::{LayoutPos, LayoutPx, LayoutRect, LayoutSize};
use super::scroll::ScrollAxes;
use crate::theme::{Color, ElevationLevel, StrokeWidth, Theme};
use crate::widget_id::WidgetId;

#[derive(Clone, Debug, PartialEq)]
pub enum PaintCommand {
    PushClip {
        rect: LayoutRect,
    },
    PopClip,
    Surface {
        rect: LayoutRect,
        elevation: ElevationLevel,
    },
    Divider {
        rect: LayoutRect,
        axis: Axis,
        color: Color,
    },
    TabStrip {
        rect: LayoutRect,
        active: PanelId,
        tabs: Vec<PanelId>,
    },
    Translate {
        delta: LayoutPos,
    },
    Untranslate,
    PanelSlot {
        rect: LayoutRect,
        id: PanelId,
    },
    LeafSlot {
        rect: LayoutRect,
        id: WidgetId,
    },
    ScrollContent {
        rect: LayoutRect,
        id: WidgetId,
        axes: ScrollAxes,
        content_size: LayoutSize,
    },
}

#[derive(Clone, Debug, PartialEq, Default)]
pub struct PaintPlan {
    pub commands: Vec<PaintCommand>,
}

#[must_use]
pub fn paint_plan(solved: &SolvedLayout, theme: &Theme) -> PaintPlan {
    PaintPlan {
        commands: walk(solved, solved.root_node(), theme),
    }
}

fn walk(layout: &SolvedLayout, node: &SolvedNode, theme: &Theme) -> Vec<PaintCommand> {
    match &node.kind {
        NodeKind::Pass => walk_children(layout, node, theme),
        NodeKind::Leaf(id) => vec![PaintCommand::LeafSlot {
            rect: node.rect,
            id: *id,
        }],
        NodeKind::ScrollRegion { id, axes, offset } => {
            core::iter::once(PaintCommand::PushClip { rect: node.rect })
                .chain(core::iter::once(PaintCommand::ScrollContent {
                    rect: node.rect,
                    id: *id,
                    axes: *axes,
                    content_size: node.content_size,
                }))
                .chain(core::iter::once(PaintCommand::Translate {
                    delta: LayoutPos::new(
                        LayoutPx::saturating(-offset.x.value()),
                        LayoutPx::saturating(-offset.y.value()),
                    ),
                }))
                .chain(walk_children(layout, node, theme))
                .chain(core::iter::once(PaintCommand::Untranslate))
                .chain(core::iter::once(PaintCommand::PopClip))
                .collect()
        }
        NodeKind::Splitter { axis, .. } | NodeKind::DockSplit { axis, .. } => {
            walk_children(layout, node, theme)
                .into_iter()
                .chain(divider_command(layout, node, *axis, theme))
                .collect()
        }
        NodeKind::DockHost { .. } => core::iter::once(PaintCommand::Surface {
            rect: node.rect,
            elevation: theme.elevation.level0,
        })
        .chain(walk_children(layout, node, theme))
        .collect(),
        NodeKind::DockTabStrip { active, tabs } => vec![PaintCommand::TabStrip {
            rect: node.rect,
            active: *active,
            tabs: tabs.clone(),
        }],
        NodeKind::DockPanel { id } => core::iter::once(PaintCommand::Surface {
            rect: node.rect,
            elevation: theme.elevation.level1,
        })
        .chain(core::iter::once(PaintCommand::PanelSlot {
            rect: node.rect,
            id: *id,
        }))
        .chain(walk_children(layout, node, theme))
        .collect(),
    }
}

fn walk_children(layout: &SolvedLayout, node: &SolvedNode, theme: &Theme) -> Vec<PaintCommand> {
    node.children
        .iter()
        .flat_map(|c| walk(layout, layout.node(*c), theme))
        .collect()
}

fn divider_command(
    layout: &SolvedLayout,
    node: &SolvedNode,
    axis: Axis,
    theme: &Theme,
) -> Option<PaintCommand> {
    if node.children.len() != 2 {
        return None;
    }
    let first = layout.node(node.children[0]);
    Some(PaintCommand::Divider {
        rect: divider_between(axis, first.rect, node.rect),
        axis,
        color: theme.colors.neutral.step(crate::theme::Step12::BORDER),
    })
}

fn divider_between(axis: Axis, first: LayoutRect, parent: LayoutRect) -> LayoutRect {
    let thickness = LayoutPx::new(StrokeWidth::HAIRLINE.value_px());
    match axis {
        Axis::Horizontal => LayoutRect::new(
            LayoutPos::new(first.max_x(), parent.min_y()),
            LayoutSize::new(thickness, parent.size.height),
        ),
        Axis::Vertical => LayoutRect::new(
            LayoutPos::new(parent.min_x(), first.max_y()),
            LayoutSize::new(parent.size.width, thickness),
        ),
    }
}
