// xfwl4 -- Wayland compositor for the Xfce Desktop Environment
//
// Copyright (C) 2026 Brian Tarricone <brian@tarricone.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

use smithay::{
    output::{Mode, Output, WeakOutput},
    reexports::{
        wayland_protocols_wlr::output_management::v1::server::{
            zwlr_output_configuration_head_v1::ZwlrOutputConfigurationHeadV1,
            zwlr_output_configuration_v1::ZwlrOutputConfigurationV1,
            zwlr_output_head_v1::{
                AdaptiveSyncState, EVT_ADAPTIVE_SYNC_SINCE, EVT_MAKE_SINCE, EVT_MODEL_SINCE, EVT_SERIAL_NUMBER_SINCE, ZwlrOutputHeadV1,
            },
            zwlr_output_manager_v1::ZwlrOutputManagerV1,
            zwlr_output_mode_v1::ZwlrOutputModeV1,
        },
        wayland_server::{
            Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, WEnum,
            backend::{ClientId, GlobalId},
        },
    },
    utils::{Logical, Point, SERIAL_COUNTER, Serial, Transform},
    wayland::{Dispatch2, GlobalDispatch2},
};

use crate::protocols::{ClientFilter, GlobalData};

pub struct WlrOutputManagementGlobalData {
    filter: ClientFilter,
}

pub struct WlrOutputManagementState {
    dh: DisplayHandle,
    _global: GlobalId,
    cur_config_serial: Serial,
    manager_instances: Vec<ZwlrOutputManagerV1>,
    heads: Vec<WlrHead>,
    configurations: Vec<WlrOutputConfiguration>,
}

pub trait WlrOutputManagementHandler: 'static {
    fn wlr_output_management_state(&mut self) -> &mut WlrOutputManagementState;

    fn on_test_configuration(&mut self, configuration: WlrOutputConfiguration);
    fn on_apply_configuration(&mut self, configuration: WlrOutputConfiguration);
}

struct WlrHead {
    instances: Vec<ZwlrOutputHeadV1>,
    output: Output,
    modes: Vec<WlrMode>,

    last_is_enabled: bool,
    last_current_mode: Option<Mode>,
    last_preferred_mode: Option<Mode>,
    last_position: Point<i32, Logical>,
    last_transform: Transform,
    last_scale: f64,
    last_adaptive_sync: AdaptiveSyncState,
}

struct WlrMode {
    instances: Vec<ZwlrOutputModeV1>,
    mode: Mode,
}

#[derive(Debug)]
pub enum OutputConfigurationUpdate {
    Enable(WlrOutputConfigurationHead),
    Disable(WeakOutput),
}

#[derive(Debug)]
pub struct WlrOutputConfiguration {
    instance: ZwlrOutputConfigurationV1,
    updates: Vec<OutputConfigurationUpdate>,
    used: bool,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ConfiguredMode {
    Advertised(Mode),
    Custom { width: i32, height: i32, refresh: i32 },
}

#[derive(Debug)]
pub struct WlrOutputConfigurationHead {
    instance: ZwlrOutputConfigurationHeadV1,
    output: WeakOutput,

    mode: Option<ConfiguredMode>,
    position: Option<Point<i32, Logical>>,
    transform: Option<Transform>,
    scale: Option<f64>,
    adaptive_sync: Option<AdaptiveSyncState>,
}

struct OutputDiff {
    enabled: Option<bool>,
    modes_added: Vec<Mode>,
    modes_removed: Vec<Mode>,
    current_mode: Option<Option<Mode>>,
    preferred_mode: Option<Option<Mode>>,
    position: Option<Point<i32, Logical>>,
    transform: Option<Transform>,
    scale: Option<f64>,
    adaptive_sync: Option<AdaptiveSyncState>,
}

impl WlrOutputManagementState {
    pub fn new<H, F>(dh: &DisplayHandle, filter: F) -> Self
    where
        H: WlrOutputManagementHandler + GlobalDispatch<ZwlrOutputManagerV1, WlrOutputManagementGlobalData>,
        F: for<'c> Fn(&'c Client) -> bool + Send + Sync + 'static,
    {
        let global = dh.create_global::<H, ZwlrOutputManagerV1, _>(4, WlrOutputManagementGlobalData { filter: Box::new(filter) });
        Self {
            dh: dh.clone(),
            _global: global,
            cur_config_serial: SERIAL_COUNTER.next_serial(),
            manager_instances: Vec::new(),
            heads: Vec::new(),
            configurations: Vec::new(),
        }
    }

