use super::*;

#[test]
fn routing_and_view_env_values_are_stable() {
    assert_eq!(InputRouting::Local.as_env(), "0");
    assert_eq!(InputRouting::Remote.as_env(), "1");
    assert_eq!(ViewMode::Unified.as_env(), "unified");
    assert_eq!(ViewMode::Breakout.as_env(), "breakout");
    assert_eq!(DisplaySurface::Preview.label(), "preview");
    assert_eq!(DisplaySurface::Window.label(), "window");
    assert_eq!(WebcamTransport::default(), WebcamTransport::Mjpeg);
    assert_eq!(WebcamTransport::Hevc.as_id(), "hevc");
    assert_eq!(WebcamTransport::Mjpeg.as_id(), "mjpeg");
    assert_eq!(WebcamTransport::Hevc.env_value(), "hevc");
    assert_eq!(WebcamTransport::Mjpeg.label(), "MJPEG");
    assert_eq!(
        WebcamTransport::from_id("h265"),
        Some(WebcamTransport::Hevc)
    );
    assert_eq!(
        WebcamTransport::from_server_codec("mjpeg"),
        Some(WebcamTransport::Mjpeg)
    );
    assert_eq!(
        WebcamTransport::from_id("jpeg"),
        Some(WebcamTransport::Mjpeg)
    );
    assert_eq!(WebcamTransport::from_id("yuy2"), None);
}

#[test]
fn preset_ids_labels_and_legacy_aliases_are_stable() {
    for (preset, id, label) in [
        (FeedSourcePreset::ThisEye, "self", "Left Eye"),
        (FeedSourcePreset::OtherEye, "other", "Right Eye"),
        (FeedSourcePreset::Off, "off", "Off"),
    ] {
        assert_eq!(preset.as_id(), id);
        assert_eq!(FeedSourcePreset::from_id(id), Some(preset));
        assert_eq!(preset.label(0), label);
    }
    assert_eq!(FeedSourcePreset::ThisEye.label(1), "Right Eye");
    assert_eq!(FeedSourcePreset::OtherEye.label(1), "Left Eye");
    assert_eq!(FeedSourcePreset::ThisEye.label(9), "This Eye");
    assert_eq!(FeedSourcePreset::from_id("bogus"), None);

    for (preset, id, label) in [
        (BreakoutSizePreset::P360, "360p", "360p"),
        (BreakoutSizePreset::P540, "540p", "540p"),
        (BreakoutSizePreset::P720, "720p", "720p"),
        (BreakoutSizePreset::P900, "900p", "900p"),
        (BreakoutSizePreset::P1080, "1080p", "1080p"),
        (BreakoutSizePreset::P1440, "1440p", "1440p"),
        (BreakoutSizePreset::Source, "source", "Source"),
        (BreakoutSizePreset::FillDisplay, "fill", "Display"),
    ] {
        assert_eq!(preset.as_id(), id);
        assert_eq!(BreakoutSizePreset::from_id(id), Some(preset));
        assert_eq!(preset.label(), label);
    }
    assert_eq!(BreakoutSizePreset::from_id("giant"), None);

    for (preset, id, label) in [
        (CaptureSizePreset::Vga, "vga", "VGA"),
        (CaptureSizePreset::P480, "480p", "480p"),
        (CaptureSizePreset::P576, "576p", "576p"),
        (CaptureSizePreset::P720, "720p", "720p"),
        (CaptureSizePreset::P1080, "1080p", "1080p"),
    ] {
        assert_eq!(preset.as_id(), id);
        assert_eq!(preset.label(), label);
        assert_eq!(preset.transport_label(), "device H.264 pass-through");
    }
    assert_eq!(
        CaptureSizePreset::from_id("360p"),
        Some(CaptureSizePreset::Vga)
    );
    assert_eq!(
        CaptureSizePreset::from_id("540p"),
        Some(CaptureSizePreset::P480)
    );
    assert_eq!(
        CaptureSizePreset::from_id("900p"),
        Some(CaptureSizePreset::P1080)
    );
    assert_eq!(
        CaptureSizePreset::from_id("source"),
        Some(CaptureSizePreset::P1080)
    );
    assert_eq!(CaptureSizePreset::from_id("unknown"), None);
    assert_eq!(CaptureSizePreset::P720.display_size(), (1280, 720));
    assert!(CaptureSizePreset::P1080.display_aspect_ratio() > 1.7);
}

