use crate::model::node::{ColumnAlignment, ReferenceType};
use crate::model::{Key, LineId, MaybeLineId, MaybeNodeId, NodeId};

#[derive(Clone, Debug, PartialEq)]
pub enum GraphNode {
    Empty,
    Document(Document),
    Section(Section),
    Quote(Quote),
    BulletList(BulletList),
    OrderedList(OrderedList),
    Leaf(Leaf),
    Raw(RawLeaf),
    HorizontalRule(HorizontalRule),
    Reference(Reference),
    Table(Table),
}

#[derive(Clone, Debug, PartialEq)]
pub struct Document {
    id: NodeId,

    child: MaybeNodeId,

    key: Key,
}

impl Document {
    pub fn id(&self) -> NodeId {
        self.id
    }

    pub fn key(&self) -> &Key {
        &self.key
    }

    pub fn child_id(&self) -> MaybeNodeId {
        self.child
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct Section {
    id: NodeId,

    prev: NodeId,
    next: MaybeNodeId,
    child: MaybeNodeId,

    line: LineId,
}

#[derive(Clone, Debug, PartialEq)]
pub struct Quote {
    id: NodeId,

    prev: NodeId,
    next: MaybeNodeId,
    child: MaybeNodeId,
}

impl Quote {
    pub fn id(&self) -> NodeId {
        self.id
    }

    pub fn child_id(&self) -> MaybeNodeId {
        self.child
    }

    pub fn next_id(&self) -> MaybeNodeId {
        self.next
    }
}

impl Section {
    pub fn id(&self) -> NodeId {
        self.id
    }

    pub fn line_id(&self) -> LineId {
        self.line
    }

    pub fn child_id(&self) -> MaybeNodeId {
        self.child
    }
    pub fn next_id(&self) -> MaybeNodeId {
        self.next
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct BulletList {
    id: NodeId,

    prev: NodeId,
    next: MaybeNodeId,
    child: MaybeNodeId,
}

#[derive(Clone, Debug, PartialEq)]
pub struct OrderedList {
    id: NodeId,

    prev: NodeId,
    next: MaybeNodeId,
    child: MaybeNodeId,
}

impl BulletList {
    pub fn id(&self) -> NodeId {
        self.id
    }

    pub fn child_id(&self) -> MaybeNodeId {
        self.child
    }
    pub fn next_id(&self) -> MaybeNodeId {
        self.next
    }
}

impl OrderedList {
    pub fn id(&self) -> NodeId {
        self.id
    }

    pub fn child_id(&self) -> MaybeNodeId {
        self.child
    }
    pub fn next_id(&self) -> MaybeNodeId {
        self.next
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct Leaf {
    id: NodeId,

    prev: NodeId,
    next: MaybeNodeId,

    line: LineId,
}

impl Leaf {
    pub fn line_id(&self) -> LineId {
        self.line
    }

    pub fn id(&self) -> NodeId {
        self.id
    }

    pub fn next_id(&self) -> MaybeNodeId {
        self.next
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct RawLeaf {
    id: NodeId,

    prev: NodeId,
    next: MaybeNodeId,

    lang: Option<String>,
    content: String,
}

#[derive(Clone, Debug, PartialEq)]
pub struct Table {
    id: NodeId,

    prev: NodeId,
    next: MaybeNodeId,

    header: Vec<LineId>,
    alignment: Vec<ColumnAlignment>,
    rows: Vec<Vec<LineId>>,
}

impl Table {
    pub fn id(&self) -> NodeId {
        self.id
    }

    pub fn header(&self) -> &Vec<LineId> {
        &self.header
    }

    pub fn alignment(&self) -> &Vec<ColumnAlignment> {
        &self.alignment
    }

    pub fn rows(&self) -> &Vec<Vec<LineId>> {
        &self.rows
    }

    pub fn next_id(&self) -> MaybeNodeId {
        self.next
    }
}

impl RawLeaf {
    pub fn lang(&self) -> Option<String> {
        self.lang.clone()
    }

    pub fn content(&self) -> &str {
        &self.content
    }

    pub fn next_id(&self) -> MaybeNodeId {
        self.next
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct HorizontalRule {
    id: NodeId,

    prev: NodeId,
    next: MaybeNodeId,
}
impl HorizontalRule {
    pub fn next_id(&self) -> MaybeNodeId {
        self.next
    }
}

#[derive(Clone, Debug, PartialEq)]
pub struct Reference {
    id: NodeId,

    prev: NodeId,
    next: MaybeNodeId,

    key: Key,
    text: String,
    reference_type: ReferenceType,
    url: String,
}

impl Reference {
    pub fn id(&self) -> NodeId {
        self.id
    }

    pub fn key(&self) -> &Key {
        &self.key
    }

    pub fn text(&self) -> &str {
        &self.text
    }

    pub fn url(&self) -> &str {
        &self.url
    }

    pub fn next_id(&self) -> MaybeNodeId {
        self.next
    }

    pub fn reference_type(&self) -> ReferenceType {
        self.reference_type
    }
}

impl GraphNode {
    pub fn prev_id(&self) -> MaybeNodeId {
        match self {
            GraphNode::Section(section) => Some(section.prev),
            GraphNode::Quote(quote) => Some(quote.prev),
            GraphNode::BulletList(list) => Some(list.prev),
            GraphNode::OrderedList(list) => Some(list.prev),
            GraphNode::Leaf(leaf) => Some(leaf.prev),
            GraphNode::Reference(reference) => Some(reference.prev),
            GraphNode::HorizontalRule(rule) => Some(rule.prev),
            GraphNode::Raw(raw) => Some(raw.prev),
            GraphNode::Table(table) => Some(table.prev),
            GraphNode::Document(_) => None,
            GraphNode::Empty => None,
        }
    }

    pub fn id(&self) -> NodeId {
        match self {
            GraphNode::Document(document) => document.id,
            GraphNode::Section(section) => section.id,
            GraphNode::Quote(quote) => quote.id,
            GraphNode::HorizontalRule(rule) => rule.id,
            GraphNode::BulletList(list) => list.id,
            GraphNode::OrderedList(list) => list.id,
            GraphNode::Leaf(leaf) => leaf.id,
            GraphNode::Raw(leaf) => leaf.id,
            GraphNode::Table(table) => table.id,
            GraphNode::Reference(reference) => reference.id,
            GraphNode::Empty => panic!(),
        }
    }

    pub fn is_ref(&self) -> bool {
        matches!(self, GraphNode::Reference(_))
    }
    pub fn is_empty(&self) -> bool {
        matches!(self, GraphNode::Empty)
    }

    pub fn is_leaf(&self) -> bool {
        matches!(
            self,
            GraphNode::Leaf(_)
                | GraphNode::Raw(_)
                | GraphNode::Reference(_)
                | GraphNode::HorizontalRule(_)
        )
    }

    pub fn is_table(&self) -> bool {
        matches!(self, GraphNode::Table(_))
    }

    pub fn is_ordered_list(&self) -> bool {
        matches!(self, GraphNode::OrderedList(_))
    }

    pub fn is_bullet_list(&self) -> bool {
        matches!(self, GraphNode::BulletList(_))
    }

    pub fn is_document(&self) -> bool {
        matches!(self, GraphNode::Document(_))
    }

    pub fn is_raw_leaf(&self) -> bool {
        matches!(self, GraphNode::Raw(_))
    }

    pub fn is_list(&self) -> bool {
        matches!(self, GraphNode::BulletList(_) | GraphNode::OrderedList(_))
    }

    pub fn is_quote(&self) -> bool {
        matches!(self, GraphNode::Quote(_))
    }

    pub fn is_rule(&self) -> bool {
        matches!(self, GraphNode::HorizontalRule(_))
    }

    pub fn is_section(&self) -> bool {
        matches!(self, GraphNode::Section(_))
    }

    pub fn is_reference(&self) -> bool {
        matches!(self, GraphNode::Reference(_))
    }

    pub fn is_horizontal_rule(&self) -> bool {
        matches!(self, GraphNode::HorizontalRule(_))
    }

    pub fn is_raw(&self) -> bool {
        matches!(self, GraphNode::Raw(_))
    }

    pub fn is_root(&self) -> bool {
        matches!(self, GraphNode::Document(_))
    }

    pub fn is_reference_to(&self, key: &Key) -> bool {
        match self {
            GraphNode::Reference(reference) => reference.key.eq(key),
            _ => false,
        }
    }

    pub fn line_id(&self) -> MaybeLineId {
        match self {
            GraphNode::Section(section) => Some(section.line),
            GraphNode::Leaf(leaf) => Some(leaf.line),
            _ => None,
        }
    }

    pub fn line_ids(&self) -> Vec<LineId> {
        match self {
            GraphNode::Section(section) => vec![section.line],
            GraphNode::Leaf(leaf) => vec![leaf.line],
            GraphNode::Table(table) => {
                let mut ids = table.header.clone();
                ids.extend(table.rows.iter().flatten().copied());
                ids
            }
            _ => Vec::new(),
        }
    }

    pub fn next_id(&self) -> MaybeNodeId {
        match self {
            GraphNode::Section(section) => section.next,
            GraphNode::Quote(quote) => quote.next,
            GraphNode::HorizontalRule(quote) => quote.next,
            GraphNode::BulletList(list) => list.next,
            GraphNode::OrderedList(list) => list.next,
            GraphNode::Leaf(leaf) => leaf.next,
            GraphNode::Raw(leaf) => leaf.next,
            GraphNode::Table(table) => table.next,
            GraphNode::Reference(reference) => reference.next,
            GraphNode::Document(_) => None,
            GraphNode::Empty => panic!(),
        }
    }

    pub fn to_symbol(&self) -> String {
        match self {
            GraphNode::Section(_) => "S",
            GraphNode::Quote(_) => "Q",
            GraphNode::HorizontalRule(_) => "R",
            GraphNode::BulletList(_) => "L",
            GraphNode::OrderedList(_) => "L",
            GraphNode::Leaf(_) => "F",
            GraphNode::Raw(_) => "C",
            GraphNode::Reference(_) => "R",
            GraphNode::Document(_) => "D",
            GraphNode::Table(_) => "T",
            GraphNode::Empty => "-",
        }
        .to_string()
    }

    pub fn child_id(&self) -> MaybeNodeId {
        match self {
            GraphNode::Document(document) => document.child,
            GraphNode::Section(section) => section.child,
            GraphNode::Quote(quote) => quote.child,
            GraphNode::BulletList(list) => list.child,
            GraphNode::OrderedList(list) => list.child,
            _ => None,
        }
    }

    pub fn is_parent_of(&self, other: NodeId) -> bool {
        self.child_id().is_some() && self.child_id().unwrap() == other
    }

    pub fn is_prev_of(&self, other: NodeId) -> bool {
        self.next_id().is_some() && self.next_id().unwrap() == other
    }

    pub fn set_next_id(&mut self, next: NodeId) {
        match self {
            GraphNode::Section(section) => section.next = Some(next),
            GraphNode::Quote(quote) => quote.next = Some(next),
            GraphNode::BulletList(list) => list.next = Some(next),
            GraphNode::OrderedList(list) => list.next = Some(next),
            GraphNode::Leaf(leaf) => leaf.next = Some(next),
            GraphNode::HorizontalRule(rule) => rule.next = Some(next),
            GraphNode::Raw(leaf) => leaf.next = Some(next),
            GraphNode::Table(table) => table.next = Some(next),
            GraphNode::Reference(reference) => reference.next = Some(next),
            GraphNode::Document(_) => panic!("cant set next for document"),
            GraphNode::Empty => panic!(),
        }
    }

    pub fn set_child_id(&mut self, child: NodeId) {
        match self {
            GraphNode::Document(document) => document.child = Some(child),
            GraphNode::Section(section) => section.child = Some(child),
            GraphNode::Quote(quote) => quote.child = Some(child),
            GraphNode::BulletList(list) => list.child = Some(child),
            GraphNode::OrderedList(list) => list.child = Some(child),
            GraphNode::Leaf(_) => panic!("cant set child for leaf"),
            GraphNode::Raw(_) => panic!("cant set child for raw"),
            GraphNode::Table(_) => panic!("cant set child for table"),
            GraphNode::HorizontalRule(_) => panic!("cant set child for rule"),
            GraphNode::Reference(_) => panic!("cant set child for reference"),
            GraphNode::Empty => panic!(),
        }
    }

    pub fn insertable(&self) -> bool {
        match self {
            GraphNode::Document(_) => true,
            GraphNode::Section(_) => true,
            GraphNode::Quote(_) => true,
            GraphNode::BulletList(_) => true,
            GraphNode::OrderedList(_) => true,
            GraphNode::Leaf(_) => false,
            GraphNode::Raw(_) => false,
            GraphNode::HorizontalRule(_) => false,
            GraphNode::Reference(_) => false,
            GraphNode::Table(_) => false,
            GraphNode::Empty => false,
        }
    }

    pub fn new_leaf(prev: NodeId, id: NodeId, line: LineId) -> GraphNode {
        GraphNode::Leaf(Leaf {
            id,
            prev,
            next: None,
            line,
        })
    }

    pub fn new_raw_leaf(
        prev: NodeId,
        id: NodeId,
        content: String,
        lang: Option<String>,
    ) -> GraphNode {
        GraphNode::Raw(RawLeaf {
            id,
            prev,
            next: None,
            lang,
            content,
        })
    }

    pub fn new_table(
        prev: NodeId,
        id: NodeId,
        header: Vec<LineId>,
        alignment: Vec<ColumnAlignment>,
        rows: Vec<Vec<LineId>>,
    ) -> GraphNode {
        GraphNode::Table(Table {
            id,
            prev,
            next: None,
            header,
            alignment,
            rows,
        })
    }

    pub fn new_ref(
        prev: NodeId,
        id: NodeId,
        key: Key,
        text: String,
        reference_type: ReferenceType,
        url: String,
    ) -> GraphNode {
        GraphNode::Reference(Reference {
            id,
            prev,
            next: None,
            key,
            text,
            reference_type,
            url,
        })
    }

    pub fn new_bullet_list(prev: NodeId, id: NodeId) -> GraphNode {
        GraphNode::BulletList(BulletList {
            id,
            prev,
            next: None,
            child: None,
        })
    }

    pub fn new_ordered_list(prev: NodeId, id: NodeId) -> GraphNode {
        GraphNode::OrderedList(OrderedList {
            id,
            prev,
            next: None,
            child: None,
        })
    }

    pub fn new_quote(prev: NodeId, id: NodeId) -> GraphNode {
        GraphNode::Quote(Quote {
            id,
            prev,
            next: None,
            child: None,
        })
    }

    pub fn new_rule(prev: NodeId, id: NodeId) -> GraphNode {
        GraphNode::HorizontalRule(HorizontalRule {
            id,
            prev,
            next: None,
        })
    }

    pub fn new_section(prev: NodeId, id: NodeId, line: LineId) -> GraphNode {
        GraphNode::Section(Section {
            id,
            prev,
            next: None,
            child: None,
            line,
        })
    }
    pub fn new_root(key: Key, id: NodeId) -> GraphNode {
        GraphNode::Document(Document {
            id,
            child: None,
            key,
        })
    }

    pub fn is_ordered(&self) -> bool {
        match self {
            GraphNode::BulletList(_) => false,
            GraphNode::OrderedList(_) => true,
            _ => false,
        }
    }

    pub fn key(&self) -> Option<Key> {
        match self {
            GraphNode::Document(document) => Some(document.key.clone()),
            _ => None,
        }
    }

    pub fn content(&self) -> Option<String> {
        match self {
            GraphNode::Raw(document) => Some(document.content.clone()),
            _ => None,
        }
    }

    pub fn lang(&self) -> Option<String> {
        match self {
            GraphNode::Raw(document) => document.lang.clone(),
            _ => None,
        }
    }

    pub fn table_header(&self) -> Option<Vec<LineId>> {
        match self {
            GraphNode::Table(table) => Some(table.header.clone()),
            _ => None,
        }
    }

    pub fn table_rows(&self) -> Option<Vec<Vec<LineId>>> {
        match self {
            GraphNode::Table(table) => Some(table.rows.clone()),
            _ => None,
        }
    }

    pub fn table_alignment(&self) -> Option<Vec<ColumnAlignment>> {
        match self {
            GraphNode::Table(table) => Some(table.alignment.clone()),
            _ => None,
        }
    }

    pub fn ref_key(&self) -> Option<Key> {
        match self {
            GraphNode::Reference(reference) => Some(reference.key.clone()),
            _ => None,
        }
    }

    pub fn ref_type(&self) -> Option<ReferenceType> {
        match self {
            GraphNode::Reference(reference) => Some(reference.reference_type),
            _ => None,
        }
    }

    pub fn ref_text(&self) -> String {
        match self {
            GraphNode::Reference(reference) => reference.text.clone(),
            _ => panic!(),
        }
    }
}