    pub fn output_created<
        H: WlrOutputManagementHandler + Dispatch<ZwlrOutputHeadV1, GlobalData> + Dispatch<ZwlrOutputModeV1, GlobalData>,
    >(
        &mut self,
        output: &Output,
    ) -> Serial {
        let mut head = WlrHead::new(output);

        for instance in &self.manager_instances {
            if let Some(client) = instance.client()
                && let Err(err) = create_and_send_head::<H>(&self.dh, &client, instance, &mut head)
            {
                tracing::info!("Failed to send new head to client {:?}: {err}", client.id());
            }
        }

        self.heads.push(head);
        self.post_configuration_change();
        self.cur_config_serial
    }

    pub fn output_changed<H: WlrOutputManagementHandler + Dispatch<ZwlrOutputModeV1, GlobalData>>(
        &mut self,
        output: &Output,
        is_enabled: bool,
    ) -> Serial {
        if let Some(head) = self.heads.iter_mut().find(|head| &head.output == output)
            && head.changed::<H>(&self.dh, output, is_enabled)
        {
            self.post_configuration_change();
        }

        self.cur_config_serial
    }

    pub fn output_destroyed(&mut self, output: &Output) -> Serial {
        let old_len = self.heads.len();
        self.heads.retain(|head| &head.output != output);

        if old_len != self.heads.len() {
            self.post_configuration_change();
        }

        self.cur_config_serial
    }

    fn post_configuration_change(&mut self) {
        self.cur_config_serial = SERIAL_COUNTER.next_serial();
        for instance in &self.manager_instances {
            instance.done(self.cur_config_serial.into());
        }
        self.configurations.clear();
    }
}

impl Drop for WlrOutputManagementState {
    fn drop(&mut self) {
        self.configurations.clear();
        self.heads.clear();
        for instance in &self.manager_instances {
            instance.finished();
        }
    }
}

impl WlrHead {
    pub(super) fn new(output: &Output) -> Self {
        Self {
            instances: Vec::new(),
            output: output.clone(),
            modes: output
                .modes()
                .into_iter()
                .map(|mode| WlrMode {
                    instances: Vec::new(),
                    mode,
                })
                .collect(),
            last_is_enabled: false,
            last_current_mode: output.current_mode(),
            last_preferred_mode: output.preferred_mode().or_else(|| output.modes().first().cloned()),
            last_position: output.current_location(),
            last_transform: output.current_transform(),
            last_scale: output.current_scale().fractional_scale(),
            last_adaptive_sync: AdaptiveSyncState::Disabled,
        }
    }

    pub(super) fn send_initial<H: WlrOutputManagementHandler + Dispatch<ZwlrOutputModeV1, GlobalData>>(
        &mut self,
        dh: &DisplayHandle,
        client: &Client,
        instance: &ZwlrOutputHeadV1,
    ) -> anyhow::Result<()> {
        let phys_props = self.output.physical_properties();

        instance.name(self.output.name());
        instance.description(self.output.description());
        if instance.version() >= EVT_MAKE_SINCE {
            instance.make(phys_props.make);
        }
        if instance.version() >= EVT_MODEL_SINCE {
            instance.model(phys_props.model);
        }
        if instance.version() >= EVT_SERIAL_NUMBER_SINCE {
            instance.serial_number(phys_props.serial_number);
        }
        instance.physical_size(phys_props.size.w, phys_props.size.h);

        let last_current_mode = self.last_current_mode;
        let last_preferred_mode = self.last_preferred_mode;
        for mode in self.modes.iter_mut() {
            let is_current = last_current_mode.as_ref() == Some(&mode.mode);
            let is_preferred = last_preferred_mode.as_ref() == Some(&mode.mode);
            create_and_send_mode::<H>(dh, client, instance, mode, is_current, is_preferred)?;
        }

        if self.last_is_enabled && self.last_current_mode.is_some() {
            instance.enabled(1);
            instance.position(self.last_position.x, self.last_position.y);
            instance.transform(self.last_transform.into());
            instance.scale(self.last_scale);
        } else {
            instance.enabled(0);
        }

        if instance.version() >= EVT_ADAPTIVE_SYNC_SINCE {
            instance.adaptive_sync(self.last_adaptive_sync);
        }

        self.instances.push(instance.clone());

        Ok(())
    }

