use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};

use spacesh_proto::ids::{GroupId, SurfaceId, WorkspaceId};
use spacesh_proto::status::SurfaceState;
use spacesh_proto::workspace::{Group, SurfaceSpec, SurfaceView, Workspace, WorkspaceView};

use crate::state_store::PersistState;
use crate::surface::SurfaceHandle;

/// Single-threaded owner of structure (workspaces/groups/trees + per-surface
/// specs) and the live actor map. Lives in the server router task.
#[derive(Default)]
pub struct Registry {
    counter: AtomicU64,
    groups: HashMap<GroupId, Group>,
    workspaces: HashMap<WorkspaceId, Workspace>,
    by_path: HashMap<String, WorkspaceId>,
    /// Live actors only. Absent id that exists in a workspace's `surfaces` = stopped.
    live: HashMap<SurfaceId, SurfaceHandle>,
    /// Ephemeral per-surface status. In-memory only (never persisted).
    states: HashMap<SurfaceId, SurfaceState>,
}

/// Parse the hex numeric suffix of an id (`"s_1f"` → `0x1f`). None if malformed.
/// All ids are minted as `format!("{prefix}_{n:x}")`, so the suffix is hex.
fn id_num(id: &str) -> Option<u64> {
    id.rsplit_once('_').and_then(|(_, hex)| u64::from_str_radix(hex, 16).ok())
}

impl Registry {
    pub fn new() -> Self {
        Self::default()
    }

    fn next_id(&self, prefix: &str) -> String {
        let n = self.counter.fetch_add(1, Ordering::Relaxed);
        format!("{prefix}_{n:x}")
    }

    pub fn new_surface_id(&self) -> SurfaceId {
        SurfaceId(self.next_id("s"))
    }

    // ---- workspaces ----

    /// Idempotent by canonicalized path. Returns (workspace_id, created?).
    pub fn open_workspace(&mut self, path: PathBuf) -> (WorkspaceId, bool) {
        let canonical = path.canonicalize().unwrap_or(path);
        let key = canonical.to_string_lossy().to_string();
        if let Some(id) = self.by_path.get(&key) {
            return (id.clone(), false);
        }
        let id = WorkspaceId(self.next_id("w"));
        let name = canonical.file_name().map(|s| s.to_string_lossy().to_string()).unwrap_or_else(|| key.clone());
        let order = self.workspaces.len() as u32;
        self.workspaces.insert(id.clone(), Workspace {
            id: id.clone(), path: key.clone(), name, group_id: None, order,
            unread: false, pinned: false, layout: None, zoomed: None, surfaces: HashMap::new(),
        });
        self.by_path.insert(key, id.clone());
        (id, true)
    }

    pub fn workspace(&self, id: &WorkspaceId) -> Option<&Workspace> {
        self.workspaces.get(id)
    }
    pub fn workspace_mut(&mut self, id: &WorkspaceId) -> Option<&mut Workspace> {
        self.workspaces.get_mut(id)
    }
    pub fn close_workspace(&mut self, id: &WorkspaceId) -> Vec<SurfaceId> {
        let Some(ws) = self.workspaces.remove(id) else { return vec![] };
        self.by_path.retain(|_, v| v != id);
        let ids: Vec<SurfaceId> = ws.surfaces.keys().cloned().collect();
        for sid in &ids {
            self.live.remove(sid);
        }
        ids
    }

    /// The workspace that owns a surface id, if any.
    pub fn workspace_of(&self, sid: &SurfaceId) -> Option<WorkspaceId> {
        self.workspaces.values().find(|w| w.surfaces.contains_key(sid)).map(|w| w.id.clone())
    }

    // ---- surfaces (structure) ----

