use mlua::{AnyUserData, ExternalError, FromLua, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};
use ratatui_core::{buffer::Buffer, layout::Rect};

use super::Cell;

const EXPECTED: &str = "expected a Row";

#[derive(Clone, Debug, Default)]
pub struct Row {
	pub(super) cells: Vec<Cell>,
	height:           u16,
	top_margin:       u16,
	bottom_margin:    u16,
	style:            ratatui_core::style::Style,
}

impl Row {
	pub fn compose(lua: &Lua) -> mlua::Result<Value> {
		let new = lua.create_function(|_, (_, cells): (Table, Vec<Cell>)| {
			Ok(Self { cells, ..Default::default() })
		})?;

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

		row.into_lua(lua)
	}

	pub(super) fn height_with_margin(&self) -> u16 {
		self.height.saturating_add(self.top_margin).saturating_add(self.bottom_margin)
	}

	pub(super) fn measure(&mut self, columns: &[Rect]) {
		let height = self
			.cells
			.iter()
			.zip(columns)
			.map(|(cell, column)| cell.height(column.width))
			.max()
			.unwrap_or_default();
		self.height = self.height.max(height).max(1);
	}

	pub(super) fn render_overlays(&self, mut area: Rect, buf: &mut Buffer, columns: &[Rect]) {
		area.y = area.y.saturating_add(self.top_margin);
		area.height = area.height.saturating_sub(self.top_margin).min(self.height);
		for (cell, column) in self.cells.iter().zip(columns) {
			cell.render_overlay(
				Rect::new(area.x.saturating_add(column.x), area.y, column.width, area.height),
				buf,
			);
		}
	}
}

impl From<Row> for ratatui_widgets::table::Row<'static> {
	fn from(value: Row) -> Self {
		Self::new(value.cells)
			.height(value.height.max(1))
			.top_margin(value.top_margin)
			.bottom_margin(value.bottom_margin)
			.style(value.style)
	}
}

impl TryFrom<&AnyUserData> for Row {
	type Error = mlua::Error;

	fn try_from(value: &AnyUserData) -> Result<Self, Self::Error> {
		if let Ok(row) = value.take() { Ok(row) } else { Err(EXPECTED.into_lua_err()) }
	}
}

impl FromLua for Row {
	fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
		match value {
			Value::UserData(ud) => Self::try_from(&ud),
			_ => Err(EXPECTED.into_lua_err()),
		}
	}
}

impl UserData for Row {
	fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
		crate::impl_style_method!(methods, style);

		methods.add_function("height", |_, (ud, value): (AnyUserData, u16)| {
			ud.borrow_mut::<Self>()?.height = value;
			Ok(ud)
		});
		methods.add_function("margin_t", |_, (ud, value): (AnyUserData, u16)| {
			ud.borrow_mut::<Self>()?.top_margin = value;
			Ok(ud)
		});
		methods.add_function("margin_b", |_, (ud, value): (AnyUserData, u16)| {
			ud.borrow_mut::<Self>()?.bottom_margin = value;
			Ok(ud)
		});
	}
}
