#[derive(Clone,Copy)]
pub struct EnumItem<'a>{
	name:Option<&'a str>,
	value:u32,
}
impl<'a> EnumItem<'a>{
	fn known_name((name,&value):(&'a std::borrow::Cow<'a,str>,&u32))->Self{
		Self{name:Some(name.as_ref()),value}
	}
}
impl From<rbx_types::Enum> for EnumItem<'_>{
	fn from(e:rbx_types::Enum)->Self{
		EnumItem{
			name:None,
			value:e.to_u32(),
		}
	}
}
impl From<EnumItem<'_>> for rbx_types::Enum{
	fn from(e:EnumItem)->rbx_types::Enum{
		rbx_types::Enum::from_u32(e.value)
	}
}
impl PartialEq for EnumItem<'_>{
	fn eq(&self,other:&EnumItem<'_>)->bool{
		self.value==other.value&&{
			// if both names are known, they must match, otherwise whatever
			match (self.name,other.name){
				(Some(lhs),Some(rhs))=>lhs==rhs,
				_=>true,
			}
		}
	}
}

#[derive(Clone,Copy)]
pub struct Enums;
impl Enums{
	pub fn get(&self,index:&str)->Option<EnumItems<'static>>{
		let db=rbx_reflection_database::get().unwrap();
		db.enums.get(index).map(|ed|EnumItems{ed})
	}
}
#[derive(Clone,Copy)]
pub struct EnumItems<'a>{
	ed:&'a rbx_reflection::EnumDescriptor<'a>,
}

impl<'a> EnumItems<'a>{
	pub fn from_value(&self,value:u32)->Option<EnumItem<'a>>{
		self.ed.items.iter().find(|&(_,&v)|v==value).map(EnumItem::known_name)
	}
	pub fn from_name(&self,name:&str)->Option<EnumItem<'a>>{
		self.ed.items.get_key_value(name).map(EnumItem::known_name)
	}
	pub fn from_enum(&self,enum_item:EnumItem)->Option<EnumItem<'a>>{
		match enum_item.name{
			Some(s)=>{
				let got=self.from_name(s)?;
				(got.value==enum_item.value).then_some(got)
			},
			None=>self.from_value(enum_item.value)
		}
	}
}

pub enum CoerceEnum<'a>{
	Integer(i64),
	String(mlua::String),
	Enum(EnumItem<'a>),
}
impl CoerceEnum<'_>{
	pub fn coerce_to<'a>(self,enum_items:EnumItems<'a>)->mlua::Result<EnumItem<'a>>{
		match self{
			CoerceEnum::Integer(int)=>enum_items.from_value(int as u32),
			CoerceEnum::String(s)=>enum_items.from_name(&*s.to_str()?),
			CoerceEnum::Enum(enum_item)=>enum_items.from_enum(enum_item),
		}.ok_or_else(||mlua::Error::runtime(format!("Bad {} EnumItem",enum_items.ed.name)))
	}
}

impl mlua::FromLua for CoerceEnum<'_>{
	fn from_lua(value:mlua::Value,_lua:&mlua::Lua)->Result<Self,mlua::Error>{
		match value{
			mlua::Value::Integer(int)=>Ok(CoerceEnum::Integer(int)),
			mlua::Value::String(s)=>Ok(CoerceEnum::String(s)),
			mlua::Value::UserData(ud)=>Ok(CoerceEnum::Enum(*ud.borrow()?)),
			other=>Err(mlua::Error::runtime(format!("Expected {} got {:?}",stringify!(Enum),other))),
		}
	}
}

pub fn set_globals(_lua:&mlua::Lua,globals:&mlua::Table)->Result<(),mlua::Error>{
	globals.set("Enum",Enums)
}

impl mlua::UserData for EnumItems<'static>{
	fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
	}
	fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
		methods.add_method("FromName",|_,this:&EnumItems,name:mlua::String|Ok(this.from_name(&*name.to_str()?)));
		methods.add_method("FromValue",|_,this:&EnumItems,value:u32|Ok(this.from_value(value)));
		methods.add_method("GetEnumItems",|_,this:&EnumItems,()|->mlua::Result<Vec<EnumItem>>{
			Ok(this.ed.items.iter().map(EnumItem::known_name).collect())
		});
		methods.add_meta_function(mlua::MetaMethod::Index,|_,(this,val):(EnumItems,mlua::String)|{
			let index=&*val.to_str()?;
			Ok(this.ed.items.get_key_value(index).map(EnumItem::known_name))
		});
	}
}
type_from_lua_userdata_lua_lifetime!(EnumItems);

impl mlua::UserData for Enums{
	fn add_fields<F:mlua::UserDataFields<Self>>(_fields:&mut F){
	}
	fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
		methods.add_meta_function(mlua::MetaMethod::Index,|_,(enums,val):(Self,mlua::String)|{
			Ok(enums.get(&*val.to_str()?))
		});
	}
}
type_from_lua_userdata!(Enums);

impl mlua::UserData for EnumItem<'_>{
	fn add_fields<F:mlua::UserDataFields<Self>>(fields:&mut F){
		fields.add_field_method_get("Name",|_,this|Ok(this.name));
		fields.add_field_method_get("Value",|_,this|Ok(this.value));
	}
	fn add_methods<M:mlua::UserDataMethods<Self>>(methods:&mut M){
		methods.add_meta_function(mlua::MetaMethod::Eq,|_,(lhs,rhs):(EnumItem<'_>,EnumItem<'_>)|{
			Ok(lhs==rhs)
		});
	}
}
type_from_lua_userdata_lua_lifetime!(EnumItem);