#[test]
fn defaults_pick_remote_unified_and_inactive_session() {
    let state = LauncherState::new();
    assert_eq!(state.routing, InputRouting::Remote);
    assert_eq!(state.view_mode, ViewMode::Unified);
    assert_eq!(state.display_surface(0), DisplaySurface::Preview);
    assert_eq!(state.display_surface(1), DisplaySurface::Preview);
    assert_eq!(state.preview_source_size(), PreviewSourceSize::default());
    assert_eq!(state.breakout_limit_size(), PreviewSourceSize::default());
    assert_eq!(state.capture_size_preset(0), CaptureSizePreset::P1080);
    assert_eq!(state.breakout_size_preset(0), BreakoutSizePreset::Source);
    assert!(!state.server_available);
    assert!(!state.remote_active);
    assert!(state.devices.camera.is_none());
    assert!(state.devices.microphone.is_none());
    assert!(state.devices.speaker.is_none());
    assert!(state.devices.keyboard.is_none());
    assert!(state.devices.mouse.is_none());
    assert!(!state.channels.camera);
    assert!(!state.channels.microphone);
    assert!(state.channels.audio);
    assert_eq!(state.audio_gain_percent, DEFAULT_AUDIO_GAIN_PERCENT);
    assert_eq!(state.audio_gain_env_value(), "2.000");
    assert_eq!(state.audio_gain_label(), "200%");
    assert_eq!(state.mic_gain_percent, DEFAULT_MIC_GAIN_PERCENT);
    assert_eq!(state.mic_gain_env_value(), "1.000");
    assert_eq!(state.mic_gain_label(), "100%");
    assert_eq!(state.capture_power.unit, "relay.service");
    assert_eq!(state.capture_power.mode, "auto");
}

#[test]
fn display_surface_updates_global_view_summary() {
    let mut state = LauncherState::new();
    state.set_display_surface(1, DisplaySurface::Window);
    assert_eq!(state.view_mode, ViewMode::Breakout);
    assert_eq!(state.breakout_count(), 1);

    state.set_display_surface(1, DisplaySurface::Preview);
    assert_eq!(state.view_mode, ViewMode::Unified);
    assert_eq!(state.breakout_count(), 0);

    state.set_view_mode(ViewMode::Breakout);
    assert_eq!(state.display_surface(0), DisplaySurface::Window);
    assert_eq!(state.display_surface(1), DisplaySurface::Window);

    state.set_display_surface(9, DisplaySurface::Window);
    assert_eq!(state.display_surface(9), DisplaySurface::Preview);
    assert_eq!(state.breakout_count(), 2);
}

#[test]
fn feed_sources_can_mirror_or_disable_a_pane() {
    let mut state = LauncherState::new();
    state.set_capture_size_preset(1, CaptureSizePreset::P1080);

    assert_eq!(state.resolved_feed_monitor_id(0), Some(0));
    assert_eq!(state.resolved_feed_monitor_id(1), Some(1));

    state.set_feed_source_preset(0, FeedSourcePreset::OtherEye);
    assert_eq!(state.resolved_feed_monitor_id(0), Some(1));
    assert_eq!(
        state.display_capture_size_choice(0),
        Some(state.capture_size_choice(1))
    );

    state.set_feed_source_preset(1, FeedSourcePreset::OtherEye);
    assert_eq!(state.resolved_feed_monitor_id(1), Some(0));

    state.set_feed_source_preset(0, FeedSourcePreset::Off);
    assert_eq!(state.resolved_feed_monitor_id(0), None);
    assert!(state.display_capture_size_choice(0).is_none());

    state.set_feed_source_preset(9, FeedSourcePreset::Off);
    assert_eq!(state.feed_source_preset(9), FeedSourcePreset::ThisEye);
    let labels: Vec<_> = state
        .feed_source_options(1)
        .into_iter()
        .map(|choice| (choice.preset, choice.label))
        .collect();
    assert_eq!(
        labels,
        vec![
            (FeedSourcePreset::ThisEye, "Right Eye"),
            (FeedSourcePreset::OtherEye, "Left Eye"),
            (FeedSourcePreset::Off, "Off"),
        ]
    );
}

