use crate::io::NodeGraphViewState;
use crate::runtime::events::ViewChange;
use jellyflow_core::core::Graph;

#[derive(Clone, Copy)]
pub(super) enum ViewStateMutationKind {
    FullState,
    Viewport,
    Selection,
}

impl ViewStateMutationKind {
    pub(super) fn sanitize(self, graph: &Graph, view_state: &mut NodeGraphViewState) {
        match self {
            Self::FullState | Self::Selection => view_state.sanitize_for_graph(graph),
            Self::Viewport => {}
        }
    }

    pub(super) fn changed(self, before: &NodeGraphViewState, after: &NodeGraphViewState) -> bool {
        match self {
            Self::FullState => !view_state_eq(before, after),
            Self::Viewport => viewport_changed(before, after),
            Self::Selection => selection_changed(before, after),
        }
    }

    pub(super) fn collect_changes(
        self,
        before: &NodeGraphViewState,
        after: &NodeGraphViewState,
    ) -> Vec<ViewChange> {
        match self {
            Self::FullState => collect_view_projection_changes(before, after),
            Self::Viewport => vec![ViewChange::viewport(after.pan, after.zoom)],
            Self::Selection => vec![selection_change(after)],
        }
    }
}

fn view_state_eq(a: &NodeGraphViewState, b: &NodeGraphViewState) -> bool {
    !viewport_changed(a, b)
        && !selection_changed(a, b)
        && a.draw_order == b.draw_order
        && a.edge_draw_order == b.edge_draw_order
        && a.group_draw_order == b.group_draw_order
}

fn collect_view_projection_changes(
    before: &NodeGraphViewState,
    after: &NodeGraphViewState,
) -> Vec<ViewChange> {
    let mut changes: Vec<ViewChange> = Vec::new();
    if viewport_projection_changed(before, after) {
        changes.push(ViewChange::viewport(after.pan, after.zoom));
    }
    if selection_changed(before, after) {
        changes.push(selection_change(after));
    }
    changes
}

fn viewport_changed(a: &NodeGraphViewState, b: &NodeGraphViewState) -> bool {
    a.pan != b.pan || a.zoom != b.zoom
}

fn viewport_projection_changed(a: &NodeGraphViewState, b: &NodeGraphViewState) -> bool {
    a.pan != b.pan || (a.zoom - b.zoom).abs() > 1.0e-6
}

fn selection_changed(a: &NodeGraphViewState, b: &NodeGraphViewState) -> bool {
    a.selected_nodes != b.selected_nodes
        || a.selected_edges != b.selected_edges
        || a.selected_groups != b.selected_groups
}

fn selection_change(view_state: &NodeGraphViewState) -> ViewChange {
    ViewChange::selection(
        view_state.selected_nodes.clone(),
        view_state.selected_edges.clone(),
        view_state.selected_groups.clone(),
    )
}
