#[cfg(feature = "it")]
use crate::it::test_backend::TestBackend;
use {
    crate::{
        acceptor::{Acceptor, AcceptorError},
        async_engine::{AsyncEngine, Phase, SpawnedFuture},
        backend::{Backend, Connector},
        backends::{
            dummy::{DummyBackend, DummyOutput},
            headless, metal, x,
        },
        cli::{CliBackend, GlobalArgs, RunArgs},
        client::{ClientId, Clients},
        clientmem::{self, ClientMemError},
        cmm::{cmm_manager::ColorManager, cmm_primaries::Primaries},
        config::ConfigProxy,
        copy_device::CopyDeviceRegistry,
        cpu_worker::{CpuWorker, CpuWorkerError},
        criteria::{
            CritMatcherIds,
            clm::{ClMatcherManager, handle_cl_changes, handle_cl_leaf_events},
            tlm::{
                TlMatcherManager, handle_tl_changes, handle_tl_just_mapped, handle_tl_leaf_events,
            },
        },
        damage::{DamageVisualizer, visualize_damage},
        dbus::Dbus,
        ei::ei_client::EiClients,
        eventfd_cache::EventfdCache,
        forker,
        format::XRGB8888,
        gfx_api::GfxApi,
        globals::Globals,
        ifs::{
            head_management::{
                HeadManagers, HeadState, jay_head_manager_session_v1::handle_jay_head_manager_done,
            },
            jay_screencast::{perform_screencast_realloc, perform_toplevel_screencasts},
            wl_output::{BlendSpace, OutputId, PersistentOutputState, WlOutputGlobal},
            wl_seat::{handle_position_hint_requests, handle_warp_mouse_to_focus},
            wl_surface::{
                NoneSurfaceExt, xdg_surface::handle_xdg_surface_configure_events,
                zwp_input_popup_surface_v2::input_popup_positioning,
            },
            wlr_output_manager::wlr_output_manager_done,
            workspace_manager::workspace_manager_done,
        },
        io_uring::{IoUring, IoUringError},
        kbvm::KbvmContext,
        leaks,
        logger::Logger,
        output_schedule::OutputSchedule,
        portal::{self, PortalStartup},
        pr_caps::{PrCapsThread, pr_caps},
        scale::Scale,
        sighand::{self, SighandError},
        state::{ConnectorData, IdleState, ScreenlockState, State, XWaylandState},
        tasks::{self, handle_const_40hz_latch, idle},
        tracy::enable_profiler,
        tree::{
            DisplayNode, NodeIds, OutputNode, TearingMode, Transform, VrrMode,
            WorkspaceDisplayOrder, WorkspaceNode, container_layout, container_render_positions,
            container_tab_render_textures, float_layout, output_render_data,
            placeholder_render_textures,
        },
        user_session::import_environment,
        utils::{
            clone3::ensure_reaper,
            clonecell::CloneCell,
            errorfmt::ErrorFmt,
            event_listener::{handle_lazy_event_sources, handle_post_layout_event_sources},
            fdcloser::FdCloser,
            nice::{did_elevate_scheduler, elevate_scheduler},
            numcell::NumCell,
            object_drop_queue::ObjectDropQueue,
            oserror::{OsError, OsErrorExt},
            queue::AsyncQueue,
            rc_eq::RcEq,
            refcounted::RefCounted,
            run_toplevel::RunToplevel,
            static_text::StaticText,
            tri::Try,
        },
        version::VERSION,
        video::drm::wait_for_syncobj::WaitForSyncobj,
        wheel::{Wheel, WheelError},
    },
    ahash::AHashSet,
    clap::ValueEnum,
    forker::ForkerProxy,
    jay_config::{_private::DEFAULT_SEAT_NAME, logging::LogLevel as ConfigLogLevel},
    linearize::Linearize,
    log::LevelFilter,
    std::{
        cell::{Cell, RefCell},
        env,
        future::Future,
        ops::Deref,
        rc::Rc,
        sync::Arc,
        time::Duration,
    },
    thiserror::Error,
    uapi::c,
};