#[test]
fn mirrored_panes_use_their_effective_source_size_for_breakout_source_labels() {
    let mut state = LauncherState::new();
    state.set_capture_size_preset(1, CaptureSizePreset::P720);
    state.set_feed_source_preset(0, FeedSourcePreset::OtherEye);

    let mirrored_source = state.effective_preview_source_size(0);
    assert_eq!(mirrored_source.width, 1280);
    assert_eq!(mirrored_source.height, 720);
    assert_eq!(mirrored_source.fps, 60);

    let mirrored_breakout = state.breakout_size_choice(0);
    assert_eq!(mirrored_breakout.preset, BreakoutSizePreset::Source);
    assert_eq!(mirrored_breakout.width, 1280);
    assert_eq!(mirrored_breakout.height, 720);

    assert_eq!(
        state.display_capture_size_preset(0),
        Some(CaptureSizePreset::P720)
    );
    assert_eq!(state.display_capture_fps(0), Some(60));
    assert_eq!(state.display_capture_bitrate_kbit(0), Some(12_000));
}

#[test]
fn zero_and_out_of_range_profile_updates_are_safe_noops() {
    let mut state = LauncherState::new();
    let original_source = state.preview_source_size();
    state.set_preview_source_profile(0, 1080, 0);
    assert_eq!(state.preview_source_size(), original_source);

    let original_limit = state.breakout_limit_size();
    state.set_breakout_limit_size(1920, 0);
    assert_eq!(state.breakout_limit_size(), original_limit);

    let original_display = state.breakout_display_size();
    state.set_breakout_display_size(0, 1080);
    assert_eq!(state.breakout_display_size(), original_display);

    state.set_breakout_display_size(3440, 1440);
    assert_eq!(state.breakout_display_size().width, 3440);
    assert_eq!(state.breakout_display_size().height, 1440);

    state.set_capture_size_preset(9, CaptureSizePreset::P720);
    assert_eq!(state.capture_size_preset(9), CaptureSizePreset::P1080);
    state.set_capture_fps(9, 0);
    assert_eq!(state.capture_fps(9), default_eye_source_mode().fps);
    state.set_capture_bitrate_kbit(9, 1);
    assert!(state.capture_bitrate_kbit(9) >= 18_000);
    state.set_breakout_size_preset(9, BreakoutSizePreset::P360);
    assert_eq!(state.breakout_size_preset(9), BreakoutSizePreset::Source);
}

#[test]
fn selecting_auto_or_blank_clears_explicit_device() {
    let mut state = LauncherState::new();
    state.select_camera(Some("/dev/video0".to_string()));
    assert_eq!(state.devices.camera.as_deref(), Some("/dev/video0"));

    state.select_camera(Some("auto".to_string()));
    assert!(state.devices.camera.is_none());

    state.select_microphone(Some("   ".to_string()));
    assert!(state.devices.microphone.is_none());
}

#[test]
fn catalog_defaults_stage_real_media_devices_without_enabling_channels() {
    let mut state = LauncherState::new();
    state.select_camera(Some("/dev/video-special".to_string()));

    let catalog = DeviceCatalog {
        cameras: vec!["/dev/video0".to_string()],
        camera_modes: [(
            "/dev/video0".to_string(),
            vec![CameraMode::new(1920, 1080, 30)],
        )]
        .into_iter()
        .collect(),
        microphones: vec!["alsa_input.usb".to_string()],
        speakers: vec!["alsa_output.usb".to_string()],
        keyboards: vec!["/dev/input/event10".to_string()],
        mice: vec!["/dev/input/event11".to_string()],
    };

    state.apply_catalog_defaults(&catalog);

    assert_eq!(state.devices.camera.as_deref(), Some("/dev/video-special"));
    assert_eq!(state.devices.microphone.as_deref(), Some("alsa_input.usb"));
    assert_eq!(state.devices.speaker.as_deref(), Some("alsa_output.usb"));
    assert!(!state.channels.camera);
    assert!(!state.channels.microphone);
    assert!(state.channels.audio);

    let mut fresh = LauncherState::new();
    fresh.apply_catalog_defaults(&catalog);
    assert_eq!(fresh.devices.camera.as_deref(), Some("/dev/video0"));
    assert_eq!(fresh.camera_quality, Some(CameraMode::new(1920, 1080, 30)));
    assert_eq!(fresh.devices.microphone.as_deref(), Some("alsa_input.usb"));
    assert_eq!(fresh.devices.speaker.as_deref(), Some("alsa_output.usb"));
}

