use super::*;

pub(crate) struct TreesDebugPlugin;

impl Plugin for TreesDebugPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(Startup, init_debug_ui)
            .add_systems(
                Update,
                (
                    (spawn_debug_buttons.run_if(on_message::<AssetEvent<Monologue>>),),
                    (
                        monologue_asset_tooltip
                            .run_if(on_message::<Pointer<Over>>.or(on_message::<Pointer<Out>>)),
                        hide_menu.run_if(any_component_changed::<NavState>),
                        clear_monologue.run_if(any_component_changed::<NavState>),
                        control_menu
                            .run_if(on_message::<Pointer<Over>>.or(on_message::<Pointer<Out>>)),
                        delete_tree.run_if(on_message::<Pointer<Click>>),
                        drag_tree.run_if(on_message::<Pointer<Drag>>),
                    )
                        .run_if(in_state(DebuggingState::On)),
                ),
            )
            .add_observer(add_dialog_option);
    }
}

#[derive(Component)]
struct MonologuesContainer;

#[derive(Component)]
struct MonologuesList;

#[derive(Component)]
struct MonologuePreview;

fn drag_tree(
    mut events: MessageReader<Pointer<Drag>>,
    state: Res<State<DebuggingState>>,
    mut query: Query<&mut Transform, With<Tree>>,
    camera: Single<(&Camera, &GlobalTransform), With<Camera>>,
    window: Single<&Window>,
) {
    debug_assert_eq!(*state.get(), DebuggingState::On);

    events.read().for_each(|event| {
        if let Ok(mut t) = query.get_mut(event.entity) {
            let world_position = window
                .cursor_position()
                .and_then(|cursor| camera.0.viewport_to_world(camera.1, cursor).ok())
                .map(|ray| {
                    // Compute ray's distance to entity
                    let distance = ray
                        .intersect_plane(t.translation, InfinitePlane3d::new(t.up()))
                        .unwrap();
                    ray.get_point(distance)
                });
            t.translation = world_position.unwrap();
        }
    });
}

/// Panel for selecting which monologue tree to spawn
fn init_debug_ui(mut commands: Commands) {
    let mut monologue_button = None;
    commands
        .spawn((
            Node {
                top: Val::Px(0.0),
                left: Val::Px(0.0),
                flex_direction: FlexDirection::Row,
                ..default()
            },
            DebuggingState::On,
        ))
        .with_children(|parent| {
            monologue_button = Some(
                parent
                    .spawn((
                        Name::new("Monologue Assignment Menu"),
                        children![Text::new("+Monologue"),],
                        Button,
                        Node {
                            min_width: Val::Px(25.0),
                            min_height: Val::Px(25.0),
                            ..default()
                        },
                        DebuggingState::On,
                        MonologuesContainer,
                    ))
                    .id(),
            );
            parent
                .spawn((
                    Name::new("Tree Planter"),
                    children![Text::new("+Tree"),],
                    Node {
                        min_width: Val::Px(25.0),
                        min_height: Val::Px(25.0),
                        ..default()
                    },
                    DebuggingState::On,
                    MonologuesContainer,
                    Button,
                ))
                .observe(spawn_tree);
        });

    commands
        .spawn((
            NavParent(monologue_button.unwrap()),
            NavState::default(),
            DebuggingState::On,
            Name::new("Container"),
            Node {
                flex_direction: FlexDirection::Row,
                top: Val::Px(25.0),
                height: Val::Percent(90.0),
                width: Val::Percent(80.0),
                ..default()
            },
        ))
        .with_children(|parent| {
            parent
                .spawn((
                    Name::new("Buttons"),
                    Node {
                        flex_direction: FlexDirection::Column,
                        height: Val::Percent(100.0),
                        width: Val::Percent(40.0),
                        overflow: Overflow::scroll_y(),
                        ..default()
                    },
                    ScrollPosition::default(),
                    MonologuesList,
                ))
                .observe(scroll);

            parent.spawn((
                Name::new("Preview"),
                MonologuePreview,
                NavParent(monologue_button.unwrap()),
                NavState::default(),
                Node {
                    flex_direction: FlexDirection::Column,
                    padding: UiRect::all(Val::Px(10.0)),
                    overflow: Overflow::scroll_y(),
                    width: Val::Percent(60.0),
                    ..default()
                },
            ));
        });
}

