use std::ops::Range;

use super::builder::{QueryBuilder, Save, SelectionKind};
use super::error::SelectorParseError;
use super::transition::Transition;
use crate::query::selector::Combinator;

#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub struct TransitionId(pub usize);

impl TransitionId {
    #[inline(always)]
    pub fn index(self) -> usize {
        self.0
    }
}

impl From<usize> for TransitionId {
    fn from(value: usize) -> Self {
        Self(value)
    }
}

#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub struct QuerySectionId(pub usize);

impl QuerySectionId {
    #[inline(always)]
    pub fn index(self) -> usize {
        self.0
    }
}

impl From<usize> for QuerySectionId {
    fn from(value: usize) -> Self {
        Self(value)
    }
}

struct PositionIterator<'query, Q: QuerySpec<'query>> {
    arena: &'query Q,
    current: Option<Position>,
}
impl<'query, Q: QuerySpec<'query>> Iterator for PositionIterator<'query, Q> {
    type Item = Position;
    fn next(&mut self) -> Option<Self::Item> {
        self.current
            .inspect(|position| self.current = position.next_sibling(self.arena))
    }
}

pub trait QuerySpec<'query> {
    fn states(&self) -> &[Transition<'query>];
    fn queries(&self) -> &[QuerySection<'query>];
    fn exit_at_section_end(&self) -> Option<QuerySectionId>;

    fn get_transition(&self, state: TransitionId) -> &Transition<'query> {
        &self.states()[state.index()]
    }

    fn get_section_selection_kind(&self, section_index: QuerySectionId) -> SelectionKind {
        self.queries()[section_index.index()].kind
    }

    fn get_selection(&self, section_index: QuerySectionId) -> &QuerySection<'query> {
        &self.queries()[section_index.index()]
    }

    fn is_descendant(&self, state: TransitionId) -> bool {
        self.get_transition(state).guard == Combinator::Descendant
    }

    fn is_save_point(&self, position: &Position) -> bool {
        debug_assert!(
            self.get_selection(position.selection)
                .range
                .contains(&position.state)
        );
        self.get_selection(position.selection).range.end.index() - 1 == position.state.index()
    }

    fn is_last_save_point(&self, position: &Position) -> bool {
        debug_assert!(position.selection.index() < self.queries().len());
        let is_last_query = self.queries().len() - 1 == position.selection.index();
        let is_last_state =
            self.get_selection(position.selection).range.end.index() - 1 == position.state.index();
        is_last_query && is_last_state
    }

    fn children(&'query self, position: &Position) -> Option<impl Iterator<Item = Position>>
    where
        Self: Sized,
    {
        position.next_child(self).map(|child| PositionIterator {
            arena: self,
            current: Some(child),
        })
    }
}

#[derive(PartialEq, Debug, Clone, Copy)]
pub struct Position {
    pub selection: QuerySectionId,
    pub state: TransitionId,
}

impl Position {
    pub fn next_transition<'query, Q: QuerySpec<'query> + ?Sized>(
        &self,
        query: &Q,
    ) -> Option<TransitionId> {
        debug_assert!(self.selection.index() < query.queries().len());
        debug_assert!(
            query
                .get_selection(self.selection)
                .range
                .contains(&self.state)
        );

        let selection_range = &query.get_selection(self.selection).range;
        if self.state.index() + 1 < selection_range.end.index() {
            Some(TransitionId(self.state.index() + 1))
        } else {
            None
        }
    }

    pub fn next_child<'query, Q: QuerySpec<'query> + ?Sized>(&self, query: &Q) -> Option<Self> {
        debug_assert!(self.selection.index() < query.queries().len());
        debug_assert!(
            query
                .get_selection(self.selection)
                .range
                .contains(&self.state)
        );

        if self.selection.index() == query.queries().len() - 1 {
            return None;
        }

        let next_selection_index = QuerySectionId(self.selection.index() + 1);
        let next_selection = query.get_selection(next_selection_index);
        if next_selection.parent.is_some_and(|p| p == self.selection) {
            return Some(Self {
                selection: next_selection_index,
                state: next_selection.range.start,
            });
        }

        None
    }

