use anyhow::Result;
use heck::*;
use pulldown_cmark::{Event, LinkType, Parser, Tag, TagEnd, html};
use std::collections::HashMap;
use std::fmt::Write;
use wit_bindgen_core::{
    Files, InterfaceGenerator as _, Source, WorldGenerator, uwriteln, wit_parser,
};
use wit_parser::*;

#[derive(Default)]
struct Markdown {
    src: Source,
    opts: Opts,
    hrefs: HashMap<String, String>,
    sizes: SizeAlign,
}

#[derive(Default, Debug, Clone)]
#[cfg_attr(feature = "clap", derive(clap::Args))]
pub struct Opts {
    /// Output a `.md` file containing HTML.
    ///
    /// This can be useful when producing files to be displayed on Github,
    /// as it doesn't render HTML files, but it does render Markdown files,
    /// which can contain HTML.
    #[cfg_attr(feature = "clap", arg(long))]
    html_in_md: bool,
}

impl Opts {
    pub fn build(&self) -> Box<dyn WorldGenerator> {
        let mut r = Markdown::default();
        r.opts = self.clone();
        Box::new(r)
    }
}

impl WorldGenerator for Markdown {
    fn preprocess(&mut self, resolve: &Resolve, world: WorldId) -> Result<()> {
        self.sizes.fill(resolve);

        let world = &resolve.worlds[world];
        uwriteln!(
            self.src,
            "# <a id=\"{}\"></a>World {}\n",
            world.name.to_snake_case(),
            world.name
        );
        self.hrefs.insert(
            world.name.to_string(),
            format!("#{}", world.name.to_snake_case()),
        );

        let mut r#gen = self.interface(resolve);

        r#gen.docs(&world.docs);
        r#gen.push_str("\n");

        // Produce a table of contents for the world.
        let mut first = true;
        for (name, import) in &world.imports {
            if first {
                r#gen.push_str(" - Imports:\n");
                first = false;
            }
            let name = &resolve.name_world_key(name);
            match import {
                WorldItem::Interface { .. } => {
                    r#gen.push_str("    - interface `");
                    r#gen.push_str(name);
                    r#gen.push_str("`\n");
                }
                WorldItem::Function(_) => {
                    r#gen.push_str("    - function `");
                    r#gen.push_str(name);
                    r#gen.push_str("`\n");
                }
                WorldItem::Type { .. } => {
                    r#gen.push_str("    - type `");
                    r#gen.push_str(name);
                    r#gen.push_str("`\n");
                }
            }
        }
        let mut first = true;
        for (name, export) in &world.exports {
            if first {
                r#gen.push_str(" - Exports:\n");
                first = false;
            }
            let name = &resolve.name_world_key(name);
            match export {
                WorldItem::Interface { .. } => {
                    r#gen.push_str("    - interface `");
                    r#gen.push_str(name);
                    r#gen.push_str("`\n");
                }
                WorldItem::Function(_) => {
                    r#gen.push_str("    - function `");
                    r#gen.push_str(name);
                    r#gen.push_str("`\n");
                }
                WorldItem::Type { .. } => {
                    r#gen.push_str("    - type `");
                    r#gen.push_str(name);
                    r#gen.push_str("`\n");
                }
            }
        }
        r#gen.push_str("\n");
        Ok(())
    }

    fn import_interface(
        &mut self,
        resolve: &Resolve,
        name: &WorldKey,
        id: InterfaceId,
        _files: &mut Files,
    ) -> Result<()> {
        let name = resolve.name_world_key(name);
        uwriteln!(
            self.src,
            "## <a id=\"{}\"></a>Import interface {name}\n",
            name.to_snake_case()
        );
        self.hrefs
            .insert(name.to_string(), format!("#{}", name.to_snake_case()));
        let mut r#gen = self.interface(resolve);
        r#gen.docs(&resolve.interfaces[id].docs);
        r#gen.push_str("\n");
        r#gen.types(id);
        r#gen.funcs(id);

        Ok(())
    }

    fn import_funcs(
        &mut self,
        resolve: &Resolve,
        world: WorldId,
        funcs: &[(&str, &Function)],
        _files: &mut Files,
    ) {
        let name = &resolve.worlds[world].name;
        uwriteln!(self.src, "## Imported functions to world `{name}`\n");
        let mut r#gen = self.interface(resolve);
        for (_, func) in funcs {
            r#gen.func(func);
        }
    }

