{ lib
, stdenv
, python312
, fetchFromGitHub
, fetchurl
, pkg-config
, gitMinimal
, portaudio
, playwright-driver
, pkgs
, tree-sitter-grammars
}:

let
  python3 = python312.override {
    self = python3;
    packageOverrides = _: super: { tree-sitter = super.tree-sitter_0_21; };
  };

  tree-sitter-language-pack = python312.pkgs.buildPythonPackage {
    pname = "tree-sitter-language-pack";
    version = "0.6.1";
    src = fetchurl {
      url = "https://files.pythonhosted.org/packages/1b/d6/d9120dd60db977534ee1dea1459fa8695bfd220d003f2b7b9b74e9df19e0/tree_sitter_language_pack-0.6.1.tar.gz";
      sha256 = "1f826jb7sikd7rsr92y8c3b4jaf8byifmr01v5i2ar4vdddmyqx4";
    };
    pyproject = true;

    build-system = with python312.pkgs; [
      setuptools
      cython
      typing-extensions
    ];

    nativeBuildInputs = with pkgs; with pkgs.tree-sitter-grammars; [
      tree-sitter
      tree-sitter-c-sharp
      tree-sitter-embedded-template
      tree-sitter-yaml
    ];

    propagatedBuildInputs = with python312.pkgs; with pkgs.tree-sitter-grammars; [
      tree-sitter
      tree-sitter-c-sharp
      tree-sitter-embedded-template
      tree-sitter-yaml
    ];

    nativeCheckInputs = [ python312.pkgs.pytestCheckHook ];
    # Without cd $out, tests fail to import the compiled cython extensions.
    # Without copying the ./tests/ directory to $out, pytest won't detect the
    # tests and run them. See also:
    # https://github.com/NixOS/nixpkgs/issues/255262
    preCheck = ''
      cp -r tests $out/${python3.sitePackages}/tree_sitter_language_pack
      cd $out
    '';

    pythonImportsCheck = [ "tree_sitter_language_pack" ];
  };

  version = "0.79.0";
  aider-chat = python3.pkgs.buildPythonPackage {
    pname = "aider-chat";
    inherit version;
    pyproject = true;

    src = fetchFromGitHub {
      owner = "Aider-AI";
      repo = "aider";
      tag = "v${version}";
      hash = "sha256-8XC/pc5caNp8C7k/YBaLSXakjM13wxFgr2RkmaArIL8=";
    };

    pythonRelaxDeps = true;

    build-system = with python3.pkgs; [ setuptools-scm ];

    dependencies = with python3.pkgs; [
      aiohappyeyeballs
      aiohttp
      aiosignal
      annotated-types
      anyio
      attrs
      backoff
      beautifulsoup4
      certifi
      cffi
      charset-normalizer
      click
      configargparse
      diff-match-patch
      diskcache
      distro
      filelock
      flake8
      frozenlist
      fsspec
      gitdb
      gitpython
      grep-ast
      h11
      httpcore
      httpx
      huggingface-hub
      idna
      importlib-resources
      jinja2
      jiter
      json5
      jsonschema
      jsonschema-specifications
      litellm
      markdown-it-py
      markupsafe
      mccabe
      mdurl
      multidict
      networkx
      numpy
      openai
      packaging
      pathspec
      pexpect
      pillow
      prompt-toolkit
      psutil
      ptyprocess
      pycodestyle
      pycparser
      pydantic
      pydantic-core
      pydub
      pyflakes
      pygments
      pypandoc
      pyperclip
      python-dotenv
      pyyaml
      referencing
      regex
      requests
      rich
      rpds-py
      scipy
      smmap
      sniffio
      sounddevice
      socksio
      soundfile
      soupsieve
      tiktoken
      tokenizers
      tqdm
      tree-sitter
      tree-sitter-languages
      tree-sitter-language-pack
      typing-extensions
      urllib3
      watchfiles
      wcwidth
      yarl
      zipp
      pip

      # Not listed in requirements
      mixpanel
      monotonic
      posthog
      propcache
      python-dateutil
    ];

    buildInputs = [ portaudio ];

    nativeCheckInputs = (with python3.pkgs; [ pytestCheckHook ]) ++ [ gitMinimal ];

    disabledTestPaths = [
      # Tests require network access
      "tests/scrape/test_scrape.py"
      # Expected 'mock' to have been called once
      "tests/help/test_help.py"
    ];

    disabledTests =
      [
        # Tests require network
        "test_urls"
        "test_get_commit_message_with_custom_prompt"
        # FileNotFoundError
        "test_get_commit_message"
        # Expected 'launch_gui' to have been called once
        "test_browser_flag_imports_streamlit"
        # AttributeError
        "test_simple_send_with_retries"
        # Expected 'check_version' to have been called once
        "test_main_exit_calls_version_check"
        # AssertionError: assert 2 == 1
        "test_simple_send_non_retryable_error"
      ]
      ++ lib.optionals stdenv.hostPlatform.isDarwin [
        # Tests fails on darwin
        "test_dark_mode_sets_code_theme"
        "test_default_env_file_sets_automatic_variable"
        # FileNotFoundError: [Errno 2] No such file or directory: 'vim'
        "test_pipe_editor"
      ];

    makeWrapperArgs = [
      "--set AIDER_CHECK_UPDATE false"
      "--set AIDER_ANALYTICS false"
    ];

    preCheck = ''
      export HOME=$(mktemp -d)
      export AIDER_ANALYTICS="false"
    '';

    optional-dependencies = with python3.pkgs; {
      playwright = [
        greenlet
        playwright
        pyee
        typing-extensions
      ];
    };

    passthru = {
      withPlaywright = aider-chat.overridePythonAttrs (
        { dependencies
        , makeWrapperArgs
        , propagatedBuildInputs ? [ ]
        , ...
        }:
        {
          dependencies = dependencies ++ aider-chat.optional-dependencies.playwright;
          propagatedBuildInputs = propagatedBuildInputs ++ [ playwright-driver.browsers ];
          makeWrapperArgs = makeWrapperArgs ++ [
            "--set PLAYWRIGHT_BROWSERS_PATH ${playwright-driver.browsers}"
            "--set PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=true"
          ];
        }
      );
    };

    meta = {
      description = "AI pair programming in your terminal";
      homepage = "https://github.com/paul-gauthier/aider";
      changelog = "https://github.com/paul-gauthier/aider/blob/v${version}/HISTORY.md";
      license = lib.licenses.asl20;
      maintainers = with lib.maintainers; [ happysalada ];
      mainProgram = "aider";
    };
  };
in
aider-chat