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",
];

#[cfg(test)]
const EXTENSION_FORMATS_COUNT: usize = 206;

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);
    }

    builtin_format_for_extension(&ext)
}

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

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_COUNT, 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")
        );
    }
}
