//! Dev-only hot reload polling and status for the `game_hot` dylib.

use std::path::PathBuf;
use std::sync::{mpsc, Mutex};
use std::time::Duration;

use bevy::prelude::*;
use game::{hot_reload_subscribe, on_hot_reload};
use hot_lib_reloader::LibReloadObserver;
use notify::Event;

/// Status label shown in the bottom status bar.
#[derive(Resource, Debug, Clone)]
pub struct HotReloadState {
    pub label: String,
}

impl Default for HotReloadState {
    fn default() -> Self {
        Self {
            label: "Hot reload: idle".into(),
        }
    }
}

pub struct HotReloadPlugin;

impl Plugin for HotReloadPlugin {
    fn build(&self, app: &mut App) {
        app.init_resource::<HotReloadState>()
            .add_systems(Startup, spawn_dylib_watcher)
            .add_systems(Last, poll_lib_reload)
            .add_systems(Last, manual_hot_reload);
    }
}

fn spawn_dylib_watcher(mut commands: Commands) {
    let Some(path) = game_hot_dylib_path() else {
        return;
    };

    let (tx, rx) = mpsc::channel();
    let watch_path = path.clone();
    std::thread::spawn(move || {
        use notify::{EventKind, RecommendedWatcher, RecursiveMode, Watcher};

        let tx = tx.clone();
        let mut watcher = RecommendedWatcher::new(
            move |result: notify::Result<Event>| {
                if let Ok(event) = result {
                    if matches!(event.kind, EventKind::Modify(_) | EventKind::Create(_)) {
                        let _ = tx.send(());
                    }
                }
            },
            notify::Config::default(),
        )
        .expect("hot reload watcher");

        if let Some(parent) = watch_path.parent() {
            let _ = watcher.watch(parent, RecursiveMode::NonRecursive);
        }

        loop {
            std::thread::sleep(Duration::from_secs(3600));
        }
    });

    commands.insert_resource(DylibWatchReceiver(Mutex::new(rx)));
    info!("Watching for game_hot dylib changes at {}", path.display());
}

#[derive(Resource)]
struct DylibWatchReceiver(Mutex<mpsc::Receiver<()>>);

fn poll_lib_reload(world: &mut World, mut observer: Local<Option<LibReloadObserver>>) {
    if observer.is_none() {
        *observer = Some(hot_reload_subscribe());
    }

    let mut building = false;
    if let Some(watch) = world.get_resource::<DylibWatchReceiver>() {
        if let Ok(rx) = watch.0.lock() {
            while rx.try_recv().is_ok() {
                building = true;
            }
        }
    }
    if building {
        world.resource_mut::<HotReloadState>().label = "Hot reload: building…".into();
    }

    let Some(obs) = observer.as_ref() else {
        return;
    };

    if obs.wait_for_reload_timeout(Duration::ZERO) {
        on_hot_reload(world);
        world.resource_mut::<HotReloadState>().label = "Hot reload: reloaded".into();
        info!("game_hot dylib reloaded");
    }
}

fn manual_hot_reload(world: &mut World) {
    let ctrl = world
        .resource::<ButtonInput<KeyCode>>()
        .pressed(KeyCode::ControlLeft)
        || world
            .resource::<ButtonInput<KeyCode>>()
            .pressed(KeyCode::ControlRight);
    let shift = world
        .resource::<ButtonInput<KeyCode>>()
        .pressed(KeyCode::ShiftLeft)
        || world
            .resource::<ButtonInput<KeyCode>>()
            .pressed(KeyCode::ShiftRight);
    let reload = world
        .resource::<ButtonInput<KeyCode>>()
        .just_pressed(KeyCode::KeyR);

    if ctrl && shift && reload {
        on_hot_reload(world);
        world.resource_mut::<HotReloadState>().label = "Hot reload: manual refresh".into();
        info!("Manual hot reload refresh (Ctrl+Shift+R)");
    }
}

fn game_hot_dylib_path() -> Option<PathBuf> {
    let target = std::env::var("CARGO_TARGET_DIR").map_or_else(
        |_| PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/../../target")),
        PathBuf::from,
    );
    let profile = if cfg!(debug_assertions) {
        "debug"
    } else {
        "release"
    };
    Some(target.join(profile).join(game_hot_lib_name()))
}

fn game_hot_lib_name() -> &'static str {
    if cfg!(target_os = "macos") {
        "libgame_hot.dylib"
    } else if cfg!(target_os = "windows") {
        "game_hot.dll"
    } else {
        "libgame_hot.so"
    }
}