    fn export_interface(
        &mut self,
        resolve: &Resolve,
        name: &WorldKey,
        id: InterfaceId,
        _files: &mut Files,
    ) -> Result<()> {
        let name = resolve.name_world_key(name);
        uwriteln!(
            self.src,
            "## <a id=\"{}\"></a>Export interface {name}\n",
            name.to_snake_case()
        );
        self.hrefs
            .insert(name.to_string(), format!("#{}", name.to_snake_case()));
        let mut r#gen = self.interface(resolve);
        r#gen.types(id);
        r#gen.funcs(id);
        Ok(())
    }

    fn export_funcs(
        &mut self,
        resolve: &Resolve,
        world: WorldId,
        funcs: &[(&str, &Function)],
        _files: &mut Files,
    ) -> Result<()> {
        let name = &resolve.worlds[world].name;
        uwriteln!(self.src, "## Exported functions from world `{name}`\n");
        let mut r#gen = self.interface(resolve);
        for (_, func) in funcs {
            r#gen.func(func);
        }
        Ok(())
    }

    fn import_types(
        &mut self,
        resolve: &Resolve,
        world: WorldId,
        types: &[(&str, TypeId)],
        _files: &mut Files,
    ) {
        let name = &resolve.worlds[world].name;
        uwriteln!(self.src, "## Exported types from world `{name}`\n");
        let mut r#gen = self.interface(resolve);
        for (name, ty) in types {
            r#gen.define_type(name, *ty);
        }
    }

    fn finish(&mut self, resolve: &Resolve, world: WorldId, files: &mut Files) -> Result<()> {
        let world = &resolve.worlds[world];
        let parser = Parser::new(&self.src);
        let mut events = Vec::new();
        // Named types are already written as links by `print_ty`, so track
        // whether we're inside one: wrapping that code span again would nest
        // an `<a>` inside an `<a>`, which isn't valid html. Markdown links
        // can't nest, so a bool is enough here.
        let mut in_link = false;
        for event in parser {
            match &event {
                Event::Start(Tag::Link { .. }) => in_link = true,
                Event::End(TagEnd::Link) => in_link = false,
                Event::Code(code) if !in_link => {
                    if let Some(dst) = self.hrefs.get(code.as_ref()) {
                        let tag = Tag::Link {
                            link_type: LinkType::Inline,
                            dest_url: dst.as_str().into(),
                            title: "".into(),
                            id: "".into(),
                        };
                        events.push(Event::Start(tag.clone()));
                        events.push(event.clone());
                        events.push(Event::End(tag.into()));
                        continue;
                    }
                }
                _ => {}
            }
            events.push(event);
        }
        let mut html_output = String::new();
        html::push_html(&mut html_output, events.into_iter());

        if self.opts.html_in_md {
            // Write the html output into a .md file.
            files.push(&format!("{}.md", world.name), html_output.as_bytes());
        } else {
            // Write the html output to an html file, and md output to a md file.
            files.push(&format!("{}.md", world.name), self.src.as_bytes());
            files.push(&format!("{}.html", world.name), html_output.as_bytes());
        }

        Ok(())
    }
}

impl Markdown {
    fn interface<'a>(&'a mut self, resolve: &'a Resolve) -> InterfaceGenerator<'a> {
        InterfaceGenerator {
            r#gen: self,
            resolve,
            types_header_printed: false,
        }
    }
}

struct InterfaceGenerator<'a> {
    r#gen: &'a mut Markdown,
    resolve: &'a Resolve,
    types_header_printed: bool,
}

