#[cfg(all(unix, not(target_os = "macos")))]
use crate::consts::plugins_clap::{
    CLIENT_MESSAGE, DESTROY_NOTIFY, STRUCTURE_NOTIFY_MASK, UNMAP_NOTIFY,
};
#[cfg(all(unix, not(target_os = "macos")))]
use crate::plugins::x11::{
    XBlackPixel, XCloseDisplay, XCreateSimpleWindow, XDefaultScreen, XDestroyWindow, XEvent,
    XFlush, XInternAtom, XMapRaised, XNextEvent, XOpenDisplay, XPending, XResizeWindow,
    XRootWindow, XSelectInput, XSetWMProtocols, XStoreName, XSync, XWhitePixel,
    set_dialog_window_type,
};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::sync::Mutex;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc;
use std::thread;
use std::time::Duration;

type SharedClapProcessor =
    Arc<maolan_engine::mutex::UnsafeMutex<maolan_engine::clap::ClapProcessor>>;

#[cfg(all(unix, not(target_os = "macos")))]
use std::ffi::{CString, c_int, c_uint, c_ulong, c_void};

#[derive(Debug, Clone)]
pub(crate) struct ClapUiClosedState {
    pub track_name: String,
    pub clip_idx: Option<usize>,
    pub instance_id: usize,
    pub plugin_path: String,
    pub state: maolan_engine::clap::ClapPluginState,
}

#[derive(Debug, Clone)]
pub(crate) struct ClapUiParamUpdate {
    pub track_name: String,
    pub clip_idx: Option<usize>,
    pub instance_id: usize,
    pub param_id: u32,
    pub value: f64,
}

#[derive(Debug, Clone)]
pub(crate) struct ClapUiStateUpdate {
    pub track_name: String,
    pub clip_idx: Option<usize>,
    pub instance_id: usize,
    pub plugin_path: String,
    pub state: maolan_engine::clap::ClapPluginState,
}

pub struct GuiClapUiHost {
    closed_tx: mpsc::Sender<ClapUiClosedState>,
    closed_rx: mpsc::Receiver<ClapUiClosedState>,
    param_tx: mpsc::Sender<ClapUiParamUpdate>,
    param_rx: mpsc::Receiver<ClapUiParamUpdate>,
    state_tx: mpsc::Sender<ClapUiStateUpdate>,
    state_rx: mpsc::Receiver<ClapUiStateUpdate>,
    active_ui_sessions: Arc<AtomicUsize>,
    pending_closed_states: Arc<AtomicUsize>,
    pending_param_updates: Arc<AtomicUsize>,
    pending_state_updates: Arc<AtomicUsize>,
    active_session_keys: Arc<Mutex<HashSet<ClapUiSessionKey>>>,
    active_processors: Arc<Mutex<HashMap<ClapUiSessionKey, SharedClapProcessor>>>,
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct ClapUiSessionKey {
    track_name: String,
    clip_idx: Option<usize>,
    instance_id: usize,
}

impl GuiClapUiHost {
    pub fn new() -> Self {
        let (closed_tx, closed_rx) = mpsc::channel();
        let (param_tx, param_rx) = mpsc::channel();
        let (state_tx, state_rx) = mpsc::channel();
        let active_ui_sessions = Arc::new(AtomicUsize::new(0));
        let pending_closed_states = Arc::new(AtomicUsize::new(0));
        let pending_param_updates = Arc::new(AtomicUsize::new(0));
        let pending_state_updates = Arc::new(AtomicUsize::new(0));
        let active_session_keys = Arc::new(Mutex::new(HashSet::new()));
        let active_processors = Arc::new(Mutex::new(HashMap::new()));
        Self {
            closed_tx,
            closed_rx,
            param_tx,
            param_rx,
            state_tx,
            state_rx,
            active_ui_sessions,
            pending_closed_states,
            pending_param_updates,
            pending_state_updates,
            active_session_keys,
            active_processors,
        }
    }

    pub fn drain_closed_states(&mut self) -> Vec<ClapUiClosedState> {
        let mut states = Vec::new();
        while let Ok(state) = self.closed_rx.try_recv() {
            self.pending_closed_states.fetch_sub(1, Ordering::Relaxed);
            states.push(state);
        }
        states
    }

