use crate::handlers;
use crate::parse::{FlagCheck, WordSet};

pub struct CommandDoc {
    pub name: &'static str,
    pub kind: DocKind,
    pub description: String,
}

pub enum DocKind {
    AlwaysSafe,
    Handler,
}

impl CommandDoc {
    pub fn handler(name: &'static str, description: impl Into<String>) -> Self {
        Self { name, kind: DocKind::Handler, description: description.into() }
    }

    pub fn wordset(name: &'static str, words: &WordSet) -> Self {
        Self::handler(name, doc(words).build())
    }

    pub fn wordset_multi(name: &'static str, words: &WordSet, multi: &[(&str, WordSet)]) -> Self {
        Self::handler(name, doc_multi(words, multi).build())
    }

    pub fn flagcheck(name: &'static str, check: &FlagCheck) -> Self {
        Self::handler(name, describe_flagcheck(check))
    }

    pub fn always_safe(name: &'static str, description: &str) -> Self {
        Self { name, kind: DocKind::AlwaysSafe, description: description.into() }
    }
}

#[derive(Default)]
pub struct DocBuilder {
    subcommands: Vec<String>,
    flags: Vec<String>,
    sections: Vec<String>,
}

impl DocBuilder {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn wordset(mut self, words: &WordSet) -> Self {
        for item in words.iter() {
            if item.starts_with('-') {
                self.flags.push(item.to_string());
            } else {
                self.subcommands.push(item.to_string());
            }
        }
        self
    }

    pub fn multi_word(mut self, multi: &[(&str, WordSet)]) -> Self {
        for (prefix, actions) in multi {
            for action in actions.iter() {
                self.subcommands.push(format!("{prefix} {action}"));
            }
        }
        self
    }

    pub fn triple_word(mut self, triples: &[(&str, &str, WordSet)]) -> Self {
        for (a, b, actions) in triples {
            for action in actions.iter() {
                self.subcommands.push(format!("{a} {b} {action}"));
            }
        }
        self
    }

    pub fn subcommand(mut self, name: impl Into<String>) -> Self {
        self.subcommands.push(name.into());
        self
    }

    pub fn section(mut self, text: impl Into<String>) -> Self {
        let s = text.into();
        if !s.is_empty() {
            self.sections.push(s);
        }
        self
    }

    pub fn build(self) -> String {
        let mut all_sections = Vec::new();
        if !self.subcommands.is_empty() {
            let mut subs = self.subcommands;
            subs.sort();
            all_sections.push(format!("Subcommands: {}.", subs.join(", ")));
        }
        if !self.flags.is_empty() {
            all_sections.push(format!("Flags: {}.", self.flags.join(", ")));
        }
        all_sections.extend(self.sections);
        if all_sections.len() > 2 {
            all_sections.join("\n\n")
        } else {
            all_sections.join(" ")
        }
    }
}

pub fn doc(words: &WordSet) -> DocBuilder {
    DocBuilder::new().wordset(words)
}

pub fn doc_multi(words: &WordSet, multi: &[(&str, WordSet)]) -> DocBuilder {
    DocBuilder::new().wordset(words).multi_word(multi)
}

pub fn wordset_items(words: &WordSet) -> String {
    let items: Vec<&str> = words.iter().collect();
    items.join(", ")
}

pub fn describe_flagcheck(check: &FlagCheck) -> String {
    let mut parts = Vec::new();
    let req: Vec<&str> = check.required().iter().collect();
    if !req.is_empty() {
        parts.push(format!("Requires: {}", req.join(", ")));
    }
    let denied: Vec<&str> = check.denied().iter().collect();
    if !denied.is_empty() {
        parts.push(format!("Denied: {}", denied.join(", ")));
    }
    format!("{}.", parts.join(". "))
}

pub fn all_command_docs() -> Vec<CommandDoc> {
    let mut docs = safe_cmd_docs();
    docs.extend(handlers::handler_docs());
    docs.sort_by_key(|d| d.name);
    docs
}