    pub fn add_surface_spec(&mut self, ws: &WorkspaceId, sid: SurfaceId, spec: SurfaceSpec) {
        if let Some(w) = self.workspaces.get_mut(ws) {
            w.surfaces.insert(sid, spec);
        }
    }
    pub fn surface_spec(&self, sid: &SurfaceId) -> Option<SurfaceSpec> {
        self.workspaces.values().find_map(|w| w.surfaces.get(sid).cloned())
    }
    /// Remove a surface from its workspace's spec map and the tree.
    pub fn remove_surface(&mut self, sid: &SurfaceId) {
        self.live.remove(sid);
        if let Some(ws) = self.workspace_of(sid) {
            if let Some(w) = self.workspaces.get_mut(&ws) {
                w.surfaces.remove(sid);
                w.layout = w.layout.take().and_then(|l| spacesh_core::ops::remove_leaf(l, sid));
                if w.zoomed.as_ref() == Some(sid) { w.zoomed = None; }
            }
        }
    }

    // ---- live actors ----

    pub fn set_live(&mut self, handle: SurfaceHandle) {
        self.live.insert(handle.id.clone(), handle);
    }
    pub fn live(&self, sid: &SurfaceId) -> Option<&SurfaceHandle> {
        self.live.get(sid)
    }
    pub fn mark_stopped(&mut self, sid: &SurfaceId) {
        self.live.remove(sid);
    }
    pub fn is_running(&self, sid: &SurfaceId) -> bool {
        self.live.contains_key(sid)
    }
    /// Ids of all currently-live surfaces.
    pub fn live_ids(&self) -> Vec<SurfaceId> {
        self.live.keys().cloned().collect()
    }

    // ---- surface state ----

    pub fn set_state(&mut self, sid: &SurfaceId, state: SurfaceState) {
        self.states.insert(sid.clone(), state);
    }
    pub fn state(&self, sid: &SurfaceId) -> SurfaceState {
        self.states.get(sid).copied().unwrap_or_default()
    }
    pub fn drop_state(&mut self, sid: &SurfaceId) {
        self.states.remove(sid);
    }

    // ---- groups ----

    pub fn create_group(&mut self, name: String, color: String) -> GroupId {
        let id = GroupId(self.next_id("g"));
        let order = self.groups.len() as u32;
        self.groups.insert(id.clone(), Group { id: id.clone(), name, color, order });
        id
    }
    pub fn group_mut(&mut self, id: &GroupId) -> Option<&mut Group> {
        self.groups.get_mut(id)
    }
    pub fn delete_group(&mut self, id: &GroupId) {
        self.groups.remove(id);
        for w in self.workspaces.values_mut() {
            if w.group_id.as_ref() == Some(id) {
                w.group_id = None;
            }
        }
    }
    pub fn groups(&self) -> Vec<Group> {
        let mut g: Vec<Group> = self.groups.values().cloned().collect();
        g.sort_by_key(|x| x.order);
        g
    }

    // ---- views & persistence ----

