rec {
  description = "FAIL";

  inputs = {
    nixpkgs.url = "nixpkgs"; # Use nixpkgs from system registry
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = {
    self,
    nixpkgs,
    flake-utils,
  }:
  # Create a shell (and possibly package) for each possible system, not only x86_64-linux
    flake-utils.lib.eachDefaultSystem (system: let
      # =========================================================================================
      # Define pkgs/stdenvs
      # =========================================================================================
      pkgs = import nixpkgs {
        inherit system;
        config.allowUnfree = true;
        overlays = [];
      };

      i386_pkgs = import nixpkgs {
        inherit system;
        config.allowUnfree = true;
        overlays = [];

        # Build crosscompiler
        crossSystem = {
          config = "i386-elf";
          libc = "newlib";
        };
      };

      boost_pkgs =
        import (builtins.fetchTarball {
          url = "https://github.com/NixOS/nixpkgs/archive/824421b1796332ad1bcb35bc7855da832c43305f.tar.gz";
          sha256 = "sha256:1w6cjnakz1yi66rs8c6nmhymsr7bj82vs2hz200ipi1sfiq8dy4y";
        }) {
          inherit system;
        };

      libdwarf_pkgs =
        import (builtins.fetchTarball {
          url = "https://github.com/NixOS/nixpkgs/archive/f597e7e9fcf37d8ed14a12835ede0a7d362314bd.tar.gz";
          sha256 = "sha256:1l79hh7jh7m8yc5mvc8dbg6s8rf30bgm994kf07xriqbzwfn158r";
        }) {
          inherit system;
        };

      inherit (pkgs) lib stdenv;

      # =========================================================================================
      # Define shell environment
      # =========================================================================================

      # Setup the shell when entering the "nix develop" environment (bash script).
      shellHook = let
        mkCmakeScript = type: let
          typeLower = lib.toLower type;
        in
          pkgs.writers.writeFish "cmake-${typeLower}.fish" ''
            cd $FLAKE_PROJECT_ROOT

            echo "Removing build directory ./cmake-build-${typeLower}/"
            rm -rf ./cmake-build-${typeLower}

            echo "Creating build directory"
            mkdir cmake-build-${typeLower}
            cd cmake-build-${typeLower}

            echo "Running cmake"
            cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE="${type}" -DCMAKE_EXPORT_COMPILE_COMMANDS="On" ..

            echo "Linking compile_commands.json"
            cd ..
            ln -sf ./cmake-build-${typeLower}/compile_commands.json ./compile_commands.json
          '';

        cmakeDebug = mkCmakeScript "Debug";
        cmakeRelease = mkCmakeScript "Release";

        mkBuildScript = type: let
          typeLower = lib.toLower type;
        in
          pkgs.writers.writeFish "cmake-build.fish" ''
            cd $FLAKE_PROJECT_ROOT/cmake-build-${typeLower}

            echo "Running cmake"
            cmake --build .
          '';

        buildDebug = mkBuildScript "Debug";
        buildRelease = mkBuildScript "Release";

        # Use this to specify commands that should be ran after entering fish shell
        initProjectShell = pkgs.writers.writeFish "init-shell.fish" ''
          echo "Entering \"${description}\" environment..."

          # Determine the project root, used e.g. in cmake scripts
          set -g -x FLAKE_PROJECT_ROOT (git rev-parse --show-toplevel)

          # C/C++:
          # abbr -a cmake-debug "${cmakeDebug}"
          # abbr -a cmake-release "${cmakeRelease}"
          # abbr -a build-debug "${buildDebug}"
          # abbr -a build-release "${buildRelease}"
        '';
      in
        builtins.concatStringsSep "\n" [
          # Launch into pure fish shell
          ''
            exec "$(type -p fish)" -C "source ${initProjectShell} && abbr -a menu '${pkgs.bat}/bin/bat "${initProjectShell}"'"
          ''
        ];

      # ===========================================================================================
      # Define custom dependencies
      # ===========================================================================================

      python = pkgs.python314.withPackages (p:
        with p; [
          setuptools
          flask
          flask-mysqldb
          pyyaml
        ]);

      libpcl = stdenv.mkDerivation rec {
        pname = "libpcl1";
        version = "1.12-2";

        src = pkgs.fetchurl {
          url = "http://launchpadlibrarian.net/521269537/${pname}_${version}_amd64.deb";
          hash = "sha256-GL3mjPAccAtRMAJPnDMCHiDf6xNvGi4oUWylOIqBjP0=";
        };

        dontBuild = true;
        nativeBuildInputs = with pkgs; [
          dpkg
          autoPatchelfHook
        ];

        installPhase = ''
          runHook preInstall

          mkdir -p $out/lib
          cp -rv usr/lib/x86_64-linux-gnu/* $out/lib/

          runHook postInstall
        '';
      };

      wasi-sdk = stdenv.mkDerivation rec {
        pname = "wasi-sdk";
        version = "29";

        src = let
          baseurl = "https://github.com/WebAssembly/wasi-sdk/releases/download";
        in
          builtins.fetchTarball {
            url = "${baseurl}/${pname}-${version}/${pname}-${version}.0-x86_64-linux.tar.gz";
            sha256 = "sha256:16afis71iqfvwiny4dz0lk9f7wbary0wa67ybwyhywr8g57ss6hq";
          };

        nativeBuildInputs = with pkgs; [
          autoPatchelfHook
        ];

        buildInputs = with pkgs; [
          libgcc.lib
        ];

        dontBuild = true;
        installPhase = ''
          runHook preInstall

          mkdir -p $out
          cp -rv ./* $out/

          runHook postInstall
        '';
      };

      iwasm = stdenv.mkDerivation rec {
        pname = "iwasm";
        version = "2.4.4";

        src = let
          baseurl = "https://github.com/bytecodealliance/wasm-micro-runtime/releases/download";
        in
          builtins.fetchTarball {
            url = "${baseurl}/WAMR-${version}/${pname}-${version}-x86_64-ubuntu-22.04.tar.gz";
            sha256 = "sha256:05irihz3yf7hpc0a59qz9i62imhrsni9xy9nxwsn6b8s92c1yzrp";
          };

        nativeBuildInputs = with pkgs; [
          autoPatchelfHook
        ];

        buildInputs = with pkgs; [
          libz
          zstd
          libgcc.lib
        ];

        dontBuild = true;
        installPhase = ''
          runHook preInstall

          mkdir -p $out/bin
          cp -rv ./* $out/bin/

          runHook postInstall
        '';
      };

      wamrc = stdenv.mkDerivation rec {
        pname = "wamrc";
        version = "2.4.4";

        src = let
          baseurl = "https://github.com/bytecodealliance/wasm-micro-runtime/releases/download";
        in
          builtins.fetchTarball {
            url = "${baseurl}/WAMR-${version}/${pname}-${version}-x86_64-ubuntu-22.04.tar.gz";
            sha256 = "sha256:0264arh03gc35z0zdvw07qdvqgfvsxr3qgl1aszghwicmdmh4sqj";
          };

        nativeBuildInputs = with pkgs; [
          autoPatchelfHook
        ];

        buildInputs = with pkgs; [
          libz
          zstd
          libgcc.lib
        ];

        dontBuild = true;
        installPhase = ''
          runHook preInstall

          mkdir -p $out/bin
          cp -rv ./* $out/bin/

          runHook postInstall
        '';
      };

      wamr = stdenv.mkDerivation {
        pname = "wamr";
        version = "2.4.4";

        src = pkgs.fetchFromGitea {
          domain = "gitea.local.chriphost.de";
          owner = "christoph";
          repo = "wamr";
          rev = "fd69a4e76ec0d384bd79f514772b7dfa240fc0d7";
          hash = "sha256-rlCx4isI0k6rC9E0hWIA9LeinqiACug7zxj9z/e4SBQ=";
        };

        dontBuild = true;
        installPhase = ''
          runHook preInstall

          mkdir -p $out
          cp -rv ./* $out/

          runHook postInstall
        '';
      };

      mkLibiwasm = {
        buildenv,
        platform,
        buildtype,
        cflags,
        extraCmakeFlags ? [],
      }:
        buildenv.mkDerivation {
          pname = "libiwasm";
          version = "2.4.4";

          src = pkgs.fetchFromGitea {
            domain = "gitea.local.chriphost.de";
            owner = "christoph";
            repo = "wamr";
            rev = "fd69a4e76ec0d384bd79f514772b7dfa240fc0d7";
            hash = "sha256-rlCx4isI0k6rC9E0hWIA9LeinqiACug7zxj9z/e4SBQ=";
          };

          nativeBuildInputs = with pkgs; [cmake];

          cmakeBuildType = buildtype;
          cmakeFlags =
            extraCmakeFlags
            ++ [
              "-DCMAKE_VERBOSE_MAKEFILE=ON"
              "-DCMAKE_COLOR_DIAGNOSTICS=ON"

              "-DWAMR_BUILD_PLATFORM=${platform}"
              "-DWAMR_BUILD_TARGET=X86_32"
              "-DWAMR_BUILD_AOT=1"
              "-DWAMR_BUILD_WAMR_COMPILER=0"
              "-DWAMR_BUILD_INTERP=1"
              "-DWAMR_BUILD_FAST_INTERP=0"
              "-DWAMR_BUILD_JIT=0"
              "-DWAMR_BUILD_FAST_JIT=0"
              "-DWAMR_BUILD_LIBC_BUILTIN=1"
              "-DWAMR_BUILD_LIBC_WASI=0"
              "-DWAMR_BUILD_SIMD=0"
            ];

          # Since GCC 15, implicit declarations are an error. Disable this.
          NIX_CFLAGS_COMPILE = "-Wno-error=implicit-function-declaration " + cflags;
        };

      libiwasm-baremetal-debug = mkLibiwasm {
        buildenv = i386_pkgs.stdenv;
        platform = "baremetal";
        buildtype = "Debug";
        cflags = "-O0 -ggdb";
        extraCmakeFlags = [
          "-DCMAKE_SYSTEM_NAME=Generic"
          "-DCMAKE_SYSTEM_PROCESSOR=i386"
          "-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY"
        ];
      };
      libiwasm-baremetal-release = mkLibiwasm {
        buildenv = i386_pkgs.stdenv;
        platform = "baremetal";
        buildtype = "Release";
        cflags = "-O2 -ggdb -DNDEBUG";
        extraCmakeFlags = [
          "-DCMAKE_SYSTEM_NAME=Generic"
          "-DCMAKE_SYSTEM_PROCESSOR=i386"
          "-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY"
        ];
      };

      libiwasm-linux-debug = mkLibiwasm {
        buildenv = pkgs.multiStdenv;
        platform = "linux";
        buildtype = "Debug";
        cflags = "-O0 -ggdb";
      };
      libiwasm-linux-release = mkLibiwasm {
        buildenv = pkgs.multiStdenv;
        platform = "linux";
        buildtype = "Release";
        cflags = "-O2 -ggdb -DNDEBUG";
      };

      # ===========================================================================================
      # Specify dependencies
      # https://nixos.org/manual/nixpkgs/stable/#ssec-stdenv-dependencies-overview
      # Just for a "nix develop" shell, buildInputs can be used for everything.
      # ===========================================================================================

      # Add dependencies to nativeBuildInputs if they are executed during the build:
      # - Those which are needed on $PATH during the build, for example cmake and pkg-config
      # - Setup hooks, for example makeWrapper/autoPatchelfHook
      # - Interpreters needed by patchShebangs for build scripts (with the --build flag), which can be the case for e.g. perl
      nativeBuildInputs = with pkgs; [
        just
        gdb
        xxd
        wabt
        grub2
        xorriso
        mariadb.client
        dbeaver-bin
        iwasm
        wamrc
        fail-bin

        # Don't install to not pollute our PATH. Just export as environment variable.
        # wasi-sdk
        # libiwasm_debug
        # libiwasm_release
      ];

      # Add dependencies to buildInputs if they will end up copied or linked into the final output or otherwise used at runtime:
      # - Libraries used by compilers, for example zlib
      # - Interpreters needed by patchShebangs for scripts which are installed, which can be the case for e.g. perl
      buildInputs = with pkgs; [];

      # ===========================================================================================
      # Define buildable + installable packages
      # ===========================================================================================

      fail-bin = stdenv.mkDerivation {
        pname = "fail";
        version = "1.0.0";
        src = ./.;

        nativeBuildInputs = with pkgs; [
          autoPatchelfHook
        ];

        buildInputs = with pkgs; [
          # FAIL runtime dependencies
          python # bochs-experiment-runner.py, resultbrowser.py
          alsa-lib # libasound.so.2
          boost_pkgs.boost174 # libboost_coroutine.so.1.74.0, libboost_regex.so.1.74.0, libboost_thread.so.1.74.0
          capstone_4 # libcapstone.so.4
          libdwarf_pkgs.libdwarf # libdwarf.so.1
          elfutils # libelf.so.1
          mariadb # libmariadb.so.3
          libpcl # libpcl.so.1
          protobuf_21 # libprotobuf.so.32
          SDL # libSDL-1.2.so.0
          libx11 # libX11.so.6
          libxrandr # libXrandr.so.2
          libz # libz.so.1
        ];

        installPhase = ''
          runHook preInstall

          mkdir -p $out
          cp -rv ./bin $out/bin
          cp -rv ./share $out/share

          runHook postInstall
        '';
      };
    in {
      # Provide package for "nix build"
      packages = {
        default = fail-bin;
        fail = fail-bin;
        wasi-sdk = wasi-sdk;
        iwasm = iwasm;
        wamrc = wamrc;
      };
      apps = {
        default = flake-utils.lib.mkApp {drv = fail-bin;};
        fail = fail-bin;
        wasi-sdk = wasi-sdk;
        iwasm = iwasm;
        wamrc = wamrc;
      };

      devShells = {
        # Provide default environment for "nix develop".
        # Other environments can be added below.
        default = pkgs.mkShell {
          inherit nativeBuildInputs buildInputs shellHook;
          name = description;

          # =========================================================================================
          # Define environment variables
          # =========================================================================================

          # Dynamic libraries from buildinputs:
          # LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;

          JUST_JUSTFILE = "./nixos.just";

          # Those are read by the justfile
          FAIL_SHARE = "${fail-bin}/share";
          WASI_ROOT = wasi-sdk;
          WAMR_ROOT = wamr;
          LIBIWASM_DEBUG = "${libiwasm-baremetal-debug}/lib";
          LIBIWASM_RELEASE = "${libiwasm-baremetal-release}/lib";
          LIBIWASM_LINUX_DEBUG = "${libiwasm-linux-debug}/lib";
          LIBIWASM_LINUX_RELEASE = "${libiwasm-linux-release}/lib";
          CROSS_CC = "${i386_pkgs.stdenv.cc}/bin/i386-elf-gcc";
          CROSS_CXX = "${i386_pkgs.stdenv.cc}/bin/i386-elf-g++";
          LINUX_CC = "${pkgs.multiStdenv.cc}/bin/gcc";
          LINUX_CXX = "${pkgs.multiStdenv.cc}/bin/g++";
        };
      };
    });
}
