use std::path::Path;

use crate::cli::FormatMappings;

// Generated by scripts/sync-formats.mjs from jscpd/packages/tokenizer/dist/index.mjs.
// Upstream getFormatByFile() uses first-writer-wins extension mappings and no
// built-in filename mappings; extensionless names are supported through
// --formats-names only.

pub const SUPPORTED_FORMATS: &[&str] = &[
    "abap",
    "actionscript",
    "ada",
    "apacheconf",
    "apl",
    "applescript",
    "arduino",
    "arff",
    "asciidoc",
    "asm6502",
    "aspnet",
    "autohotkey",
    "autoit",
    "bash",
    "basic",
    "batch",
    "bison",
    "brainfuck",
    "bro",
    "c",
    "c-header",
    "clike",
    "clojure",
    "coffeescript",
    "comments",
    "cpp",
    "cpp-header",
    "crystal",
    "csharp",
    "csp",
    "css-extras",
    "css",
    "d",
    "dart",
    "diff",
    "django",
    "docker",
    "eiffel",
    "elixir",
    "elm",
    "erb",
    "erlang",
    "flow",
    "fortran",
    "fsharp",
    "gdscript",
    "gedcom",
    "gherkin",
    "git",
    "glsl",
    "go",
    "graphql",
    "groovy",
    "haml",
    "handlebars",
    "haskell",
    "haxe",
    "hpkp",
    "hsts",
    "http",
    "ichigojam",
    "icon",
    "inform7",
    "ini",
    "io",
    "j",
    "java",
    "javascript",
    "jolie",
    "json",
    "jsx",
    "julia",
    "keymap",
    "kotlin",
    "latex",
    "less",
    "liquid",
    "lisp",
    "livescript",
    "lolcode",
    "lua",
    "makefile",
    "markdown",
    "markup",
    "matlab",
    "mel",
    "mizar",
    "monkey",
    "n4js",
    "nasm",
    "nginx",
    "nim",
    "nix",
    "nsis",
    "objectivec",
    "ocaml",
    "opencl",
    "oz",
    "parigp",
    "pascal",
    "perl",
    "php",
    "plsql",
    "powershell",
    "processing",
    "prolog",
    "properties",
    "protobuf",
    "pug",
    "puppet",
    "pure",
    "python",
    "q",
    "qore",
    "r",
    "reason",
    "renpy",
    "rest",
    "rip",
    "roboconf",
    "ruby",
    "rust",
    "sas",
    "sass",
    "scala",
    "scheme",
    "scss",
    "svelte",
    "smalltalk",
    "smarty",
    "soy",
    "sql",
    "stylus",
    "swift",
    "tap",
    "tcl",
    "textile",
    "tsx",
    "tt2",
    "twig",
    "typescript",
    "txt",
    "vbnet",
    "velocity",
    "verilog",
    "vhdl",
    "vim",
    "visual-basic",
    "astro",
    "vue",
    "wasm",
    "wiki",
    "xeora",
    "xojo",
    "xquery",
    "yaml",
    "abnf",
    "agda",
    "antlr4",
    "apex",
    "aql",
    "armasm",
    "awk",
    "bicep",
    "bnf",
    "cfscript",
    "cfml",
    "cmake",
    "cobol",
    "csv",
    "cypher",
    "dhall",
    "dns-zone-file",
    "dot",
    "ebnf",
    "editorconfig",
    "excel-formula",
    "factor",
    "ftl",
    "gcode",
    "gettext",
    "gml",
    "go-module",
    "hcl",
    "hlsl",
    "idris",
    "ignore",
    "jq",
    "json5",
    "kusto",
    "lilypond",
    "linker-script",
    "llvm",
    "log",
    "mermaid",
    "mongodb",
    "n1ql",
    "odin",
    "openqasm",
    "plant-uml",
    "powerquery",
    "promql",
    "purescript",
    "qsharp",
    "racket",
    "regex",
    "rego",
    "rescript",
    "robotframework",
    "shell-session",
    "smali",
    "solidity",
    "sparql",
    "stata",
    "toml",
    "turtle",
    "typoscript",
    "unrealscript",
    "uri",
    "vala",
    "wgsl",
    "wolfram",
    "zig",
];

