use std::{
    any::Any,
    cell::RefCell,
    mem::{self, ManuallyDrop},
    rc::Rc,
    time::{Duration, Instant},
};

use glam::vec2;
use slotmap::Key;
use wgui::{
    components::{checkbox::ComponentCheckbox, slider::ComponentSlider},
    event::{CallbackDataCommon, EventAlterables, EventCallback},
    parser::Fetchable,
    widget::EventResult,
};

#[cfg(feature = "wayvr")]
use crate::{backend::task::TaskType, windowing::OverlaySelector};
use crate::{
    backend::{input::HoverResult, task::TaskContainer},
    gui::panel::{button::BUTTON_EVENTS, GuiPanel, NewGuiPanelParams, OnCustomAttribFunc},
    overlays::edit::{
        lock::InteractLockHandler, pos::PositioningHandler, tab::ButtonPaneTabSwitcher,
    },
    state::AppState,
    subsystem::hid::WheelDelta,
    windowing::{
        backend::{DummyBackend, OverlayBackend, OverlayEventData, RenderResources, ShouldRender},
        window::OverlayWindowConfig,
        OverlayID,
    },
};

mod lock;
mod pos;
pub(crate) mod tab;

pub(super) struct LongPressButtonState {
    pub(super) pressed: Instant,
}
impl Default for LongPressButtonState {
    fn default() -> Self {
        Self {
            pressed: Instant::now(),
        }
    }
}

struct EditModeState {
    tasks: Rc<RefCell<TaskContainer>>,
    id: Rc<RefCell<OverlayID>>,
    delete: LongPressButtonState,
    tabs: ButtonPaneTabSwitcher,
    lock: InteractLockHandler,
    pos: PositioningHandler,
}

type EditModeWrapPanel = GuiPanel<EditModeState>;

#[derive(Default)]
pub struct EditWrapperManager {
    edit_mode: bool,
    panel_pool: Vec<EditModeWrapPanel>,
}

impl EditWrapperManager {
    pub fn wrap_edit_mode(
        &mut self,
        id: OverlayID,
        owc: &mut OverlayWindowConfig,
        app: &mut AppState,
    ) -> anyhow::Result<()> {
        if owc.editing {
            return Ok(());
        }

        log::debug!("EditMode wrap on {}", owc.name);
        let inner = mem::replace(&mut owc.backend, Box::new(DummyBackend {}));
        let mut panel = self.panel_pool.pop();
        if panel.is_none() {
            panel = Some(make_edit_panel(app)?);
        }
        let mut panel = panel.unwrap();
        reset_panel(&mut panel, id, owc)?;

        owc.backend = Box::new(EditModeBackendWrapper {
            inner: ManuallyDrop::new(inner),
            panel: ManuallyDrop::new(panel),
        });
        owc.editing = true;

        Ok(())
    }

    pub fn unwrap_edit_mode(&mut self, owc: &mut OverlayWindowConfig) {
        if !owc.editing {
            return;
        }

        log::debug!("EditMode unwrap on {}", owc.name);
        let wrapper = mem::replace(&mut owc.backend, Box::new(DummyBackend {}));
        let mut wrapper: Box<dyn Any> = wrapper;
        let wrapper = wrapper
            .downcast_mut::<EditModeBackendWrapper>()
            .expect("Wrong type to unwrap");

        let panel = unsafe { ManuallyDrop::take(&mut wrapper.panel) };
        self.panel_pool.push(panel);

        let inner = unsafe { ManuallyDrop::take(&mut wrapper.inner) };
        owc.backend = inner;
        owc.editing = false;

        // wrapper is destroyed with nothing left inside
    }
}

pub struct EditModeBackendWrapper {
    panel: ManuallyDrop<EditModeWrapPanel>,
    inner: ManuallyDrop<Box<dyn OverlayBackend>>,
}

