use std::{any::TypeId, cell::Ref, marker::PhantomData};

use crate::{query::{AsQuery, Fetch, Query}, Archetype, ComponentColumn, Entity, World};

use super::{Relation, RelationOriginComponent};

pub struct FetchRelatePair<'a, T> {
    col: &'a ComponentColumn,
    arch: &'a Archetype,
    _phantom: PhantomData<&'a T>
}

impl<'a, R> Fetch<'a> for FetchRelatePair<'a, R>
where
    R: Relation,
{
    type Item = (Entity, Ref<'a, R>, Entity);

    fn dangling() -> Self {
        unreachable!()
    }

    unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item {
        let comp: Ref<RelationOriginComponent<R>> = self.col.get(entity.0 as usize);
        let rel_target = comp.target;
        let rel_origin = self.arch.entity_at_index(entity).unwrap();

        let comp = Ref::map(comp, |r| &r.relation);
        (rel_origin, comp, rel_target)
    }
}

pub struct QueryRelatePair<R> {
    _marker: PhantomData<R>,
}

impl<R> Copy for QueryRelatePair<R> {}

impl<R> Clone for QueryRelatePair<R> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<R> QueryRelatePair<R> {
    pub fn new() -> Self {
        Self {
            _marker: PhantomData
        }
    }
}

impl<R> Query for QueryRelatePair<R>
where
    R: Relation + 'static
{
    type Item<'a> = (Entity, Ref<'a, R>, Entity);

    type Fetch<'a> = FetchRelatePair<'a, R>;

    fn new() -> Self {
        QueryRelatePair::<R>::new()
    }

    fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
        let tyid = crate::DynTypeId::Rust(TypeId::of::<RelationOriginComponent<R>>());
        archetype.has_column(tyid)
    }

    unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
        let _ = tick;
        let col = archetype.get_column(TypeId::of::<RelationOriginComponent<R>>())
            .expect("You ignored 'can_visit_archetype'!");

        FetchRelatePair {
            col,
            arch: archetype,
            _phantom: PhantomData,
        }
    }
}

/// A query that fetches the origin, and target of a relation of type `R`.
/// 
/// It provides it as a tuple in the following format: `(origin, relation, target)`.
/// Similar to [`RelatesTo`](super::RelatesTo), you can use
/// [`ViewState::relate_pair`](crate::relation::ViewState::relate_pair) to get a view that
/// fetches the pair, or unlike [`RelatesTo`](super::RelatesTo), you can do the common
/// procedure of using [`World::view`].
pub struct RelatePair<R: Relation> {
    _marker: PhantomData<R>,
}

impl<R: Relation> AsQuery for RelatePair<R> {
    type Query = QueryRelatePair<R>;
}