//! Tests for various types of file (video, image, compressed, etc).
//!
//! Currently this is dependent on the file’s name and extension, because
//! those are the only metadata that we have access to without reading the
//! file’s contents.
//!
//! # Contributors
//! Please keep these lists sorted. If you're using vim, :sort i

use phf::{phf_map, Map};

use crate::fs::File;

#[derive(Debug, Clone)]
pub enum FileType {
    Image,
    Video,
    Music,
    Lossless, // Lossless music, rather than any other kind of data...
    Crypto,
    Document,
    Compressed,
    Temp,
    Compiled,
    Build, // A “build file is something that can be run or activated somehow in order to
    // kick off the build of a project. It’s usually only present in directories full of
    // source code.
    Source,
}

/// Mapping from full filenames to file type.
const FILENAME_TYPES: Map<&'static str, FileType> = phf_map! {
    /* Immediate file - kick off the build of a project */
    "Brewfile"           => FileType::Build,
    "bsconfig.json"      => FileType::Build,
    "BUILD"              => FileType::Build,
    "BUILD.bazel"        => FileType::Build,
    "build.gradle"       => FileType::Build,
    "build.sbt"          => FileType::Build,
    "build.xml"          => FileType::Build,
    "Cargo.toml"         => FileType::Build,
    "CMakeLists.txt"     => FileType::Build,
    "composer.json"      => FileType::Build,
    "configure"          => FileType::Build,
    "Containerfile"      => FileType::Build,
    "Dockerfile"         => FileType::Build,
    "Earthfile"          => FileType::Build,
    "flake.nix"          => FileType::Build,
    "Gemfile"            => FileType::Build,
    "GNUmakefile"        => FileType::Build,
    "Gruntfile.coffee"   => FileType::Build,
    "Gruntfile.js"       => FileType::Build,
    "jsconfig.json"      => FileType::Build,
    "Justfile"           => FileType::Build,
    "justfile"           => FileType::Build,
    "Makefile"           => FileType::Build,
    "makefile"           => FileType::Build,
    "meson.build"        => FileType::Build,
    "mix.exs"            => FileType::Build,
    "package.json"       => FileType::Build,
    "Pipfile"            => FileType::Build,
    "PKGBUILD"           => FileType::Build,
    "Podfile"            => FileType::Build,
    "pom.xml"            => FileType::Build,
    "Procfile"           => FileType::Build,
    "pyproject.toml"     => FileType::Build,
    "Rakefile"           => FileType::Build,
    "RoboFile.php"       => FileType::Build,
    "SConstruct"         => FileType::Build,
    "tsconfig.json"      => FileType::Build,
    "Vagrantfile"        => FileType::Build,
    "webpack.config.cjs" => FileType::Build,
    "webpack.config.js"  => FileType::Build,
    "WORKSPACE"          => FileType::Build,
    /* Cryptology files */
    "id_dsa"             => FileType::Crypto,
    "id_ecdsa"           => FileType::Crypto,
    "id_ecdsa_sk"        => FileType::Crypto,
    "id_ed25519"         => FileType::Crypto,
    "id_ed25519_sk"      => FileType::Crypto,
    "id_rsa"             => FileType::Crypto,
};