    pub fn workspace_view(&self, id: &WorkspaceId) -> Option<WorkspaceView> {
        let w = self.workspaces.get(id)?;
        Some(self.to_view(w))
    }
    fn to_view(&self, w: &Workspace) -> WorkspaceView {
        let surfaces = w.surfaces.iter().map(|(sid, spec)| {
            (sid.clone(), SurfaceView {
                spec: spec.clone(),
                running: self.live.contains_key(sid),
                state: self.state(sid),
            })
        }).collect();
        WorkspaceView {
            id: w.id.clone(), path: w.path.clone(), name: w.name.clone(),
            group_id: w.group_id.clone(), order: w.order, unread: w.unread, pinned: w.pinned,
            layout: w.layout.clone(), zoomed: w.zoomed.clone(), surfaces,
        }
    }
    pub fn status(&self) -> (Vec<Group>, Vec<WorkspaceView>) {
        let mut ws: Vec<WorkspaceView> = self.workspaces.values().map(|w| self.to_view(w)).collect();
        ws.sort_by_key(|w| w.order);
        (self.groups(), ws)
    }
    pub fn persist_state(&self) -> PersistState {
        let mut workspaces: Vec<Workspace> = self.workspaces.values().cloned().collect();
        workspaces.sort_by_key(|w| w.order);
        PersistState { version: 1, groups: self.groups(), workspaces }
    }
    /// Replace all structure from a loaded snapshot (cold start). Clears live map.
    pub fn restore(&mut self, state: PersistState) {
        self.groups = state.groups.into_iter().map(|g| (g.id.clone(), g)).collect();
        self.workspaces.clear();
        self.by_path.clear();
        self.live.clear();
        self.states.clear();

        // Advance the id counter past every restored id. The in-memory counter
        // resets to 0 on each daemon start; without this reseed, after a restart
        // `new_surface_id()` re-mints ids that already exist — producing duplicate
        // leaves in a workspace tree (same panel rendered twice, focus/search/
        // output routing keyed by surface id all break) and cross-workspace id
        // collisions.
        let mut max_id = 0u64;
        for gid in self.groups.keys() {
            if let Some(n) = id_num(&gid.0) { max_id = max_id.max(n + 1); }
        }
        for w in &state.workspaces {
            if let Some(n) = id_num(&w.id.0) { max_id = max_id.max(n + 1); }
            for sid in w.surfaces.keys() {
                if let Some(n) = id_num(&sid.0) { max_id = max_id.max(n + 1); }
            }
        }
        self.counter.store(max_id, Ordering::Relaxed);

        for w in state.workspaces {
            let mut w = w;
            // Heal a tree already corrupted by a duplicate leaf (pre-fix state).
            w.layout = w.layout.take().and_then(spacesh_core::ops::dedupe_leaves);
            if let Some(z) = &w.zoomed {
                if !w.surfaces.contains_key(z) { w.zoomed = None; }
            }
            self.by_path.insert(w.path.clone(), w.id.clone());
            self.workspaces.insert(w.id.clone(), w);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use spacesh_proto::layout::{LayoutNode as LN, Orient};

    fn spec() -> SurfaceSpec {
        SurfaceSpec { command: "/bin/sh".into(), args: vec![], cwd: "/tmp".into(),
            agent_label: None, cols: 80, rows: 24, autostart: false }
    }

    #[test]
    fn open_is_idempotent() {
        let mut r = Registry::new();
        let (a, c1) = r.open_workspace(std::env::temp_dir());
        let (b, c2) = r.open_workspace(std::env::temp_dir());
        assert_eq!(a, b);
        assert!(c1 && !c2);
    }

    #[test]
    fn surface_running_then_stopped() {
        let mut r = Registry::new();
        let (ws, _) = r.open_workspace(std::env::temp_dir());
        let sid = r.new_surface_id();
        r.add_surface_spec(&ws, sid.clone(), spec());
        assert!(!r.is_running(&sid)); // spec present, no live actor = stopped
        let v = r.workspace_view(&ws).unwrap();
        assert_eq!(v.surfaces.get(&sid).unwrap().running, false);
    }

    #[test]
    fn remove_surface_updates_tree() {
        let mut r = Registry::new();
        let (ws, _) = r.open_workspace(std::env::temp_dir());
        let s1 = r.new_surface_id();
        let s2 = r.new_surface_id();
        r.add_surface_spec(&ws, s1.clone(), spec());
        r.add_surface_spec(&ws, s2.clone(), spec());
        r.workspace_mut(&ws).unwrap().layout = Some(LN::Split {
            orient: Orient::H, ratios: vec![0.5, 0.5],
            children: vec![LN::leaf(s1.clone()), LN::leaf(s2.clone())],
        });
        r.remove_surface(&s2);
        let w = r.workspace(&ws).unwrap();
        assert!(!w.surfaces.contains_key(&s2));
        assert_eq!(w.layout, Some(LN::leaf(s1))); // split collapsed
    }

    #[test]
    fn restore_advances_counter_past_existing_ids() {
        // After a daemon restart the counter must not re-mint a restored id.
        let mut r = Registry::new();
        let mut surfaces = HashMap::new();
        surfaces.insert(SurfaceId("s_5".into()), spec());
        let st = PersistState {
            version: 1, groups: vec![],
            workspaces: vec![Workspace {
                id: WorkspaceId("w_2".into()), path: "/p".into(), name: "p".into(),
                group_id: None, order: 0, unread: false, pinned: false,
                layout: Some(LN::leaf(SurfaceId("s_5".into()))), zoomed: None, surfaces,
            }],
        };
        r.restore(st);
        // max restored id is s_5 (hex 5) → next minted must be s_6, no collision.
        assert_eq!(r.new_surface_id(), SurfaceId("s_6".into()));
    }

    #[test]
    fn restore_heals_duplicate_leaf() {
        // A persisted tree with s_1 twice (the production corruption) heals to one.
        let mut r = Registry::new();
        let mut surfaces = HashMap::new();
        surfaces.insert(SurfaceId("s_0".into()), spec());
        surfaces.insert(SurfaceId("s_1".into()), spec());
        let tree = LN::Split {
            orient: Orient::H, ratios: vec![1.0 / 3.0; 3],
            children: vec![LN::leaf(SurfaceId("s_0".into())), LN::leaf(SurfaceId("s_1".into())), LN::leaf(SurfaceId("s_1".into()))],
        };
        let st = PersistState {
            version: 1, groups: vec![],
            workspaces: vec![Workspace {
                id: WorkspaceId("w_0".into()), path: "/p".into(), name: "p".into(),
                group_id: None, order: 0, unread: false, pinned: false,
                layout: Some(tree), zoomed: None, surfaces,
            }],
        };
        r.restore(st);
        let w = r.workspace(&WorkspaceId("w_0".into())).unwrap();
        assert_eq!(
            spacesh_core::ops::leaves(w.layout.as_ref().unwrap()),
            vec![SurfaceId("s_0".into()), SurfaceId("s_1".into())]
        );
    }

    #[test]
    fn restore_round_trips_through_persist_state() {
        let mut r = Registry::new();
        let (ws, _) = r.open_workspace(std::env::temp_dir());
        r.add_surface_spec(&ws, r.new_surface_id(), spec());
        let state = r.persist_state();
        let mut r2 = Registry::new();
        r2.restore(state.clone());
        assert_eq!(r2.persist_state().workspaces.len(), state.workspaces.len());
    }

    #[test]
    fn delete_group_ungroups_members() {
        let mut r = Registry::new();
        let (ws, _) = r.open_workspace(std::env::temp_dir());
        let g = r.create_group("prod".into(), "#fff".into());
        r.workspace_mut(&ws).unwrap().group_id = Some(g.clone());
        r.delete_group(&g);
        assert!(r.workspace(&ws).unwrap().group_id.is_none());
    }

    #[test]
    fn state_defaults_idle_and_can_be_set() {
        let mut r = Registry::new();
        let (ws, _) = r.open_workspace(std::env::temp_dir());
        let sid = r.new_surface_id();
        r.add_surface_spec(&ws, sid.clone(), spec());
        assert_eq!(r.state(&sid), spacesh_proto::status::SurfaceState::Idle);
        r.set_state(&sid, spacesh_proto::status::SurfaceState::Work);
        assert_eq!(r.state(&sid), spacesh_proto::status::SurfaceState::Work);
        let v = r.workspace_view(&ws).unwrap();
        assert_eq!(v.surfaces.get(&sid).unwrap().state, spacesh_proto::status::SurfaceState::Work);
    }

    #[test]
    fn remove_surface_clears_zoom() {
        let mut r = Registry::new();
        let (ws, _) = r.open_workspace(std::env::temp_dir());
        let s1 = r.new_surface_id();
        r.add_surface_spec(&ws, s1.clone(), spec());
        r.workspace_mut(&ws).unwrap().zoomed = Some(s1.clone());
        r.remove_surface(&s1);
        assert!(r.workspace(&ws).unwrap().zoomed.is_none());
    }

    #[test]
    fn drop_state_resets_to_idle() {
        let mut r = Registry::new();
        let (ws, _) = r.open_workspace(std::env::temp_dir());
        let sid = r.new_surface_id();
        r.add_surface_spec(&ws, sid.clone(), spec());
        r.set_state(&sid, spacesh_proto::status::SurfaceState::Error);
        r.drop_state(&sid);
        assert_eq!(r.state(&sid), spacesh_proto::status::SurfaceState::Idle);
    }
}
