use std::collections::HashMap;

use super::{Generator, Resolver};
use crate::documentation::{Method, Property};
use pulldown_cmark::{CowStr, Event, Tag};

/// Callbacks to encode markdown input in a given format.
///
/// This trait should be implemented if you want to make your own backend.
pub trait Callbacks {
    /// File extension for the files generated by this callback.
    fn extension(&self) -> &'static str;
    /// Drive the generation process.
    ///
    /// This returns a map from file names (relative to the output directory) to
    /// their contents.
    ///
    /// You can find inspiration about how to implement this in the source code, for
    /// example in `src/backend/html.rs`.
    fn generate_files(&mut self, generator: Generator) -> HashMap<String, String>;
    /// Called before encoding each method.
    ///
    /// **Default**: does nothing
    fn start_method(&mut self, _s: &mut String, _resolver: &Resolver, _method: &Method) {}
    /// Called before encoding each property.
    ///
    /// **Default**: does nothing
    fn start_property(&mut self, _s: &mut String, _resolver: &Resolver, _property: &Property) {}
    /// Encode the stream of `events` in `s`.
    fn encode(&mut self, s: &mut String, events: Vec<Event<'_>>);
}

impl dyn Callbacks {
    /// Default start_method implementation, implemented on `dyn Callbacks` to avoid
    /// code duplication.
    ///
    /// This will create a level 3 header that looks like (in markdown):
    /// ```markdown
    /// ### <a id="func-name"></a>func name(arg1: [type](link), ...) -> [type](link)
    /// ________
    /// ```
    ///
    /// With appropriate linking.
    pub fn start_method_default(&mut self, s: &mut String, property: &Resolver, method: &Method) {
        let link = &format!("<a id=\"func-{}\"></a>", method.name);
        self.encode(
            s,
            vec![
                Event::Start(Tag::Heading(3)),
                Event::Html(CowStr::Borrowed(link)),
            ],
        );
        let mut method_header = String::from("func ");
        method_header.push_str(&method.name);
        method_header.push('(');
        for (index, (name, typ, _)) in method.parameters.iter().enumerate() {
            method_header.push_str(&name);
            method_header.push_str(": ");
            self.encode(s, vec![Event::Text(CowStr::Borrowed(&method_header))]);
            method_header.clear();
            self.encode(s, property.encode_type(typ));
            if index + 1 != method.parameters.len() {
                method_header.push_str(", ");
            }
        }
        method_header.push_str(") -> ");
        let mut last_events = vec![Event::Text(CowStr::Borrowed(&method_header))];
        last_events.extend(property.encode_type(&method.return_type));
        last_events.push(Event::End(Tag::Heading(3)));
        last_events.push(Event::Rule);
        self.encode(s, last_events);
    }

    /// Default start_property implementation, implemented on `dyn Callbacks` to avoid
    /// code duplication.
    ///
    /// This will create a level 3 header that looks like (in markdown):
    /// ```markdown
    /// ### <a id="property-name"></a> name: [type](link)
    /// ________
    /// ```
    ///
    /// With appropriate linking.
    pub fn start_property_default(
        &mut self,
        s: &mut String,
        resolver: &Resolver,
        property: &Property,
    ) {
        let link = &format!(
            "<a id=\"property-{}\"></a> {}: ",
            property.name, property.name
        );
        self.encode(
            s,
            vec![
                Event::Start(Tag::Heading(3)),
                Event::Html(CowStr::Borrowed(link)),
            ],
        );
        let mut last_events = resolver.encode_type(&property.typ);
        last_events.push(Event::End(Tag::Heading(3)));
        last_events.push(Event::Rule);
        self.encode(s, last_events);
    }
}

impl std::fmt::Debug for dyn Callbacks {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        formatter.write_str("Callbacks")
    }
}