    pub fn pop_param_update(&mut self) -> Option<ClapUiParamUpdate> {
        if let Ok(update) = self.param_rx.try_recv() {
            self.pending_param_updates.fetch_sub(1, Ordering::Relaxed);
            Some(update)
        } else {
            None
        }
    }

    pub fn pop_state_update(&mut self) -> Option<ClapUiStateUpdate> {
        if let Ok(update) = self.state_rx.try_recv() {
            self.pending_state_updates.fetch_sub(1, Ordering::Relaxed);
            Some(update)
        } else {
            None
        }
    }

    pub fn has_pending_ui_work(&self) -> bool {
        self.active_ui_sessions.load(Ordering::Acquire) != 0
            || self.pending_closed_states.load(Ordering::Acquire) != 0
            || self.pending_param_updates.load(Ordering::Acquire) != 0
            || self.pending_state_updates.load(Ordering::Acquire) != 0
    }

    pub fn open_editor(
        &mut self,
        track_name: &str,
        clip_idx: Option<usize>,
        instance_id: usize,
        plugin_spec: &str,
        processor: SharedClapProcessor,
    ) -> Result<(), String> {
        let session_key = ClapUiSessionKey {
            track_name: track_name.to_string(),
            clip_idx,
            instance_id,
        };
        if let Ok(mut active) = self.active_session_keys.lock()
            && !active.insert(session_key.clone())
        {
            return Ok(());
        }
        let track_name = track_name.to_string();
        let plugin_path = plugin_spec.to_string();
        let closed_tx = self.closed_tx.clone();
        let param_tx = self.param_tx.clone();
        let state_tx = self.state_tx.clone();
        let active_ui_sessions = self.active_ui_sessions.clone();
        let active_ui_sessions_spawn = active_ui_sessions.clone();
        let active_session_keys = self.active_session_keys.clone();
        let active_processors = self.active_processors.clone();
        let session_key_for_thread = session_key.clone();
        let pending_closed_states = self.pending_closed_states.clone();
        let pending_param_updates = self.pending_param_updates.clone();
        let pending_state_updates = self.pending_state_updates.clone();
        active_ui_sessions.fetch_add(1, Ordering::AcqRel);
        if let Ok(mut procs) = active_processors.lock() {
            procs.insert(session_key.clone(), processor.clone());
        }
        thread::Builder::new()
            .name("clap-ui".to_string())
            .spawn(move || {
                match open_editor_blocking(
                    &plugin_path,
                    processor,
                    |param_id, value| {
                        let update = ClapUiParamUpdate {
                            track_name: track_name.clone(),
                            clip_idx,
                            instance_id,
                            param_id,
                            value,
                        };
                        if param_tx.send(update).is_ok() {
                            pending_param_updates.fetch_add(1, Ordering::AcqRel);
                        }
                    },
                    |state| {
                        let update = ClapUiStateUpdate {
                            track_name: track_name.clone(),
                            clip_idx,
                            instance_id,
                            plugin_path: plugin_path.clone(),
                            state,
                        };
                        if state_tx.send(update).is_ok() {
                            pending_state_updates.fetch_add(1, Ordering::AcqRel);
                        }
                    },
                ) {
                    Ok(Some(saved_state)) => {
                        let closed = ClapUiClosedState {
                            track_name,
                            clip_idx,
                            instance_id,
                            plugin_path,
                            state: saved_state,
                        };
                        if closed_tx.send(closed).is_ok() {
                            pending_closed_states.fetch_add(1, Ordering::AcqRel);
                        }
                    }
                    Ok(None) => {}
                    Err(err) => {
                        tracing::error!("Failed to open CLAP editor: {err}");
                    }
                }
                if let Ok(mut active) = active_session_keys.lock() {
                    active.remove(&session_key_for_thread);
                }
                if let Ok(mut procs) = active_processors.lock() {
                    procs.remove(&session_key_for_thread);
                }
                active_ui_sessions_spawn.fetch_sub(1, Ordering::AcqRel);
            })
            .map_err(|e| {
                if let Ok(mut active) = self.active_session_keys.lock() {
                    active.remove(&session_key);
                }
                if let Ok(mut procs) = self.active_processors.lock() {
                    procs.remove(&session_key);
                }
                active_ui_sessions.fetch_sub(1, Ordering::AcqRel);
                format!("Failed to spawn CLAP UI thread: {e}")
            })?;
        Ok(())
    }
}

fn open_editor_blocking(
    _plugin_spec: &str,
    processor: SharedClapProcessor,
    mut on_param: impl FnMut(u32, f64),
    mut on_state: impl FnMut(maolan_engine::clap::ClapPluginState),
) -> Result<Option<maolan_engine::clap::ClapPluginState>, String> {
    let gui_info = processor.gui_info()?;
    processor.ui_begin_session();
    let result = if cfg!(all(unix, not(target_os = "macos"))) && gui_info.api == "x11" {
        #[cfg(all(unix, not(target_os = "macos")))]
        {
            if gui_info.supports_embedded {
                run_x11_embedded(processor.clone(), &mut on_param, &mut on_state)
            } else {
                run_x11_floating(processor.clone(), &mut on_param, &mut on_state)
            }
        }
        #[cfg(not(all(unix, not(target_os = "macos"))))]
        {
            Err("X11 CLAP UI is unavailable on this platform".to_string())
        }
    } else {
        processor.gui_create(&gui_info.api, !gui_info.supports_embedded)?;
        let result = (|| {
            processor.gui_show()?;
            run_ui_loop(&processor, || false, &mut on_param, &mut on_state);
            Ok(())
        })();
        processor.gui_hide();
        processor.gui_destroy();
        result
    };
    processor.ui_end_session();
    result?;
    Ok(processor.snapshot_state().ok())
}

fn run_ui_loop<F>(
    processor: &SharedClapProcessor,
    mut pump_platform_close: F,
    on_param: &mut impl FnMut(u32, f64),
    on_state: &mut impl FnMut(maolan_engine::clap::ClapPluginState),
) where
    F: FnMut() -> bool,
{
    loop {
        if processor.ui_should_close() || pump_platform_close() {
            break;
        }
        processor.gui_on_main_thread();
        for timer_id in processor.ui_take_due_timers() {
            processor.gui_on_timer(timer_id);
        }
        emit_param_updates(processor, on_param);
        emit_state_updates(processor, on_state);
        thread::sleep(Duration::from_millis(16));
    }
}

fn emit_param_updates(processor: &SharedClapProcessor, on_param: &mut impl FnMut(u32, f64)) {
    for update in processor.ui_take_param_updates() {
        if update.value.is_finite() {
            on_param(update.param_id, update.value);
        }
    }
}

fn emit_state_updates(
    processor: &SharedClapProcessor,
    on_state: &mut impl FnMut(maolan_engine::clap::ClapPluginState),
) {
    if let Some(state) = processor.ui_take_state_update() {
        on_state(state);
    }
}

#[cfg(all(unix, not(target_os = "macos")))]
fn run_x11_embedded(
    processor: SharedClapProcessor,
    on_param: &mut impl FnMut(u32, f64),
    on_state: &mut impl FnMut(maolan_engine::clap::ClapPluginState),
) -> Result<(), String> {
    let display = unsafe { XOpenDisplay(std::ptr::null()) };
    if display.is_null() {
        return Err("Failed to open X display for CLAP UI".to_string());
    }

    let screen = unsafe { XDefaultScreen(display) };
    let root = unsafe { XRootWindow(display, screen) };
    let black = unsafe { XBlackPixel(display, screen) };
    let white = unsafe { XWhitePixel(display, screen) };

    let mut width = 900u32;
    let mut height = 600u32;

    let window =
        unsafe { XCreateSimpleWindow(display, root, 120, 120, width, height, 1, black, white) };
    if window == 0 {
        unsafe { XCloseDisplay(display) };
        return Err("Failed to create X11 window for CLAP UI".to_string());
    }

    let embed_window =
        unsafe { XCreateSimpleWindow(display, window, 0, 0, width, height, 0, black, white) };
    if embed_window == 0 {
        unsafe {
            XDestroyWindow(display, window);
            XCloseDisplay(display);
        }
        return Err("Failed to create X11 embed window for CLAP UI".to_string());
    }

    let title = CString::new(processor.name()).map_err(|e| e.to_string())?;
    unsafe {
        XStoreName(display, window, title.as_ptr());
        XSelectInput(display, window, STRUCTURE_NOTIFY_MASK);
        XSelectInput(display, embed_window, STRUCTURE_NOTIFY_MASK);
    }

    let wm_delete_atom_name = CString::new("WM_DELETE_WINDOW").map_err(|e| e.to_string())?;
    let wm_delete = unsafe { XInternAtom(display, wm_delete_atom_name.as_ptr(), 0) };
    let wm_protocols_atom_name = CString::new("WM_PROTOCOLS").map_err(|e| e.to_string())?;
    let wm_protocols = unsafe { XInternAtom(display, wm_protocols_atom_name.as_ptr(), 0) };
    if wm_delete != 0 {
        let mut protocols = [wm_delete];
        unsafe {
            XSetWMProtocols(display, window, protocols.as_mut_ptr(), 1);
        }
    }
    set_dialog_window_type(display, window);

    let result = (|| {
        processor.gui_create("x11", false)?;
        processor.gui_create("x11", false)?;
        if let Ok((new_width, new_height)) = processor.gui_get_size() {
            width = new_width.max(320);
            height = new_height.max(240);
            unsafe {
                XResizeWindow(display, window, width, height);
                XResizeWindow(display, embed_window, width, height);
            }
        }
        processor.gui_set_parent_x11(embed_window as usize)?;

        processor.gui_show()?;
        unsafe {
            XMapRaised(display, embed_window);
            XMapRaised(display, window);
            XFlush(display);
        }
        run_ui_loop(
            &processor,
            || pump_platform_events_x11(display, window, wm_delete, wm_protocols),
            on_param,
            on_state,
        );
        Ok(())
    })();

    processor.gui_hide();
    processor.gui_destroy();
    unsafe {
        XDestroyWindow(display, embed_window);
        XDestroyWindow(display, window);
        XSync(display, 0);
        XCloseDisplay(display);
    }
    result
}

#[cfg(all(unix, not(target_os = "macos")))]
fn run_x11_floating(
    processor: SharedClapProcessor,
    on_param: &mut impl FnMut(u32, f64),
    on_state: &mut impl FnMut(maolan_engine::clap::ClapPluginState),
) -> Result<(), String> {
    let display = unsafe { XOpenDisplay(std::ptr::null()) };
    if display.is_null() {
        return Err("Failed to open X display for CLAP UI".to_string());
    }

    let screen = unsafe { XDefaultScreen(display) };
    let root = unsafe { XRootWindow(display, screen) };
    let windows_before = get_top_level_windows(display, root);

    let result = (|| {
        processor.gui_create("x11", true)?;
        processor.gui_show()?;
        thread::sleep(Duration::from_millis(200));
        unsafe { XSync(display, 0) };

        let windows_after = get_top_level_windows(display, root);
        let new_top_levels: Vec<c_ulong> = windows_after
            .into_iter()
            .filter(|window| !windows_before.contains(window))
            .collect();
        let mut tracked_windows = new_top_levels;

        let wm_delete_atom_name = CString::new("WM_DELETE_WINDOW").map_err(|e| e.to_string())?;
        let wm_delete = unsafe { XInternAtom(display, wm_delete_atom_name.as_ptr(), 0) };
        let wm_protocols_atom_name = CString::new("WM_PROTOCOLS").map_err(|e| e.to_string())?;
        let wm_protocols = unsafe { XInternAtom(display, wm_protocols_atom_name.as_ptr(), 0) };

        if !tracked_windows.is_empty() {
            for window in &tracked_windows {
                unsafe {
                    XSelectInput(display, *window, STRUCTURE_NOTIFY_MASK);
                }
                set_dialog_window_type(display, *window);
            }
        }

        run_ui_loop(
            &processor,
            || {
                if tracked_windows.is_empty() {
                    let windows_after = get_top_level_windows(display, root);
                    let new_top_levels: Vec<c_ulong> = windows_after
                        .into_iter()
                        .filter(|window| !windows_before.contains(window))
                        .collect();
                    if !new_top_levels.is_empty() {
                        for window in &new_top_levels {
                            unsafe {
                                XSelectInput(display, *window, STRUCTURE_NOTIFY_MASK);
                            }
                            set_dialog_window_type(display, *window);
                        }
                        tracked_windows.extend(new_top_levels);
                    }
                    false
                } else {
                    pump_platform_events_x11_any(display, &tracked_windows, wm_delete, wm_protocols)
                        || tracked_windows_gone(display, root, &tracked_windows)
                }
            },
            on_param,
            on_state,
        );
        Ok(())
    })();

    processor.gui_hide();
    processor.gui_destroy();
    unsafe {
        XSync(display, 0);
        XCloseDisplay(display);
    }
    result
}

#[cfg(all(unix, not(target_os = "macos")))]
fn get_top_level_windows(display: *mut c_void, root: c_ulong) -> Vec<c_ulong> {
    let mut windows = Vec::new();
    #[link(name = "X11")]
    unsafe extern "C" {
        fn XQueryTree(
            display: *mut c_void,
            w: c_ulong,
            root_return: *mut c_ulong,
            parent_return: *mut c_ulong,
            children_return: *mut *mut c_ulong,
            nchildren_return: *mut c_uint,
        ) -> c_int;
        fn XFree(data: *mut c_void) -> c_int;
    }

    unsafe {
        let mut root_return: c_ulong = 0;
        let mut parent_return: c_ulong = 0;
        let mut children: *mut c_ulong = std::ptr::null_mut();
        let mut nchildren: c_uint = 0;

        if XQueryTree(
            display,
            root,
            &mut root_return,
            &mut parent_return,
            &mut children,
            &mut nchildren,
        ) != 0
            && !children.is_null()
        {
            windows.reserve(nchildren as usize);
            for i in 0..nchildren {
                windows.push(*children.add(i as usize));
            }
            XFree(children.cast::<c_void>());
        }
    }
    windows
}

#[cfg(all(unix, not(target_os = "macos")))]
fn tracked_windows_gone(display: *mut c_void, root: c_ulong, tracked_windows: &[c_ulong]) -> bool {
    if display.is_null() || root == 0 || tracked_windows.is_empty() {
        return false;
    }
    let visible = get_top_level_windows(display, root);
    tracked_windows
        .iter()
        .all(|window| !visible.contains(window))
}

#[cfg(all(unix, not(target_os = "macos")))]
fn pump_platform_events_x11(
    display: *mut c_void,
    window: c_ulong,
    wm_delete: c_ulong,
    wm_protocols: c_ulong,
) -> bool {
    if display.is_null() || window == 0 {
        return false;
    }
    unsafe {
        let pending = XPending(display);
        if pending > 0 {
            let mut event: XEvent = std::mem::zeroed();
            for _ in 0..pending {
                XNextEvent(display, &mut event);
                let event_type = event.type_;
                if event_type == DESTROY_NOTIFY {
                    return true;
                }
                if event_type == UNMAP_NOTIFY && event.xunmap.window == window {
                    return true;
                }
                if event_type == CLIENT_MESSAGE {
                    let msg = event.xclient;
                    if wm_delete != 0
                        && wm_protocols != 0
                        && msg.window == window
                        && msg.message_type == wm_protocols
                        && msg.format == 32
                        && (msg.data.longs[0] as c_ulong) == wm_delete
                    {
                        return true;
                    }
                }
            }
        }
    }
    false
}

#[cfg(all(unix, not(target_os = "macos")))]
fn pump_platform_events_x11_any(
    display: *mut c_void,
    windows: &[c_ulong],
    wm_delete: c_ulong,
    wm_protocols: c_ulong,
) -> bool {
    if display.is_null() || windows.is_empty() {
        return false;
    }
    unsafe {
        let pending = XPending(display);
        if pending <= 0 {
            return false;
        }
        let mut event: XEvent = std::mem::zeroed();
        for _ in 0..pending {
            XNextEvent(display, &mut event);
            let event_type = event.type_;
            if event_type == DESTROY_NOTIFY && windows.contains(&event.xdestroywindow.window) {
                return true;
            }
            if event_type == UNMAP_NOTIFY && windows.contains(&event.xunmap.window) {
                return true;
            }
            if event_type == CLIENT_MESSAGE {
                let msg = event.xclient;
                if wm_delete != 0
                    && wm_protocols != 0
                    && windows.contains(&msg.window)
                    && msg.message_type == wm_protocols
                    && msg.format == 32
                    && (msg.data.longs[0] as c_ulong) == wm_delete
                {
                    return true;
                }
            }
        }
    }
    false
}