    pub fn next_sibling<'query, Q: QuerySpec<'query> + ?Sized>(&self, query: &Q) -> Option<Self> {
        debug_assert!(self.selection.index() < query.queries().len());
        debug_assert!(
            query
                .get_selection(self.selection)
                .range
                .contains(&self.state)
        );

        query
            .get_selection(self.selection)
            .next_sibling
            .map(|sibling| Self {
                selection: sibling,
                state: query.get_selection(sibling).range.start,
            })
    }

    pub fn back<'query, Q: QuerySpec<'query> + ?Sized>(&mut self, query: &Q) {
        debug_assert!(self.selection.index() < query.queries().len());
        debug_assert!(self.state < query.get_selection(self.selection).range.end);

        let selection = query.get_selection(self.selection);
        if self.state.index() > selection.range.start.index() {
            self.state = TransitionId(self.state.index() - 1);
        } else if let Some(parent) = selection.parent {
            self.selection = parent;
            self.state = TransitionId(query.get_selection(self.selection).range.end.index() - 1);
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct QuerySection<'query> {
    pub source: &'query str,
    pub range: Range<TransitionId>,
    pub parent: Option<QuerySectionId>,
    pub next_sibling: Option<QuerySectionId>,
    pub save: Save,
    pub kind: SelectionKind,
}

impl<'query> QuerySection<'query> {
    pub fn new(
        source: &'query str,
        save: Save,
        kind: SelectionKind,
        range: Range<TransitionId>,
        parent: Option<QuerySectionId>,
    ) -> Self {
        Self {
            source,
            save,
            kind,
            range,
            parent,
            next_sibling: None,
        }
    }

    pub const fn new_const(
        source: &'query str,
        save: Save,
        kind: SelectionKind,
        range: Range<TransitionId>,
        parent: Option<QuerySectionId>,
        next_sibling: Option<QuerySectionId>,
    ) -> Self {
        Self {
            source,
            save,
            kind,
            range,
            parent,
            next_sibling,
        }
    }
}

#[derive(Debug, PartialEq, Clone)]
pub struct Query<'query> {
    pub states: Box<[Transition<'query>]>,
    pub queries: Box<[QuerySection<'query>]>,
    pub exit_at_section_end: Option<QuerySectionId>,
}

impl<'query> QuerySpec<'query> for Query<'query> {
    fn states(&self) -> &[Transition<'query>] {
        &self.states
    }

    fn queries(&self) -> &[QuerySection<'query>] {
        &self.queries
    }

    fn exit_at_section_end(&self) -> Option<QuerySectionId> {
        self.exit_at_section_end
    }
}

#[derive(Debug, PartialEq, Clone)]
pub struct StaticQuery<'query, const N_STATES: usize, const N_SECTIONS: usize> {
    pub states: [Transition<'query>; N_STATES],
    pub queries: [QuerySection<'query>; N_SECTIONS],
    pub exit_at_section_end: Option<QuerySectionId>,
}

impl<'query, const N_STATES: usize, const N_SECTIONS: usize>
    StaticQuery<'query, N_STATES, N_SECTIONS>
{
    pub const fn new(
        states: [Transition<'query>; N_STATES],
        queries: [QuerySection<'query>; N_SECTIONS],
        exit_at_section_end: Option<QuerySectionId>,
    ) -> Self {
        Self {
            states,
            queries,
            exit_at_section_end,
        }
    }
}

impl<'query, const N_STATES: usize, const N_SECTIONS: usize> QuerySpec<'query>
    for StaticQuery<'query, N_STATES, N_SECTIONS>
{
    fn states(&self) -> &[Transition<'query>] {
        &self.states
    }

    fn queries(&self) -> &[QuerySection<'query>] {
        &self.queries
    }

    fn exit_at_section_end(&self) -> Option<QuerySectionId> {
        self.exit_at_section_end
    }
}

impl<'query> Query<'query> {
    pub fn first(
        query: &'query str,
        save: Save,
    ) -> Result<QueryBuilder<'query>, SelectorParseError> {
        let states = Transition::generate_transitions_from_string(query)?;
        let queries = vec![QuerySection::new(
            query,
            save,
            SelectionKind::First,
            TransitionId(0)..TransitionId(states.len()),
            None,
        )];

        Ok(QueryBuilder {
            states,
            selection: queries,
        })
    }

    pub fn all(query: &'query str, save: Save) -> Result<QueryBuilder<'query>, SelectorParseError> {
        let states = Transition::generate_transitions_from_string(query)?;
        let queries = vec![QuerySection::new(
            query,
            save,
            SelectionKind::All,
            TransitionId(0)..TransitionId(states.len()),
            None,
        )];

        Ok(QueryBuilder {
            states,
            selection: queries,
        })
    }
}

#[cfg(test)]
mod tests {
    use crate::query::compiler::transition::Transition;
    use crate::query::selector::AttributeSelection;
    use crate::query::selector::AttributeSelectionKind;
    use crate::query::selector::AttributeSelections;
    use crate::query::selector::ClassSelections;
    use crate::query::selector::Combinator;
    use crate::query::selector::ElementPredicate;
    use crate::{Query, QuerySection, QuerySectionId, Save, SelectionKind, TransitionId};

    #[test]
    fn test_query_builder_one_selection() {
        let query = Query::all("a", Save::all()).unwrap().build();

        assert_eq!(
            query.states.iter().as_slice(),
            [Transition {
                predicate: ElementPredicate {
                    name: Some("a"),
                    id: None,
                    classes: ClassSelections::from_static(&[]),
                    attributes: AttributeSelections::from_static(&[])
                },
                guard: Combinator::Descendant,
            }]
        );

        assert_eq!(
            query.queries.iter().as_slice(),
            [QuerySection {
                source: "a",
                save: Save::all(),
                kind: SelectionKind::All,
                parent: None,
                range: TransitionId(0)..TransitionId(1),
                next_sibling: None,
            }]
        );
    }

    #[test]
    fn test_query_builder_chainned_selection() {
        let query = Query::first("span", Save::all())
            .unwrap()
            .all("a", Save::all())
            .unwrap()
            .build();

        assert_eq!(
            query.states.iter().as_slice(),
            [
                Transition {
                    predicate: ElementPredicate {
                        name: Some("span"),
                        id: None,
                        classes: ClassSelections::from_static(&[]),
                        attributes: AttributeSelections::from_static(&[])
                    },
                    guard: Combinator::Descendant,
                },
                Transition {
                    predicate: ElementPredicate {
                        name: Some("a"),
                        id: None,
                        classes: ClassSelections::from_static(&[]),
                        attributes: AttributeSelections::from_static(&[])
                    },
                    guard: Combinator::Descendant,
                }
            ]
        );
    }

    #[test]
    fn test_query_builder_chainned_multi_element_selection() {
        let query = Query::first("span#top.inner", Save::all())
            .unwrap()
            .all("a#link1.foo[href^=\"https\"]", Save::all())
            .unwrap()
            .build();

        assert_eq!(query.states.len(), 2);
        assert_eq!(query.queries.len(), 2);
        assert_eq!(
            query.states[1].predicate,
            ElementPredicate {
                name: Some("a"),
                id: Some("link1"),
                classes: ClassSelections::from_static(&["foo"]),
                attributes: AttributeSelections::from(vec![AttributeSelection {
                    name: "href",
                    value: Some("https"),
                    kind: AttributeSelectionKind::Prefix,
                }]),
            }
        );
    }

    #[test]
    fn test_query_builder_chainned_multi_element_selection_with_branching() {
        let query = Query::first("div", Save::all())
            .unwrap()
            .then(|ctx| {
                Ok([
                    ctx.all("a", Save::all())?,
                    ctx.first("p.note", Save::none())?,
                ])
            })
            .unwrap()
            .build();

        assert_eq!(query.queries.len(), 3);
        assert_eq!(query.queries[1].next_sibling, Some(QuerySectionId(2)));
        assert_eq!(query.queries[2].next_sibling, None);
    }
}
