use crate::{
    MessageData, PropsData, Scalar, make_widget, pre_hooks,
    widget::{
        WidgetId, WidgetIdOrRef,
        component::{
            containers::content_box::{ContentBoxProps, content_box},
            interactive::navigation::{
                NavContainerActive, NavItemActive, NavJumpActive, use_nav_container_active,
                use_nav_item, use_nav_jump_direction_active,
            },
        },
        context::{WidgetContext, WidgetMountOrChangeContext},
        node::WidgetNode,
        unit::content::ContentBoxContentReposition,
        utils::Vec2,
    },
};
use serde::{Deserialize, Serialize};

#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]
#[props_data(crate::props::PropsData)]
#[prefab(crate::Prefab)]
pub struct FloatBoxProps {
    #[serde(default)]
    pub bounds_left: Option<Scalar>,
    #[serde(default)]
    pub bounds_right: Option<Scalar>,
    #[serde(default)]
    pub bounds_top: Option<Scalar>,
    #[serde(default)]
    pub bounds_bottom: Option<Scalar>,
}

#[derive(PropsData, Debug, Default, Clone, Serialize, Deserialize)]
#[props_data(crate::props::PropsData)]
#[prefab(crate::Prefab)]
pub struct FloatBoxNotifyProps(
    #[serde(default)]
    #[serde(skip_serializing_if = "WidgetIdOrRef::is_none")]
    pub WidgetIdOrRef,
);

#[derive(PropsData, Debug, Copy, Clone, Serialize, Deserialize)]
#[props_data(crate::props::PropsData)]
#[prefab(crate::Prefab)]
pub struct FloatBoxState {
    #[serde(default)]
    pub position: Vec2,
    #[serde(default = "FloatBoxState::default_zoom")]
    pub zoom: Scalar,
}

impl Default for FloatBoxState {
    fn default() -> Self {
        Self {
            position: Default::default(),
            zoom: Self::default_zoom(),
        }
    }
}

impl FloatBoxState {
    fn default_zoom() -> Scalar {
        1.0
    }
}

#[derive(MessageData, Debug, Default, Clone)]
#[message_data(crate::messenger::MessageData)]
pub struct FloatBoxNotifyMessage {
    pub sender: WidgetId,
    pub state: FloatBoxState,
    pub prev: FloatBoxState,
}

#[derive(MessageData, Debug, Clone)]
#[message_data(crate::messenger::MessageData)]
pub struct FloatBoxChangeMessage {
    pub sender: WidgetId,
    pub change: FloatBoxChange,
}

#[derive(Debug, Clone)]
pub enum FloatBoxChange {
    Absolute(FloatBoxState),
    RelativePosition(Vec2),
    RelativeZoom(Scalar),
}

pub fn use_float_box(context: &mut WidgetContext) {
    fn notify(context: &WidgetMountOrChangeContext, data: FloatBoxNotifyMessage) {
        if let Ok(FloatBoxNotifyProps(notify)) = context.props.read()
            && let Some(to) = notify.read()
        {
            context.messenger.write(to, data);
        }
    }

    context.life_cycle.mount(|context| {
        let props = context.props.read_cloned_or_default::<FloatBoxProps>();
        let mut data = context.props.read_cloned_or_default::<FloatBoxState>();
        if let Some(limit) = props.bounds_left {
            data.position.x = data.position.x.max(limit);
        }
        if let Some(limit) = props.bounds_right {
            data.position.x = data.position.x.min(limit);
        }
        if let Some(limit) = props.bounds_top {
            data.position.y = data.position.y.max(limit);
        }
        if let Some(limit) = props.bounds_bottom {
            data.position.y = data.position.y.min(limit);
        }
        notify(
            &context,
            FloatBoxNotifyMessage {
                sender: context.id.to_owned(),
                state: data,
                prev: data,
            },
        );
        let _ = context.state.write_with(data);
    });

    context.life_cycle.change(|context| {
        let props = context.props.read_cloned_or_default::<FloatBoxProps>();
        let mut dirty = false;
        let mut data = context.state.read_cloned_or_default::<FloatBoxState>();
        let prev = data;
        for msg in context.messenger.messages {
            if let Some(msg) = msg.as_any().downcast_ref::<FloatBoxChangeMessage>() {
                match msg.change {
                    FloatBoxChange::Absolute(value) => {
                        data = value;
                        dirty = true;
                    }
                    FloatBoxChange::RelativePosition(delta) => {
                        data.position.x -= delta.x;
                        data.position.y -= delta.y;
                        dirty = true;
                    }
                    FloatBoxChange::RelativeZoom(delta) => {
                        data.zoom *= delta;
                        dirty = true;
                    }
                }
            }
        }
        if dirty {
            if let Some(limit) = props.bounds_left {
                data.position.x = data.position.x.max(limit);
            }
            if let Some(limit) = props.bounds_right {
                data.position.x = data.position.x.min(limit);
            }
            if let Some(limit) = props.bounds_top {
                data.position.y = data.position.y.max(limit);
            }
            if let Some(limit) = props.bounds_bottom {
                data.position.y = data.position.y.min(limit);
            }
            notify(
                &context,
                FloatBoxNotifyMessage {
                    sender: context.id.to_owned(),
                    state: data.to_owned(),
                    prev,
                },
            );
            let _ = context.state.write_with(data);
        }
    });
}

#[pre_hooks(use_nav_container_active, use_nav_jump_direction_active, use_nav_item)]
pub fn nav_float_box(mut context: WidgetContext) -> WidgetNode {
    let WidgetContext {
        key,
        props,
        listed_slots,
        ..
    } = context;

    let props = props
        .clone()
        .without::<NavContainerActive>()
        .without::<NavJumpActive>()
        .without::<NavItemActive>();

    make_widget!(float_box)
        .key(key)
        .merge_props(props)
        .listed_slots(listed_slots)
        .into()
}

#[pre_hooks(use_float_box)]
pub fn float_box(mut context: WidgetContext) -> WidgetNode {
    let WidgetContext {
        key,
        props,
        state,
        mut listed_slots,
        ..
    } = context;

    let mut props = props.read_cloned_or_default::<ContentBoxProps>();
    let state = state.read_cloned_or_default::<FloatBoxState>();
    props.content_reposition = ContentBoxContentReposition {
        offset: Vec2 {
            x: -state.position.x,
            y: -state.position.y,
        },
        scale: Vec2 {
            x: state.zoom,
            y: state.zoom,
        },
    };

    for item in listed_slots.iter_mut() {
        if let Some(p) = item.props_mut() {
            p.write(state);
            if !p.has::<FloatBoxNotifyProps>() {
                p.write(FloatBoxNotifyProps(context.id.to_owned().into()));
            }
        }
    }

    make_widget!(content_box)
        .key(key)
        .with_props(props)
        .listed_slots(listed_slots)
        .into()
}