pub const MAX_EXTENTS: i32 = (1 << 22) - 1;

pub const MIN_SCALE: Scale = Scale::from_wl(60);
pub const MAX_SCALE: Scale = Scale::from_int(16);

pub fn start_compositor(global: GlobalArgs, args: RunArgs) {
    sighand::reset_all();
    let reaper_pid = ensure_reaper();
    let caps = pr_caps().into_comp();
    let caps_thread = if caps.has_nice() {
        elevate_scheduler();
        Some(caps.into_thread())
    } else {
        drop(caps);
        None
    };
    let forker = create_forker(reaper_pid);
    let portal = portal::run_from_compositor(global.log_level);
    enable_profiler();
    let logger = Logger::install_compositor(global.log_level);
    let portal = match portal {
        Ok(p) => Some(p),
        Err(e) => {
            log::error!("Could not spawn portal: {}", ErrorFmt(e));
            None
        }
    };
    let res = start_compositor2(
        Some(forker),
        portal,
        Some(logger.clone()),
        args,
        None,
        caps_thread,
    );
    leaks::log_leaked();
    if let Err(e) = res {
        let e = ErrorFmt(e);
        log::error!("A fatal error occurred: {}", e);
        eprintln!("A fatal error occurred: {}", e);
        eprintln!("See {} for more details.", logger.path());
        std::process::exit(1);
    }
    log::info!("Exit");
}

#[cfg(feature = "it")]
pub fn start_compositor_for_test(future: TestFuture) -> Result<(), CompositorError> {
    let res = start_compositor2(None, None, None, RunArgs::default(), Some(future), None);
    leaks::log_leaked();
    res
}

fn create_forker(reaper_pid: c::pid_t) -> Rc<ForkerProxy> {
    match ForkerProxy::create(reaper_pid) {
        Ok(f) => Rc::new(f),
        Err(e) => fatal!("Could not create a forker process: {}", ErrorFmt(e)),
    }
}