// When you pointer goes off of the '+' or any of it's children make the entire menu invisible
fn control_menu(
    mut over_events: MessageReader<Pointer<Over>>,
    mut out_events: MessageReader<Pointer<Out>>,
    nav_children: Query<&NavParent>,
    children: Query<&ChildOf>,
    nav_parents: Query<&NavChildren>,
    parents: Query<&Children>,
    mut nav: Query<&mut NavState>,
    hover_map: Res<HoverMap>,
) {
    over_events.read().for_each(|over| {
        let root = nav_children.root_ancestor(over.entity);

        nav_parents.iter_descendants(root).for_each(|child| {
            if let Ok(mut n) = nav.get_mut(child) {
                *n = NavState::Open;
            }
        });
    });

    // Gives us the enities covered by the HoverMap
    let is_hovered: Vec<&Entity> = hover_map
        .iter()
        .flat_map(|(_, submap)| {
            let x: Vec<&Entity> = submap.iter().map(|(node, _)| node).collect();
            x
        })
        .collect();

    // For all pointer out events
    out_events.read().for_each(|out| {
        // If a relative of out.entity is hovered, do nothing
        // Otherwise set to closed
        let root = children.root_ancestor(out.entity);
        let tree_still_hovered = parents
            .iter_descendants(root)
            .any(|child| is_hovered.contains(&&child));
        if !tree_still_hovered {
            if let Ok(mut n) = nav.get_mut(root) {
                *n = NavState::Closed;
            }
            parents.iter_descendants(root).for_each(|child| {
                if let Ok(mut n) = nav.get_mut(child) {
                    *n = NavState::Closed;
                }
            })
        }
    })
}

/// When a (Text) dialog option is added:
/// 1. Add the Button component
/// 2. Change the color to Orange
/// 3. Add observers for click (select) and hover (change color)
fn add_dialog_option(event: On<Add, DialogOption>, mut commands: Commands) {
    commands
        .entity(event.entity)
        .insert(Button)
        .insert(Node {
            width: Val::Percent(100.0),
            ..default()
        })
        .insert(TextLayout::new_with_justify(Justify::Center))
        .insert(TextColor(ORANGE.into()))
        .observe(choose_dialog_option)
        .observe(hover_dialog_option_over)
        .observe(hover_dialog_option_out);
}

fn assign_monologue_event(
    event: On<Pointer<Click>>,
    mut events: MessageWriter<AssignMonologue>,
    monologues: Query<&TreeMonologue>,
) {
    let TreeMonologue(handle) = monologues.get(event.entity).unwrap();
    events.write(AssignMonologue(*handle));
}

/// Observer for the "Plant a new tree" button in the debug UI
fn spawn_tree(_event: On<Pointer<Click>>, mut events: MessageWriter<PlantTree>) {
    events.write(PlantTree(None));
}

fn clear_monologue(
    mut nodes: Query<
        (Entity, &NavState),
        (Changed<NavState>, With<MonologuePreview>, Without<Text>),
    >,
    mut commands: Commands,
) {
    nodes.iter_mut().for_each(|(e, n)| {
        if matches!(n, NavState::Closed) {
            commands.entity(e).despawn_related::<Children>();
        }
    });
}

// When you pointer over the '+' make the entire menu visible
fn hide_menu(mut nodes: Query<(&mut Visibility, &NavState), Changed<NavState>>) {
    nodes.iter_mut().for_each(|(mut v, n)| {
        *v = match n {
            NavState::Open => Visibility::Inherited,
            NavState::Closed => Visibility::Hidden,
        };
    });
}

fn delete_tree(
    mut events: MessageReader<Pointer<Click>>,
    mut commands: Commands,
    query: Query<Entity, With<Tree>>,
) {
    events.read().for_each(|event| {
        if matches!(event.event.button, PointerButton::Middle) && query.contains(event.entity) {
            debug!("Middle Click -> Despawning {}", event.entity);
            commands.entity(event.entity).despawn();
        }
    })
}

