use std::ops::Deref;

use mlua::{FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataFields, UserDataMethods, Value};
use yazi_macro::impl_data_any;
use yazi_shim::ratatui::Padable;

use super::Pad;

#[derive(Clone, Copy, Debug, Default, FromLua)]
pub struct Rect(pub ratatui_core::layout::Rect);

impl_data_any!(Rect, from_into_lua = inherit);

impl Deref for Rect {
	type Target = ratatui_core::layout::Rect;

	fn deref(&self) -> &Self::Target { &self.0 }
}

impl From<ratatui_core::layout::Rect> for Rect {
	fn from(rect: ratatui_core::layout::Rect) -> Self { Self(rect) }
}

impl From<ratatui_core::layout::Size> for Rect {
	fn from(size: ratatui_core::layout::Size) -> Self {
		Self(ratatui_core::layout::Rect {
			x:      0,
			y:      0,
			width:  size.width,
			height: size.height,
		})
	}
}

impl Rect {
	pub fn compose(lua: &Lua) -> mlua::Result<Value> {
		let new = lua.create_function(|_, (_, args): (Table, Table)| {
			Ok(Self(ratatui_core::layout::Rect {
				x:      args.raw_get("x").unwrap_or_default(),
				y:      args.raw_get("y").unwrap_or_default(),
				width:  args.raw_get("w").unwrap_or_default(),
				height: args.raw_get("h").unwrap_or_default(),
			}))
		})?;

		let rect = lua.create_table()?;
		rect.set_metatable(Some(lua.create_table_from([(MetaMethod::Call.name(), new)])?))?;
		rect.into_lua(lua)
	}

	fn patch(self, t: Table) -> mlua::Result<Self> {
		Ok(Self(ratatui_core::layout::Rect {
			x:      t.raw_get::<Option<_>>("x")?.unwrap_or(self.x),
			y:      t.raw_get::<Option<_>>("y")?.unwrap_or(self.y),
			width:  t.raw_get::<Option<_>>("w")?.unwrap_or(self.width),
			height: t.raw_get::<Option<_>>("h")?.unwrap_or(self.height),
		}))
	}
}

impl UserData for Rect {
	fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
		fields.add_field_method_get("x", |_, me| Ok(me.x));
		fields.add_field_method_get("y", |_, me| Ok(me.y));
		fields.add_field_method_get("w", |_, me| Ok(me.width));
		fields.add_field_method_get("h", |_, me| Ok(me.height));

		fields.add_field_method_set("x", |_, me, x| Ok(me.0.x = x));
		fields.add_field_method_set("y", |_, me, y| Ok(me.0.y = y));
		fields.add_field_method_set("w", |_, me, w| Ok(me.0.width = w));
		fields.add_field_method_set("h", |_, me, h| Ok(me.0.height = h));

		fields.add_field_method_get("left", |_, me| Ok(me.left()));
		fields.add_field_method_get("right", |_, me| Ok(me.right()));
		fields.add_field_method_get("top", |_, me| Ok(me.top()));
		fields.add_field_method_get("bottom", |_, me| Ok(me.bottom()));
	}

	fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
		methods.add_method("pad", |_, me, pad: Pad| Ok(Self(me.padding(pad))));
		methods.add_method("contains", |_, me, Self(rect)| Ok(me.contains(rect.into())));

		methods.add_meta_method(MetaMethod::Call, |_, me, t: Table| me.patch(t));
	}
}
