use crate::widgets::Pane;
use nightshade::ecs::map::Map;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Serialize, Deserialize, Clone)]
pub struct ProjectLayout {
    pub name: String,
    pub tree: egui_tiles::Tree<Pane>,
}

impl ProjectLayout {
    pub fn new(name: String, tree: egui_tiles::Tree<Pane>) -> Self {
        Self { name, tree }
    }
}

#[derive(Serialize, Deserialize, Clone)]
#[serde(default)]
pub struct EditorProject {
    pub version: String,
    pub project_name: Option<String>,
    pub layouts: Vec<ProjectLayout>,
    pub maps: HashMap<String, Map>,
    pub active_layout_index: usize,
    pub active_map_name: Option<String>,
}

impl Default for EditorProject {
    fn default() -> Self {
        Self {
            version: env!("CARGO_PKG_VERSION").to_string(),
            project_name: None,
            layouts: Vec::new(),
            maps: HashMap::new(),
            active_layout_index: 0,
            active_map_name: None,
        }
    }
}

impl EditorProject {
    pub fn new(project_name: Option<String>) -> Self {
        Self {
            version: env!("CARGO_PKG_VERSION").to_string(),
            project_name,
            layouts: Vec::new(),
            maps: HashMap::new(),
            active_layout_index: 0,
            active_map_name: None,
        }
    }

    pub fn active_layout(&self) -> Option<&ProjectLayout> {
        self.layouts.get(self.active_layout_index)
    }

    pub fn active_layout_mut(&mut self) -> Option<&mut ProjectLayout> {
        self.layouts.get_mut(self.active_layout_index)
    }

    pub fn add_layout(&mut self, name: String, tree: egui_tiles::Tree<Pane>) {
        self.layouts.push(ProjectLayout::new(name, tree));
    }

    pub fn active_map(&self) -> Option<&Map> {
        self.active_map_name
            .as_ref()
            .and_then(|name| self.maps.get(name))
    }

    pub fn add_map(&mut self, name: String, map: Map) {
        self.maps.insert(name.clone(), map);
        if self.active_map_name.is_none() {
            self.active_map_name = Some(name);
        }
    }

    pub fn remove_map(&mut self, name: &str) -> Option<Map> {
        let removed = self.maps.remove(name);
        if self.active_map_name.as_deref() == Some(name) {
            self.active_map_name = self.maps.keys().next().cloned();
        }
        removed
    }

    pub fn set_active_map(&mut self, name: &str) -> bool {
        if self.maps.contains_key(name) {
            self.active_map_name = Some(name.to_string());
            true
        } else {
            false
        }
    }

    #[cfg(not(target_arch = "wasm32"))]
    pub fn load(path: &std::path::Path) -> Result<Self, String> {
        let path_str = path.to_string_lossy().to_lowercase();
        if path_str.ends_with(".project.bin") {
            let bytes = std::fs::read(path)
                .map_err(|error| format!("Failed to read project file: {error}"))?;
            let project = bincode::deserialize::<EditorProject>(&bytes)
                .map_err(|error| format!("Failed to parse project file: {error}"))?;
            Ok(project)
        } else {
            let json = std::fs::read_to_string(path)
                .map_err(|error| format!("Failed to read project file: {error}"))?;
            let project = serde_json::from_str::<EditorProject>(&json)
                .map_err(|error| format!("Failed to parse project file: {error}"))?;
            Ok(project)
        }
    }

    #[cfg(not(target_arch = "wasm32"))]
    pub fn save(&self, path: &std::path::Path) -> Result<(), String> {
        let path_str = path.to_string_lossy().to_lowercase();
        if path_str.ends_with(".project.bin") {
            let bytes = bincode::serialize(self)
                .map_err(|error| format!("Failed to serialize project: {error}"))?;
            std::fs::write(path, bytes)
                .map_err(|error| format!("Failed to write project file: {error}"))?;
        } else {
            let json = serde_json::to_string_pretty(self)
                .map_err(|error| format!("Failed to serialize project: {error}"))?;
            std::fs::write(path, json)
                .map_err(|error| format!("Failed to write project file: {error}"))?;
        }
        Ok(())
    }
}

#[derive(Default)]
pub struct ProjectState {
    pub is_open: bool,
    pub is_modified: bool,
}
