//! Project workspace I/O and user-local preferences (separate from `assets/project.ron`).

use bevy::prelude::*;
use serde::{Deserialize, Serialize};
use settings::{ProjectKind, ProjectSettings, ProjectSettingsIo, DEFAULT_PROJECT_PATH};

use crate::scene_io::SceneIo;

const USER_CONFIG_DIR: &str = ".config/bevy-fps";
const USER_PREFS_FILE: &str = "editor_prefs.ron";

/// User-local editor preferences (not checked into the repo).
#[derive(Debug, Clone, Serialize, Deserialize, Resource)]
pub struct UserPreferences {
    pub recent_projects: Vec<String>,
    pub recent_levels: Vec<String>,
    pub last_project_root: Option<String>,
    /// Serialized [`crate::ui::layout::DockLayoutSnapshot`] (RON).
    pub dock_layout: Option<String>,
    #[serde(default)]
    pub save_before_play: bool,
    /// Hierarchy search filter (persisted between sessions).
    #[serde(default)]
    pub hierarchy_filter: String,
    /// Hierarchy paths expanded in the outliner (name paths like `Group/Cube`).
    #[serde(default)]
    pub hierarchy_expanded_paths: Vec<String>,
    #[serde(default = "default_true")]
    pub hierarchy_show_generated: bool,
    #[serde(default)]
    pub hierarchy_show_runtime: bool,
    #[serde(default)]
    pub hierarchy_sort_mode: crate::ui::hierarchy_state::HierarchySort,
    #[serde(default = "default_true")]
    pub scene_autosave_enabled: bool,
    #[serde(default = "default_autosave_interval_secs")]
    pub scene_autosave_interval_secs: u64,
    #[serde(default = "default_recovery_generations")]
    pub scene_recovery_generations: usize,
}

fn default_true() -> bool {
    true
}

const fn default_autosave_interval_secs() -> u64 {
    120
}

const fn default_recovery_generations() -> usize {
    5
}

impl Default for UserPreferences {
    fn default() -> Self {
        Self {
            recent_projects: Vec::new(),
            recent_levels: Vec::new(),
            last_project_root: None,
            dock_layout: None,
            save_before_play: false,
            hierarchy_filter: String::new(),
            hierarchy_expanded_paths: Vec::new(),
            hierarchy_show_generated: true,
            hierarchy_show_runtime: false,
            hierarchy_sort_mode: crate::ui::hierarchy_state::HierarchySort::default(),
            scene_autosave_enabled: true,
            scene_autosave_interval_secs: default_autosave_interval_secs(),
            scene_recovery_generations: default_recovery_generations(),
        }
    }
}

/// Active project workspace metadata (mirrors manifest fields in `ProjectSettings`).
#[derive(Resource, Debug, Clone)]
pub struct ProjectWorkspace {
    pub root: String,
    pub project_id: String,
    pub project_kind: ProjectKind,
    pub last_opened_scene: Option<String>,
    pub settings_path: String,
    pub settings_dirty: bool,
    pub layout_dirty: bool,
}

impl Default for ProjectWorkspace {
    fn default() -> Self {
        let root = std::env::current_dir()
            .ok()
            .and_then(|path| path.into_os_string().into_string().ok())
            .unwrap_or_else(|| ".".into());
        Self {
            root,
            project_id: String::new(),
            project_kind: ProjectKind::default(),
            last_opened_scene: None,
            settings_path: DEFAULT_PROJECT_PATH.into(),
            settings_dirty: false,
            layout_dirty: false,
        }
    }
}

pub struct ProjectIoPlugin;

impl Plugin for ProjectIoPlugin {
    fn build(&self, app: &mut App) {
        app.init_resource::<UserPreferences>()
            .init_resource::<ProjectWorkspace>()
            .add_systems(Startup, load_user_preferences_on_startup)
            .add_systems(Update, sync_workspace_from_settings_system);
    }
}