const EXTENSION_FORMATS: &[(&str, &str)] = &[
    ("as", "actionscript"),
    ("ada", "ada"),
    ("apl", "apl"),
    ("asp", "aspnet"),
    ("aspx", "aspnet"),
    ("sh", "bash"),
    ("ksh", "bash"),
    ("bash", "bash"),
    ("bas", "basic"),
    ("b", "brainfuck"),
    ("bf", "brainfuck"),
    ("c", "c"),
    ("z80", "c"),
    ("h", "c-header"),
    ("cljs", "clojure"),
    ("clj", "clojure"),
    ("cljc", "clojure"),
    ("cljx", "clojure"),
    ("edn", "clojure"),
    ("coffee", "coffeescript"),
    ("cpp", "cpp"),
    ("c++", "cpp"),
    ("cc", "cpp"),
    ("cxx", "cpp"),
    ("hpp", "cpp-header"),
    ("h++", "cpp-header"),
    ("hh", "cpp-header"),
    ("hxx", "cpp-header"),
    ("cr", "crystal"),
    ("cs", "csharp"),
    ("css", "css"),
    ("gss", "css"),
    ("d", "d"),
    ("dart", "dart"),
    ("diff", "diff"),
    ("patch", "diff"),
    ("e", "eiffel"),
    ("elm", "elm"),
    ("erl", "erlang"),
    ("erlang", "erlang"),
    ("f", "fortran"),
    ("for", "fortran"),
    ("f77", "fortran"),
    ("f90", "fortran"),
    ("fs", "fsharp"),
    ("gd", "gdscript"),
    ("feature", "gherkin"),
    ("go", "go"),
    ("graphql", "graphql"),
    ("groovy", "groovy"),
    ("gradle", "groovy"),
    ("haml", "haml"),
    ("hb", "handlebars"),
    ("hbs", "handlebars"),
    ("handlebars", "handlebars"),
    ("hs", "haskell"),
    ("lhs", "haskell"),
    ("hx", "haxe"),
    ("hxml", "haxe"),
    ("ini", "ini"),
    ("java", "java"),
    ("js", "javascript"),
    ("es", "javascript"),
    ("es6", "javascript"),
    ("mjs", "javascript"),
    ("cjs", "javascript"),
    ("json", "json"),
    ("map", "json"),
    ("jsonld", "json"),
    ("jsx", "jsx"),
    ("jl", "julia"),
    ("kt", "kotlin"),
    ("kts", "kotlin"),
    ("tex", "latex"),
    ("less", "less"),
    ("cl", "lisp"),
    ("lisp", "lisp"),
    ("el", "lisp"),
    ("ls", "livescript"),
    ("lua", "lua"),
    ("md", "markdown"),
    ("markdown", "markdown"),
    ("mkd", "markdown"),
    ("html", "markup"),
    ("htm", "markup"),
    ("xml", "markup"),
    ("xsl", "markup"),
    ("xslt", "markup"),
    ("svg", "markup"),
    ("ejs", "markup"),
    ("jsp", "markup"),
    ("nsh", "nsis"),
    ("nsi", "nsis"),
    ("m", "objectivec"),
    ("mm", "objectivec"),
    ("ocaml", "ocaml"),
    ("ml", "ocaml"),
    ("mli", "ocaml"),
    ("mll", "ocaml"),
    ("mly", "ocaml"),
    ("oz", "oz"),
    ("pas", "pascal"),
    ("p", "pascal"),
    ("pl", "perl"),
    ("pm", "perl"),
    ("php", "php"),
    ("phtml", "php"),
    ("plsql", "plsql"),
    ("ps1", "powershell"),
    ("psd1", "powershell"),
    ("psm1", "powershell"),
    ("pro", "prolog"),
    ("properties", "properties"),
    ("proto", "protobuf"),
    ("pug", "pug"),
    ("jade", "pug"),
    ("pp", "puppet"),
    ("puppet", "puppet"),
    ("py", "python"),
    ("pyx", "python"),
    ("pxd", "python"),
    ("pxi", "python"),
    ("q", "q"),
    ("r", "r"),
    ("R", "r"),
    ("rb", "ruby"),
    ("rs", "rust"),
    ("sas", "sas"),
    ("sass", "sass"),
    ("scala", "scala"),
    ("scm", "scheme"),
    ("ss", "scheme"),
    ("scss", "scss"),
    ("svelte", "svelte"),
    ("st", "smalltalk"),
    ("smarty", "smarty"),
    ("tpl", "smarty"),
    ("soy", "soy"),
    ("sql", "sql"),
    ("cql", "sql"),
    ("styl", "stylus"),
    ("stylus", "stylus"),
    ("swift", "swift"),
    ("tap", "tap"),
    ("tcl", "tcl"),
    ("textile", "textile"),
    ("tsx", "tsx"),
    ("tt2", "tt2"),
    ("twig", "twig"),
    ("ts", "typescript"),
    ("mts", "typescript"),
    ("cts", "typescript"),
    ("txt", "txt"),
    ("vb", "vbnet"),
    ("vtl", "velocity"),
    ("v", "verilog"),
    ("vhd", "vhdl"),
    ("vhdl", "vhdl"),
    ("astro", "astro"),
    ("vue", "vue"),
    ("xy", "xquery"),
    ("xquery", "xquery"),
    ("yaml", "yaml"),
    ("yml", "yaml"),
    ("g4", "antlr4"),
    ("cls", "apex"),
    ("trigger", "apex"),
    ("apex", "apex"),
    ("awk", "awk"),
    ("bicep", "bicep"),
    ("cfc", "cfscript"),
    ("cfm", "cfml"),
    ("cmake", "cmake"),
    ("csv", "csv"),
    ("dot", "dot"),
    ("gv", "dot"),
    ("xlsx", "excel-formula"),
    ("xls", "excel-formula"),
    ("po", "gettext"),
    ("tf", "hcl"),
    ("hcl", "hcl"),
    ("idr", "idris"),
    ("gitignore", "ignore"),
    ("json5", "json5"),
    ("ly", "lilypond"),
    ("ld", "linker-script"),
    ("ll", "llvm"),
    ("log", "log"),
    ("qasm", "openqasm"),
    ("puml", "plant-uml"),
    ("plantuml", "plant-uml"),
    ("pq", "powerquery"),
    ("purs", "purescript"),
    ("qs", "qsharp"),
    ("rkt", "racket"),
    ("res", "rescript"),
    ("robot", "robotframework"),
    ("sol", "solidity"),
    ("rq", "sparql"),
    ("toml", "toml"),
    ("ttl", "turtle"),
    ("uc", "unrealscript"),
    ("wgsl", "wgsl"),
    ("wl", "wolfram"),
    ("nb", "wolfram"),
    ("zig", "zig"),
];

