use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::ids::{GroupId, SurfaceId, WorkspaceId};
use crate::layout::LayoutNode;
use crate::status::SurfaceState;

/// Everything needed to (re)create a panel's process.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SurfaceSpec {
    pub command: String,
    #[serde(default)]
    pub args: Vec<String>,
    pub cwd: String,
    #[serde(default)]
    pub agent_label: Option<String>,
    pub cols: u16,
    pub rows: u16,
    #[serde(default)]
    pub autostart: bool,
}

/// A colored, ordered collection of workspaces.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Group {
    pub id: GroupId,
    pub name: String,
    pub color: String,
    pub order: u32,
}

/// Persisted workspace: structure only (no live process state).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Workspace {
    pub id: WorkspaceId,
    pub path: String,
    pub name: String,
    #[serde(default)]
    pub group_id: Option<GroupId>,
    pub order: u32,
    #[serde(default)]
    pub unread: bool,
    /// Pinned to the sidebar's Favorites section.
    #[serde(default)]
    pub pinned: bool,
    /// None = empty workspace (no panels yet).
    #[serde(default)]
    pub layout: Option<LayoutNode>,
    /// The single maximized surface for this workspace, if any.
    #[serde(default)]
    pub zoomed: Option<SurfaceId>,
    pub surfaces: HashMap<SurfaceId, SurfaceSpec>,
}

/// Per-surface view in `status` — spec plus live lifecycle flag.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SurfaceView {
    pub spec: SurfaceSpec,
    /// true = has a live actor/PTY; false = stopped (in tree, no process).
    pub running: bool,
    /// Ephemeral agent-activity status (meaningful while running).
    #[serde(default)]
    pub state: SurfaceState,
}

/// Workspace view in `status` / `workspace_changed`: structure + per-surface state.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WorkspaceView {
    pub id: WorkspaceId,
    pub path: String,
    pub name: String,
    pub group_id: Option<GroupId>,
    pub order: u32,
    pub unread: bool,
    #[serde(default)]
    pub pinned: bool,
    pub layout: Option<LayoutNode>,
    #[serde(default)]
    pub zoomed: Option<SurfaceId>,
    pub surfaces: HashMap<SurfaceId, SurfaceView>,
}

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

    #[test]
    fn surface_spec_round_trips() {
        let s = SurfaceSpec {
            command: "claude".into(),
            args: vec![],
            cwd: "/tmp".into(),
            agent_label: Some("claude".into()),
            cols: 80,
            rows: 24,
            autostart: false,
        };
        let j = serde_json::to_string(&s).unwrap();
        let back: SurfaceSpec = serde_json::from_str(&j).unwrap();
        assert_eq!(back, s);
    }

    #[test]
    fn workspace_round_trips_with_empty_layout() {
        let w = Workspace {
            id: WorkspaceId("w_1".into()),
            path: "/tmp/p".into(),
            name: "p".into(),
            group_id: None,
            order: 0,
            unread: false,
            pinned: false,
            layout: None,
            zoomed: None,
            surfaces: HashMap::new(),
        };
        let j = serde_json::to_string(&w).unwrap();
        let back: Workspace = serde_json::from_str(&j).unwrap();
        assert_eq!(back, w);
    }

    #[test]
    fn workspace_pinned_round_trips_and_defaults_false() {
        let w = Workspace {
            id: WorkspaceId("w_1".into()), path: "/tmp/p".into(), name: "p".into(),
            group_id: None, order: 0, unread: false, pinned: true, layout: None,
            zoomed: None, surfaces: HashMap::new(),
        };
        let back: Workspace = serde_json::from_str(&serde_json::to_string(&w).unwrap()).unwrap();
        assert!(back.pinned);
        // Old state.json without the field deserializes to pinned=false.
        let legacy = r#"{"id":"w_1","path":"/tmp/p","name":"p","order":0,"surfaces":{}}"#;
        let old: Workspace = serde_json::from_str(legacy).unwrap();
        assert!(!old.pinned);
    }

    #[test]
    fn workspace_round_trips_with_zoom() {
        let w = Workspace {
            id: WorkspaceId("w_1".into()), path: "/tmp/p".into(), name: "p".into(),
            group_id: None, order: 0, unread: false, pinned: false, layout: None,
            zoomed: Some(SurfaceId("s_1".into())), surfaces: HashMap::new(),
        };
        let back: Workspace = serde_json::from_str(&serde_json::to_string(&w).unwrap()).unwrap();
        assert_eq!(back, w);
    }
}