fn load_user_preferences(mut commands: Commands) {
    let mut prefs = read_user_preferences();
    if let Ok(root) = std::env::current_dir().and_then(|root| root.canonicalize()) {
        remember_project_root(&mut prefs, &root);
        if let Err(error) = write_user_preferences(&prefs) {
            warn!("Failed to remember active project: {error}");
        }
    }
    commands.insert_resource(prefs);
}

/// Startup system; registered before [`crate::ui::apply_ui_state_from_prefs`].
pub(crate) fn load_user_preferences_on_startup(commands: Commands) {
    load_user_preferences(commands);
}

fn sync_workspace_from_settings_system(
    settings: Res<ProjectSettings>,
    settings_io: Res<ProjectSettingsIo>,
    scene_io: Res<SceneIo>,
    mut workspace: ResMut<ProjectWorkspace>,
) {
    if !settings_io.is_changed() && !settings.is_changed() && !scene_io.is_changed() {
        return;
    }
    sync_workspace_from_settings(&mut workspace, &settings, &settings_io, &scene_io);
}

fn user_prefs_path() -> Option<std::path::PathBuf> {
    std::env::var_os("HOME").map(|home| {
        std::path::PathBuf::from(home)
            .join(USER_CONFIG_DIR)
            .join(USER_PREFS_FILE)
    })
}

pub fn read_user_preferences() -> UserPreferences {
    let Some(path) = user_prefs_path() else {
        return UserPreferences::default();
    };
    match std::fs::read_to_string(path) {
        Ok(text) => ron::from_str(&text).unwrap_or_default(),
        Err(_) => UserPreferences::default(),
    }
}

pub fn write_user_preferences(prefs: &UserPreferences) -> Result<(), String> {
    let path = user_prefs_path().ok_or_else(|| "HOME not set".to_string())?;
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
    }
    let text = ron::ser::to_string_pretty(prefs, ron::ser::PrettyConfig::default())
        .map_err(|e| e.to_string())?;
    std::fs::write(path, text).map_err(|e| e.to_string())
}

pub fn push_recent(list: &mut Vec<String>, entry: String, max: usize) {
    list.retain(|item| item != &entry);
    list.insert(0, entry);
    list.truncate(max);
}

pub fn remember_project_root(preferences: &mut UserPreferences, root: &std::path::Path) {
    let root = root.display().to_string();
    push_recent(&mut preferences.recent_projects, root.clone(), 12);
    preferences.last_project_root = Some(root);
}

pub fn sync_workspace_from_settings(
    workspace: &mut ProjectWorkspace,
    settings: &ProjectSettings,
    settings_io: &ProjectSettingsIo,
    scene_io: &SceneIo,
) {
    workspace.project_id.clone_from(&settings.project_id);
    workspace.project_kind = settings.project_kind;
    workspace.last_opened_scene = scene_io
        .active_path
        .as_ref()
        .map(|path| path.display().to_string());
    workspace.settings_path = settings_io.path.clone();
    workspace.settings_dirty = settings_io.dirty;
}

pub fn window_title(
    scene_io: &SceneIo,
    workspace: &ProjectWorkspace,
    settings: &ProjectSettings,
) -> String {
    let mut parts = vec![format!("{} — Bevy FPS Editor", settings.name)];
    if !workspace.root.is_empty() && workspace.root != "." {
        parts.push(workspace.root.clone());
    }
    if let Some(path) = &scene_io.active_path {
        parts.push(path.display().to_string());
    }
    if scene_io.dirty {
        parts.push("*".into());
    }
    if workspace.settings_dirty {
        parts.push("[project*]".into());
    }
    if workspace.layout_dirty {
        parts.push("[layout*]".into());
    }
    parts.join(" — ")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn remembering_project_deduplicates_and_updates_last_root() {
        let mut preferences = UserPreferences {
            recent_projects: vec!["/old".into(), "/project".into()],
            ..Default::default()
        };

        remember_project_root(&mut preferences, std::path::Path::new("/project"));

        assert_eq!(preferences.last_project_root.as_deref(), Some("/project"));
        assert_eq!(preferences.recent_projects, vec!["/project", "/old"]);
    }
}