#[derive(Debug, Error)]
pub enum CompositorError {
    #[error("The client acceptor caused an error")]
    AcceptorError(#[from] AcceptorError),
    #[error("The signal handler caused an error")]
    SighandError(#[from] SighandError),
    #[error("The clientmem subsystem caused an error")]
    ClientmemError(#[from] ClientMemError),
    #[error("The timer subsystem caused an error")]
    WheelError(#[from] WheelError),
    #[error("Could not create an io-uring")]
    IoUringError(#[from] IoUringError),
    #[error("Could not create cpu worker")]
    CpuWorkerError(#[from] CpuWorkerError),
}

pub const WAYLAND_DISPLAY: &str = "WAYLAND_DISPLAY";
pub const LIBEI_SOCKET: &str = "LIBEI_SOCKET";
pub const DISPLAY: &str = "DISPLAY";

const STATIC_VARS: &[(&str, &str)] = &[
    ("XDG_CURRENT_DESKTOP", "jay"),
    ("XDG_SESSION_TYPE", "wayland"),
    ("_JAVA_AWT_WM_NONREPARENTING", "1"),
];

pub type TestFuture = Box<dyn Fn(&Rc<State>) -> Box<dyn Future<Output = ()>>>;

fn start_compositor2(
    forker: Option<Rc<ForkerProxy>>,
    portal: Option<PortalStartup>,
    logger: Option<Arc<Logger>>,
    run_args: RunArgs,
    test_future: Option<TestFuture>,
    caps_thread: Option<PrCapsThread>,
) -> Result<(), CompositorError> {
    let pid = uapi::getpid();
    log::info!("pid = {pid}");
    log::info!("version = {VERSION}");
    if did_elevate_scheduler() {
        log::info!("Running with elevated scheduler: SCHED_RR");
    }
    init_fd_limit();
    leaks::init();
    clientmem::init()?;
    let kb_ctx = KbvmContext::default();
    let kb_keymap = kb_ctx
        .parse_keymap(include_str!("keymap.xkb").as_bytes())
        .unwrap();
    let engine = AsyncEngine::new();
    let ring = IoUring::new(&engine, 32)?;
    let _signal_future = sighand::install(&engine, &ring)?;
    let wheel = Wheel::new(&engine, &ring)?;
    let (_run_toplevel_future, run_toplevel) = RunToplevel::install(&engine);
    let node_ids = NodeIds::default();
    let scales = RefCounted::default();
    scales.add(Scale::from_int(1));
    let cpu_worker = Rc::new(CpuWorker::new(&ring, &engine)?);
    let color_manager = ColorManager::new();
    let crit_ids = Rc::new(CritMatcherIds::default());
    let eventfd_cache = EventfdCache::new(&ring, &engine);
    let explicit_config_dir = run_args.config_dir.clone();
    let state = Rc::new(State {
        pid,
        kb_ctx,
        backend: CloneCell::new(Rc::new(DummyBackend)),
        forker: Default::default(),
        default_keymap: kb_keymap,
        eng: engine.clone(),
        render_ctx: Default::default(),
        drm_feedback: Default::default(),
        drm_feedback_consumers: Default::default(),
        render_ctx_version: NumCell::new(1),
        render_ctx_ever_initialized: Cell::new(false),
        cursors: Default::default(),
        wheel,
        clients: Clients::new(),
        globals: Globals::new(),
        connector_ids: Default::default(),
        root: Rc::new(DisplayNode::new(node_ids.next())),
        workspaces: Default::default(),
        dummy_output: Default::default(),
        node_ids,
        backend_events: AsyncQueue::new(),
        seat_ids: Default::default(),
        seat_queue: Default::default(),
        slow_clients: AsyncQueue::new(),
        none_surface_ext: Rc::new(NoneSurfaceExt),
        tree_changed_sent: Cell::new(false),
        config: Default::default(),
        input_device_ids: Default::default(),
        input_device_handlers: Default::default(),
        theme: Default::default(),
        pending_container_layout: Default::default(),
        pending_container_render_positions: Default::default(),
        pending_output_render_data: Default::default(),
        pending_float_layout: Default::default(),
        pending_input_popup_positioning: Default::default(),
        pending_toplevel_screencasts: Default::default(),
        pending_screencast_reallocs_or_reconfigures: Default::default(),
        pending_placeholder_render_textures: Default::default(),
        pending_container_tab_render_textures: Default::default(),
        dbus: Dbus::new(&engine, &ring, &run_toplevel),
        fdcloser: FdCloser::new(),
        logger: logger.clone(),
        connectors: Default::default(),
        outputs: Default::default(),
        wlr_output_managers: Default::default(),
        drm_devs: Default::default(),
        status: Default::default(),
        idle: IdleState {
            input: Default::default(),
            change: Default::default(),
            timeout: Cell::new(Duration::from_secs(10 * 60)),
            grace_period: Cell::new(Duration::from_secs(5)),
            timeout_changed: Default::default(),
            inhibitors: Default::default(),
            inhibitors_changed: Default::default(),
            inhibited_idle_notifications: Default::default(),
            backend_idle: Cell::new(true),
            in_grace_period: Cell::new(false),
        },
        run_args,
        xwayland: XWaylandState {
            enabled: Cell::new(true),
            running: Cell::new(false),
            pidfd: Default::default(),
            handler: Default::default(),
            queue: Default::default(),
            ipc_device_ids: Default::default(),
            use_wire_scale: Default::default(),
            wire_scale: Default::default(),
            windows: Default::default(),
            client: Default::default(),
            display: Default::default(),
        },
        acceptor: Default::default(),
        serial: Default::default(),
        idle_inhibitor_ids: Default::default(),
        run_toplevel,
        config_dir: explicit_config_dir.or_else(config_dir),
        config_file_id: NumCell::new(1),
        tracker: Default::default(),
        data_offer_ids: Default::default(),
        data_source_ids: Default::default(),
        drm_dev_ids: Default::default(),
        ring: ring.clone(),
        lock: ScreenlockState {
            locked: Cell::new(false),
            lock: Default::default(),
        },
        scales,
        cursor_sizes: Default::default(),
        hardware_tick_cursor: Default::default(),
        testers: Default::default(),
        render_ctx_watchers: Default::default(),
        workspace_watchers: Default::default(),
        default_workspace_capture: Cell::new(true),
        default_gfx_api: Cell::new(GfxApi::Vulkan),
        activation_tokens: Default::default(),
        toplevel_lists: Default::default(),
        dma_buf_ids: Default::default(),
        drm_feedback_ids: Default::default(),
        direct_scanout_enabled: Cell::new(true),
        persistent_output_states: Default::default(),
        double_click_interval_usec: Cell::new(400 * 1000),
        double_click_distance: Cell::new(5),
        create_default_seat: Cell::new(true),
        subsurface_ids: Default::default(),
        wait_for_syncobj: Rc::new(WaitForSyncobj::new(&ring, &engine)),
        explicit_sync_enabled: Cell::new(true),
        explicit_sync_supported: Default::default(),
        keyboard_state_ids: Default::default(),
        physical_keyboard_ids: Default::default(),
        security_context_acceptors: Default::default(),
        tagged_acceptors: Default::default(),
        cursor_user_group_ids: Default::default(),
        cursor_user_ids: Default::default(),
        cursor_user_groups: Default::default(),
        cursor_user_group_hardware_cursor: Default::default(),
        input_device_group_ids: Default::default(),
        tablet_ids: Default::default(),
        tablet_tool_ids: Default::default(),
        tablet_pad_ids: Default::default(),
        damage_visualizer: DamageVisualizer::new(&engine, &color_manager),
        default_vrr_mode: Cell::new(*VrrMode::NEVER),
        default_vrr_cursor_hz: Cell::new(None),
        default_tearing_mode: Cell::new(*TearingMode::VARIANT_3),
        ei_acceptor: Default::default(),
        ei_acceptor_future: Default::default(),
        enable_ei_acceptor: Default::default(),
        ei_clients: EiClients::new(),
        slow_ei_clients: Default::default(),
        cpu_worker,
        ui_drag_enabled: Cell::new(true),
        ui_drag_threshold_squared: Cell::new(10),
        toplevels: Default::default(),
        const_40hz_latch: Default::default(),
        tray_item_ids: Default::default(),
        data_control_device_ids: Default::default(),
        workspace_managers: Default::default(),
        color_management_enabled: Cell::new(false),
        color_manager,
        float_above_fullscreen: Cell::new(false),
        icons: Default::default(),
        show_pin_icon: Cell::new(false),
        cl_matcher_manager: ClMatcherManager::new(&crit_ids),
        tl_matcher_manager: TlMatcherManager::new(&crit_ids),
        caps_thread,
        toplevel_managers: Default::default(),
        node_at_tree: Default::default(),
        position_hint_requests: Default::default(),
        pending_warp_mouse_to_focus: Default::default(),
        backend_connector_state_serials: Default::default(),
        head_names: Default::default(),
        head_managers: Default::default(),
        head_managers_async: Default::default(),
        show_bar: Cell::new(true),
        enable_primary_selection: Cell::new(true),
        xdg_surface_configure_events: Default::default(),
        workspace_display_order: Cell::new(WorkspaceDisplayOrder::Manual),
        outputs_without_hc: Default::default(),
        udmabuf: Default::default(),
        gfx_ctx_changed: Default::default(),
        copy_device_registry: Rc::new(CopyDeviceRegistry::new(&ring, &engine, &eventfd_cache)),
        supports_presentation_feedback: Default::default(),
        eventfd_cache,
        lazy_event_sources: Default::default(),
        post_layout_event_sources: Default::default(),
        bo_drop_queue: Rc::new(ObjectDropQueue::new(&ring)),
        virtual_outputs: Default::default(),
        clean_logs_older_than: Default::default(),
    });
    state.tracker.register(ClientId::from_raw(0));
    create_dummy_output(&state);
    let (acceptor, _acceptor_future) = Acceptor::install(&state)?;
    if let Some(forker) = forker {
        forker.install(&state);
        forker.setenv(
            WAYLAND_DISPLAY.as_bytes(),
            acceptor.socket_name().as_bytes(),
        );
        for (key, val) in STATIC_VARS {
            forker.setenv(key.as_bytes(), val.as_bytes());
        }
    }
    let mut _portal = None;
    if let (Some(portal), Some(logger)) = (portal, &logger) {
        _portal = Some(engine.spawn(
            "portal",
            portal.spawn(engine.clone(), ring.clone(), logger.clone()),
        ));
    }
    let _compositor = engine.spawn("compositor", start_compositor3(state.clone(), test_future));
    ring.run()?;
    state.clear();
    engine.clear();
    Ok(())
}

async fn start_compositor3(state: Rc<State>, test_future: Option<TestFuture>) {
    let is_test = test_future.is_some();

    let backend = match create_backend(&state, test_future).await {
        Some(b) => b,
        _ => {
            log::error!("Could not create a backend");
            state.ring.stop();
            return;
        }
    };
    state.backend.set(backend.clone());
    state
        .supports_presentation_feedback
        .set(backend.supports_presentation_feedback());

    if backend.import_environment() {
        if let Some(acc) = state.acceptor.get() {
            import_environment(&state, WAYLAND_DISPLAY, acc.socket_name()).await;
        }
        for (key, val) in STATIC_VARS {
            import_environment(&state, key, val).await;
        }
    }

    let config = load_config(&state, is_test);
    config.configure(false);
    state.config.set(Some(Rc::new(config)));

    if state.create_default_seat.get() && state.globals.seats.is_empty() {
        state.create_seat(DEFAULT_SEAT_NAME);
    }
    state.perform_clean_logs_older_than();
    state.update_ei_acceptor();

    let _geh = start_global_event_handlers(&state);
    state.start_xwayland();

    match backend.run().await {
        Err(e) => log::error!("Backend failed: {}", ErrorFmt(e.deref())),
        _ => log::error!("Backend stopped without an error"),
    }
    state.ring.stop();
}

fn load_config(
    state: &Rc<State>,
    #[allow(clippy::allow_attributes, unused_variables)] for_test: bool,
) -> ConfigProxy {
    #[cfg(feature = "it")]
    if for_test {
        return ConfigProxy::for_test(state);
    }
    match ConfigProxy::from_config_dir(state) {
        Ok(c) => c,
        Err(e) => {
            log::warn!("Could not load config.so: {}", ErrorFmt(e));
            log::warn!("Using default config");
            ConfigProxy::default(state)
        }
    }
}

fn start_global_event_handlers(state: &Rc<State>) -> Vec<SpawnedFuture<()>> {
    let eng = &state.eng;

    vec![
        eng.spawn(
            "backend events",
            tasks::handle_backend_events(state.clone()),
        ),
        eng.spawn("slow client", tasks::handle_slow_clients(state.clone())),
        eng.spawn(
            "handware cursor tick",
            tasks::handle_hardware_cursor_tick(state.clone()),
        ),
        eng.spawn2(
            "container layout",
            Phase::Layout,
            container_layout(state.clone()),
        ),
        eng.spawn2(
            "container render positions",
            Phase::PostLayout,
            container_render_positions(state.clone()),
        ),
        eng.spawn2(
            "placeholder textures",
            Phase::PostLayout,
            placeholder_render_textures(state.clone()),
        ),
        eng.spawn2(
            "container tab textures",
            Phase::PostLayout,
            container_tab_render_textures(state.clone()),
        ),
        eng.spawn2(
            "output render",
            Phase::PostLayout,
            output_render_data(state.clone()),
        ),
        eng.spawn(
            "wlr output manager done",
            wlr_output_manager_done(state.clone()),
        ),
        eng.spawn2("float layout", Phase::Layout, float_layout(state.clone())),
        eng.spawn2("idle", Phase::PostLayout, idle(state.clone())),
        eng.spawn2(
            "input, popup positioning",
            Phase::PostLayout,
            input_popup_positioning(state.clone()),
        ),
        eng.spawn2(
            "toplevel screencast present",
            Phase::Present,
            perform_toplevel_screencasts(state.clone()),
        ),
        eng.spawn2(
            "screencast realloc",
            Phase::PostLayout,
            perform_screencast_realloc(state.clone()),
        ),
        eng.spawn2(
            "visualize damage",
            Phase::PostLayout,
            visualize_damage(state.clone()),
        ),
        eng.spawn(
            "slow ei clients",
            tasks::handle_slow_ei_clients(state.clone()),
        ),
        eng.spawn2(
            "const 40hz latch",
            Phase::Present,
            handle_const_40hz_latch(state.clone()),
        ),
        eng.spawn(
            "workspace manager done",
            workspace_manager_done(state.clone()),
        ),
        eng.spawn("cl matcher manager", handle_cl_changes(state.clone())),
        eng.spawn(
            "cl matcher leaf events",
            handle_cl_leaf_events(state.clone()),
        ),
        eng.spawn("tl matcher manager", handle_tl_changes(state.clone())),
        eng.spawn(
            "tl matcher leaf events",
            handle_tl_leaf_events(state.clone()),
        ),
        eng.spawn2(
            "tl matcher just mapped",
            Phase::Layout,
            handle_tl_just_mapped(state.clone()),
        ),
        eng.spawn(
            "position hint requests",
            handle_position_hint_requests(state.clone()),
        ),
        eng.spawn2(
            "jay head manager send done",
            Phase::Layout,
            handle_jay_head_manager_done(state.clone()),
        ),
        eng.spawn2(
            "xdg_surface configure events",
            Phase::PostLayout,
            handle_xdg_surface_configure_events(state.clone()),
        ),
        eng.spawn(
            "lazy event sources",
            handle_lazy_event_sources(state.clone()),
        ),
        eng.spawn2(
            "post layout event sources",
            Phase::PostLayout,
            handle_post_layout_event_sources(state.clone()),
        ),
        eng.spawn(
            "warp mouse to focus",
            handle_warp_mouse_to_focus(state.clone()),
        ),
    ]
}

async fn create_backend(
    state: &Rc<State>,
    #[allow(clippy::allow_attributes, unused_variables)] test_future: Option<TestFuture>,
) -> Option<Rc<dyn Backend>> {
    #[cfg(feature = "it")]
    if let Some(tf) = test_future {
        return Some(Rc::new(TestBackend::new(state, tf)));
    }
    let mut backends = &state.run_args.backends[..];
    if backends.is_empty() {
        backends = &[CliBackend::X11, CliBackend::Metal];
    }
    let mut tried_backends = AHashSet::new();
    for &backend in backends {
        if !tried_backends.insert(backend) {
            continue;
        }
        match backend {
            CliBackend::X11 => {
                log::info!("Trying to create X backend");
                match x::create(state).await {
                    Ok(b) => return Some(b),
                    Err(e) => {
                        log::error!("Could not create X backend: {}", ErrorFmt(e));
                    }
                }
            }
            CliBackend::Metal => {
                log::info!("Trying to create metal backend");
                match metal::create(state).await {
                    Ok(b) => return Some(b),
                    Err(e) => {
                        log::error!("Could not create metal backend: {}", ErrorFmt(e));
                    }
                }
            }
            CliBackend::Headless => {
                log::info!("Trying to create headless backend");
                match headless::create(state).await {
                    Ok(b) => return Some(b),
                    Err(e) => {
                        log::error!("Could not create headless backend: {}", ErrorFmt(e));
                    }
                }
            }
        }
    }
    None
}

fn init_fd_limit() {
    let res = OsError::tri(|| {
        let mut cur = uapi::getrlimit(c::RLIMIT_NOFILE as _).to_os_error()?;
        if cur.rlim_cur < cur.rlim_max {
            log::info!(
                "Increasing file descriptor limit from {} to {}",
                cur.rlim_cur,
                cur.rlim_max
            );
            cur.rlim_cur = cur.rlim_max;
            uapi::setrlimit(c::RLIMIT_NOFILE as _, &cur).to_os_error()?;
        }
        Ok(())
    });
    if let Err(e) = res {
        log::warn!("Could not increase file descriptor limit: {}", ErrorFmt(e));
    }
}

fn create_dummy_output(state: &Rc<State>) {
    let output_id = OutputId::new("jay-dummy-connector", "jay", "jay-dummy-output", "");
    let persistent_state = Rc::new(PersistentOutputState::default());
    let id = state.connector_ids.next();
    let connector = Rc::new(DummyOutput { id }) as Rc<dyn Connector>;
    let backend_state = connector.state();
    let name = Rc::new("Dummy".to_string());
    let head_name = state.head_names.next();
    let head_state = HeadState {
        connector_id: id,
        name: RcEq(name.clone()),
        position: (0, 0),
        size: (0, 0),
        active: false,
        connected: false,
        transform: Transform::None,
        scale: Default::default(),
        wl_output: None,
        connector_enabled: true,
        in_compositor_space: false,
        mode: Default::default(),
        monitor_info: None,
        inherent_non_desktop: false,
        override_non_desktop: None,
        vrr: false,
        vrr_mode: VrrMode::Never,
        tearing_enabled: backend_state.tearing,
        tearing_active: false,
        tearing_mode: TearingMode::Never,
        format: XRGB8888,
        color_space: backend_state.color_space,
        eotf: backend_state.eotf,
        supported_formats: Default::default(),
        brightness: None,
        blend_space: BlendSpace::Srgb,
        use_native_gamut: false,
        vrr_cursor_hz: None,
        persistent_state: Some(RcEq(persistent_state.clone())),
    };
    let connector_data = Rc::new(ConnectorData {
        id,
        connector,
        handler: Cell::new(None),
        connected: Cell::new(true),
        name,
        description: Default::default(),
        drm_dev: None,
        async_event: Default::default(),
        damaged: Cell::new(false),
        damage: Default::default(),
        needs_vblank_emulation: Cell::new(false),
        damage_intersect: Default::default(),
        state: RefCell::new(backend_state),
        head_managers: HeadManagers::new(head_name, head_state),
        wlr_output_heads: Default::default(),
    });
    let schedule = Rc::new(OutputSchedule::new(
        state,
        &connector_data,
        &persistent_state,
    ));
    let dummy_output = Rc::new(OutputNode {
        id: state.node_ids.next(),
        global: Rc::new(WlOutputGlobal::new(
            state.globals.name(),
            state,
            &connector_data,
            Some(Vec::new()),
            0,
            0,
            &output_id,
            &persistent_state,
            Vec::new(),
            Vec::new(),
            Primaries::SRGB,
            None,
        )),
        jay_outputs: Default::default(),
        workspaces: Default::default(),
        workspace: Default::default(),
        seat_state: Default::default(),
        layers: Default::default(),
        exclusive_zones: Default::default(),
        workspace_rect: Default::default(),
        workspace_rect_rel: Default::default(),
        non_exclusive_rect_rel: Default::default(),
        bar_rect: Default::default(),
        bar_rect_rel: Default::default(),
        bar_rect_with_separator: Default::default(),
        bar_rect_with_separator_rel: Default::default(),
        bar_separator_rect: Default::default(),
        bar_separator_rect_rel: Default::default(),
        non_exclusive_rect: Default::default(),
        render_data: Default::default(),
        state: state.clone(),
        is_dummy: true,
        status: Default::default(),
        scroll: Default::default(),
        pointer_positions: Default::default(),
        pointer_down: Default::default(),
        lock_surface: Default::default(),
        hardware_cursor: Default::default(),
        update_render_data_scheduled: Cell::new(false),
        screencasts: Default::default(),
        hardware_cursor_needs_render: Cell::new(false),
        screencopies: Default::default(),
        title_visible: Cell::new(false),
        schedule,
        vblank_event: Default::default(),
        latch_event: Default::default(),
        presentation_event: Default::default(),
        render_margin_ns: Default::default(),
        flip_margin_ns: Default::default(),
        ext_copy_sessions: Default::default(),
        before_latch_event: Default::default(),
        tray_start_rel: Default::default(),
        tray_items: Default::default(),
        ext_workspace_groups: Default::default(),
        pinned: Default::default(),
        tearing: Default::default(),
        active_zwlr_gamma_control: Default::default(),
        workspace_switched: state.lazy_event_sources.create_source(),
    });
    let dummy_workspace = WorkspaceNode::new(&dummy_output, "dummy", true);
    dummy_workspace.may_capture.set(false);
    *dummy_workspace.output_link.borrow_mut() =
        Some(dummy_output.workspaces.add_last(dummy_workspace.clone()));
    dummy_output.show_workspace(&dummy_workspace);
    state.dummy_output.set(Some(dummy_output));
}

pub fn config_dir() -> Option<String> {
    if let Ok(dir) = env::var("JAY_CONFIG_DIR") {
        return Some(dir);
    }
    if let Ok(xdg) = env::var("XDG_CONFIG_HOME") {
        Some(format!("{}/jay", xdg))
    } else if let Ok(home) = env::var("HOME") {
        Some(format!("{}/.config/jay", home))
    } else {
        log::warn!("Neither XDG_CONFIG_HOME nor HOME are set. Using default config.");
        None
    }
}

#[derive(ValueEnum, Debug, Copy, Clone, Hash, Default, Eq, PartialEq, Linearize)]
pub enum LogLevel {
    Trace,
    Debug,
    #[default]
    Info,
    Warn,
    Error,
    Off,
}

impl Into<LevelFilter> for LogLevel {
    fn into(self) -> LevelFilter {
        match self {
            LogLevel::Trace => LevelFilter::Trace,
            LogLevel::Debug => LevelFilter::Debug,
            LogLevel::Info => LevelFilter::Info,
            LogLevel::Warn => LevelFilter::Warn,
            LogLevel::Error => LevelFilter::Error,
            LogLevel::Off => LevelFilter::Off,
        }
    }
}

impl From<LevelFilter> for LogLevel {
    fn from(value: LevelFilter) -> Self {
        match value {
            LevelFilter::Trace => LogLevel::Trace,
            LevelFilter::Debug => LogLevel::Debug,
            LevelFilter::Info => LogLevel::Info,
            LevelFilter::Warn => LogLevel::Warn,
            LevelFilter::Error => LogLevel::Error,
            LevelFilter::Off => LogLevel::Off,
        }
    }
}

impl StaticText for LogLevel {
    fn text(&self) -> &'static str {
        match self {
            LogLevel::Off => "Off",
            LogLevel::Error => "Error",
            LogLevel::Warn => "Warn",
            LogLevel::Info => "Info",
            LogLevel::Debug => "Debug",
            LogLevel::Trace => "Trace",
        }
    }
}

impl From<ConfigLogLevel> for LogLevel {
    fn from(value: ConfigLogLevel) -> Self {
        match value {
            ConfigLogLevel::Trace => LogLevel::Trace,
            ConfigLogLevel::Debug => LogLevel::Debug,
            ConfigLogLevel::Info => LogLevel::Info,
            ConfigLogLevel::Warn => LogLevel::Warn,
            ConfigLogLevel::Error => LogLevel::Error,
        }
    }
}
