use std::{io::Cursor, ops::RangeInclusive};

use ariadne::{Label, Report, ReportKind, Source};
use serde::Serialize;
use tsify_next::Tsify;

#[derive(Debug, Clone, Serialize, PartialEq, Eq, Tsify)]
#[tsify(into_wasm_abi)]
pub struct Completion {
    pub at: RangeInclusive<usize>,
    pub filter: Option<bool>,
    pub items: Vec<CompletionItem>,
}

impl Completion {
    pub fn new(at: RangeInclusive<usize>) -> Self {
        Self {
            at,
            filter: None,
            items: Vec::default(),
        }
    }

    pub fn add_item(&mut self, item: CompletionItem) {
        self.items.push(item);
    }

    pub fn add_items(&mut self, items: Vec<CompletionItem>) {
        self.items.extend(items);
    }

    pub fn filter(&mut self, filter: bool) {
        self.filter = Some(filter);
    }

    pub fn make_pretty(&self, hamelin: &String) -> Result<String, std::fmt::Error> {
        // Needed to make write!(string_buf, ...) work
        use std::fmt::Write;

        let input = hamelin.as_str();
        let source = Source::from(input);
        let mut string_buf = String::new();

        let mut colors = ariadne::ColorGenerator::default();
        let kind = ReportKind::Advice;
        let mut report =
            Report::build(kind, self.at.clone()).with_message("Completion suggestions to offer");

        for item in self.items.iter() {
            report = report.with_label(
                Label::new(self.at.clone())
                    .with_message(format!(
                        "{} {}({:?})",
                        item.label,
                        item.detail
                            .as_ref()
                            .map(|d| format!("[{}] ", d))
                            .unwrap_or_default(),
                        item.kind
                    ))
                    .with_color(colors.next()),
            );
        }

        if let Some(filter) = self.filter {
            report = report.with_note(format!("editor should filter: {}", filter))
        }

        let final_report = report.finish();
        let buf = Vec::new();
        let mut cursor = Cursor::new(buf);
        final_report
            .write(source, &mut cursor)
            .map_err(|_| std::fmt::Error)?;
        write!(string_buf, "{}", String::from_utf8_lossy(cursor.get_ref()))?;

        Ok(string_buf)
    }
}

#[derive(Debug, Clone, Serialize, PartialEq, Eq, Tsify)]
#[tsify(into_wasm_abi)]
pub struct CompletionItem {
    /// What to show
    pub label: String,
    /// What to actually put down
    pub apply_string: String,
    /// Small text next to the label.
    pub detail: Option<String>,
    /// Floating shit out to the right.
    pub description: Option<String>,
    /// Icon
    pub kind: CompletionItemKind,
    // TODO: Structure this, somehow?
    //       Yeeting back code-mirror snippet style doesn't seem right.
    pub snippet: Option<String>,
    /// Which section of text.
    pub section: Option<String>,
}

impl CompletionItem {
    pub fn new<S>(label: S) -> Self
    where
        S: Into<String> + Clone,
    {
        Self {
            label: label.clone().into(),
            apply_string: label.into(),
            detail: None,
            description: None,
            kind: CompletionItemKind::Text,
            snippet: None,
            section: None,
        }
    }

    pub fn with_apply_string<S>(mut self, apply_string: S) -> Self
    where
        S: Into<String>,
    {
        self.apply_string = apply_string.into();
        self
    }

    pub fn with_detail<S>(mut self, detail: S) -> Self
    where
        S: Into<String>,
    {
        self.detail = Some(detail.into());
        self
    }

    pub fn with_description<S>(mut self, description: S) -> Self
    where
        S: Into<String>,
    {
        self.description = Some(description.into());
        self
    }

    pub fn with_kind(mut self, kind: CompletionItemKind) -> Self {
        self.kind = kind;
        self
    }

    pub fn with_snippet(mut self, snippet: String) -> Self {
        self.snippet = Some(snippet);
        self
    }

    pub fn with_section(mut self, section: String) -> Self {
        self.section = Some(section);
        self
    }
}

#[derive(Debug, Clone, Serialize, PartialEq, Eq, Tsify)]
#[tsify(into_wasm_abi)]
pub enum CompletionItemKind {
    Text,
    Variable,
    Function,
    Command,
    Keyword,
}
