use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMethods, Value};
use ratatui_core::widgets::Widget;
use ratatui_widgets::borders::Borders;

use super::{Area, Edge};
use crate::elements::Spatial;

#[derive(Clone, Debug, Default)]
pub struct Bar {
	area: Area,

	edge:   Edge,
	symbol: String,
	style:  ratatui_core::style::Style,
}

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

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

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

	fn try_from(value: &AnyUserData) -> Result<Self, Self::Error> { value.take() }
}

impl Spatial for Bar {
	fn area(&self) -> Area { self.area }

	fn set_area(&mut self, area: Area) { self.area = area; }
}

impl Widget for &Bar {
	fn render(self, rect: ratatui_core::layout::Rect, buf: &mut ratatui_core::buffer::Buffer)
	where
		Self: Sized,
	{
		if rect.area() == 0 {
			return;
		}

		let symbol = if !self.symbol.is_empty() {
			&self.symbol
		} else if self.edge.intersects(Borders::TOP | Borders::BOTTOM) {
			"─"
		} else if self.edge.intersects(Borders::LEFT | Borders::RIGHT) {
			"│"
		} else {
			" "
		};

		if self.edge.contains(Borders::LEFT) {
			for y in rect.top()..rect.bottom() {
				buf[(rect.left(), y)].set_style(self.style).set_symbol(symbol);
			}
		}
		if self.edge.contains(Borders::TOP) {
			for x in rect.left()..rect.right() {
				buf[(x, rect.top())].set_style(self.style).set_symbol(symbol);
			}
		}
		if self.edge.contains(Borders::RIGHT) {
			let x = rect.right() - 1;
			for y in rect.top()..rect.bottom() {
				buf[(x, y)].set_style(self.style).set_symbol(symbol);
			}
		}
		if self.edge.contains(Borders::BOTTOM) {
			let y = rect.bottom() - 1;
			for x in rect.left()..rect.right() {
				buf[(x, y)].set_style(self.style).set_symbol(symbol);
			}
		}
	}
}

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

		methods.add_function("edge", |_, (ud, edge): (AnyUserData, Edge)| {
			ud.borrow_mut::<Self>()?.edge = edge;
			Ok(ud)
		});
		methods.add_function("symbol", |_, (ud, symbol): (AnyUserData, String)| {
			ud.borrow_mut::<Self>()?.symbol = symbol;
			Ok(ud)
		});
	}
}