    pub(super) fn changed<H: WlrOutputManagementHandler + Dispatch<ZwlrOutputModeV1, GlobalData>>(
        &mut self,
        dh: &DisplayHandle,
        output: &Output,
        is_enabled: bool,
    ) -> bool {
        if let Some(diff) = OutputDiff::new(output, is_enabled, self) {
            for mode in diff.modes_added {
                let is_current = output.current_mode().as_ref() == Some(&mode);
                let is_preferred = output.preferred_mode().as_ref() == Some(&mode);

                let mut mode = WlrMode {
                    instances: Vec::new(),
                    mode,
                };

                for instance in self.instances.iter() {
                    if let Some(client) = instance.client()
                        && let Err(err) = create_and_send_mode::<H>(dh, &client, instance, &mut mode, is_current, is_preferred)
                    {
                        tracing::info!("Failed to send new mode to client {:?}: {err}", client.id());
                    }
                }

                self.modes.push(mode);
            }

            for mode in diff.modes_removed {
                self.modes.retain(|wlr_mode| wlr_mode.mode != mode);
            }

            let current_mode = diff.current_mode.flatten().or(self.last_current_mode);
            let is_currently_enabled = diff.enabled.unwrap_or(self.last_is_enabled) && current_mode.is_some();

            if let Some(position) = diff.position {
                if self.last_is_enabled {
                    for instance in self.instances.iter() {
                        instance.position(position.x, position.y);
                    }
                }

                self.last_position = position;
            }

            if let Some(transform) = diff.transform {
                if self.last_is_enabled {
                    let prot_transform = transform.into();
                    for instance in self.instances.iter() {
                        instance.transform(prot_transform);
                    }
                }

                self.last_transform = transform;
            }

            if let Some(scale) = diff.scale {
                if self.last_is_enabled {
                    for instance in self.instances.iter() {
                        instance.scale(scale);
                    }
                }

                self.last_scale = scale;
            }

            if is_currently_enabled && let Some(current_mode) = current_mode {
                if let Some(wlr_mode) = self.modes.iter().find(|wlr_mode| wlr_mode.mode == current_mode) {
                    let newly_enabled = diff.enabled.unwrap_or(false);
                    let mode_changed = diff.current_mode.is_some();

                    if newly_enabled || mode_changed {
                        for instance in self.instances.iter() {
                            if let Some(client) = instance.client() {
                                for mode_instance in &wlr_mode.instances {
                                    if mode_instance.client().as_ref() == Some(&client) {
                                        if newly_enabled {
                                            instance.enabled(1);
                                        }

                                        instance.current_mode(mode_instance);

                                        if newly_enabled {
                                            instance.position(self.last_position.x, self.last_position.y);
                                            instance.transform(self.last_transform.into());
                                            instance.scale(self.last_scale);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                self.last_is_enabled = true;
                self.last_current_mode = Some(current_mode);
            } else if !diff.enabled.unwrap_or(true) {
                for instance in self.instances.iter() {
                    instance.enabled(0);
                }

                self.last_is_enabled = false;
            }

            if let Some(preferred_mode) = diff.preferred_mode {
                if let Some(preferred_mode) = preferred_mode
                    && let Some(wlr_mode) = self.modes.iter().find(|wlr_mode| wlr_mode.mode == preferred_mode)
                {
                    for instance in self.instances.iter() {
                        if let Some(client) = instance.client() {
                            for mode_instance in &wlr_mode.instances {
                                if mode_instance.client().as_ref() == Some(&client) {
                                    mode_instance.preferred();
                                }
                            }
                        }
                    }
                }

                self.last_preferred_mode = preferred_mode;
            }

            if let Some(adaptive_sync) = diff.adaptive_sync {
                for instance in self.instances.iter() {
                    instance.adaptive_sync(adaptive_sync);
                }

                self.last_adaptive_sync = adaptive_sync;
            }

            true
        } else {
            false
        }
    }
}

impl Drop for WlrHead {
    fn drop(&mut self) {
        for instance in self.instances.iter() {
            instance.finished();
        }
    }
}

impl Drop for WlrMode {
    fn drop(&mut self) {
        for instance in &self.instances {
            instance.finished();
        }
    }
}

impl OutputConfigurationUpdate {
    fn enable_mut(&mut self) -> Option<&mut WlrOutputConfigurationHead> {
        match self {
            Self::Enable(head) => Some(head),
            _ => None,
        }
    }
}

impl WlrOutputConfiguration {
    pub fn updates(&self) -> &[OutputConfigurationUpdate] {
        &self.updates
    }

    pub fn update_for_output(&self, output: &Output) -> Option<&OutputConfigurationUpdate> {
        self.updates.iter().find(|update| match update {
            OutputConfigurationUpdate::Enable(head) => &head.output == output,
            OutputConfigurationUpdate::Disable(weak_output) => weak_output == output,
        })
    }

    pub fn send_succeeded(&self) {
        self.instance.succeeded();
    }

    pub fn send_failed(&self) {
        self.instance.failed();
    }

    pub fn send_cancelled(&self) {
        self.instance.cancelled();
    }
}

impl Drop for WlrOutputConfiguration {
    fn drop(&mut self) {
        if !self.used {
            self.instance.cancelled();
        }
    }
}

impl WlrOutputConfigurationHead {
    pub fn output(&self) -> Option<Output> {
        self.output.upgrade()
    }

    pub fn mode(&self) -> Option<ConfiguredMode> {
        self.mode
    }

    pub fn position(&self) -> Option<Point<i32, Logical>> {
        self.position
    }

    pub fn transform(&self) -> Option<Transform> {
        self.transform
    }

    pub fn scale(&self) -> Option<f64> {
        self.scale
    }

    pub fn adaptive_sync(&self) -> Option<AdaptiveSyncState> {
        self.adaptive_sync
    }
}

impl OutputDiff {
    fn new(output: &Output, is_enabled: bool, head: &WlrHead) -> Option<Self> {
        let mut modes_added = output.modes();
        modes_added.retain(|new_mode| !head.modes.iter().any(|old_mode| old_mode.mode == *new_mode));

        let mut modes_removed = head.modes.iter().map(|mode| mode.mode).collect::<Vec<_>>();
        modes_removed.retain(|old_mode| !output.modes().iter().any(|new_mode| old_mode == new_mode));

        let enabled = is_enabled && output.current_mode().is_some();

        let preferred_mode = output.preferred_mode().or_else(|| output.modes().first().cloned());

        let diff = OutputDiff {
            enabled: (head.last_is_enabled != enabled).then_some(enabled),
            modes_added,
            modes_removed,
            current_mode: (head.last_current_mode != output.current_mode()).then(|| output.current_mode()),
            preferred_mode: (head.last_preferred_mode != preferred_mode).then_some(preferred_mode),
            position: (head.last_position != output.current_location()).then(|| output.current_location()),
            transform: (head.last_transform != output.current_transform()).then(|| output.current_transform()),
            scale: (head.last_scale != output.current_scale().fractional_scale()).then(|| output.current_scale().fractional_scale()),
            adaptive_sync: (head.last_adaptive_sync != AdaptiveSyncState::Disabled).then_some(AdaptiveSyncState::Disabled),
        };

        if diff.enabled.is_some()
            || !diff.modes_added.is_empty()
            || !diff.modes_removed.is_empty()
            || diff.current_mode.is_some()
            || diff.preferred_mode.is_some()
            || diff.position.is_some()
            || diff.transform.is_some()
            || diff.scale.is_some()
            || diff.adaptive_sync.is_some()
        {
            Some(diff)
        } else {
            None
        }
    }
}

impl<D: WlrOutputManagementHandler> GlobalDispatch2<ZwlrOutputManagerV1, D> for WlrOutputManagementGlobalData
where
    D: Dispatch<ZwlrOutputManagerV1, GlobalData> + Dispatch<ZwlrOutputHeadV1, GlobalData> + Dispatch<ZwlrOutputModeV1, GlobalData>,
{
    fn bind(
        &self,
        state: &mut D,
        handle: &DisplayHandle,
        client: &Client,
        resource: New<ZwlrOutputManagerV1>,
        data_init: &mut DataInit<'_, D>,
    ) {
        let instance = data_init.init(resource, GlobalData);

        let state = state.wlr_output_management_state();

        for head in state.heads.iter_mut() {
            if let Err(err) = create_and_send_head::<D>(handle, client, &instance, head) {
                tracing::info!("Failed to send head to client on new bind: {err}");
            }
        }
        instance.done(state.cur_config_serial.into());

        state.manager_instances.push(instance);
    }

    fn can_view(&self, client: &Client) -> bool {
        (self.filter)(client)
    }
}

impl<D: WlrOutputManagementHandler> Dispatch2<ZwlrOutputManagerV1, D> for GlobalData
where
    D: Dispatch<ZwlrOutputConfigurationV1, GlobalData>,
{
    fn request(
        &self,
        state: &mut D,
        client: &Client,
        resource: &ZwlrOutputManagerV1,
        request: <ZwlrOutputManagerV1 as Resource>::Request,
        _dhandle: &DisplayHandle,
        data_init: &mut DataInit<'_, D>,
    ) {
        use smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_manager_v1::Request;

        match request {
            Request::CreateConfiguration { id, serial } => {
                let instance = data_init.init(id, GlobalData);
                if serial != state.wlr_output_management_state().cur_config_serial.into() {
                    instance.cancelled();
                } else {
                    let config = WlrOutputConfiguration {
                        instance,
                        updates: Vec::new(),
                        used: false,
                    };
                    state.wlr_output_management_state().configurations.push(config);
                }
            }

            Request::Stop => self.destroyed(state, client.id(), resource),

            _ => (),
        }
    }

    fn destroyed(&self, state: &mut D, _client: ClientId, resource: &ZwlrOutputManagerV1) {
        state
            .wlr_output_management_state()
            .manager_instances
            .retain(|instance| instance != resource);
    }
}

impl<D: WlrOutputManagementHandler> Dispatch2<ZwlrOutputHeadV1, D> for GlobalData {
    fn request(
        &self,
        state: &mut D,
        client: &Client,
        resource: &ZwlrOutputHeadV1,
        request: <ZwlrOutputHeadV1 as Resource>::Request,
        _dhandle: &DisplayHandle,
        _data_init: &mut DataInit<'_, D>,
    ) {
        use smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_head_v1::Request;

        if let Request::Release = request {
            self.destroyed(state, client.id(), resource)
        }
    }

    fn destroyed(&self, state: &mut D, _client: ClientId, resource: &ZwlrOutputHeadV1) {
        for head in state.wlr_output_management_state().heads.iter_mut() {
            head.instances.retain(|instance| instance != resource);
        }
    }
}

impl<D: WlrOutputManagementHandler> Dispatch2<ZwlrOutputModeV1, D> for GlobalData {
    fn request(
        &self,
        state: &mut D,
        client: &Client,
        resource: &ZwlrOutputModeV1,
        request: <ZwlrOutputModeV1 as Resource>::Request,
        _dhandle: &DisplayHandle,
        _data_init: &mut DataInit<'_, D>,
    ) {
        use smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_mode_v1::Request;

        if let Request::Release = request {
            self.destroyed(state, client.id(), resource)
        }
    }

    fn destroyed(&self, state: &mut D, _client: ClientId, resource: &ZwlrOutputModeV1) {
        'outer: for head in state.wlr_output_management_state().heads.iter_mut() {
            for mode in head.modes.iter_mut() {
                let len = mode.instances.len();
                mode.instances.retain(|instance| instance != resource);
                if len != mode.instances.len() {
                    break 'outer;
                }
            }
        }
    }
}

impl<D: WlrOutputManagementHandler> Dispatch2<ZwlrOutputConfigurationV1, D> for GlobalData
where
    D: Dispatch<ZwlrOutputConfigurationHeadV1, GlobalData>,
{
    fn request(
        &self,
        state: &mut D,
        client: &Client,
        resource: &ZwlrOutputConfigurationV1,
        request: <ZwlrOutputConfigurationV1 as Resource>::Request,
        _dhandle: &DisplayHandle,
        data_init: &mut DataInit<'_, D>,
    ) {
        use smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_configuration_v1::{Error, Request};

        match request {
            Request::EnableHead { id, head } => {
                let instance = data_init.init(id, GlobalData);
                let state = state.wlr_output_management_state();

                if let Some(config) = state.configurations.iter_mut().find(|config| &config.instance == resource) {
                    if config.used {
                        resource.post_error(Error::AlreadyUsed, "this configuration has already been tested or applied");
                    } else if let Some(output) = state
                        .heads
                        .iter()
                        .find(|wlr_head| wlr_head.instances.contains(&head))
                        .map(|head| &head.output)
                    {
                        if config.update_for_output(output).is_some() {
                            resource.post_error(
                                Error::AlreadyConfiguredHead,
                                "enabled_head() or disable_head() already called for this head on this configuration",
                            );
                        } else {
                            let config_head = WlrOutputConfigurationHead {
                                instance,
                                output: output.downgrade(),
                                mode: None,
                                position: None,
                                transform: None,
                                scale: None,
                                adaptive_sync: None,
                            };
                            config.updates.push(OutputConfigurationUpdate::Enable(config_head));
                        }
                    } else {
                        state.configurations.retain(|config| &config.instance != resource);
                        resource.cancelled();
                    }
                } else {
                    resource.cancelled();
                }
            }

            Request::DisableHead { head } => {
                let state = state.wlr_output_management_state();

                if let Some(config) = state.configurations.iter_mut().find(|config| &config.instance == resource) {
                    if config.used {
                        resource.post_error(Error::AlreadyUsed, "this configuration has already been tested or applied");
                    } else if let Some(output) = state
                        .heads
                        .iter()
                        .find(|wlr_head| wlr_head.instances.contains(&head))
                        .map(|head| &head.output)
                    {
                        if config.update_for_output(output).is_some() {
                            resource.post_error(
                                Error::AlreadyConfiguredHead,
                                "enabled_head() or disable_head() already called for this head on this configuration",
                            );
                        } else {
                            config.updates.push(OutputConfigurationUpdate::Disable(output.downgrade()));
                        }
                    } else {
                        state.configurations.retain(|config| &config.instance != resource);
                        resource.cancelled();
                    }
                } else {
                    resource.cancelled();
                }
            }

            Request::Test => {
                let handler = state;
                let state = handler.wlr_output_management_state();
                if let Some(pos) = state.configurations.iter().position(|config| &config.instance == resource) {
                    let mut config = state.configurations.remove(pos);
                    if config.used {
                        resource.post_error(Error::AlreadyUsed, "this configuration has already been tested or applied");
                    } else {
                        config.used = true;
                        handler.on_test_configuration(config);
                    }
                } else {
                    resource.cancelled();
                }
            }

            Request::Apply => {
                let handler = state;
                let state = handler.wlr_output_management_state();
                if let Some(pos) = state.configurations.iter().position(|config| &config.instance == resource) {
                    let mut config = state.configurations.remove(pos);
                    if config.used {
                        resource.post_error(Error::AlreadyUsed, "this configuration has already been tested or applied");
                    } else {
                        config.used = true;
                        handler.on_apply_configuration(config);
                    }
                } else {
                    resource.cancelled();
                }
            }

            Request::Destroy => {
                if let Some(config) = state
                    .wlr_output_management_state()
                    .configurations
                    .iter_mut()
                    .find(|config| &config.instance == resource)
                {
                    config.used = true;
                }

                self.destroyed(state, client.id(), resource);
            }

            _ => (),
        }
    }

    fn destroyed(&self, state: &mut D, _client: ClientId, resource: &ZwlrOutputConfigurationV1) {
        state
            .wlr_output_management_state()
            .configurations
            .retain(|config| &config.instance != resource);
    }
}

impl<D: WlrOutputManagementHandler> Dispatch2<ZwlrOutputConfigurationHeadV1, D> for GlobalData {
    fn request(
        &self,
        state: &mut D,
        _client: &Client,
        resource: &ZwlrOutputConfigurationHeadV1,
        request: <ZwlrOutputConfigurationHeadV1 as Resource>::Request,
        _dhandle: &DisplayHandle,
        _data_init: &mut DataInit<'_, D>,
    ) {
        use smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_configuration_head_v1::{Error, Request};
        use smithay::reexports::wayland_protocols_wlr::output_management::v1::server::zwlr_output_configuration_v1::Error as ConfigError;

        let state = state.wlr_output_management_state();

        if let Some(config) = state.configurations.iter_mut().find(|config| {
            config
                .updates
                .iter()
                .any(|update| matches!(update, OutputConfigurationUpdate::Enable(head) if &head.instance == resource))
        }) && config.used
        {
            config
                .instance
                .post_error(ConfigError::AlreadyUsed, "this configuration has already been tested or applied");
        } else if let Some(config_head) = state.configurations.iter_mut().find_map(|config| {
            config
                .updates
                .iter_mut()
                .find_map(|update| update.enable_mut().filter(|head| &head.instance == resource))
        }) {
            match request {
                Request::SetMode { mode } => {
                    if config_head.mode.is_some() {
                        resource.post_error(Error::AlreadySet, "mode has already been set");
                    } else if let Some(mode) = state.heads.iter().find_map(|head| {
                        head.modes.iter().find_map(|wlr_mode| {
                            wlr_mode
                                .instances
                                .iter()
                                .find_map(|instance| (*instance == mode).then_some(wlr_mode.mode))
                        })
                    }) {
                        config_head.mode = Some(ConfiguredMode::Advertised(mode));
                    } else {
                        resource.post_error(Error::InvalidMode, "mode does not belong to head");
                    }
                }

                Request::SetCustomMode { width, height, refresh } => {
                    if config_head.mode.is_some() {
                        resource.post_error(Error::AlreadySet, "mode has already been set");
                    } else if width <= 0 || height <= 0 || refresh <= 1000 {
                        resource.post_error(Error::InvalidCustomMode, "custom mode had invalid values");
                    } else {
                        config_head.mode = Some(ConfiguredMode::Custom { width, height, refresh });
                    }
                }

                Request::SetPosition { x, y } => {
                    if config_head.position.is_some() {
                        resource.post_error(Error::AlreadySet, "position has already been set");
                    } else if x < 0 && y < 0 {
                        // Strange that there's no error for this case...
                    } else {
                        config_head.position = Some((x, y).into());
                    }
                }

                Request::SetScale { scale } => {
                    if config_head.scale.is_some() {
                        resource.post_error(Error::AlreadySet, "scale has already been set");
                    } else if scale <= 0. || scale >= 10. {
                        resource.post_error(Error::InvalidScale, "scale must be > 0 and <= 10");
                    } else {
                        config_head.scale = Some(scale);
                    }
                }

                Request::SetTransform { transform } => {
                    if config_head.scale.is_some() {
                        resource.post_error(Error::AlreadySet, "transform has already been set");
                    } else {
                        match transform {
                            WEnum::Unknown(n) => resource.post_error(Error::InvalidTransform, format!("unknown transform value {n}")),
                            WEnum::Value(transform) => config_head.transform = Some(transform.into()),
                        }
                    }
                }

                Request::SetAdaptiveSync { state } => {
                    if config_head.scale.is_some() {
                        resource.post_error(Error::AlreadySet, "adaptive sync has already been set");
                    } else {
                        match state {
                            WEnum::Unknown(n) => {
                                resource.post_error(Error::InvalidAdaptiveSyncState, format!("unknown adaptive sync state value {n}"))
                            }
                            WEnum::Value(state) => config_head.adaptive_sync = Some(state),
                        }
                    }
                }

                _ => (),
            }
        }
    }

    fn destroyed(&self, state: &mut D, _client: ClientId, resource: &ZwlrOutputConfigurationHeadV1) {
        for config in state.wlr_output_management_state().configurations.iter_mut() {
            let len = config.updates.len();
            config
                .updates
                .retain(|update| !matches!(update, OutputConfigurationUpdate::Enable(head) if &head.instance != resource));
            if len != config.updates.len() {
                break;
            }
        }
    }
}

fn create_and_send_head<H: WlrOutputManagementHandler + Dispatch<ZwlrOutputHeadV1, GlobalData> + Dispatch<ZwlrOutputModeV1, GlobalData>>(
    dh: &DisplayHandle,
    client: &Client,
    manager_instance: &ZwlrOutputManagerV1,
    head: &mut WlrHead,
) -> anyhow::Result<()> {
    let instance = client.create_resource::<ZwlrOutputHeadV1, _, H>(dh, manager_instance.version(), GlobalData)?;
    manager_instance.head(&instance);
    head.send_initial::<H>(dh, client, &instance)?;
    head.instances.push(instance);

    Ok(())
}

fn create_and_send_mode<H: WlrOutputManagementHandler + Dispatch<ZwlrOutputModeV1, GlobalData>>(
    dh: &DisplayHandle,
    client: &Client,
    head_instance: &ZwlrOutputHeadV1,
    mode: &mut WlrMode,
    is_current: bool,
    is_preferred: bool,
) -> anyhow::Result<()> {
    let instance = client.create_resource::<ZwlrOutputModeV1, _, H>(dh, head_instance.version(), GlobalData)?;
    head_instance.mode(&instance);

    instance.size(mode.mode.size.w, mode.mode.size.h);
    instance.refresh(mode.mode.refresh);
    if is_preferred {
        instance.preferred();
    }

    if is_current {
        head_instance.current_mode(&instance);
    }

    mode.instances.push(instance.clone());

    Ok(())
}