impl InterfaceGenerator<'_> {
    fn funcs(&mut self, id: InterfaceId) {
        let iface = &self.resolve.interfaces[id];
        if iface.functions.is_empty() {
            return;
        }
        self.push_str("----\n\n");
        self.push_str("### Functions\n\n");
        for (_name, func) in iface.functions.iter() {
            self.func(func);
        }
    }

    fn func(&mut self, func: &Function) {
        self.push_str(&format!(
            "#### <a id=\"{0}\"></a>`",
            func.name.to_snake_case()
        ));
        self.r#gen
            .hrefs
            .insert(func.name.clone(), format!("#{}", func.name.to_snake_case()));
        self.push_str(&func.name);
        self.push_str(": func`");
        self.push_str("\n\n");
        self.docs(&func.docs);

        if func.params.len() > 0 {
            self.push_str("\n");
            self.push_str("##### Params\n\n");
            for Param { name, ty, .. } in func.params.iter() {
                self.push_str(&format!(
                    "- <a id=\"{f}.{p}\"></a>`{}`: ",
                    name,
                    f = func.name.to_snake_case(),
                    p = name.to_snake_case(),
                ));
                self.print_ty(ty);
                self.push_str("\n");
            }
        }

        if let Some(ty) = &func.result {
            self.push_str("\n##### Return values\n\n");
            self.push_str(&format!(
                "- <a id=\"{f}.0\"></a> ",
                f = func.name.to_snake_case(),
            ));
            self.print_ty(ty);
            self.push_str("\n");
        }

        self.push_str("\n");
    }

    fn push_str(&mut self, s: &str) {
        self.r#gen.src.push_str(s);
    }

    fn print_ty(&mut self, ty: &Type) {
        match ty {
            Type::Bool => self.push_str("`bool`"),
            Type::U8 => self.push_str("`u8`"),
            Type::S8 => self.push_str("`s8`"),
            Type::U16 => self.push_str("`u16`"),
            Type::S16 => self.push_str("`s16`"),
            Type::U32 => self.push_str("`u32`"),
            Type::S32 => self.push_str("`s32`"),
            Type::U64 => self.push_str("`u64`"),
            Type::S64 => self.push_str("`s64`"),
            Type::F32 => self.push_str("`f32`"),
            Type::F64 => self.push_str("`f64`"),
            Type::Char => self.push_str("`char`"),
            Type::String => self.push_str("`string`"),
            Type::ErrorContext => self.push_str("`error-context`"),
            Type::Id(id) => {
                let ty = &self.resolve.types[*id];
                if let Some(name) = &ty.name {
                    self.push_str("[`");
                    self.push_str(name);
                    self.push_str("`](#");
                    self.push_str(&name.to_snake_case());
                    self.push_str(")");
                    return;
                }
                match &ty.kind {
                    TypeDefKind::Type(t) => self.print_ty(t),
                    TypeDefKind::Tuple(t) => {
                        self.push_str("(");
                        for (i, t) in t.types.iter().enumerate() {
                            if i > 0 {
                                self.push_str(", ");
                            }
                            self.print_ty(t);
                        }
                        self.push_str(")");
                    }
                    TypeDefKind::Record(_)
                    | TypeDefKind::Resource
                    | TypeDefKind::Flags(_)
                    | TypeDefKind::Enum(_)
                    | TypeDefKind::Variant(_) => {
                        // These types are always named, so we will have
                        // printed the name above, so we don't need to print
                        // the contents.
                        assert!(ty.name.is_some());
                    }
                    TypeDefKind::Option(t) => {
                        self.push_str("option<");
                        self.print_ty(t);
                        self.push_str(">");
                    }
                    TypeDefKind::Result(r) => match (r.ok, r.err) {
                        (Some(ok), Some(err)) => {
                            self.push_str("result<");
                            self.print_ty(&ok);
                            self.push_str(", ");
                            self.print_ty(&err);
                            self.push_str(">");
                        }
                        (None, Some(err)) => {
                            self.push_str("result<_, ");
                            self.print_ty(&err);
                            self.push_str(">");
                        }
                        (Some(ok), None) => {
                            self.push_str("result<");
                            self.print_ty(&ok);
                            self.push_str(">");
                        }
                        (None, None) => {
                            self.push_str("result");
                        }
                    },
                    TypeDefKind::List(t) => {
                        self.push_str("list<");
                        self.print_ty(t);
                        self.push_str(">");
                    }
                    TypeDefKind::Future(t) => match t {
                        Some(t) => {
                            self.push_str("future<");
                            self.print_ty(t);
                            self.push_str(">");
                        }
                        None => {
                            self.push_str("future");
                        }
                    },
                    TypeDefKind::Stream(t) => match t {
                        Some(t) => {
                            self.push_str("stream<");
                            self.print_ty(t);
                            self.push_str(">");
                        }
                        None => {
                            self.push_str("stream");
                        }
                    },
                    TypeDefKind::Handle(Handle::Own(ty)) => {
                        self.push_str("own<");
                        self.print_ty(&Type::Id(*ty));
                        self.push_str(">");
                    }
                    TypeDefKind::Handle(Handle::Borrow(ty)) => {
                        self.push_str("borrow<");
                        self.print_ty(&Type::Id(*ty));
                        self.push_str(">");
                    }
                    TypeDefKind::Unknown => unreachable!(),
                    TypeDefKind::FixedLengthList(..) => todo!(),
                    TypeDefKind::Map(key, value) => {
                        self.push_str("map<");
                        self.print_ty(key);
                        self.push_str(", ");
                        self.print_ty(value);
                        self.push_str(">");
                    }
                }
            }
        }
    }

    fn docs(&mut self, docs: &Docs) {
        let docs = match &docs.contents {
            Some(docs) => docs,
            None => "\n",
        };
        for line in docs.lines() {
            self.r#gen.src.push_str_literal(line.trim());
            self.push_str("\n");
        }
    }

    fn print_type_header(&mut self, type_: &str, name: &str) {
        if !self.types_header_printed {
            self.push_str("----\n\n");
            self.push_str("### Types\n\n");
            self.types_header_printed = true;
        }
        self.push_str(&format!(
            "#### <a id=\"{}\"></a>`{} {}`\n",
            name.to_snake_case(),
            type_,
            name,
        ));
        self.r#gen
            .hrefs
            .insert(name.to_string(), format!("#{}", name.to_snake_case()));
    }
}

impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> {
    fn resolve(&self) -> &'a Resolve {
        self.resolve
    }

    fn type_record(&mut self, _id: TypeId, name: &str, record: &Record, docs: &Docs) {
        self.print_type_header("record", name);
        self.push_str("\n");
        self.docs(docs);
        self.push_str("\n##### Record Fields\n\n");
        for field in record.fields.iter() {
            self.push_str(&format!(
                "- <a id=\"{r}.{f}\"></a>`{name}`: ",
                r = name.to_snake_case(),
                f = field.name.to_snake_case(),
                name = field.name,
            ));
            self.r#gen.hrefs.insert(
                format!("{}::{}", name, field.name),
                format!("#{}.{}", name.to_snake_case(), field.name.to_snake_case()),
            );
            self.print_ty(&field.ty);
            if field.docs.contents.is_some() {
                self.r#gen.src.indent(1);
                self.push_str("\n<p>");
                self.docs(&field.docs);
                self.r#gen.src.deindent(1);
            }
            self.push_str("\n");
        }
        self.push_str("\n");
    }

    fn type_resource(&mut self, _id: TypeId, name: &str, docs: &Docs) {
        self.print_type_header("resource", name);
        self.push_str("\n");
        self.docs(docs);
    }

    fn type_tuple(&mut self, _id: TypeId, name: &str, tuple: &Tuple, docs: &Docs) {
        self.print_type_header("tuple", name);
        self.push_str("\n");
        self.docs(docs);
        self.push_str("\n##### Tuple Fields\n\n");
        for (i, ty) in tuple.types.iter().enumerate() {
            self.push_str(&format!(
                "- <a id=\"{r}.{f}\"></a>`{name}`: ",
                r = name.to_snake_case(),
                f = i,
                name = i,
            ));
            self.r#gen.hrefs.insert(
                format!("{name}::{i}"),
                format!("#{}.{}", name.to_snake_case(), i),
            );
            self.print_ty(ty);
            self.push_str("\n");
        }
        self.push_str("\n");
    }

    fn type_flags(&mut self, _id: TypeId, name: &str, flags: &Flags, docs: &Docs) {
        self.print_type_header("flags", name);
        self.push_str("\n");
        self.docs(docs);
        self.push_str("\n##### Flags members\n\n");
        for flag in flags.flags.iter() {
            self.push_str(&format!(
                "- <a id=\"{r}.{f}\"></a>`{name}`: ",
                r = name.to_snake_case(),
                f = flag.name.to_snake_case(),
                name = flag.name,
            ));
            self.r#gen.hrefs.insert(
                format!("{}::{}", name, flag.name),
                format!("#{}.{}", name.to_snake_case(), flag.name.to_snake_case()),
            );
            if flag.docs.contents.is_some() {
                self.r#gen.src.indent(1);
                self.push_str("\n<p>");
                self.docs(&flag.docs);
                self.r#gen.src.deindent(1);
            }
            self.push_str("\n");
        }
        self.push_str("\n");
    }

    fn type_variant(&mut self, _id: TypeId, name: &str, variant: &Variant, docs: &Docs) {
        self.print_type_header("variant", name);
        self.push_str("\n");
        self.docs(docs);
        self.push_str("\n##### Variant Cases\n\n");
        for case in variant.cases.iter() {
            self.push_str(&format!(
                "- <a id=\"{v}.{c}\"></a>`{name}`",
                v = name.to_snake_case(),
                c = case.name.to_snake_case(),
                name = case.name,
            ));
            self.r#gen.hrefs.insert(
                format!("{}::{}", name, case.name),
                format!("#{}.{}", name.to_snake_case(), case.name.to_snake_case()),
            );
            if let Some(ty) = &case.ty {
                self.push_str(": ");
                self.print_ty(ty);
            }
            if case.docs.contents.is_some() {
                self.r#gen.src.indent(1);
                self.push_str("\n<p>");
                self.docs(&case.docs);
                self.r#gen.src.deindent(1);
            }
            self.push_str("\n");
        }
        self.push_str("\n");
    }

    fn type_enum(&mut self, _id: TypeId, name: &str, enum_: &Enum, docs: &Docs) {
        self.print_type_header("enum", name);
        self.push_str("\n");
        self.docs(docs);
        self.push_str("\n##### Enum Cases\n\n");
        for case in enum_.cases.iter() {
            self.push_str(&format!(
                "- <a id=\"{v}.{c}\"></a>`{name}`",
                v = name.to_snake_case(),
                c = case.name.to_snake_case(),
                name = case.name,
            ));
            self.r#gen.hrefs.insert(
                format!("{}::{}", name, case.name),
                format!("#{}.{}", name.to_snake_case(), case.name.to_snake_case()),
            );
            if case.docs.contents.is_some() {
                self.r#gen.src.indent(1);
                self.push_str("\n<p>");
                self.docs(&case.docs);
                self.r#gen.src.deindent(1);
            }
            self.push_str("\n");
        }
        self.push_str("\n");
    }

    fn type_option(&mut self, _id: TypeId, name: &str, payload: &Type, docs: &Docs) {
        self.print_type_header("type", name);
        self.push_str("option<");
        self.print_ty(payload);
        self.push_str(">");
        self.push_str("\n");
        self.docs(docs);
    }

    fn type_result(&mut self, _id: TypeId, name: &str, result: &Result_, docs: &Docs) {
        self.print_type_header("type", name);
        match (result.ok, result.err) {
            (Some(ok), Some(err)) => {
                self.push_str("result<");
                self.print_ty(&ok);
                self.push_str(", ");
                self.print_ty(&err);
                self.push_str(">");
            }
            (None, Some(err)) => {
                self.push_str("result<_, ");
                self.print_ty(&err);
                self.push_str(">");
            }
            (Some(ok), None) => {
                self.push_str("result<");
                self.print_ty(&ok);
                self.push_str(">");
            }
            (None, None) => {
                self.push_str("result");
            }
        }
        self.push_str("\n");
        self.docs(docs);
    }

    fn type_alias(&mut self, _id: TypeId, name: &str, ty: &Type, docs: &Docs) {
        self.print_type_header("type", name);
        self.print_ty(ty);
        self.push_str("\n<p>");
        self.docs(docs);
        self.push_str("\n");
    }

    fn type_list(&mut self, id: TypeId, name: &str, _ty: &Type, docs: &Docs) {
        self.type_alias(id, name, &Type::Id(id), docs);
    }

    fn type_map(&mut self, id: TypeId, name: &str, _key: &Type, _value: &Type, docs: &Docs) {
        self.type_alias(id, name, &Type::Id(id), docs);
    }

    fn type_fixed_length_list(
        &mut self,
        id: TypeId,
        name: &str,
        _ty: &Type,
        _size: u32,
        docs: &Docs,
    ) {
        self.type_alias(id, name, &Type::Id(id), docs);
    }

    fn type_future(&mut self, id: TypeId, name: &str, ty: &Option<Type>, docs: &Docs) {
        _ = (id, name, ty, docs);
        todo!()
    }

    fn type_stream(&mut self, id: TypeId, name: &str, ty: &Option<Type>, docs: &Docs) {
        _ = (id, name, ty, docs);
        todo!()
    }

    fn type_builtin(&mut self, id: TypeId, name: &str, ty: &Type, docs: &Docs) {
        self.type_alias(id, name, ty, docs)
    }
}
