//! L10 tests for the pure `Workspace::move_tab_to_new_window` operation
//! (ARCHITECTURE §10): a tab is *moved*, never duplicated; an emptied source
//! window is dropped; an active moved tab hands activity back to a sibling.

use domain::{
    LayoutError, LayoutNode, LayoutTree, LeafCell, NodeId, PersistedMonitorState,
    PersistedWindowKind, PersistedWindowPosition, PersistedWindowSize, PersistedWindowState,
    ProjectId, Tab, TabId, Window, WindowId, WindowStateSnapshot, Workspace,
};
use uuid::Uuid;

fn tid(n: u128) -> TabId {
    TabId::from_uuid(Uuid::from_u128(n))
}
fn wid(n: u128) -> WindowId {
    WindowId::from_uuid(Uuid::from_u128(n))
}
fn leaf_tree() -> LayoutTree {
    LayoutTree::new(LayoutNode::Leaf(LeafCell {
        id: NodeId::from_uuid(Uuid::from_u128(900)),
        session: None,
        agent: None,
        conversation_id: None,
        engine_session_id: None,
        agent_was_running: false,
    }))
}
fn tab(n: u128) -> Tab {
    Tab {
        id: tid(n),
        project_id: ProjectId::from_uuid(Uuid::from_u128(1000 + n)),
        layout: leaf_tree(),
    }
}

/// Count how many windows contain a tab with the given id.
fn occurrences(ws: &Workspace, tab: TabId) -> usize {
    ws.windows
        .iter()
        .filter(|w| w.tabs.iter().any(|t| t.id == tab))
        .count()
}

#[test]
fn move_tab_from_multi_tab_window_keeps_source_and_creates_new() {
    let src = Window::new(wid(1), vec![tab(1), tab(2)], tid(1)).unwrap();
    let ws = Workspace { windows: vec![src] };

    let next = ws.move_tab_to_new_window(tid(1), wid(99)).unwrap();

    assert_eq!(next.windows.len(), 2, "source kept + new window");
    // The moved tab appears exactly once (moved, not duplicated).
    assert_eq!(occurrences(&next, tid(1)), 1);
    // Source window kept tab 2 and fell back its active tab to it.
    let source = next.windows.iter().find(|w| w.id == wid(1)).unwrap();
    assert_eq!(source.tabs.len(), 1);
    assert_eq!(source.active_tab, tid(2));
    // New window holds the moved tab, active.
    let detached = next.windows.iter().find(|w| w.id == wid(99)).unwrap();
    assert_eq!(detached.tabs.len(), 1);
    assert_eq!(detached.active_tab, tid(1));
}

#[test]
fn move_only_tab_removes_the_emptied_source_window() {
    let src = Window::new(wid(1), vec![tab(1)], tid(1)).unwrap();
    let ws = Workspace { windows: vec![src] };

    let next = ws.move_tab_to_new_window(tid(1), wid(99)).unwrap();

    assert_eq!(next.windows.len(), 1, "emptied source dropped");
    assert_eq!(next.windows[0].id, wid(99));
    assert_eq!(occurrences(&next, tid(1)), 1);
}

#[test]
fn move_unknown_tab_is_rejected() {
    let ws = Workspace {
        windows: vec![Window::new(wid(1), vec![tab(1)], tid(1)).unwrap()],
    };
    assert!(matches!(
        ws.move_tab_to_new_window(tid(404), wid(99)).unwrap_err(),
        LayoutError::TabNotFound(t) if t == tid(404)
    ));
}

#[test]
fn move_non_active_tab_leaves_source_active_unchanged() {
    let src = Window::new(wid(1), vec![tab(1), tab(2)], tid(1)).unwrap();
    let ws = Workspace { windows: vec![src] };

    let next = ws.move_tab_to_new_window(tid(2), wid(99)).unwrap();

    let source = next.windows.iter().find(|w| w.id == wid(1)).unwrap();
    assert_eq!(source.active_tab, tid(1), "active tab unchanged");
    assert_eq!(source.tabs.len(), 1);
}

#[test]
fn window_state_snapshot_round_trips_with_camel_case_schema() {
    let snapshot = WindowStateSnapshot::new(vec![PersistedWindowState {
        label: "view-tickets-0000000000000000000000000000002a".to_owned(),
        kind: PersistedWindowKind::View,
        panel: Some("tickets".to_owned()),
        project_id: Some(ProjectId::from_uuid(Uuid::from_u128(42))),
        plugin_layout: None,
        url: Some(
            "index.html?panel=tickets&project=00000000-0000-0000-0000-00000000002a".to_owned(),
        ),
        visible: true,
        maximized: false,
        fullscreen: false,
        outer_position: Some(PersistedWindowPosition { x: 12, y: 34 }),
        outer_size: Some(PersistedWindowSize {
            width: 1200,
            height: 800,
        }),
        monitor: Some(PersistedMonitorState {
            name: Some("HDMI-1".to_owned()),
            scale_factor: Some(1.25),
            position: Some(PersistedWindowPosition { x: 0, y: 0 }),
            size: Some(PersistedWindowSize {
                width: 1920,
                height: 1080,
            }),
        }),
        last_focused_at: Some(123),
    }]);

    let json = serde_json::to_string(&snapshot).unwrap();
    assert!(json.contains("\"projectId\""), "json was {json}");
    assert!(json.contains("\"outerPosition\""), "json was {json}");
    assert!(json.contains("\"scaleFactor\""), "json was {json}");
    assert!(!json.contains("project_id"), "no snake_case leak: {json}");

    let decoded: WindowStateSnapshot = serde_json::from_str(&json).unwrap();
    assert_eq!(decoded, snapshot);
}