impl OverlayBackend for EditModeBackendWrapper {
    fn init(&mut self, app: &mut crate::state::AppState) -> anyhow::Result<()> {
        self.inner.init(app)?;
        self.panel.init(app)
    }
    fn pause(&mut self, app: &mut crate::state::AppState) -> anyhow::Result<()> {
        self.inner.pause(app)?;
        self.panel.pause(app)
    }
    fn resume(&mut self, app: &mut crate::state::AppState) -> anyhow::Result<()> {
        self.inner.resume(app)?;
        self.panel.resume(app)
    }
    fn should_render(&mut self, app: &mut crate::state::AppState) -> anyhow::Result<ShouldRender> {
        {
            let mut local_tasks = self.panel.state.tasks.borrow_mut();
            app.tasks.transfer_from(&mut local_tasks);
        }

        let i = self.inner.should_render(app)?;

        if !matches!(i, ShouldRender::Unable)
            && let Some(ref frame_meta) = self.inner.frame_meta()
        {
            let (width_px, height_px) = (frame_meta.extent[0], frame_meta.extent[1]);

            let new_size = vec2(width_px as _, height_px as _);
            if self.panel.max_size != new_size {
                log::debug!("EditWrapperGui size {} → {new_size}", self.panel.max_size);
                self.panel.max_size = new_size;

                let gui_scale = width_px.min(height_px) as f32 / 550.0;
                self.panel.gui_scale = gui_scale;
                self.panel.update_layout()?;
            }
        } else {
            return Ok(ShouldRender::Unable);
        }

        let p = self.panel.should_render(app)?;

        #[allow(clippy::match_same_arms)]
        Ok(match (i, p) {
            (ShouldRender::Should, ShouldRender::Should) => ShouldRender::Should,
            (ShouldRender::Should, ShouldRender::Can) => ShouldRender::Should,
            (ShouldRender::Can, ShouldRender::Should) => ShouldRender::Should,
            (ShouldRender::Can, ShouldRender::Can) => ShouldRender::Can,
            _ => ShouldRender::Unable,
        })
    }
    fn render(
        &mut self,
        app: &mut crate::state::AppState,
        rdr: &mut RenderResources,
    ) -> anyhow::Result<()> {
        self.inner.render(app, rdr)?;
        self.panel.render(app, rdr)
    }
    fn frame_meta(&mut self) -> Option<crate::windowing::backend::FrameMeta> {
        self.inner.frame_meta()
    }
    fn on_hover(
        &mut self,
        app: &mut crate::state::AppState,
        hit: &crate::backend::input::PointerHit,
    ) -> HoverResult {
        // pass through hover events to force pipewire to capture frames for us
        let _ = self.inner.on_hover(app, hit);
        self.panel.on_hover(app, hit)
    }
    fn on_left(&mut self, app: &mut crate::state::AppState, pointer: usize) {
        self.inner.on_left(app, pointer);
        self.panel.on_left(app, pointer);
    }
    fn on_pointer(
        &mut self,
        app: &mut crate::state::AppState,
        hit: &crate::backend::input::PointerHit,
        pressed: bool,
    ) {
        self.panel.on_pointer(app, hit, pressed);
    }
    fn on_scroll(
        &mut self,
        app: &mut crate::state::AppState,
        hit: &crate::backend::input::PointerHit,
        delta: WheelDelta,
    ) {
        self.panel.on_scroll(app, hit, delta);
    }
    fn notify(&mut self, app: &mut AppState, event_data: OverlayEventData) -> anyhow::Result<()> {
        self.panel.notify(app, event_data)
    }
    fn get_interaction_transform(&mut self) -> Option<glam::Affine2> {
        self.inner.get_interaction_transform()
    }
}

fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
    let state = EditModeState {
        id: Rc::new(RefCell::new(OverlayID::null())),
        tasks: Rc::new(RefCell::new(TaskContainer::new())),
        delete: LongPressButtonState {
            pressed: Instant::now(),
        },
        tabs: ButtonPaneTabSwitcher::default(),
        lock: InteractLockHandler::default(),
        pos: PositioningHandler::default(),
    };

    let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, attribs, _app| {
        for (name, kind) in &BUTTON_EVENTS {
            let Some(action) = attribs.get_value(name) else {
                continue;
            };

            let mut args = action.split_whitespace();
            let Some(command) = args.next() else {
                continue;
            };

            let callback: EventCallback<AppState, EditModeState> = match command {
                "::EditModeToggleLock" => Box::new(move |common, _data, app, state| {
                    let sel = OverlaySelector::Id(*state.id.borrow());
                    let task = state.lock.toggle(common, app);
                    app.tasks.enqueue(TaskType::Overlay(sel, task));
                    Ok(EventResult::Consumed)
                }),
                "::EditModeTab" => {
                    let tab_name = args.next().unwrap().to_owned();
                    Box::new(move |common, _data, _app, state| {
                        state.tabs.tab_button_clicked(common, &tab_name);
                        Ok(EventResult::Consumed)
                    })
                }
                "::EditModeSetPos" => {
                    let pos_key = args.next().unwrap().to_owned();
                    Box::new(move |common, _data, app, state| {
                        let sel = OverlaySelector::Id(*state.id.borrow());
                        let task = state.pos.pos_button_clicked(common, &pos_key);
                        app.tasks.enqueue(TaskType::Overlay(sel, task));
                        Ok(EventResult::Consumed)
                    })
                }
                "::EditModeDeletePress" => Box::new(move |_common, _data, _app, state| {
                    state.delete.pressed = Instant::now();
                    // TODO: animate to light up button after 2s
                    Ok(EventResult::Consumed)
                }),
                "::EditModeDeleteRelease" => Box::new(move |_common, _data, app, state| {
                    if state.delete.pressed.elapsed() < Duration::from_secs(2) {
                        return Ok(EventResult::Pass);
                    }
                    app.tasks.enqueue(TaskType::Overlay(
                        OverlaySelector::Id(*state.id.borrow()),
                        Box::new(move |_app, owc| {
                            owc.active_state = None;
                        }),
                    ));
                    Ok(EventResult::Consumed)
                }),
                _ => return,
            };

            let id = layout.add_event_listener(attribs.widget_id, *kind, callback);
            log::debug!("Registered {action} on {:?} as {id:?}", attribs.widget_id);
        }
    });

    let mut panel = GuiPanel::new_from_template(
        app,
        "gui/edit.xml",
        state,
        NewGuiPanelParams {
            on_custom_attrib: Some(on_custom_attrib),
            resize_to_parent: true,
            ..Default::default()
        },
    )?;

    panel.state.pos = PositioningHandler::new(&mut panel)?;
    panel.state.lock = InteractLockHandler::new(&mut panel)?;
    panel.state.tabs = ButtonPaneTabSwitcher::new(&mut panel, &["none", "pos", "alpha", "curve"])?;

    set_up_checkbox(&mut panel, "additive_box", cb_assign_additive)?;
    set_up_slider(&mut panel, "alpha_slider", cb_assign_alpha)?;
    set_up_slider(&mut panel, "curve_slider", cb_assign_curve)?;

    Ok(panel)
}

