use std::marker::PhantomData;

use super::ast_node::AstNode;
use super::macros::children_iter;
use super::macros::empty_iter;
use super::macros::multi_field_iter;
use crate::types::Bold;
use crate::types::Code;
use crate::types::Document;
use crate::types::DocumentElement;
use crate::types::Element;
use crate::types::Heading;
use crate::types::Italic;
use crate::types::Object;
use crate::types::Paragraph;
use crate::types::PlainList;
use crate::types::PlainListItem;
use crate::types::PlainText;
use crate::types::RadioLink;
use crate::types::RegularLink;
use crate::types::Section;
use crate::types::StrikeThrough;
use crate::types::Underline;
use crate::types::Verbatim;

/// Iterator over the AST nodes contained within the starting node.
///
/// This only iterates over the children, not the starting node itself. So an AstNodeIter::PlainList would only return PlainListItems, not the PlainList.
///
/// This only iterates over AST nodes, so an AstNodeIter::Heading would iterate over both the title and section contents, but it would not iterate over simple strings like the TODO keyword or priority.
pub enum AstNodeIter<'r, 's> {
    // Document Nodes
    Document(DocumentIter<'r, 's>),
    Heading(HeadingIter<'r, 's>),
    Section(SectionIter<'r, 's>),
    // Elements
    Paragraph(ParagraphIter<'r, 's>),
    PlainList(PlainListIter<'r, 's>),
    PlainListItem(PlainListItemIter<'r, 's>),
    // GreaterBlock(GreaterBlockIter<'r, 's>),
    // DynamicBlock(DynamicBlockIter<'r, 's>),
    // FootnoteDefinition(FootnoteDefinitionIter<'r, 's>),
    // Comment(CommentIter<'r, 's>),
    // Drawer(DrawerIter<'r, 's>),
    // PropertyDrawer(PropertyDrawerIter<'r, 's>),
    // Table(TableIter<'r, 's>),
    // VerseBlock(VerseBlockIter<'r, 's>),
    // CommentBlock(CommentBlockIter<'r, 's>),
    // ExampleBlock(ExampleBlockIter<'r, 's>),
    // ExportBlock(ExportBlockIter<'r, 's>),
    // SrcBlock(SrcBlockIter<'r, 's>),
    // Clock(ClockIter<'r, 's>),
    // DiarySexp(DiarySexpIter<'r, 's>),
    // Planning(PlanningIter<'r, 's>),
    // FixedWidthArea(FixedWidthAreaIter<'r, 's>),
    // HorizontalRule(HorizontalRuleIter<'r, 's>),
    // Keyword(KeywordIter<'r, 's>),
    // BabelCall(BabelCallIter<'r, 's>),
    // LatexEnvironment(LatexEnvironmentIter<'r, 's>),
    // Objects
    Bold(BoldIter<'r, 's>),
    Italic(ItalicIter<'r, 's>),
    Underline(UnderlineIter<'r, 's>),
    StrikeThrough(StrikeThroughIter<'r, 's>),
    Code(CodeIter<'r, 's>),
    Verbatim(VerbatimIter<'r, 's>),
    PlainText(PlainTextIter<'r, 's>),
    RegularLink(RegularLinkIter<'r, 's>),
    RadioLink(RadioLinkIter<'r, 's>),
    // RadioTarget(RadioTargetIter<'r, 's>),
    // PlainLink(PlainLinkIter<'r, 's>),
    // AngleLink(AngleLinkIter<'r, 's>),
    // OrgMacro(OrgMacroIter<'r, 's>),
    // Entity(EntityIter<'r, 's>),
    // LatexFragment(LatexFragmentIter<'r, 's>),
    // ExportSnippet(ExportSnippetIter<'r, 's>),
    // FootnoteReference(FootnoteReferenceIter<'r, 's>),
    // Citation(CitationIter<'r, 's>),
    // CitationReference(CitationReferenceIter<'r, 's>),
    // InlineBabelCall(InlineBabelCallIter<'r, 's>),
    // InlineSourceBlock(InlineSourceBlockIter<'r, 's>),
    // LineBreak(LineBreakIter<'r, 's>),
    // Target(TargetIter<'r, 's>),
    // StatisticsCookie(StatisticsCookieIter<'r, 's>),
    // Subscript(SubscriptIter<'r, 's>),
    // Superscript(SuperscriptIter<'r, 's>),
    // Timestamp(TimestampIter<'r, 's>),
}

pub struct DocumentIter<'r, 's> {
    zeroth_section_next: std::option::Iter<'r, Section<'s>>,
    children_next: std::slice::Iter<'r, Heading<'s>>,
}

impl<'r, 's> Iterator for DocumentIter<'r, 's> {
    type Item = AstNode<'r, 's>;

    fn next(&mut self) -> Option<Self::Item> {
        self.zeroth_section_next
            .next()
            .map(Into::<AstNode>::into)
            .or_else(|| self.children_next.next().map(Into::<AstNode>::into))
    }
}

impl<'r, 's> IntoIterator for &'r Document<'s> {
    type Item = AstNode<'r, 's>;

    type IntoIter = DocumentIter<'r, 's>;

    fn into_iter(self) -> Self::IntoIter {
        DocumentIter {
            zeroth_section_next: self.zeroth_section.iter(),
            children_next: self.children.iter(),
        }
    }
}

pub struct HeadingIter<'r, 's> {
    title_next: std::slice::Iter<'r, Object<'s>>,
    children_next: std::slice::Iter<'r, DocumentElement<'s>>,
}

impl<'r, 's> Iterator for HeadingIter<'r, 's> {
    type Item = AstNode<'r, 's>;

    fn next(&mut self) -> Option<Self::Item> {
        self.title_next
            .next()
            .map(Into::<AstNode>::into)
            .or_else(|| self.children_next.next().map(Into::<AstNode>::into))
    }
}

impl<'r, 's> IntoIterator for &'r Heading<'s> {
    type Item = AstNode<'r, 's>;

    type IntoIter = HeadingIter<'r, 's>;

    fn into_iter(self) -> Self::IntoIter {
        HeadingIter {
            title_next: self.title.iter(),
            children_next: self.children.iter(),
        }
    }
}

children_iter!(Section<'s>, SectionIter, std::slice::Iter<'r, Element<'s>>);
children_iter!(
    Paragraph<'s>,
    ParagraphIter,
    std::slice::Iter<'r, Object<'s>>
);
children_iter!(
    PlainList<'s>,
    PlainListIter,
    std::slice::Iter<'r, PlainListItem<'s>>
);

multi_field_iter!(
    PlainListItem<'s>,
    PlainListItemIter,
    tag,
    std::slice::Iter<'r, Object<'s>>,
    children,
    std::slice::Iter<'r, Element<'s>>
);
children_iter!(Bold<'s>, BoldIter, std::slice::Iter<'r, Object<'s>>);
children_iter!(Italic<'s>, ItalicIter, std::slice::Iter<'r, Object<'s>>);
children_iter!(
    Underline<'s>,
    UnderlineIter,
    std::slice::Iter<'r, Object<'s>>
);
children_iter!(
    StrikeThrough<'s>,
    StrikeThroughIter,
    std::slice::Iter<'r, Object<'s>>
);
empty_iter!(Code<'s>, CodeIter);
empty_iter!(Verbatim<'s>, VerbatimIter);
empty_iter!(PlainText<'s>, PlainTextIter);
empty_iter!(RegularLink<'s>, RegularLinkIter);
children_iter!(
    RadioLink<'s>,
    RadioLinkIter,
    std::slice::Iter<'r, Object<'s>>
);