use super::*;

pub(crate) struct BaseUiPlugin;

impl Plugin for BaseUiPlugin {
    fn build(&self, app: &mut App) {
        app.init_resource::<Style>().add_systems(
            Update,
            (
                add_ui_node.run_if(any_component_added::<Node>),
                add_ui_button
                    .run_if(any_component_added::<Button>)
                    .after(add_ui_node),
                add_ui_text
                    .run_if(any_component_added::<Text>)
                    .after(add_ui_node),
                navs_on_top.run_if(any_component_changed::<NavState>),
            ),
        );
    }
}

/// A resource for supplying style info, e.g., color pallette
#[derive(Resource)]
pub struct Style {
    primary: Color,
    secondary: Color,
    accent: Color,
}

impl Default for Style {
    fn default() -> Self {
        Style {
            primary: WHITE.into(),
            secondary: BLACK.into(),
            accent: ORANGE_RED.into(),
        }
    }
}

fn add_ui_button(added: Query<Entity, Added<Button>>, mut commands: Commands) {
    fn over(event: On<Pointer<Over>>, mut query: Query<&mut BorderColor>, style: Res<Style>) {
        if let Ok(mut bc) = query.get_mut(event.entity) {
            debug!("pointer over {:?}", event.entity);
            bc.set_all(style.accent);
        }
    }

    fn out(event: On<Pointer<Out>>, mut query: Query<&mut BorderColor>, style: Res<Style>) {
        if let Ok(mut bc) = query.get_mut(event.entity) {
            debug!("pointer out {:?}", event.entity);
            bc.set_all(style.secondary);
        }
    }

    fn pressed(
        event: On<Pointer<Press>>,
        mut query: Query<&mut BackgroundColor>,
        style: Res<Style>,
    ) {
        if let Ok(mut bg) = query.get_mut(event.entity) {
            debug!("pointer pressed {:?}", event.entity);
            bg.0 = style.accent;
        }
    }

    fn released(
        event: On<Pointer<Release>>,
        mut query: Query<&mut BackgroundColor>,
        style: Res<Style>,
    ) {
        if let Ok(mut bg) = query.get_mut(event.entity) {
            debug!("pointer released {:?}", event.entity);
            bg.0 = style.primary;
        }
    }

    added.iter().for_each(|e| {
        debug!("Updating button: {:?}", e);
        // Observe with hover over/out + click reactors
        commands
            .entity(e)
            .observe(over)
            .observe(out)
            .observe(pressed)
            .observe(released);
    })
}

fn add_ui_text(added: Query<Entity, Added<Text>>, mut commands: Commands) {
    fn pressed(
        event: On<Pointer<Press>>,
        mut query: Query<(&mut TextColor, &ChildOf)>,
        buttons: Query<Entity, With<Button>>,
        style: Res<Style>,
    ) {
        // ONLY DO THIS IF CHILD OF BUTTON
        if let Ok((mut tc, ChildOf(p))) = query.get_mut(event.entity)
            && buttons.contains(*p)
        {
            debug!("pointer pressed {:?}", event.entity);
            tc.0 = style.primary;
        }
    }

    fn released(
        event: On<Pointer<Release>>,
        mut query: Query<(&mut TextColor, &ChildOf)>,
        buttons: Query<Entity, With<Button>>,
        style: Res<Style>,
    ) {
        // ONLY DO THIS IF CHILD OF BUTTON
        if let Ok((mut tc, ChildOf(p))) = query.get_mut(event.entity)
            && buttons.contains(*p)
        {
            debug!("pointer released {:?}", event.entity);
            tc.0 = style.secondary;
        }
    }

    added.iter().for_each(|e| {
        debug!("Updating text: {:?}", e);
        // Observe with hover over/out + click reactors
        commands.entity(e).observe(pressed).observe(released);
    })
}

fn add_ui_node(
    mut added: Query<(Entity, &mut Node), Added<Node>>,
    style: Res<Style>,
    text: Query<Entity, With<Text>>,
    images: Query<Entity, With<ImageNode>>,
    mut commands: Commands,
) {
    added.iter_mut().for_each(|(e, mut n)| {
        debug!("Updating node: {:?}", e);

        let mut this = commands.entity(e);

        if text.contains(e) {
            // Only change text color for text
            this.insert_if_new(TextColor(style.secondary));
        } else if images.contains(e) {
            // Do nothing
        } else {
            // Update Node border
            n.border = UiRect::all(Val::Px(2.0));

            // Add extra stuff for non-text nodes
            this.insert_if_new(BackgroundColor(style.primary));
            this.insert_if_new(BorderColor::all(style.secondary));
            this.insert_if_new(BorderRadius::all(Val::Px(5.0)));
        }
    })
}

/// Marker component for handling Resource -> Ui Sync
#[derive(Component, Default, Debug)]
pub struct SyncResource<R: Resource + Default + Display>(R);

/// Sync a generic resource to the UI
///
/// Mostly useful for quick n' dirty getting data to the user
pub fn sync_resource_to_ui<R: Resource + Default + Display>(
    mut q: Query<(&mut Text, &mut Visibility), With<SyncResource<R>>>,
    r: Res<R>,
) {
    q.iter_mut().for_each(|(mut t, mut v)| {
        t.0 = format!("{}", *r);
        *v = Visibility::Inherited;
    });
}

/// Marker component for handling Resource -> Ui Sync
#[derive(Component, Default, Debug)]
pub struct SyncSingleton<C: Component + Default + Display>(C);

/// Sync a singleton entity's component to the UI
///
/// Mostly useful for quick n' dirty getting data to the user
pub fn sync_singleton_to_ui<C: Component + Default + Display>(
    mut q: Query<(&mut Text, &mut Visibility), With<SyncSingleton<C>>>,
    c: Single<&C>,
) {
    q.iter_mut().for_each(|(mut t, mut v)| {
        t.0 = format!("{}", *c);
        *v = Visibility::Inherited;
    });
}

/// Updates the scroll position of scrollable nodes in response to mouse input
pub fn scroll(event: On<Pointer<Scroll>>, mut scrollers: Query<&mut ScrollPosition>) {
    let Pointer {
        event: Scroll { unit, x, y, .. },
        ..
    } = event.event();

    let (dx, dy) = match unit {
        MouseScrollUnit::Line => (x * 16.0, y * 16.0),
        MouseScrollUnit::Pixel => (x * 1., y * 1.),
    };

    if let Ok(mut pos) = scrollers.get_mut(event.entity) {
        pos.x -= dx;
        pos.y -= dy;
    }
}

/// Track who your parent is for navigation in the UI
/// Think the file menu is a child of the "file" button
#[derive(Component, Debug)]
#[relationship(relationship_target = NavChildren)]
pub struct NavParent(pub Entity);

/// Back-relationship for nav-parent
#[derive(Component, Debug)]
#[relationship_target(relationship = NavParent)]
pub struct NavChildren(Vec<Entity>);

/// Track if a navigation [sub]tree is open or closed
#[derive(Component, Debug, Default)]
pub enum NavState {
    Open,
    #[default]
    Closed,
}

fn navs_on_top(changed: Query<(Entity, &NavState), Changed<NavState>>, mut commands: Commands) {
    changed.iter().for_each(|(e, ns)| match ns {
        NavState::Open => {
            commands.entity(e).insert(GlobalZIndex(i32::MAX / 2));
        }
        NavState::Closed => {
            commands.entity(e).remove::<GlobalZIndex>();
        }
    })
}