/// Add the "script: path/to/file.mono" tooltip info
fn monologue_asset_tooltip(
    mut over_events: MessageReader<Pointer<Over>>,
    mut out_events: MessageReader<Pointer<Out>>,
    mut tooltip: ResMut<ToolTip>,
    server: Res<AssetServer>,
    trees: Query<(&Tree, Option<&TreeMonologue>)>,
) {
    out_events
        .read()
        .filter_map(|Pointer { entity, .. }| trees.contains(*entity).then_some(*entity))
        .for_each(|_| {
            tooltip.remove("Script");
        });

    over_events
        .read()
        .filter_map(|Pointer { entity, .. }| trees.contains(*entity).then_some(*entity))
        .for_each(|e| match trees.get(e) {
            Ok((_tree, Some(TreeMonologue(id)))) => match server.get_path(*id) {
                Some(p) => tooltip.insert("Script", format!("{p}")),
                None => tooltip.insert("Script", "A".into()),
            },
            Ok((_tree, None)) => {
                tooltip.insert("Script", "N/A".into());
            }
            _ => (),
        });
}

/// When a dialog option is chosen (clicked on) we do the following:
fn choose_dialog_option(
    event: On<Pointer<Click>>,
    mut dialog_events: MessageWriter<DialogEvent>,
    mut commands: Commands,
    texts: Query<&Text>,
    options: Query<Entity, With<DialogOption>>,
    dialog_box: Single<Entity, With<DialogBox>>,
) {
    debug!("Choosing dialog {:?}", event.entity);

    debug!("Despawning dialog options");
    options.iter().for_each(|e| {
        commands.entity(e).despawn();
    });

    debug!("Inserting dialog line");
    if let Ok(t) = texts.get(event.entity) {
        commands.entity(*dialog_box).with_children(|parent| {
            parent.spawn((t.clone(), DialogLine));
        });
    }

    // event the next dialog line
    dialog_events.write(DialogEvent::NextBatch);
}

fn preview_monologue(
    event: On<Pointer<Over>>,
    container: Single<Entity, (With<MonologuePreview>, Without<Button>, Without<Text>)>,
    tree_monologue: Query<&TreeMonologue, With<Button>>,
    monologues: Res<Assets<Monologue>>,
    mut commands: Commands,
) {
    // Get the handle for this button's monologuie
    if let Ok(TreeMonologue(id)) = tree_monologue.get(event.entity) {
        // Get the monologue data
        if let Some(monologue) = monologues.get(*id) {
            commands.entity(*container).despawn_related::<Children>();

            // Spawn the monologue
            commands.entity(*container).with_children(|parent| {
                let mut i = 0;
                while let Some(batch) = monologue.get(i) {
                    parent.spawn((Text::new("---"), MonologuePreview));
                    debug!("---");

                    for (n, item) in batch.lines.iter().enumerate() {
                        parent.spawn((Text::new(format!("{i}.{n}: {item}")), MonologuePreview));
                        debug!("{i}.{n}: {item}");
                    }

                    // TODO: Just implement iter_batches or something
                    i += 1;
                }

                parent.spawn((Text::new("---"), MonologuePreview));
                debug!("---");
            });
        }
    }
}

fn spawn_debug_buttons(
    mut events: MessageReader<AssetEvent<Monologue>>,
    mut commands: Commands,
    container: Single<Entity, (With<MonologuesList>, Without<Button>)>,
    server: Res<AssetServer>,
) {
    debug_assert!(
        !events.is_empty(),
        "Should only spawn buttons when monologues are loaded"
    );

    events.read().for_each(|event| {
        // If this is an "Asset was loaded" event
        // These are just asset events for monologues so no need to check the asset type
        if let AssetEvent::LoadedWithDependencies { id } = event {
            // Get the handle from the asset ID
            let handle = server.get_id_handle(*id).unwrap();
            // Spawn a button in the box containing buttons
            commands.entity(*container).with_children(|parent| {
                parent
                    .spawn((
                        Button,
                        children![
                            Text::new(
                                handle
                                    .path()
                                    .unwrap()
                                    .to_string()
                                    .trim_prefix("trees/")
                                    .trim_suffix(".mono"),
                            ),
                            TextLayout::new(Justify::Left, LineBreak::WordBoundary),
                        ],
                        TreeMonologue(handle.id()),
                        MonologuesList,
                    ))
                    .observe(assign_monologue_event)
                    .observe(preview_monologue);
            });
        }
    });
}