fn reset_panel(
    panel: &mut EditModeWrapPanel,
    id: OverlayID,
    owc: &mut OverlayWindowConfig,
) -> anyhow::Result<()> {
    *panel.state.id.borrow_mut() = id;
    let state = owc.active_state.as_mut().unwrap();

    let mut alterables = EventAlterables::default();
    let mut common = CallbackDataCommon {
        alterables: &mut alterables,
        state: &panel.layout.state,
    };

    let c = panel
        .parser_state
        .fetch_component_as::<ComponentSlider>("alpha_slider")?;
    c.set_value(&mut common, state.alpha);

    let c = panel
        .parser_state
        .fetch_component_as::<ComponentSlider>("curve_slider")?;
    c.set_value(&mut common, state.curvature.unwrap_or(0.0));

    let c = panel
        .parser_state
        .fetch_component_as::<ComponentCheckbox>("additive_box")?;
    c.set_checked(&mut common, state.additive);

    panel.state.pos.reset(&mut common, state.positioning);
    panel.state.lock.reset(&mut common, state.interactable);
    panel.state.tabs.reset(&mut common);

    panel.layout.process_alterables(alterables)?;

    Ok(())
}

const fn cb_assign_alpha(_app: &mut AppState, owc: &mut OverlayWindowConfig, alpha: f32) {
    owc.dirty = true;
    owc.active_state.as_mut().unwrap().alpha = alpha;
}

fn cb_assign_curve(_app: &mut AppState, owc: &mut OverlayWindowConfig, curvature: f32) {
    owc.dirty = true;
    owc.active_state.as_mut().unwrap().curvature = if curvature < 0.005 {
        None
    } else {
        Some(curvature)
    };
}

const fn cb_assign_additive(_app: &mut AppState, owc: &mut OverlayWindowConfig, additive: bool) {
    owc.dirty = true;
    owc.active_state.as_mut().unwrap().additive = additive;
}

fn set_up_slider(
    panel: &mut EditModeWrapPanel,
    id: &str,
    callback: fn(&mut AppState, &mut OverlayWindowConfig, f32),
) -> anyhow::Result<()> {
    let slider = panel
        .parser_state
        .fetch_component_as::<ComponentSlider>(id)?;
    let tasks = panel.state.tasks.clone();
    let overlay_id = panel.state.id.clone();
    slider.on_value_changed(Box::new(move |_common, e| {
        let mut tasks = tasks.borrow_mut();
        let e_value = e.value;

        tasks.enqueue(TaskType::Overlay(
            OverlaySelector::Id(*overlay_id.borrow()),
            Box::new(move |app, owc| callback(app, owc, e_value)),
        ));
        Ok(())
    }));

    Ok(())
}

fn set_up_checkbox(
    panel: &mut EditModeWrapPanel,
    id: &str,
    callback: fn(&mut AppState, &mut OverlayWindowConfig, bool),
) -> anyhow::Result<()> {
    let checkbox = panel
        .parser_state
        .fetch_component_as::<ComponentCheckbox>(id)?;
    let tasks = panel.state.tasks.clone();
    let overlay_id = panel.state.id.clone();
    checkbox.on_toggle(Box::new(move |_common, e| {
        let mut tasks = tasks.borrow_mut();
        let e_checked = e.checked;

        tasks.enqueue(TaskType::Overlay(
            OverlaySelector::Id(*overlay_id.borrow()),
            Box::new(move |app, owc| callback(app, owc, e_checked)),
        ));
        Ok(())
    }));

    Ok(())
}