pub fn format_for_path<'a>(
    path: &Path,
    formats_exts: &'a FormatMappings,
    formats_names: &'a FormatMappings,
) -> Option<&'a str> {
    let file_name = path.file_name()?.to_string_lossy();
    if !formats_names.is_empty()
        && let Some(format) = formats_names.find_format_for_value(&file_name)
    {
        return Some(format);
    }

    let ext = path.extension()?.to_string_lossy();
    if !formats_exts.is_empty() {
        return formats_exts.find_format_for_value(&ext);
    }

    EXTENSION_FORMATS
        .iter()
        .find_map(|(candidate, format)| (*candidate == ext).then_some(*format))
}

pub fn supported_formats() -> Vec<&'static str> {
    SUPPORTED_FORMATS.to_vec()
}

#[cfg(test)]
mod tests {
    use std::path::Path;

    use crate::cli::FormatMappings;

    #[test]
    fn maps_module_typescript_extensions_like_upstream() {
        let formats_exts = FormatMappings::default();
        let formats_names = FormatMappings::default();

        assert_eq!(
            super::format_for_path(Path::new("index.mts"), &formats_exts, &formats_names),
            Some("typescript")
        );
        assert_eq!(
            super::format_for_path(Path::new("index.cts"), &formats_exts, &formats_names),
            Some("typescript")
        );
    }

    #[test]
    fn maps_javascript_module_extensions_like_upstream() {
        let formats_exts = FormatMappings::default();
        let formats_names = FormatMappings::default();

        assert_eq!(
            super::format_for_path(Path::new("index.es"), &formats_exts, &formats_names),
            Some("javascript")
        );
        assert_eq!(
            super::format_for_path(Path::new("index.es6"), &formats_exts, &formats_names),
            Some("javascript")
        );
    }

    #[test]
    fn syncs_supported_format_surface_from_upstream() {
        assert_eq!(super::SUPPORTED_FORMATS.len(), 223);
        assert_eq!(super::EXTENSION_FORMATS.len(), 206);
        assert_eq!(super::supported_formats().first(), Some(&"abap"));
        assert!(super::supported_formats().contains(&"typescript"));
        assert!(super::supported_formats().contains(&"excel-formula"));
    }

    #[test]
    fn maps_long_tail_extensions_like_upstream() {
        let formats_exts = FormatMappings::default();
        let formats_names = FormatMappings::default();

        assert_eq!(
            super::format_for_path(Path::new("schema.g4"), &formats_exts, &formats_names),
            Some("antlr4")
        );
        assert_eq!(
            super::format_for_path(Path::new("component.bicep"), &formats_exts, &formats_names),
            Some("bicep")
        );
        assert_eq!(
            super::format_for_path(Path::new("formula.xlsx"), &formats_exts, &formats_names),
            Some("excel-formula")
        );
    }

    #[test]
    fn maps_header_extensions_like_upstream() {
        let formats_exts = FormatMappings::default();
        let formats_names = FormatMappings::default();

        assert_eq!(
            super::format_for_path(Path::new("foo.h"), &formats_exts, &formats_names),
            Some("c-header")
        );
        assert_eq!(
            super::format_for_path(Path::new("foo.hpp"), &formats_exts, &formats_names),
            Some("cpp-header")
        );
    }

    #[test]
    fn extensionless_names_require_formats_names_like_upstream() {
        let formats_exts = FormatMappings::default();
        let formats_names = FormatMappings::default();

        assert_eq!(
            super::format_for_path(Path::new("Makefile"), &formats_exts, &formats_names),
            None
        );

        let formats_names = FormatMappings::from_pairs(vec![(
            "makefile".to_string(),
            vec!["Makefile".to_string(), "GNUmakefile".to_string()],
        )]);

        assert_eq!(
            super::format_for_path(Path::new("Makefile"), &formats_exts, &formats_names),
            Some("makefile")
        );
    }
}
