{ lib
, stdenv
, buildPythonPackage
, fetchFromGitHub
, pythonOlder
, runCommand
, python
, git
, zcl-advanced-platform
, click
, setuptools
, lark
, jinja2
, stringcase
, clang-tools
, gn
, pkg-config
, libnl
, openssl
, glib
, ninja
, pyproject-hooks
, autoPatchelfHook

, aenum
, coloredlogs
, construct
, cryptography
, dacite
, ecdsa
, rich
, pyyaml
, ipdb
, deprecation
, mobly
, pygobject3

, ipython
, dbus-python
}:

let
  matterVersion = "1.2.0.1";  # check matter_sdk_ref in https://github.com/home-assistant-libs/chip-wheels/blob/main/.github/workflows/build.yaml
  matterSDKSrc = (fetchFromGitHub {
    owner = "project-chip";
    repo = "connectedhomeip";
    rev = "v${matterVersion}";
    hash = "sha256-CSOeTd0A95nePjrceCyVntRn36dz2q8Z9b8l2ehxBOQ=";
    forceFetchGit = true;
  }).overrideAttrs (old: {
    # Checkout submodules before .git directories are removed.
    nativeBuildInputs = old.nativeBuildInputs ++ [
      python
    ];
    env.NIX_PREFETCH_GIT_CHECKOUT_HOOK = ''
      python $dir/scripts/checkout_submodules.py --shallow --platform linux
    '';
  });

  zapPregenerated = stdenv.mkDerivation rec {
    pname = "matter-sdk-zap";
    version = matterVersion;

    src = matterSDKSrc;

    nativeBuildInputs = [
      python

      click
      lark
      jinja2
      stringcase

      zcl-advanced-platform  # zap-cli
      clang-tools  # clang-format
    ];

    postPatch = ''
      patchShebangs --build scripts
    '';

    buildPhase = ''
      runHook preBuild
      # Generate ZAP code
      python scripts/codepregen.py $out
      runHook postBuild
    '';
  };

  baseline = rec {
    pname = "home-assistant-chip";
    version = "2023.12.0";
    pyproject = false;

    disabled = pythonOlder "3.7";

    src = fetchFromGitHub {
      owner = "home-assistant-libs";
      repo = "chip-wheels";
      rev = version;
      hash = "sha256-/FUyP+DASaXSRFWuSnhSv+kWFR2bQATJr7QdeMV6kdg=";
    };

    inherit matterSDKSrc zapPregenerated;

    prePatch = ''
      cp -R $matterSDKSrc project-chip
      chmod -R +w project-chip
    '';

    postPatch = ''
      pushd project-chip
      rm ../*-Use-data-as-platform-storage-location.patch
      for patch in ../*.patch; do
        patch -p1 < "$patch"
      done
      patchShebangs --build scripts
      sed -i 's,[>=]=[0-9][^;]*,,g' \
        third_party/pigweed/repo/pw_env_setup/py/pw_env_setup/virtualenv_setup/python_base_requirements.txt
      popd
    '';

    buildInputs = [
      libnl
      openssl
      glib
    ];

    nativeBuildInputs = [
      gn  # gn
      ninja  # ninja
      pkg-config  # pkg-config
    ];

    gnArgs = [];

    configurePhase = ''
      runHook preConfigure

      pushd project-chip

      # Empty pigweed_environment
      echo "# Empty pigweed environment" > build_overrides/pigweed_environment.gni

      # Generate gn build scripts
      gn gen --check --fail-on-unused-args out --args=" \
          enable_rtti=true \
          enable_pylib=true \
          chip_config_memory_debug_checks=false \
          chip_config_memory_debug_dmalloc=false \
          chip_mdns=\"minimal\" \
          chip_minmdns_default_policy=\"libnl\" \
          chip_python_version=\"${version}\" \
          chip_python_package_prefix=\"home-assistant-chip\" \
          chip_python_platform_tag=\"manylinux_2_31\" \
          chip_code_pre_generated_directory=\"$zapPregenerated\" \
          $gnArgs
      "

      popd

      runHook postConfigure
    '';
  };

  src-wheels = stdenv.mkDerivation (baseline // {
    pname = "${baseline.pname}-src-wheels";

    gnArgs = [
      ''pw_build_PIP_CONSTRAINTS=["//scripts/setup/constraints.txt","//extra_constraints.txt"]''
    ];

    preConfigure = ''
      cp ${./compiled_requirements.txt} project-chip/extra_constraints.txt
    '';

    buildPhase = ''
      runHook preBuild

      pushd project-chip

      export HOME=$NIX_BUILD_TOP/home
      mkdir $HOME
      ninja -C out $(gn ls out --as=output '//:matter_build_venv.vendor_wheels')

      popd

      runHook postBuild
    '';

    nativeBuildInputs = baseline.nativeBuildInputs ++ [
      setuptools
    ];

    installPhase = ''
      runHook preInstall

      mkdir $out
      cp -R ./project-chip/out/python/gen/matter_build_venv.vendor_wheels/wheels $out/wheels
      sed -e 's/#.*//g' -e '/^\s*$/d' project-chip/out/python/gen/matter_build_venv/compiled_requirements.txt > $out/compiled_requirements.txt

      runHook postInstall
    '';

    outputHashMode = "recursive";
    outputHashAlgo = "sha256";
    outputHash = "sha256:11c0v6yc4wxnbkvya0jjf51rpbdfa9dk372ci46mhf0yzb7j5icz";
  });

  out-wheels = stdenv.mkDerivation (baseline // {
    pname = "${baseline.pname}-wheels";

    env.NIX_CFLAGS_COMPILE = "-Wno-error=cpp -Wno-cpp";

    gnArgs = [
      "pw_build_PYTHON_PIP_INSTALL_OFFLINE=true"
      "pw_build_PYTHON_PIP_INSTALL_DISABLE_CACHE=true"
      ''pw_build_PYTHON_PIP_INSTALL_FIND_LINKS=["//wheels"]''
    ];

    nativeBuildInputs = baseline.nativeBuildInputs ++ [
      setuptools
      glib  # gdbus-codegen

      pyproject-hooks
    ];

    buildPhase = ''
      runHook preBuild

      pushd project-chip

      ln -s ${src-wheels}/wheels wheels

      export HOME=$NIX_BUILD_TOP/home
      mkdir $HOME
      ninja -C out chip-repl

      popd

      runHook postBuild
    '';

    installPhase = ''
      runHook preInstall

      mkdir $out

      mkdir $out/wheels
      cp project-chip/out/controller/python/*.whl $out/wheels

      mkdir $out/wheels-short
      for f in $out/wheels/*.whl; do
        basef="''${f##*/}"
        ln -s "$f" $out/wheels-short/''${basef%%-*}.whl
      done

      runHook postInstall
    '';
  });

  outputWheel = name: overrides: buildPythonPackage ({
    pname = name;
    inherit (baseline) version;
    src = out-wheels;
    format = "wheel";

    preUnpack = ''
      src="$(readlink -f "$src/wheels-short/${lib.replaceStrings ["-"] ["_"] name}.whl")"
    '';
  } // overrides);
in
rec {
  inherit src-wheels;
  wheels = out-wheels;
  core = outputWheel "home-assistant-chip-core" {
    propagatedBuildInputs = [
      aenum
      coloredlogs
      construct
      cryptography
      dacite
      ecdsa
      rich
      pyyaml
      ipdb
      deprecation
      mobly
      pygobject3

      clusters
    ];

    buildInputs = [
      libnl
    ];

    nativeBuildInputs = [
      autoPatchelfHook
    ];

    pythonNamespaces = [
      "chip"
      "chip.clusters"
    ];

    pythonImportsCheck = [
      "chip"
      "chip.ble"
      "chip.configuration"
      "chip.clusters"
      "chip.discovery"
      "chip.exceptions"
      "chip.native"
      "chip.storage"
    ];

    doCheck = false;  # no tests
  };
  clusters = outputWheel "home-assistant-chip-clusters" {
    propagatedBuildInputs = [
      aenum
      dacite
    ];

    pythonNamespaces = [
      "chip"
    ];

    pythonImportsCheck = [
      "chip.clusters.ClusterObjects"
      "chip.clusters.enum"
      "chip.clusters.Objects"
      "chip.clusters.TestObjects"
      "chip.clusters.Types"
      "chip.tlv"
    ];
  };
  repl = outputWheel "home-assistant-chip-repl" {
    propagatedBuildInputs = [
      core
      clusters

      coloredlogs
      ipython
      rich
      dbus-python
      pygobject3
    ];

    pythonNamespaces = [
      "chip"
    ];

    pythonImportsCheck = [
      "chip.ChipBluezMgr"
    ];
  };
}