{ nixpkgs ? import ./Bild/Nixpkgs.nix }:

rec {
  constants = import ./Bild/Constants.nix;

  # internal usage
  private = {
    inherit nixpkgs;

    # provided by .envrc
    root = builtins.getEnv "BIZ_ROOT";

    selectAttrs = deps: packageSet:
      nixpkgs.lib.attrsets.attrVals deps packageSet;

    # returns true if a is a subset of b, where a and b are attrsets
    subset = a: b: builtins.all
      (x: builtins.elem x b) a;

    # 44 = lib.strings.stringLength "/nix/store/gia2r9mxhc900y1m97dlmr1g3rm3ich3-"
    dropNixStore = s: nixpkgs.lib.strings.substring 44 (nixpkgs.lib.strings.stringLength s) s;

    haskellDeps = import ./Bild/Deps/Haskell.nix;

    ghcWith = nixpkgs.haskell.packages.${constants.ghcCompiler}.ghcWithHoogle;

    sbclWith = nixpkgs.lispPackages_new.sbclWithPackages;

    ghcPackageSetFull = private.ghcWith private.haskellDeps;
    ghcPackageSetBild = private.ghcWith (hpkgs: with hpkgs; [
      aeson async base bytestring conduit conduit-extra containers directory
      docopt filepath process protolude rainbow regex-applicative split tasty
      tasty-hunit tasty-quickcheck text neat-interpolation
      wai # can remove when removed from Biz.Log
    ]);
    ghcPackageSetMin = private.ghcWith (hpkgs: with hpkgs; []);
  };

  # generally-useful things from nixpkgs
  inherit (nixpkgs) lib stdenv sources;

  # expose some packages for inclusion in os/image builds
  pkgs = with nixpkgs.pkgs; { inherit git; };

  # remove this when I switch to all-nix builds
  bildRuntimeDeps = with nixpkgs; [
    pkg-config
    private.ghcPackageSetMin
    gnutls
    rustc
    # c deps
    gcc gdb valgrind argp-standalone SDL
    # lisp deps
    guile
    (private.sbclWith (p: with p; [asdf alexandria])) # just enough to build Example.lisp
  ];

  # a standard nix build for `bild` - this should be the only hand-written
  # builder we need
  bild = stdenv.mkDerivation {
    name = "bild";
    src = ../.;
    nativeBuildInputs = [ private.ghcPackageSetBild ];
    buildInputs = [ nixpkgs.makeWrapper ];
    propagatedBuildInputs = bildRuntimeDeps;
    strictDeps = true;
    buildPhase = ''
      mkdir -p $out/bin $out/lib/ghc-${private.ghcPackageSetFull.version}
      cp -r \
        ${private.ghcPackageSetFull}/lib/ghc-${private.ghcPackageSetFull.version}/package.conf.d \
        $out/lib/ghc-${private.ghcPackageSetFull.version}
      ghc \
        -Werror \
        -i. \
        --make Biz/Bild.hs \
        -main-is Biz.Bild \
        -o $out/bin/bild
    '';
    installPhase = ''
      wrapProgram $out/bin/bild \
        --prefix PATH : ${lib.makeBinPath [ private.ghcPackageSetBild ]} \
        --set GHC_PACKAGE_PATH \
        $out/lib/ghc-${private.ghcPackageSetFull.version}/package.conf.d
    '';
  };

  # wrapper around bild
  runBildAnalyze = main: stdenv.mkDerivation rec {
    name = "bild-analysis";
    src = ../.;
    USER = "nixbld";
    HOSTNAME = "nix-sandbox";
    # this is the default sandbox path where bild will be working:
    BIZ_ROOT = "/build/biz";
    # we need to remove the $src root because bild expects paths relative to the
    # working directory:
    MAIN = "." + lib.strings.removePrefix (toString src) (toString main);
    buildPhase = ''
      mkdir $out
      ${bild}/bin/bild --json "$MAIN" 1> $out/analysis.json \
        2> >(tee -a $out/stderr >&2)
    '';
    installPhase = "exit 0";
  };

  # gather data needed for compiling by analyzing the main module
  analyze = main:
    let
      path = lib.strings.removePrefix (builtins.getEnv "BIZ_ROOT" + "/") (toString main);
    in
    lib.attrsets.getAttrFromPath [path]
      (lib.trivial.importJSON
        (runBildAnalyze main + "/analysis.json"));

  # build a ghc executable
  ghc = main:
    let
      data = analyze main;
      ghc = private.ghcWith (hp: private.selectAttrs data.langdeps hp);
      module = lib.strings.concatStringsSep "." data.namespace.path;
    in stdenv.mkDerivation {
      name = module;
      src = ../.;
      nativeBuildInputs = [ ghc ] ++ private.selectAttrs data.sysdeps nixpkgs.pkgs;
      strictDeps = true;
      buildPhase = ''
        set -eux
        mkdir -p $out/bin
        : compiling with ghc
        ${ghc}/bin/ghc \
          -Werror \
          -i. \
          --make ${main} \
          -main-is ${module} \
          -o $out/bin/${data.out}
      '';
      # the install process was handled above
      installPhase = "exit 0";
    } // { env = ghc; };

  env = let
    linters = with nixpkgs.pkgs; [ ormolu hlint deadnix indent ];
  in nixpkgs.pkgs.mkShell {
    name = "bizdev";
    # this should just be dev tools
    buildInputs = with nixpkgs.pkgs; linters ++ bildRuntimeDeps ++ [
      bild
      ctags
      figlet
      git
      haskell.packages.${constants.ghcCompiler}.fast-tags
      hlint
      lolcat
      #nixops # fails to build
      ormolu
      (private.nixpkgs.python3.withPackages(p: with p; [
        transformers
        pytorch
        private.nixpkgs.python3Packages.bitsandbytes
        private.nixpkgs.python3Packages.accelerate
        # lint tools:
        black
        pylint
      ]))

      shellcheck
      wemux
    ];
    shellHook = ''
      export GHC_PACKAGE_PATH=${bild}/lib/ghc-${private.ghcPackageSetFull.version}/package.conf.d
    '';
  };

  # build an operating system. 'cfg' is the NixOS config
  os = cfg: (nixpkgs.nixos (_args: cfg)).toplevel;

  # build a rust executable
  rust = main:
    let
      data = analyze main;
      rustc = nixpkgs.pkgs.rustc;
    in stdenv.mkDerivation {
      name = lib.string.concatStringsSep "::" data.namespace.path;
      src = ../.;
      nativeBuildInputs = [ rustc ];
      strictDeps = true;
      buildPhase = ''
        set -eux
        mkdir -p $out/bin
        : compiling with rustc
        ${rustc}/bin/rustc \
          ${main} \
          -o $out/bin/${data.out}
      '';
      installPhase = "exit 0";
    };

  # build a docker image
  image = nixpkgs.pkgs.dockerTools.buildImage;
}