#[test]
fn camera_quality_tracks_selected_camera_supported_modes() {
    let catalog = DeviceCatalog {
        cameras: vec!["cam-a".to_string(), "cam-b".to_string()],
        camera_modes: [
            (
                "cam-a".to_string(),
                vec![
                    CameraMode::new(1920, 1080, 30),
                    CameraMode::new(1920, 1080, 20),
                    CameraMode::new(1280, 720, 30),
                    CameraMode::new(1280, 720, 20),
                ],
            ),
            ("cam-b".to_string(), vec![CameraMode::new(1280, 720, 30)]),
        ]
        .into_iter()
        .collect(),
        ..DeviceCatalog::default()
    };

    let mut state = LauncherState::new();
    state.apply_catalog_defaults(&catalog);
    assert_eq!(state.devices.camera.as_deref(), Some("cam-a"));
    assert_eq!(
        state.camera_quality_options(&catalog),
        vec![
            CameraMode::new(1920, 1080, 30),
            CameraMode::new(1920, 1080, 20),
            CameraMode::new(1280, 720, 30),
            CameraMode::new(1280, 720, 20)
        ]
    );

    state.select_camera_quality(Some(CameraMode::new(1280, 720, 30)));
    assert_eq!(
        state.selected_camera_quality(&catalog),
        Some(CameraMode::new(1280, 720, 30))
    );

    state.select_camera(Some("cam-b".to_string()));
    state.normalize_camera_quality(&catalog);
    assert_eq!(state.camera_quality, Some(CameraMode::new(1280, 720, 30)));

    state.select_camera(None);
    state.normalize_camera_quality(&catalog);
    assert_eq!(state.camera_quality, None);
}

#[test]
fn audio_gain_is_clamped_and_formatted_for_launcher_and_runtime() {
    let mut state = LauncherState::new();
    state.set_audio_gain_percent(350);
    assert_eq!(state.audio_gain_percent, 350);
    assert_eq!(state.audio_gain_label(), "350%");
    assert_eq!(state.audio_gain_env_value(), "3.500");

    state.set_audio_gain_percent(10_000);
    assert_eq!(state.audio_gain_percent, MAX_AUDIO_GAIN_PERCENT);
    assert_eq!(state.audio_gain_label(), "800%");
    assert_eq!(state.audio_gain_env_value(), "8.000");

    state.set_mic_gain_percent(325);
    assert_eq!(state.mic_gain_percent, 325);
    assert_eq!(state.mic_gain_label(), "325%");
    assert_eq!(state.mic_gain_env_value(), "3.250");

    state.set_mic_gain_percent(10_000);
    assert_eq!(state.mic_gain_percent, MAX_MIC_GAIN_PERCENT);
    assert_eq!(state.mic_gain_label(), "400%");
    assert_eq!(state.mic_gain_env_value(), "4.000");
}

#[test]
fn start_and_stop_remote_only_report_changes_once() {
    let mut state = LauncherState::new();
    assert!(state.start_remote());
    assert!(!state.start_remote());
    assert!(state.remote_active);

    assert!(state.stop_remote());
    assert!(!state.stop_remote());
    assert!(!state.remote_active);
}

#[test]
fn status_line_mentions_all_user_visible_controls() {
    let mut state = LauncherState::new();
    state.set_server_available(true);
    state.set_routing(InputRouting::Local);
    state.set_view_mode(ViewMode::Unified);
    state.select_camera(Some("/dev/video0".to_string()));
    state.select_camera_quality(Some(CameraMode::new(1920, 1080, 30)));
    state.select_microphone(Some("alsa_input.usb".to_string()));
    state.select_speaker(Some("alsa_output.usb".to_string()));
    state.set_camera_channel_enabled(true);
    state.set_microphone_channel_enabled(true);
    state.set_audio_channel_enabled(true);
    state.select_keyboard(Some("/dev/input/event-kbd".to_string()));
    state.select_mouse(Some("/dev/input/event-mouse".to_string()));
    state.set_preview_source_profile(1920, 1080, 30);
    state.start_remote();

    let status = state.status_line();
    assert!(status.contains("mode=local"));
    assert!(status.contains("server=true"));
    assert!(status.contains("view=unified"));
    assert!(status.contains("active=true"));
    assert!(status.contains("source=1920x1080"));
    assert!(status.contains("d1=preview"));
    assert!(status.contains("d2=preview"));
    assert!(status.contains("camera=/dev/video0"));
    assert!(status.contains("camera_quality=1080p@30"));
    assert!(status.contains("mic=alsa_input.usb"));
    assert!(status.contains("speaker=alsa_output.usb"));
    assert!(status.contains("audio_gain=200%"));
    assert!(status.contains("kbd=/dev/input/event-kbd"));
    assert!(status.contains("mouse=/dev/input/event-mouse"));
}