pub fn render_markdown(docs: &[CommandDoc]) -> String {
    let mut out = String::from(
        "# Supported Commands\n\
         \n\
         Auto-generated by `safe-chains --list-commands`.\n\
         \n\
         Any command with only `--version` or `--help` as its sole argument is always allowed.\n\
         \n\
         ## Unconditionally Safe\n\
         \n\
         These commands are allowed with any arguments.\n\
         \n\
         | Command | Description |\n\
         |---------|-------------|\n",
    );

    for doc in docs.iter().filter(|d| matches!(d.kind, DocKind::AlwaysSafe)) {
        out.push_str(&format!("| `{}` | {} |\n", doc.name, doc.description));
    }

    out.push_str("\n## Handled Commands\n\nThese commands are allowed with specific subcommands or flags.\n\n");

    for doc in docs.iter().filter(|d| matches!(d.kind, DocKind::Handler)) {
        out.push_str(&format!("### `{}`\n\n{}\n\n", doc.name, doc.description));
    }

    out
}

fn safe_cmd_docs() -> Vec<CommandDoc> {
    handlers::SAFE_CMD_ENTRIES
        .iter()
        .map(|&(name, description)| CommandDoc::always_safe(name, description))
        .collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn builder_two_sections_inline() {
        let ws = WordSet::new(&["--version", "list", "show"]);
        assert_eq!(doc(&ws).build(), "Subcommands: list, show. Flags: --version.");
    }

    #[test]
    fn builder_subcommands_only() {
        let ws = WordSet::new(&["list", "show"]);
        assert_eq!(doc(&ws).build(), "Subcommands: list, show.");
    }

    #[test]
    fn builder_flags_only() {
        let ws = WordSet::new(&["--check", "--version"]);
        assert_eq!(doc(&ws).build(), "Flags: --check, --version.");
    }

    #[test]
    fn builder_three_sections_newlines() {
        let ws = WordSet::new(&["--version", "list", "show"]);
        assert_eq!(
            doc(&ws).section("Guarded: foo (bar only).").build(),
            "Subcommands: list, show.\n\nFlags: --version.\n\nGuarded: foo (bar only)."
        );
    }

    #[test]
    fn builder_multi_word_merged() {
        let ws = WordSet::new(&["--version", "info", "show"]);
        let multi: &[(&str, WordSet)] =
            &[("config", WordSet::new(&["get", "list"]))];
        assert_eq!(
            doc_multi(&ws, multi).build(),
            "Subcommands: config get, config list, info, show. Flags: --version."
        );
    }

    #[test]
    fn builder_multi_word_with_extra_section() {
        let ws = WordSet::new(&["--version", "show"]);
        let multi: &[(&str, WordSet)] =
            &[("config", WordSet::new(&["get", "list"]))];
        assert_eq!(
            doc_multi(&ws, multi).section("Guarded: foo.").build(),
            "Subcommands: config get, config list, show.\n\nFlags: --version.\n\nGuarded: foo."
        );
    }

    #[test]
    fn builder_no_flags_with_extra_stays_inline() {
        let ws = WordSet::new(&["list", "show"]);
        assert_eq!(
            doc(&ws).section("Also: foo.").build(),
            "Subcommands: list, show. Also: foo."
        );
    }

    #[test]
    fn builder_custom_sections_only() {
        assert_eq!(
            DocBuilder::new()
                .section("Read-only: foo.")
                .section("Always safe: bar.")
                .section("Guarded: baz.")
                .build(),
            "Read-only: foo.\n\nAlways safe: bar.\n\nGuarded: baz."
        );
    }

    #[test]
    fn builder_triple_word() {
        let ws = WordSet::new(&["--version", "diff"]);
        let triples: &[(&str, &str, WordSet)] =
            &[("git", "remote", WordSet::new(&["list"]))];
        assert_eq!(
            doc(&ws).triple_word(triples).build(),
            "Subcommands: diff, git remote list. Flags: --version."
        );
    }

    #[test]
    fn builder_subcommand_method() {
        let ws = WordSet::new(&["--version", "list"]);
        assert_eq!(
            doc(&ws).subcommand("plugin-list").build(),
            "Subcommands: list, plugin-list. Flags: --version."
        );
    }

    #[test]
    fn flagcheck_description() {
        let fc = FlagCheck::new(&["--check"], &["--force"]);
        assert_eq!(
            describe_flagcheck(&fc),
            "Requires: --check. Denied: --force."
        );
    }

    #[test]
    fn flagcheck_required_only() {
        let fc = FlagCheck::new(&["--check"], &[]);
        assert_eq!(describe_flagcheck(&fc), "Requires: --check.");
    }
}
