use std::collections::HashMap;

use sable_markdown::render::UrlRewriter;
use sable_vault::{SharedVault, Vault};
use tera::Filter;

use crate::templates::FilterError;

pub(super) struct Markdown {
    vault: SharedVault,
}

impl Markdown {
    pub(super) const fn new(vault: SharedVault) -> Self {
        Self { vault }
    }
}

impl Filter for Markdown {
    fn filter(
        &self,
        value: &tera::Value,
        _args: &HashMap<String, tera::Value>,
    ) -> tera::Result<tera::Value> {
        let tera::Value::String(raw) = value else {
            return Err(tera::Error::call_filter(
                "markdown",
                FilterError::new("filter only accepts string as an imput"),
            ));
        };

        let root = match sable_markdown::parser::parse_markdown(raw) {
            Ok(root) => root,
            Err(err) => {
                return Err(tera::Error::call_filter("markdown", FilterError::new(err)));
            }
        };

        let vault = self.vault.read();

        let config = sable_markdown::render::Config::default()
            .with_link_rewriter(LinkRewriter { _vault: &vault })
            .with_wikilink_rewriter(WikiLinkRewriter { vault: &vault });

        let rendered = sable_markdown::render::render_html(&root, &config);

        drop(config);
        drop(vault);

        Ok(tera::Value::String(rendered))
    }
}

struct LinkRewriter<'v> {
    _vault: &'v Vault,
}

impl UrlRewriter for LinkRewriter<'_> {
    fn rewrite(&self, path: &str, _target: Option<&str>) -> String {
        if path.starts_with("ftp") || path.starts_with("http") || path.starts_with("mailto") {
            return path.to_string();
        }

        crate::slugify_path(path)
    }
}

struct WikiLinkRewriter<'v> {
    vault: &'v Vault,
}

impl UrlRewriter for WikiLinkRewriter<'_> {
    fn rewrite(&self, path: &str, target: Option<&str>) -> String {
        if path.starts_with("ftp") || path.starts_with("http") || path.starts_with("mailto") {
            return path.to_string();
        }

        let Some((path, _note)) = self
            .vault
            .find_note_by_title(path)
            .or_else(|| self.vault.find_note_by_name(path))
        else {
            tracing::warn!(path=?path, target=?target, "unknown wikilink note");

            return path.to_string();
        };

        let path = path.slug.with_extension("html");

        format!("/{}", url_escape::encode_path(path.as_str()))
    }
}