#[test]
fn capture_power_status_updates_snapshot_state() {
    let mut state = LauncherState::new();
    state.set_capture_power(CapturePowerStatus {
        available: true,
        enabled: true,
        unit: "relay.service".to_string(),
        detail: "active/running".to_string(),
        active_leases: 2,
        mode: "forced-on".to_string(),
        detected_devices: 2,
    });

    assert!(state.capture_power.available);
    assert!(state.capture_power.enabled);
    assert_eq!(state.capture_power.active_leases, 2);
    assert!(state.status_line().contains("power=on"));
}

#[test]
fn calibration_status_tracks_proto_unavailable_and_status_line() {
    let mut state = LauncherState::new();
    assert!(!state.calibration.available);
    assert_eq!(state.calibration.active_audio_offset_us, 0);

    let unavailable = CalibrationStatus::unavailable("server unreachable");
    assert!(!unavailable.available);
    assert_eq!(unavailable.detail, "server unreachable");

    state.set_calibration(CalibrationStatus::from_proto(
        lesavka_common::lesavka::CalibrationState {
            profile: "mjpeg".to_string(),
            factory_audio_offset_us: 0,
            factory_video_offset_us: 0,
            default_audio_offset_us: -40_000,
            default_video_offset_us: 1_000,
            active_audio_offset_us: -35_000,
            active_video_offset_us: 2_000,
            source: "manual".to_string(),
            confidence: "measured".to_string(),
            updated_at: "now".to_string(),
            detail: "operator-set".to_string(),
        },
    ));

    assert!(state.calibration.available);
    assert_eq!(state.calibration.profile, "mjpeg");
    assert_eq!(state.calibration.factory_audio_offset_us, 0);
    assert_eq!(state.calibration.factory_video_offset_us, 0);
    assert_eq!(state.calibration.default_audio_offset_us, -40_000);
    assert_eq!(state.calibration.default_video_offset_us, 1_000);
    assert_eq!(state.calibration.active_audio_offset_us, -35_000);
    assert_eq!(state.calibration.active_video_offset_us, 2_000);
    assert_eq!(state.calibration.source, "manual");
    assert_eq!(state.calibration.confidence, "measured");
    assert_eq!(state.calibration.updated_at, "now");
    assert_eq!(state.calibration.detail, "operator-set");
    assert!(state.status_line().contains("cal=manual:-35.0ms"));
}

#[test]
fn server_availability_tracks_reachability() {
    let mut state = LauncherState::new();
    assert!(!state.server_available);
    state.set_server_available(true);
    assert!(state.server_available);
}

#[test]
fn server_identity_and_media_caps_trim_blank_values() {
    let mut state = LauncherState::new();
    state.set_server_version(Some("   ".to_string()));
    assert_eq!(state.server_version, None);

    state.set_server_version(Some(" 0.16.0 ".to_string()));
    assert_eq!(state.server_version.as_deref(), Some("0.16.0"));

    state.set_server_media_caps(
        Some(true),
        Some(false),
        Some("   ".to_string()),
        Some(" mjpeg ".to_string()),
    );
    assert_eq!(state.server_camera, Some(true));
    assert_eq!(state.server_microphone, Some(false));
    assert_eq!(state.server_camera_output, None);
    assert_eq!(state.server_camera_codec.as_deref(), Some("mjpeg"));
    state.select_webcam_transport(WebcamTransport::Hevc);
    assert_eq!(state.effective_webcam_transport(), WebcamTransport::Hevc);

    state.set_server_media_caps(
        Some(true),
        Some(false),
        Some("uvc".to_string()),
        Some("mjpeg".to_string()),
    );
    assert_eq!(
        state.effective_webcam_transport(),
        WebcamTransport::Mjpeg,
        "UVC handoff should follow the server codec contract"
    );

    state.set_server_media_caps(
        None,
        None,
        Some(" uvc ".to_string()),
        Some("   ".to_string()),
    );
    assert_eq!(state.server_camera, None);
    assert_eq!(state.server_microphone, None);
    assert_eq!(state.server_camera_output.as_deref(), Some("uvc"));
    assert_eq!(state.server_camera_codec, None);
    assert_eq!(state.webcam_transport, WebcamTransport::Hevc);
    assert_eq!(state.effective_webcam_transport(), WebcamTransport::Hevc);
}

