use bevy::prelude::*;
use ui::{
    DebugConsole, autofocus, autoscroll, handle_open_console, process_prompt, update_content,
    update_scroll_position,
};

use crate::{AppState, cleanup::despawn, game::GameplaySet};

use super::DebugEvent;

mod cli;
mod ui;

pub(super) fn plugin(app: &mut App) {
    app.init_state::<ConsoleState>()
        .add_systems(
            OnEnter(ConsoleState::Open),
            (open_console, (autofocus, autoscroll).after(open_console)),
        )
        .add_systems(
            Update,
            (
                handle_open_console
                    .in_set(GameplaySet)
                    .run_if(in_state(ConsoleState::Closed).and(in_state(AppState::Ingame))),
                update_content.run_if(resource_changed::<ConsoleLog>),
                (
                    update_scroll_position,
                    process_prompt,
                    execute_console_events,
                )
                    .run_if(in_state(ConsoleState::Open)),
            ),
        )
        .add_systems(OnExit(ConsoleState::Open), despawn::<DebugConsole>)
        .insert_resource::<ConsoleLog>(ConsoleLog::new());
}

/// the usize is a read index
#[derive(Resource, Clone)]
struct ConsoleLog(Vec<ConsoleEvent>, usize);

impl ConsoleLog {
    fn new() -> Self {
        Self(
            vec![ConsoleEvent::Output(String::from(
                "Welcome to the dev console :3\n (i should probably put some proper info into this some time)",
            ))],
            1,
        )
    }

    fn unread(&mut self) -> &[ConsoleEvent] {
        let res = &self.0[self.1..];
        self.1 = self.0.len();
        res
    }

    pub fn input(&mut self, content: &str) {
        self.0.push(ConsoleEvent::Input(content.to_string()))
    }

    pub fn output(&mut self, content: &str) {
        self.0.push(ConsoleEvent::Output(content.to_string()))
    }

    pub fn error(&mut self, content: &str) {
        self.0.push(ConsoleEvent::Error(content.to_string()))
    }
}

#[derive(Clone, Debug)]
enum ConsoleEvent {
    Input(String),
    Output(String),
    Error(String),
}

const OPEN_CONSOLE_DEFAULT: KeyCode = KeyCode::KeyC;

#[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash, Reflect)]
enum ConsoleState {
    #[default]
    Closed,
    Open,
}

fn open_console(mut c: Commands, console_data: Res<ConsoleLog>, asset_server: Res<AssetServer>) {
    log::info!("opening debug console...");
    c.spawn(ui::console(console_data.clone(), asset_server.clone()));
}

fn execute_console_events(
    mut ev_reader: EventReader<DebugEvent>,
    mut log: ResMut<ConsoleLog>,
    mut next_state: ResMut<NextState<ConsoleState>>,
) {
    for ev in ev_reader.read() {
        match ev {
            DebugEvent::CloseDebugConsole => next_state.set(ConsoleState::Closed),
            DebugEvent::PrintToConsole { text } => log.output(text),
            _ => todo!(),
        };
    }
}
