use std::panic::Location;

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

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

use super::{DebugEvent, DebugSet};

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)).in_set(DebugSet),
        )
        .add_systems(
            Update,
            (
                handle_open_console.run_if(in_state(ConsoleState::Closed)),
                update_content.run_if(resource_changed::<ConsoleLog>),
                (
                    update_scroll_position,
                    process_prompt,
                    execute_console_events,
                )
                    .run_if(in_state(ConsoleState::Open)),
            )
                .in_set(DebugSet),
        )
        .add_systems(
            OnExit(ConsoleState::Open),
            despawn::<DebugConsole>.in_set(DebugSet),
        )
        .insert_resource::<ConsoleLog>(ConsoleLog::new());
}

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

impl ConsoleLog {
    fn new() -> Self {
        Self(
            vec![ConsoleEvent::Output(format!(
                "Running {} ({})\nMeow!! :3",
                env!("CARGO_PKG_NAME"),
                env!("CARGO_PKG_VERSION")
            ))],
            1,
        )
    }

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

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

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

    pub fn err(&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, error: false } => log.write(text),
            DebugEvent::PrintToConsole { text, error: true } => log.err(text),
            _ => {}
        };
    }
}

pub fn exec_script(
    file: In<Option<(String, String)>>,
    mut dbg_writer: EventWriter<DebugEvent>,
    mut logger: ResMut<ConsoleLog>,
) {
    let Some((file, file_name)) = &*file else {
        return;
    };

    for (line_nr, line) in file.lines().enumerate() {
        if !line.trim().is_empty() && !line.trim().starts_with("#") {
            match respond(line) {
                Ok(ev) => {
                    dbg_writer.write(ev);
                }
                Err(err) => logger.err(&format!("{err}\n\n({file_name}:{line_nr})")),
            }
        }
    }
}