#[test]
fn breakout_size_choices_track_the_negotiated_source_size() {
    let mut state = LauncherState::new();
    state.set_preview_source_profile(1920, 1080, 60);
    state.set_breakout_limit_size(2560, 1440);

    let source = state.capture_size_choice(0);
    assert_eq!(source.width, 1920);
    assert_eq!(source.height, 1080);
    assert_eq!(source.fps, 60);
    assert_eq!(source.max_bitrate_kbit, 18_000);

    state.set_capture_size_preset(0, CaptureSizePreset::P480);
    let compact_capture = state.capture_size_choice(0);
    assert_eq!(compact_capture.preset, CaptureSizePreset::P720);
    assert_eq!(compact_capture.width, 1280);
    assert_eq!(compact_capture.height, 720);
    assert_eq!(compact_capture.fps, 60);
    assert_eq!(compact_capture.max_bitrate_kbit, 12_000);

    let effective_source = state.effective_preview_source_size(0);
    assert_eq!(effective_source.width, 1280);
    assert_eq!(effective_source.height, 720);
    assert_eq!(effective_source.fps, 60);

    let display = state.breakout_size_choice(0);
    assert_eq!(display.width, 1280);
    assert_eq!(display.height, 720);

    state.set_breakout_size_preset(0, BreakoutSizePreset::P360);
    let smaller = state.breakout_size_choice(0);
    assert_eq!(smaller.width, 640);
    assert_eq!(smaller.height, 360);

    state.set_breakout_size_preset(0, BreakoutSizePreset::P540);
    let compact = state.breakout_size_choice(0);
    assert_eq!(compact.width, 960);
    assert_eq!(compact.height, 540);

    let capture_options = state.capture_size_options();
    assert_eq!(capture_options.len(), 2);
    assert_eq!(capture_options[0].preset, CaptureSizePreset::P1080);
    assert_eq!(capture_options[0].width, 1920);
    assert_eq!(capture_options[0].height, 1080);
    assert_eq!(capture_options[0].fps, 60);
    assert_eq!(capture_options[0].max_bitrate_kbit, 18_000);

    let breakout_options = state.breakout_size_options(0);
    assert!(breakout_options.len() >= 5);
    assert!(breakout_options.iter().any(|choice| {
        choice.preset == BreakoutSizePreset::Source && choice.width == 1280 && choice.height == 720
    }));
}

#[test]
fn capture_option_methods_report_native_timing_and_bitrate_tiers() {
    let mut state = LauncherState::new();
    state.set_preview_source_profile(1920, 1080, 30);

    let fps_options = state.capture_fps_options();
    assert_eq!(fps_options.len(), 1);
    assert_eq!(fps_options[0].fps, 30);

    let bitrate_options = state.capture_bitrate_options();
    assert_eq!(bitrate_options.len(), 1);
    assert_eq!(bitrate_options[0].max_bitrate_kbit, 12_000);
}

#[test]
fn swap_key_binding_tracks_selected_key_and_binding_mode() {
    let mut state = LauncherState::new();
    assert_eq!(state.swap_key, "pause");
    assert!(!state.swap_key_binding);

    let token = state.begin_swap_key_binding();
    assert!(state.swap_key_binding);
    assert_eq!(token, state.swap_key_binding_token);

    state.set_swap_key("F8");
    assert_eq!(state.swap_key, "f8");

    state.set_swap_key("   ");
    assert_eq!(state.swap_key, "off");

    state.finish_swap_key_binding();
    assert!(!state.swap_key_binding);
}

#[test]
fn swap_key_binding_timeout_only_cancels_matching_attempt() {
    let mut state = LauncherState::new();
    let first = state.begin_swap_key_binding();
    let second = state.begin_swap_key_binding();

    assert!(!state.cancel_swap_key_binding(first));
    assert!(state.swap_key_binding);
    assert!(state.cancel_swap_key_binding(second));
    assert!(!state.swap_key_binding);
}

#[test]
fn complete_swap_key_binding_updates_value_and_ends_binding() {
    let mut state = LauncherState::new();
    state.begin_swap_key_binding();

    state.complete_swap_key_binding("F12");

    assert_eq!(state.swap_key, "f12");
    assert!(!state.swap_key_binding);
}

