use crate::util::static_ustr;
use rbx_dom_weak::{types::Ref,InstanceBuilder,WeakDom};

#[derive(Debug)]
pub enum ServicesError{
	WorkspaceNotFound,
}
impl std::fmt::Display for ServicesError{
	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
		write!(f,"{self:?}")
	}
}
impl std::error::Error for ServicesError{}

pub struct Services{
	pub(crate) game:Ref,
	pub(crate) workspace:Ref,
}

impl Services{
	fn find_services(dom:&WeakDom)->Result<Services,ServicesError>{
		Ok(Services{
			workspace:*dom.root().children().iter().find(|&&r|
				dom.get_by_ref(r).is_some_and(|instance|instance.class=="Workspace")
			).ok_or(ServicesError::WorkspaceNotFound)?,
			game:dom.root_ref(),
		})
	}
}

pub type LuaAppData=&'static mut WeakDom;
pub struct Context{
	pub(crate)dom:WeakDom,
	pub(crate)services:Services,
}

impl Context{
	pub fn from_place(dom:WeakDom)->Result<Context,ServicesError>{
		let services=Services::find_services(&dom)?;
		Ok(Self{dom,services})
	}
	pub fn script_singleton(source:String)->(Context,crate::runner::instance::Instance){
		let script=InstanceBuilder::new("Script")
			.with_property("Source",rbx_types::Variant::String(source));
		let script_ref=script.referent();
		let dom=WeakDom::new(
			InstanceBuilder::new("DataModel")
			.with_child(script)
		);
		let context=Self::from_model(dom);
		(context,crate::runner::instance::Instance::new_unchecked(script_ref))
	}
	/// Creates an iterator over all items of a particular class.
	pub fn superclass_iter<'a>(&'a self,superclass:&'a str)->impl Iterator<Item=Ref>+'a{
		let db=rbx_reflection_database::get().unwrap();
		let Some(superclass)=db.classes.get(superclass)else{
			panic!("Invalid class");
		};
		self.dom.descendants().filter_map(|instance|{
			let class=db.classes.get(instance.class.as_str())?;
			db.has_superclass(class,superclass).then(||instance.referent())
		})
	}
	pub fn scripts(&self)->Vec<crate::runner::instance::Instance>{
		self.superclass_iter("Script")
			.filter_map(|script_ref|{
				let script=self.dom.get_by_ref(script_ref)?;
				if let None|Some(rbx_dom_weak::types::Variant::Bool(false))=script.properties.get(&static_ustr("Disabled")){
					return Some(crate::runner::instance::Instance::new_unchecked(script_ref));
				}
				None
			})
			.collect()
	}

	pub fn from_model(mut dom:WeakDom)->Context{
		//snapshot root instances
		let children=dom.root().children().to_owned();

		//insert services
		let game=dom.root_ref();
		let terrain_bldr=InstanceBuilder::new("Terrain")
		.with_properties([
			("CFrame",rbx_dom_weak::types::Variant::CFrame(rbx_dom_weak::types::CFrame::new(rbx_dom_weak::types::Vector3::new(0.0,0.0,0.0),rbx_dom_weak::types::Matrix3::identity()))),
			("Size",rbx_dom_weak::types::Variant::Vector3(rbx_dom_weak::types::Vector3::new(1.0,1.0,1.0))),
			("Velocity",rbx_dom_weak::types::Variant::Vector3(rbx_dom_weak::types::Vector3::new(0.0,0.0,0.0))),
			("Transparency",rbx_dom_weak::types::Variant::Float32(0.0)),
			("Color",rbx_dom_weak::types::Variant::Color3uint8(rbx_dom_weak::types::Color3uint8::new(255,255,255))),
			("CanCollide",rbx_dom_weak::types::Variant::Bool(true)),
		]);
		let workspace=dom.insert(game,
			InstanceBuilder::new("Workspace")
				//Set Workspace.Terrain property equal to Terrain
				.with_property("Terrain",terrain_bldr.referent())
				.with_child(terrain_bldr)
		);

		//transfer original root instances into workspace
		for instance in children{
			dom.transfer_within(instance,workspace);
		}

		{
			//Lowercase and upper case workspace property!
			let game=dom.root_mut();
			// TODO: DELETE THIS!
			game.properties.insert(static_ustr("workspace"),rbx_types::Variant::Ref(workspace));
			game.properties.insert(static_ustr("Workspace"),rbx_types::Variant::Ref(workspace));
		}
		dom.insert(game,InstanceBuilder::new("Lighting"));

		let services=Services{game,workspace};
		Self{dom,services}
	}
}

impl AsRef<WeakDom> for Context{
	fn as_ref(&self)->&WeakDom{
		&self.dom
	}
}
