use std::{fmt, sync::Arc};

use derive_more::From;
use lsp_types::Url;

use crate::{
    line_index::LineIndex,
    syntax::{
        bibtex, build_log,
        latex::{self, LatexAnalyzerContext},
    },
    DocumentLanguage, Environment,
};

#[derive(Debug, Clone)]
pub struct LatexDocumentData {
    pub green: rowan::GreenNode,
    pub extras: Arc<latex::Extras>,
}

#[derive(Debug, Clone)]
pub struct BibtexDocumentData {
    pub green: rowan::GreenNode,
}

#[derive(Debug, Clone, From)]
pub enum DocumentData {
    Latex(Box<LatexDocumentData>),
    Bibtex(BibtexDocumentData),
    BuildLog(Arc<build_log::Parse>),
}

impl DocumentData {
    #[must_use]
    pub fn language(&self) -> DocumentLanguage {
        match self {
            Self::Latex(_) => DocumentLanguage::Latex,
            Self::Bibtex(_) => DocumentLanguage::Bibtex,
            Self::BuildLog(_) => DocumentLanguage::BuildLog,
        }
    }

    #[must_use]
    pub fn as_latex(&self) -> Option<&LatexDocumentData> {
        if let Self::Latex(data) = self {
            Some(data)
        } else {
            None
        }
    }

    #[must_use]
    pub fn as_bibtex(&self) -> Option<&BibtexDocumentData> {
        if let Self::Bibtex(data) = self {
            Some(data)
        } else {
            None
        }
    }

    #[must_use]
    pub fn as_build_log(&self) -> Option<&build_log::Parse> {
        if let Self::BuildLog(v) = self {
            Some(v)
        } else {
            None
        }
    }
}

#[derive(Clone)]
pub struct Document {
    pub uri: Arc<Url>,
    pub text: Arc<String>,
    pub line_index: Arc<LineIndex>,
    pub data: DocumentData,
}

impl fmt::Debug for Document {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.uri)
    }
}

impl Document {
    #[must_use]
    pub fn parse(
        environment: &Environment,
        uri: Arc<Url>,
        text: Arc<String>,
        language: DocumentLanguage,
    ) -> Self {
        let line_index = Arc::new(LineIndex::new(&text));
        let data = match language {
            DocumentLanguage::Latex => {
                let green = latex::parse(&text).green;
                let root = latex::SyntaxNode::new_root(green.clone());

                let base_uri = match &environment.options.root_directory {
                    Some(root_dir) => {
                        let root_dir = environment.current_directory.join(&root_dir);
                        Url::from_directory_path(root_dir)
                            .map_or_else(|()| Arc::clone(&uri), Arc::new)
                    }
                    None => Arc::clone(&uri),
                };

                let mut context = LatexAnalyzerContext {
                    environment,
                    extras: latex::Extras::default(),
                    document_uri: Arc::clone(&uri),
                    base_uri,
                };
                latex::analyze(&mut context, &root);
                let extras = Arc::new(context.extras);
                DocumentData::Latex(Box::new(LatexDocumentData { green, extras }))
            }
            DocumentLanguage::Bibtex => {
                let green = bibtex::parse(&text);
                DocumentData::Bibtex(BibtexDocumentData { green })
            }
            DocumentLanguage::BuildLog => {
                let data = Arc::new(build_log::parse(&text));
                DocumentData::BuildLog(data)
            }
        };

        Self {
            uri,
            text,
            line_index,
            data,
        }
    }
}