#[test]
fn push_note_accumulates_operator_context() {
    let mut state = LauncherState::new();
    state.push_note("preview warm");
    state.push_note("relay linked");

    assert_eq!(state.notes, vec!["preview warm", "relay linked"]);
}

#[test]
fn capture_size_presets_map_to_real_device_modes() {
    let mut state = LauncherState::new();
    state.set_preview_source_profile(1920, 1080, 60);
    state.set_capture_size_preset(0, CaptureSizePreset::P1080);
    let source = state.capture_size_choice(0);
    assert_eq!(source.width, 1920);
    assert_eq!(source.height, 1080);
    assert_eq!(source.fps, 60);
    assert!(source.max_bitrate_kbit >= 18_000);

    state.set_capture_size_preset(0, CaptureSizePreset::P720);
    let hd = state.capture_size_choice(0);
    assert_eq!(hd.preset, CaptureSizePreset::P720);
    assert_eq!(hd.width, 1280);
    assert_eq!(hd.height, 720);
    assert_eq!(hd.fps, 60);

    state.set_capture_size_preset(0, CaptureSizePreset::P576);
    let compact = state.capture_size_choice(0);
    assert_eq!(compact.preset, CaptureSizePreset::P720);
    assert_eq!(compact.width, 1280);
    assert_eq!(compact.height, 720);
    assert_eq!(compact.fps, 60);

    state.set_capture_size_preset(0, CaptureSizePreset::Vga);
    let small = state.capture_size_choice(0);
    assert_eq!(small.preset, CaptureSizePreset::P720);
    assert_eq!(small.width, 1280);
    assert_eq!(small.height, 720);
    assert_eq!(small.fps, 60);
}

#[test]
fn source_capture_knobs_follow_the_selected_native_mode() {
    let mut state = LauncherState::new();
    state.set_preview_source_profile(1920, 1080, 60);

    state.set_capture_size_preset(1, CaptureSizePreset::P1080);
    let defaults = state.capture_size_choice(1);
    assert_eq!(defaults.width, 1920);
    assert_eq!(defaults.height, 1080);
    assert_eq!(defaults.fps, 60);
    assert_eq!(defaults.max_bitrate_kbit, 18_000);

    state.set_capture_fps(1, 24);
    state.set_capture_bitrate_kbit(1, 8_500);
    let tuned = state.capture_size_choice(1);
    assert_eq!(tuned.preset, CaptureSizePreset::P1080);
    assert_eq!(tuned.width, 1920);
    assert_eq!(tuned.height, 1080);
    assert_eq!(tuned.fps, 60);
    assert_eq!(tuned.max_bitrate_kbit, 18_000);
}

#[test]
fn capture_option_helpers_report_native_fps_and_bitrate_tiers() {
    let full_hd = PreviewSourceSize {
        width: 1920,
        height: 1080,
        fps: 30,
    };
    assert_eq!(capture_fps_options(full_hd)[0].fps, 30);
    assert_eq!(capture_bitrate_options(full_hd)[0].max_bitrate_kbit, 12_000);
    assert_eq!(estimate_source_bitrate_kbit(1280, 720, 30), 6_000);
    assert_eq!(estimate_source_bitrate_kbit(640, 480, 15), 2_500);

    let compact = PreviewSourceSize {
        width: 640,
        height: 480,
        fps: 0,
    };
    assert_eq!(capture_fps_options(compact)[0].fps, 1);
    assert_eq!(capture_bitrate_options(compact)[0].max_bitrate_kbit, 2_500);
}

#[test]
fn standard_fit_preserves_aspect_when_width_is_limiting() {
    assert_eq!(fit_standard_dimensions(1000, 2000, 1920, 1080), (1000, 562));
}

#[test]
fn source_capture_ignores_manual_fps_and_bitrate_knobs() {
    let mut state = LauncherState::new();
    state.set_preview_source_profile(1920, 1080, 60);
    state.set_capture_size_preset(0, CaptureSizePreset::P720);
    state.set_capture_fps(0, 60);
    state.set_capture_bitrate_kbit(0, 24_000);

    let source = state.capture_size_choice(0);
    assert_eq!(source.preset, CaptureSizePreset::P720);
    assert_eq!(source.width, 1280);
    assert_eq!(source.height, 720);
    assert_eq!(source.fps, 60);
    assert_eq!(source.max_bitrate_kbit, 12_000);
}
