use serde::{Deserialize, Serialize};
use std::ops::RangeInclusive;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BlockType {
    Paragraph,
    Heading,
    BulletListItem,
    NumberedListItem,
    Todo,
    Quote,
    Divider,
    CodeBlock,
    PageReference,
    BlockReference,
    MediaPlaceholder,
    ProgressPlaceholder,
}

impl BlockType {
    pub fn from_editor_label(label: &str) -> Option<Self> {
        match label {
            "paragraph" => Some(Self::Paragraph),
            "heading" => Some(Self::Heading),
            "bullet_list_item" => Some(Self::BulletListItem),
            "numbered_list_item" => Some(Self::NumberedListItem),
            "todo" => Some(Self::Todo),
            "quote" => Some(Self::Quote),
            "divider" => Some(Self::Divider),
            "code_block" => Some(Self::CodeBlock),
            "page_reference" => Some(Self::PageReference),
            "block_reference" => Some(Self::BlockReference),
            "media_placeholder" => Some(Self::MediaPlaceholder),
            "progress_placeholder" => Some(Self::ProgressPlaceholder),
            _ => None,
        }
    }

    pub fn as_editor_label(&self) -> &'static str {
        match self {
            Self::Paragraph => "paragraph",
            Self::Heading => "heading",
            Self::BulletListItem => "bullet_list_item",
            Self::NumberedListItem => "numbered_list_item",
            Self::Todo => "todo",
            Self::Quote => "quote",
            Self::Divider => "divider",
            Self::CodeBlock => "code_block",
            Self::PageReference => "page_reference",
            Self::BlockReference => "block_reference",
            Self::MediaPlaceholder => "media_placeholder",
            Self::ProgressPlaceholder => "progress_placeholder",
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ReferenceKind {
    Page,
    Block,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BlockReference {
    pub kind: ReferenceKind,
    pub target_id: String,
    pub label: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct BlockContent {
    pub text: String,
    pub language: Option<String>,
    pub reference: Option<BlockReference>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DocumentBlock {
    pub id: String,
    pub block_type: BlockType,
    pub parent_id: Option<String>,
    pub indent: u16,
    pub collapsed: bool,
    pub heading_level: Option<u8>,
    pub checked: Option<bool>,
    pub content: BlockContent,
}

impl DocumentBlock {
    pub fn new(id: impl Into<String>, block_type: BlockType) -> Self {
        Self {
            id: id.into(),
            block_type,
            parent_id: None,
            indent: 0,
            collapsed: false,
            heading_level: None,
            checked: None,
            content: BlockContent::default(),
        }
    }

    pub fn with_parent(mut self, parent_id: impl Into<String>, indent: u16) -> Self {
        self.parent_id = Some(parent_id.into());
        self.indent = indent;
        self
    }

    pub fn with_heading_level(mut self, level: u8) -> Self {
        self.heading_level = Some(level);
        self
    }

    pub fn with_text(mut self, text: impl Into<String>) -> Self {
        self.content.text = text.into();
        self
    }

    pub fn with_checked(mut self, checked: bool) -> Self {
        self.checked = Some(checked);
        self
    }

    pub fn with_language(mut self, language: impl Into<String>) -> Self {
        self.content.language = Some(language.into());
        self
    }

    pub fn with_reference(
        mut self,
        kind: ReferenceKind,
        target_id: impl Into<String>,
        label: Option<String>,
    ) -> Self {
        self.content.reference = Some(BlockReference {
            kind,
            target_id: target_id.into(),
            label,
        });
        self
    }

    pub fn with_collapsed(mut self, collapsed: bool) -> Self {
        self.collapsed = collapsed;
        self
    }

    pub fn set_block_type(mut self, block_type: BlockType) -> Self {
        self.block_type = block_type;
        self
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct DocumentModel {
    blocks: Vec<DocumentBlock>,
}

impl DocumentModel {
    pub fn new(blocks: Vec<DocumentBlock>) -> Self {
        Self { blocks }
    }

    pub fn empty() -> Self {
        Self::default()
    }

    pub fn blocks(&self) -> &[DocumentBlock] {
        &self.blocks
    }

    pub(crate) fn blocks_mut(&mut self) -> &mut Vec<DocumentBlock> {
        &mut self.blocks
    }

    pub fn push(&mut self, block: DocumentBlock) {
        self.blocks.push(block);
    }

    pub fn block(&self, id: &str) -> Option<&DocumentBlock> {
        self.blocks.iter().find(|block| block.id == id)
    }

    pub(crate) fn block_mut(&mut self, id: &str) -> Option<&mut DocumentBlock> {
        self.blocks.iter_mut().find(|block| block.id == id)
    }

    pub fn contains_id(&self, id: &str) -> bool {
        self.block(id).is_some()
    }

    pub fn index_of(&self, id: &str) -> Option<usize> {
        self.blocks.iter().position(|block| block.id == id)
    }

    pub fn children_of<'a>(&'a self, parent_id: Option<&str>) -> Vec<&'a DocumentBlock> {
        self.blocks
            .iter()
            .filter(|block| block.parent_id.as_deref() == parent_id)
            .collect()
    }

    pub fn is_descendant_of(&self, block_id: &str, ancestor_id: &str) -> bool {
        let mut current_parent = self
            .block(block_id)
            .and_then(|block| block.parent_id.as_deref());
        while let Some(parent_id) = current_parent {
            if parent_id == ancestor_id {
                return true;
            }
            current_parent = self
                .block(parent_id)
                .and_then(|parent| parent.parent_id.as_deref());
        }
        false
    }

    pub fn subtree_range(&self, block_id: &str) -> Option<RangeInclusive<usize>> {
        let start = self.index_of(block_id)?;
        let mut end = start;
        for next_index in (start + 1)..self.blocks.len() {
            if self.is_descendant_of(&self.blocks[next_index].id, block_id) {
                end = next_index;
            } else {
                break;
            }
        }
        Some(start..=end)
    }
}
