use core::marker::PhantomData;
use core::mem::{needs_drop, size_of, MaybeUninit};

use crate::query::{append_term, validate_returned_fields, ParamError, QueryEach, ResourceAccess};
use crate::{raw, Component, Entity, World, WorldRef};

pub use raw::Phase;

#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
#[repr(transparent)]
pub struct SystemId(raw::SystemId);

impl SystemId {
    #[inline]
    pub const fn raw(self) -> raw::SystemId {
        self.0
    }
}

impl From<raw::SystemId> for SystemId {
    #[inline]
    fn from(id: raw::SystemId) -> Self {
        Self(id)
    }
}

#[derive(Clone, Copy)]
pub struct EachCtx<'a> {
    world: WorldRef<'a>,
    entity: Entity,
}

impl<'a> EachCtx<'a> {
    #[inline]
    pub(crate) unsafe fn new(iter: &raw::Iter, row: usize) -> Self {
        Self {
            world: WorldRef::from_raw(iter.world),
            entity: Entity::from_raw(*iter.entities.add(row)),
        }
    }

    #[inline]
    pub const fn entity(self) -> Entity {
        self.entity
    }

    #[inline]
    pub const fn world(self) -> WorldRef<'a> {
        self.world
    }

    #[inline]
    pub fn add<T: Component>(self) {
        self.world.add::<T>(self.entity);
    }

    #[inline]
    pub fn remove<T: Component>(self) {
        self.world.remove::<T>(self.entity);
    }

    #[inline]
    pub fn set<T: Component>(self, value: T) {
        self.world.set(self.entity, value);
    }

    #[inline]
    pub fn kill(self) {
        self.world.kill(self.entity);
    }

    #[inline]
    pub fn is_a(self, base: Entity) {
        self.world.is_a(self.entity, base);
    }
}

pub struct System<'world> {
    world: &'world mut World,
    name: String,
    desc: raw::SystemDesc,
    term_index: u16,
}

impl<'world> System<'world> {
    #[inline]
    pub(crate) fn new(world: &'world mut World, name: &str) -> Self {
        Self {
            world,
            name: name.to_owned(),
            desc: raw::SystemDesc::default(),
            term_index: 0,
        }
    }

    #[inline]
    pub fn require<T: Component>(mut self) -> Self {
        self.append_component::<T>(raw::TermAccess::Filter);
        self
    }

    #[inline]
    pub fn exclude<T: Component>(mut self) -> Self {
        self.append_component::<T>(raw::TermAccess::Not);
        self
    }

    #[inline]
    pub fn is_a(mut self, base: Entity) -> Self {
        self.desc.query.is_a = base.id();
        self
    }

    #[inline]
    pub fn phase(mut self, phase: Phase) -> Self {
        self.desc.phase = phase;
        self
    }

    #[inline]
    pub fn disabled(mut self) -> Self {
        self.desc.disabled = true;
        self
    }

    #[inline]
    pub fn after(mut self, system: SystemId) -> Self {
        let slot = self
            .desc
            .after
            .iter_mut()
            .find(|slot| **slot == 0)
            .expect("systems can depend on at most four prior systems");
        *slot = system.raw();
        self
    }

    #[inline]
    pub fn each<F, Marker>(self, func: F) -> SystemId
    where
        F: QueryEach<Marker> + 'static,
    {
        self.try_each(func).unwrap_or_else(|err| panic!("{err}"))
    }

    #[inline]
    pub fn try_each<F, Marker>(mut self, func: F) -> Result<SystemId, ParamError>
    where
        F: QueryEach<Marker> + 'static,
    {
        assert!(
            size_of::<F>() == 0 && !needs_drop::<F>(),
            "system callbacks must be stateless"
        );
        let _ = func;

        F::append_terms(self.world, &mut self.desc.query, &mut self.term_index)?;
        let mut resource_access = ResourceAccess::default();
        F::validate_resources(self.world, &mut resource_access)?;
        validate_returned_fields(&self.desc.query)?;

        self.desc.name = self.world.retain_system_name(&self.name);
        self.desc.callback = Some(system_callback::<F, Marker>);

        Ok(unsafe { raw::ecs_system_init(self.world.as_raw_mut(), &self.desc).into() })
    }

    #[inline]
    fn append_component<T: Component>(&mut self, access: raw::TermAccess) {
        let id = T::id(self.world);
        append_term(&mut self.desc.query, &mut self.term_index, id, access)
            .expect("too many query terms");
    }
}

unsafe extern "C" fn system_callback<F, Marker>(iter: *mut raw::Iter)
where
    F: QueryEach<Marker> + 'static,
{
    debug_assert!(!iter.is_null());
    debug_assert_eq!(size_of::<F>(), 0);
    debug_assert!(!needs_drop::<F>());

    let mut func = MaybeUninit::<F>::zeroed().assume_init();
    F::run(&mut func, &mut *iter);
}

#[doc(hidden)]
pub struct SystemMarker<T>(PhantomData<T>);
