use bevy::{
    input::mouse::{MouseScrollUnit, MouseWheel},
    input_focus::InputFocus,
    picking::hover::HoverMap,
    prelude::*,
};

mod components;

#[derive(Component)]
pub(super) struct DebugConsole;
#[derive(Component)]
struct TextInput;

use bevy_ui_text_input::TextSubmitEvent;
pub(super) use components::console;
use components::{Content, Prompt};

use crate::game::debug::DebugEvent;

use super::{ConsoleEvent, ConsoleLog, ConsoleState, OPEN_CONSOLE_DEFAULT, cli::respond};

// stolen from scrolling example
pub fn update_scroll_position(
    mut mouse_wheel_events: EventReader<MouseWheel>,
    hover_map: Res<HoverMap>,
    mut scrolled_node_query: Query<&mut ScrollPosition>,
) {
    for mouse_wheel_event in mouse_wheel_events.read() {
        let (dx, dy) = match mouse_wheel_event.unit {
            MouseScrollUnit::Line => (mouse_wheel_event.x * 18., mouse_wheel_event.y * 18.),
            MouseScrollUnit::Pixel => (mouse_wheel_event.x, mouse_wheel_event.y),
        };

        for (_pointer, pointer_map) in hover_map.iter() {
            for (entity, _hit) in pointer_map.iter() {
                if let Ok(mut scroll_position) = scrolled_node_query.get_mut(*entity) {
                    scroll_position.offset_x -= dx;
                    scroll_position.offset_y -= dy;
                }
            }
        }
    }
}

pub fn autofocus(mut input_focus: ResMut<InputFocus>, prompt: Single<Entity, With<Prompt>>) {
    input_focus.set(*prompt);
}

pub fn autoscroll(mut scrollables: Single<&mut ScrollPosition, With<Content>>) {
    scrollables.offset_y = f32::MAX;
}

pub fn handle_open_console(
    kb_input: Res<ButtonInput<KeyCode>>,
    mut console_open_state: ResMut<NextState<ConsoleState>>,
) {
    if kb_input.pressed(OPEN_CONSOLE_DEFAULT) {
        console_open_state.set(ConsoleState::Open);
    }
}

pub fn process_prompt(
    mut log: ResMut<ConsoleLog>,
    mut submit_reader: EventReader<TextSubmitEvent>,
    mut debug_event_writer: EventWriter<DebugEvent>,
    prompt: Single<Entity, With<Prompt>>,
) {
    for submit in submit_reader.read() {
        if submit.entity == *prompt && !submit.text.trim().is_empty() {
            if submit.text.trim() != "close" {
                log.input(&submit.text);
            }
            match respond(&submit.text) {
                Ok(debug_ev) => {
                    debug_event_writer.write(debug_ev);
                }
                Err(e) => log.error(&e),
            }
        }
    }
}

pub fn update_content(
    mut c: Commands,
    content: Single<Entity, With<Content>>,
    mut log: ResMut<ConsoleLog>,
    asset_server: Res<AssetServer>,
    mut scrollables: Single<&mut ScrollPosition, With<Content>>,
) {
    c.entity(*content).with_children(|parent| {
        for item in log.unread() {
            match dbg!(item) {
                ConsoleEvent::Input(s) => {
                    parent.spawn(components::input(s.to_string(), &asset_server));
                }
                ConsoleEvent::Output(s) => {
                    parent.spawn(components::output(s.to_string(), &asset_server));
                }
                ConsoleEvent::Error(s) => {
                    parent.spawn(components::error(
                        // dirty hack so they're not called subcommands
                        s.replace("subcommand", "command").to_string(),
                        &asset_server,
                    ));
                }
                _ => {}
            };
        }
    });
    scrollables.offset_y = f32::MAX;
}