/// Mapping from lowercase file extension to file type.  If an image, video, music, or lossless
/// extension is added also update the extension icon map.
const EXTENSION_TYPES: Map<&'static str, FileType> = phf_map! {
    /* Immediate file - kick off the build of a project */
    "ninja"      => FileType::Build,
    /* Image files */
    "arw"        => FileType::Image,
    "avif"       => FileType::Image,
    "bmp"        => FileType::Image,
    "cbr"        => FileType::Image,
    "cbz"        => FileType::Image,
    "cr2"        => FileType::Image,
    "dvi"        => FileType::Image,
    "eps"        => FileType::Image,
    "fodg"       => FileType::Image,
    "gif"        => FileType::Image,
    "heic"       => FileType::Image,
    "heif"       => FileType::Image,
    "ico"        => FileType::Image,
    "j2c"        => FileType::Image,
    "j2k"        => FileType::Image,
    "jfi"        => FileType::Image,
    "jfif"       => FileType::Image,
    "jif"        => FileType::Image,
    "jp2"        => FileType::Image,
    "jpe"        => FileType::Image,
    "jpeg"       => FileType::Image,
    "jpf"        => FileType::Image,
    "jpg"        => FileType::Image,
    "jpx"        => FileType::Image,
    "jxl"        => FileType::Image,
    "nef"        => FileType::Image,
    "odg"        => FileType::Image,
    "orf"        => FileType::Image,
    "pbm"        => FileType::Image,
    "pgm"        => FileType::Image,
    "png"        => FileType::Image,
    "pnm"        => FileType::Image,
    "ppm"        => FileType::Image,
    "ps"         => FileType::Image,
    "psd"        => FileType::Image,
    "pxm"        => FileType::Image,
    "raw"        => FileType::Image,
    "qoi"        => FileType::Image,
    "svg"        => FileType::Image,
    "tif"        => FileType::Image,
    "tiff"       => FileType::Image,
    "webp"       => FileType::Image,
    "xcf"        => FileType::Image,
    "xpm"        => FileType::Image,
    /* Video files */
    "avi"        => FileType::Video,
    "flv"        => FileType::Video,
    "h264"       => FileType::Video,
    "heics"      => FileType::Video,
    "m2ts"       => FileType::Video,
    "m2v"        => FileType::Video,
    "m4v"        => FileType::Video,
    "mkv"        => FileType::Video,
    "mov"        => FileType::Video,
    "mp4"        => FileType::Video,
    "mpeg"       => FileType::Video,
    "mpg"        => FileType::Video,
    "ogm"        => FileType::Video,
    "ogv"        => FileType::Video,
    "video"      => FileType::Video,
    "vob"        => FileType::Video,
    "webm"       => FileType::Video,
    "wmv"        => FileType::Video,
    /* Music files */
    "aac"        => FileType::Music, // Advanced Audio Coding
    "m4a"        => FileType::Music,
    "mka"        => FileType::Music,
    "mp2"        => FileType::Music,
    "mp3"        => FileType::Music,
    "ogg"        => FileType::Music,
    "opus"       => FileType::Music,
    "wma"        => FileType::Music,
    /* Lossless music, rather than any other kind of data... */
    "aif"        => FileType::Lossless,
    "aifc"       => FileType::Lossless,
    "aiff"       => FileType::Lossless,
    "alac"       => FileType::Lossless,
    "ape"        => FileType::Lossless,
    "flac"       => FileType::Lossless,
    "pcm"        => FileType::Lossless,
    "wav"        => FileType::Lossless,
    "wv"         => FileType::Lossless,
    /* Cryptology files */
    "age"        => FileType::Crypto, // age encrypted file
    "asc"        => FileType::Crypto, // GnuPG ASCII armored file
    "cer"        => FileType::Crypto,
    "crt"        => FileType::Crypto,
    "csr"        => FileType::Crypto, // PKCS#10 Certificate Signing Request
    "gpg"        => FileType::Crypto, // GnuPG encrypted file
    "kbx"        => FileType::Crypto, // GnuPG keybox
    "md5"        => FileType::Crypto, // MD5 checksum
    "p12"        => FileType::Crypto, // PKCS#12 certificate (Netscape)
    "pem"        => FileType::Crypto, // Privacy-Enhanced Mail certificate
    "pfx"        => FileType::Crypto, // PKCS#12 certificate (Microsoft)
    "pgp"        => FileType::Crypto, // PGP security key
    "pub"        => FileType::Crypto, // Public key
    "sha1"       => FileType::Crypto, // SHA-1 hash
    "sha224"     => FileType::Crypto, // SHA-224 hash
    "sha256"     => FileType::Crypto, // SHA-256 hash
    "sha384"     => FileType::Crypto, // SHA-384 hash
    "sha512"     => FileType::Crypto, // SHA-512 hash
    "sig"        => FileType::Crypto, // GnuPG signed file
    "signature"  => FileType::Crypto, // e-Filing Digital Signature File (India)
    /* Document files */
    "djvu"       => FileType::Document,
    "doc"        => FileType::Document,
    "docx"       => FileType::Document,
    "eml"        => FileType::Document,
    "fodp"       => FileType::Document,
    "fods"       => FileType::Document,
    "fodt"       => FileType::Document,
    "fotd"       => FileType::Document,
    "gdoc"       => FileType::Document,
    "key"        => FileType::Document,
    "keynote"    => FileType::Document,
    "numbers"    => FileType::Document,
    "odp"        => FileType::Document,
    "ods"        => FileType::Document,
    "odt"        => FileType::Document,
    "pages"      => FileType::Document,
    "pdf"        => FileType::Document,
    "ppt"        => FileType::Document,
    "pptx"       => FileType::Document,
    "rtf"        => FileType::Document, // Rich Text Format
    "xls"        => FileType::Document,
    "xlsm"       => FileType::Document,
    "xlsx"       => FileType::Document,
    /* Compressed/archive files */
    "7z"         => FileType::Compressed, // 7-Zip
    "ar"         => FileType::Compressed,
    "arj"        => FileType::Compressed,
    "br"         => FileType::Compressed, // Brotli
    "bz"         => FileType::Compressed, // bzip
    "bz2"        => FileType::Compressed, // bzip2
    "bz3"        => FileType::Compressed, // bzip3
    "cpio"       => FileType::Compressed,
    "deb"        => FileType::Compressed, // Debian
    "dmg"        => FileType::Compressed,
    "gz"         => FileType::Compressed, // gzip
    "iso"        => FileType::Compressed,
    "lz"         => FileType::Compressed,
    "lz4"        => FileType::Compressed,
    "lzh"        => FileType::Compressed,
    "lzma"       => FileType::Compressed,
    "lzo"        => FileType::Compressed,
    "phar"       => FileType::Compressed, // PHP PHAR
    "qcow"       => FileType::Compressed,
    "qcow2"      => FileType::Compressed,
    "rar"        => FileType::Compressed,
    "rpm"        => FileType::Compressed,
    "tar"        => FileType::Compressed,
    "taz"        => FileType::Compressed,
    "tbz"        => FileType::Compressed,
    "tbz2"       => FileType::Compressed,
    "tc"         => FileType::Compressed,
    "tgz"        => FileType::Compressed,
    "tlz"        => FileType::Compressed,
    "txz"        => FileType::Compressed,
    "tz"         => FileType::Compressed,
    "xz"         => FileType::Compressed,
    "vdi"        => FileType::Compressed,
    "vhd"        => FileType::Compressed,
    "vhdx"       => FileType::Compressed,
    "vmdk"       => FileType::Compressed,
    "z"          => FileType::Compressed,
    "zip"        => FileType::Compressed,
    "zst"        => FileType::Compressed, // Zstandard
    /* Temporary files */
    "bak"        => FileType::Temp,
    "bk"         => FileType::Temp,
    "bkp"        => FileType::Temp,
    "crdownload" => FileType::Temp,
    "download"   => FileType::Temp,
    "fdmdownload"=> FileType::Temp,
    "part"       => FileType::Temp,
    "swn"        => FileType::Temp,
    "swo"        => FileType::Temp,
    "swp"        => FileType::Temp,
    "tmp"        => FileType::Temp,
    /* Compiler output files */
    "a"          => FileType::Compiled, // Unix static library
    "bundle"     => FileType::Compiled, // macOS application bundle
    "class"      => FileType::Compiled, // Java class file
    "cma"        => FileType::Compiled, // OCaml bytecode library
    "cmi"        => FileType::Compiled, // OCaml interface
    "cmo"        => FileType::Compiled, // OCaml bytecode object
    "cmx"        => FileType::Compiled, // OCaml bytecode object for inlining
    "dll"        => FileType::Compiled, // Windows dynamic link library
    "dylib"      => FileType::Compiled, // Mach-O dynamic library
    "elc"        => FileType::Compiled, // Emacs compiled lisp
    "elf"        => FileType::Compiled, // Executable and Linkable Format
    "ko"         => FileType::Compiled, // Linux kernel module
    "lib"        => FileType::Compiled, // Windows static library
    "o"          => FileType::Compiled, // Compiled object file
    "obj"        => FileType::Compiled, // Compiled object file
    "pyc"        => FileType::Compiled, // Python compiled code
    "pyd"        => FileType::Compiled, // Python dynamic module
    "pyo"        => FileType::Compiled, // Python optimized code
    "so"         => FileType::Compiled, // Unix shared library
    "zwc"        => FileType::Compiled, // zsh compiled file
    /* Source code */
    "applescript"=> FileType::Source, // Apple script
    "as"         => FileType::Source, // Action script
    "asa"        => FileType::Source, // asp
    "awk"        => FileType::Source, // awk
    "c"          => FileType::Source, // C/C++
    "c++"        => FileType::Source, // C/C++
    "c++m"       => FileType::Source, // C/C++ module
    "cabal"      => FileType::Source, // Cabal
    "cc"         => FileType::Source, // C/C++
    "ccm"        => FileType::Source, // C/C++ module
    "clj"        => FileType::Source, // Clojure
    "cp"         => FileType::Source, // C/C++ Xcode
    "cpp"        => FileType::Source, // C/C++
    "cppm"       => FileType::Source, // C/C++ module
    "cr"         => FileType::Source, // Crystal
    "cs"         => FileType::Source, // C#
    "css"        => FileType::Source, // css
    "csx"        => FileType::Source, // C#
    "cu"         => FileType::Source, // CUDA
    "cxx"        => FileType::Source, // C/C++
    "cxxm"       => FileType::Source, // C/C++ module
    "cypher"     => FileType::Source, // Cypher (query language)
    "d"          => FileType::Source, // D
    "dart"       => FileType::Source, // Dart
    "di"         => FileType::Source, // D
    "dpr"        => FileType::Source, // Delphi Pascal
    "el"         => FileType::Source, // Lisp
    "elm"        => FileType::Source, // Elm
    "erl"        => FileType::Source, // Erlang
    "ex"         => FileType::Source, // Elixir
    "exs"        => FileType::Source, // Elixir
    "f"          => FileType::Source, // Fortran
    "f90"        => FileType::Source, // Fortran
    "fnl"        => FileType::Source, // Fennel
    "for"        => FileType::Source, // Fortran
    "fs"         => FileType::Source, // F#
    "fsh"        => FileType::Source, // Fragment shader
    "fsi"        => FileType::Source, // F#
    "fsx"        => FileType::Source, // F#
    "gd"         => FileType::Source, // GDScript
    "go"         => FileType::Source, // Go
    "gradle"     => FileType::Source, // Gradle
    "groovy"     => FileType::Source, // Groovy
    "gvy"        => FileType::Source, // Groovy
    "h"          => FileType::Source, // C/C++ header
    "h++"        => FileType::Source, // C/C++ header
    "hh"         => FileType::Source, // C/C++ header
    "hpp"        => FileType::Source, // C/C++ header
    "hs"         => FileType::Source, // Haskell
    "htc"        => FileType::Source, // JavaScript
    "hxx"        => FileType::Source, // C/C++ header
    "inc"        => FileType::Source,
    "inl"        => FileType::Source, // C/C++ Microsoft
    "ipynb"      => FileType::Source, // Jupyter Notebook
    "ixx"        => FileType::Source, // C/C++ module
    "java"       => FileType::Source, // Java
    "jl"         => FileType::Source, // Julia
    "js"         => FileType::Source, // JavaScript
    "jsx"        => FileType::Source, // React
    "kt"         => FileType::Source, // Kotlin
    "kts"        => FileType::Source, // Kotlin
    "kusto"      => FileType::Source, // Kusto (query language)
    "less"       => FileType::Source, // less
    "lhs"        => FileType::Source, // Haskell
    "lisp"       => FileType::Source, // Lisp
    "ltx"        => FileType::Source, // LaTeX
    "lua"        => FileType::Source, // Lua
    "m"          => FileType::Source, // Matlab
    "malloy"     => FileType::Source, // Malloy (query language)
    "matlab"     => FileType::Source, // Matlab
    "ml"         => FileType::Source, // OCaml
    "mli"        => FileType::Source, // OCaml
    "mn"         => FileType::Source, // Matlab
    "nb"         => FileType::Source, // Mathematica
    "p"          => FileType::Source, // Pascal
    "pas"        => FileType::Source, // Pascal
    "php"        => FileType::Source, // PHP
    "pl"         => FileType::Source, // Perl
    "pm"         => FileType::Source, // Perl
    "pod"        => FileType::Source, // Perl
    "pp"         => FileType::Source, // Puppet
    "prql"       => FileType::Source, // PRQL
    "ps1"        => FileType::Source, // PowerShell
    "psd1"       => FileType::Source, // PowerShell
    "psm1"       => FileType::Source, // PowerShell
    "purs"       => FileType::Source, // PureScript
    "py"         => FileType::Source, // Python
    "r"          => FileType::Source, // R
    "rb"         => FileType::Source, // Ruby
    "rs"         => FileType::Source, // Rust
    "rq"         => FileType::Source, // SPARQL (query language)
    "sass"       => FileType::Source, // Sass
    "scala"      => FileType::Source, // Scala
    "scm"        => FileType::Source, // Scheme
    "scss"       => FileType::Source, // Sass
    "sld"        => FileType::Source, // Scheme Library Definition
    "sql"        => FileType::Source, // SQL
    "ss"         => FileType::Source, // Scheme Source
    "swift"      => FileType::Source, // Swift
    "tcl"        => FileType::Source, // TCL
    "tex"        => FileType::Source, // LaTeX
    "ts"         => FileType::Source, // TypeScript
    "v"          => FileType::Source, // V
    "vb"         => FileType::Source, // Visual Basic
    "vsh"        => FileType::Source, // Vertex shader
    "zig"        => FileType::Source, // Zig
};

impl FileType {
    /// Lookup the file type based on the file's name, by the file name
    /// lowercase extension, or if the file could be compiled from related
    /// source code.
    pub(crate) fn get_file_type(file: &File<'_>) -> Option<FileType> {
        // Case-insensitive readme is checked first for backwards compatibility.
        if file.name.to_lowercase().starts_with("readme") {
            return Some(Self::Build);
        }
        if let Some(file_type) = FILENAME_TYPES.get(&file.name) {
            return Some(file_type.clone());
        }
        if let Some(file_type) = file.ext.as_ref().and_then(|ext| EXTENSION_TYPES.get(ext)) {
            return Some(file_type.clone());
        }
        if file.name.ends_with('~') || (file.name.starts_with('#') && file.name.ends_with('#')) {
            return Some(Self::Temp);
        }
        if let Some(dir) = file.parent_dir {
            if file
                .get_source_files()
                .iter()
                .any(|path| dir.contains(path))
            {
                return Some(Self::Compiled);
            }
        }
        None
    }
}
