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

let
  constants = import ./Bild/Constants.nix;

  # expose some attrs from stable, keep this minimal and simple
  stable = let stable = nixpkgs.nixos-24_05;
  in {
    inherit (stable)
      sources lib makeWrapper ccacheStdenv haskell sbcl python3 nixos mkShell
      dockerTools pkgs;
    stdenv = stable.ccacheStdenv;
  };

  unstable = nixpkgs.nixos-unstable-small;

  # get the .src attributes of all drvs in each pkgset in the `sources` list,
  # and concat them with `:` into a Unix-style search path.
  # makeSourcesPath :: [pkgset] -> str
  makeSourcesPath = with stable;
    sources:
    lib.trivial.pipe sources [
      (builtins.map lib.attrsets.attrValues)
      lib.lists.flatten
      (builtins.filter (pkg: pkg != null))
      (builtins.map (pkg: if pkg ? src then pkg.src else pkg))
      (lib.strings.concatStringsSep ":")
    ];

  # this is the main library definitions, recursive references can be made with
  # `self.thing`, like in Python objects
  self = {
    # provided by .envrc
    root = builtins.getEnv "CODEROOT";

    inherit (stable) sources lib makeWrapper stdenv;

    haskell = rec {
      inherit (constants) ghcCompiler;

      ghcVersion = ghcPackageSetFull.version;

      # all available packages
      deps = import ./Bild/Deps/Haskell.nix;
      packages = self.lib.attrsets.getAttrs self.haskell.deps
        stable.haskell.packages."${constants.ghcCompiler}";

      # make a ghc with dependencies
      ghcWith = stable.haskell.packages.${ghcCompiler}.ghcWithHoogle;

      # ghc with all packages, used for generating bild's package database
      ghcPackageSetFull = ghcWith (p: self.lib.attrsets.attrVals deps p);

      # bild's dependencies, needs to be hand-written
      ghcPackageSetBild = 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
          hostname
          wai # can remove when removed from Biz.Log
        ]);
    };

    lisp = { sbclWith = stable.sbcl.withPackages; };

    python = {
      packages = self.lib.attrsets.getAttrs (import ./Bild/Deps/Python.nix)
        stable.python3.pkgs;
      pythonWith = stable.python3.withPackages;
      buildPythonApplication = stable.python3.pkgs.buildPythonApplication;
    };

    # c packages are just stable, filtered to just the list of deps i want
    c.packages =
      self.lib.attrsets.getAttrs (import ./Bild/Deps/C.nix) stable.pkgs;

    # exposed packages for inclusion in builds
    pkgs = with stable.pkgs; {
      inherit bat bc cmark universal-ctags deadnix fd figlet fzf git
        git-branchless gitlint groff guile hlint indent jq lolcat mypy nixfmt
        ormolu pkg-config ripgrep rustc tree wemux;
      llama-cpp = unstable.llama-cpp;
      llm = python3.withPackages (p: with p; [ p.llm-ollama ]);
      ollama = unstable.ollama;
      ruff = unstable.ruff;
      shellcheck = unstable.shellcheck;
    };

    # a standard nix build for bild, for bootstrapping. this should be the only
    # hand-written builder we need
    bild = self.stdenv.mkDerivation {
      name = "bild";
      srcs = self.lib.fileset.toSource {
        root = ../.;
        fileset = self.lib.fileset.unions [
          ../Alpha.hs
          ../Biz/Bild.hs
          ../Biz/Bild/Meta.hs
          ../Biz/Cli.hs
          ../Biz/Log.hs
          ../Biz/Namespace.hs
          ../Biz/Test.hs
        ];
      };
      nativeBuildInputs = [ self.haskell.ghcPackageSetBild ];
      buildInputs = [ self.makeWrapper ];
      propagatedBuildInputs = with self.pkgs; [
        pkg-config
        git
        # this is just to get access to ghc-pkg in bild
        (self.haskell.ghcWith (_: [ ]))

        # lisp deps, remove this when i implement nix builds for lisp
        guile
        (self.lisp.sbclWith
          (p: with p; [ alexandria ])) # just enough to build Example.lisp
      ];
      strictDeps = true;
      ghcVersion = self.haskell.ghcVersion;
      buildPhase = ''
        mkdir -p $out/bin $out/lib/ghc-$ghcVersion
        cp -r \
          ${self.haskell.ghcPackageSetFull}/lib/ghc-$ghcVersion/package.conf.d \
          $out/lib/ghc-$ghcVersion
        ghc \
          -threaded \
          -Werror \
          -Wall \
          -Winvalid-haddock \
          -haddock \
          -i. \
          --make Biz/Bild.hs \
          -main-is Biz.Bild \
          -o $out/bin/bild
      '';
      installPhase = ''
        wrapProgram $out/bin/bild \
          --prefix PATH : ${
            self.lib.makeBinPath [
              self.haskell.ghcPackageSetBild
              self.pkgs.git
            ]
          } \
          --set GHC_PACKAGE_PATH \
          $out/lib/ghc-$ghcVersion/package.conf.d
      '';
    };

    # wrapper around bild
    runBildAnalyze = target:
      self.stdenv.mkDerivation rec {
        name = "bild-analysis";
        src = ../.;
        USER = "nixbld";
        HOSTNAME = "nix-sandbox";
        # we need to remove the $src root because bild expects paths relative to the
        # working directory:
        TARGET = "."
          + self.lib.strings.removePrefix (toString src) (toString target);
        buildPhase = ''
          export CODEROOT=$(pwd)
          mkdir $out
          ${self.bild}/bin/bild --plan "$TARGET" 1> $out/analysis.json \
            2> >(tee -a $out/stderr >&2)
        '';
        installPhase = "exit 0";
      };

    # gather data needed for compiling by analyzing the main module. returns the
    # json object of the build
    analyze = target:
      builtins.readFile (self.runBildAnalyze target + "/analysis.json");

    # this does a bild build for the given target, but entirely in nix. its kinda
    # like IFD, but not as costly, i think
    run = target:
      import ./Bild/Builder.nix {
        analysisJSON = self.analyze target;
        bild = self;
      };

    # the main development environment
    env = stable.mkShell {
      name = "bizdev";
      # this should just be dev tools
      buildInputs = with self.pkgs; [
        bat
        bc
        self.bild
        universal-ctags
        fd
        figlet
        fzf
        git
        git-branchless
        gitlint
        jq
        lolcat
        llm
        ormolu
        ripgrep
        tree
        wemux
      ];
      shellHook = ''
        export GHC_PACKAGE_PATH=${self.bild}/lib/ghc-${self.haskell.ghcVersion}/package.conf.d
        export ALL_SOURCES=${
          makeSourcesPath [
            self.python.packages
            self.haskell.packages
            self.c.packages
            self.sources
          ]
        }
      '';
    };

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

    # build a docker image
    image = stable.dockerTools.buildImage;
  };
in self