{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs =
    {
      self,
      nixpkgs,
      flake-utils,
    }:
    flake-utils.lib.eachDefaultSystem (
      system:
      let
        pkgs = import nixpkgs {
          inherit system;
          config = {
            # The Android SDK components and Android Studio are unfree, and the
            # SDK requires accepting its license non-interactively.
            allowUnfree = true;
            android_sdk.accept_license = true;
          };
        };

        buildToolsVersion = "35.0.0";

        # A minimal SDK: just the pieces needed to compile & assemble the app.
        # (No emulator/system-image here — those live in the `emulate` output.)
        androidComposition = pkgs.androidenv.composeAndroidPackages {
          platformVersions = [ "35" ];
          buildToolsVersions = [ buildToolsVersion ];
          includeEmulator = false;
          includeSystemImages = false;
          includeNDK = false;
        };

        androidSdk = androidComposition.androidsdk;
        androidHome = "${androidSdk}/libexec/android-sdk";

        # The built app, referenced by both `packages.default` and the emulator.
        musicRemote = pkgs.callPackage ./package.nix {
          inherit androidSdk androidHome buildToolsVersion;
        };
        applicationId = "ca.ksamad.encore";
      in
      {
        formatter = pkgs.nixfmt-rfc-style;

        # `nix build` -> reproducible debug APK in ./result/encore.apk
        packages.default = musicRemote;

        # `nix develop` -> command-line dev shell (gradle + SDK wired up).
        devShells.default = pkgs.mkShell {
          buildInputs = [
            pkgs.jdk17
            pkgs.gradle
            androidSdk
          ];

          ANDROID_HOME = androidHome;
          ANDROID_SDK_ROOT = androidHome;

          # Same NixOS aapt2 fix as in package.nix, but as a Gradle system prop
          # so plain `gradle assembleDebug` works inside the shell.
          GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${androidHome}/build-tools/${buildToolsVersion}/aapt2";

          shellHook = ''
            echo "encore dev shell — try: gradle assembleDebug"
          '';
        };

        # `nix develop .#studio` -> full Android Studio (GUI + emulator manager).
        devShells.studio = pkgs.mkShell {
          buildInputs = [ pkgs.androidStudioPackages.stable ];
        };

        # `nix run .#emulate` -> boots an emulator, then installs and launches the
        # app on it. `app` points at the built package directory (the script globs
        # `*.apk` inside it); `package`/`activity` make it auto-start after boot.
        packages.emulate = pkgs.androidenv.emulateApp {
          name = "emulate-Encore";
          platformVersion = "35";
          abiVersion = "x86_64";
          # Bare AOSP image. This app only needs the base Android framework
          # (TCP/HTTP networking for the MPD client); it uses no Google Play
          # Services APIs, so `google_apis`/`_playstore` would just be extra
          # weight. Switch to "google_apis" only if you add a Google SDK.
          systemImageType = "default";

          app = musicRemote;
          package = applicationId;
          activity = "${applicationId}.MainActivity";
        };
      }
    );
}
