{
  lib,
  stdenv,
  mkShell,
  writeShellScriptBin,
  # Elixir/Erlang
  elixir,
  # Databases
  postgresql_17,
  # Build tools for NIF deps (bcrypt_elixir via elixir_make)
  gcc,
  gnumake,
  pkg-config,
  # Development tools
  inotify-tools,
  # LSPs and formatters
  elixir-ls,
  nixfmt,
  shellcheck,
  # Pre-commit hooks
  pre-commit-hooks,
}:

let
  # PostgreSQL with PostGIS extension
  pg = postgresql_17.withPackages (ps: [
    ps.postgis
  ]);

  # PostgreSQL data directory (local to project)
  pgDataDir = ".nix-postgres";
  pgPort = "5432";
  pgHost = "localhost";

  # Service management flag
  servicesFlag = ".nix-services-started";

  # Version file to detect when PG config changes (extensions added/removed, version bump)
  pgVersionFile = ".nix-postgres-version";

  # Script to start PostgreSQL
  startServices = writeShellScriptBin "start-services" ''
    set -e

    echo "🚀 Starting development services..."

    # Check if PG version/extensions changed and reinit if needed.
    # Must run BEFORE the servicesFlag guard so already-running clusters
    # get reinitialized when extensions or versions change.
    pg_needs_reinit=false
    if [ -d "${pgDataDir}" ] && [ -f "${pgVersionFile}" ]; then
      if [ "$(cat ${pgVersionFile})" != "${pg}" ]; then
        echo "⚠️  PostgreSQL configuration changed (extensions or version updated)"
        pg_needs_reinit=true
      fi
    fi
    if [ -d "${pgDataDir}" ] && [ ! -f "${pgVersionFile}" ]; then
      pg_needs_reinit=true
    fi

    if $pg_needs_reinit; then
      echo "🗑️  Stopping old PostgreSQL and removing cluster..."
      ${pg}/bin/pg_ctl -D ${pgDataDir} stop -m fast 2>/dev/null || true
      rm -rf ${pgDataDir}
      rm -f ${servicesFlag}
    fi

    # Check if already started (with correct version)
    if [ -f "${servicesFlag}" ]; then
      echo "✓ Services already running (remove ${servicesFlag} to force restart)"
      exit 0
    fi

    # Initialize PostgreSQL if needed
    if [ ! -d "${pgDataDir}" ]; then
      echo "📦 Initializing PostgreSQL database..."
      ${pg}/bin/initdb -D ${pgDataDir} -U $USER --encoding=UTF8 --locale=en_US.UTF-8

      # Configure PostgreSQL
      cat >> ${pgDataDir}/postgresql.conf << EOF
    # Development configuration
    # max_connections sized for the test pool (schedulers_online * 16)
    max_connections = 300
    shared_buffers = 128MB
    fsync = off  # Faster for development (DO NOT use in production)
    synchronous_commit = off
    full_page_writes = off
    EOF

      # Record current PG version for future change detection
      echo -n "${pg}" > ${pgVersionFile}
    fi

    # Check if PostgreSQL is already running on this port
    if ${pg}/bin/pg_isready -h ${pgHost} -p ${pgPort} > /dev/null 2>&1; then
      echo "✓ PostgreSQL already running on port ${pgPort}"
    else
      # Start PostgreSQL
      echo "🐘 Starting PostgreSQL on port ${pgPort}..."
      ${pg}/bin/pg_ctl -D ${pgDataDir} -l ${pgDataDir}/logfile -o "-p ${pgPort}" start

      # Wait for PostgreSQL to be ready
      for i in {1..30}; do
        if ${pg}/bin/pg_isready -h ${pgHost} -p ${pgPort} > /dev/null 2>&1; then
          break
        fi
        if [ $i -eq 30 ]; then
          echo "❌ PostgreSQL failed to start within 30 seconds"
          echo "    Check ${pgDataDir}/logfile for details"
          exit 1
        fi
        sleep 1
      done
      echo "✓ PostgreSQL started successfully"
    fi

    # Create postgres role if it doesn't exist (dev.exs/test.exs connect as postgres)
    ${pg}/bin/psql -h ${pgHost} -p ${pgPort} -U $USER -d postgres -tc \
      "SELECT 1 FROM pg_roles WHERE rolname='postgres'" | grep -q 1 || \
      ${pg}/bin/psql -h ${pgHost} -p ${pgPort} -U $USER -d postgres -c \
        "CREATE ROLE postgres WITH SUPERUSER LOGIN PASSWORD 'postgres';"

    # Create development and test databases
    for db in aprsme_dev aprsme_test; do
      ${pg}/bin/psql -h ${pgHost} -p ${pgPort} -U $USER -d postgres -tc \
        "SELECT 1 FROM pg_database WHERE datname='$db'" | grep -q 1 || \
        ${pg}/bin/createdb -h ${pgHost} -p ${pgPort} -U $USER $db
    done

    # Enable PostGIS extension for dev and test databases
    for db in aprsme_dev aprsme_test; do
      ${pg}/bin/psql -h ${pgHost} -p ${pgPort} -U $USER -d $db -c \
        "CREATE EXTENSION IF NOT EXISTS postgis CASCADE;" 2>/dev/null || \
        echo "⚠️  PostGIS not available for $db (optional)"
    done

    echo "✓ PostgreSQL started successfully"

    # Mark services as started
    touch ${servicesFlag}

    echo ""
    echo "✨ Development environment ready!"
    echo ""
    echo "PostgreSQL: ${pgHost}:${pgPort} (databases: aprsme_dev, aprsme_test)"
    echo ""
  '';

  # Script to stop PostgreSQL
  stopServices = writeShellScriptBin "stop-services" ''
    set -e

    echo "🛑 Stopping development services..."

    # Stop PostgreSQL
    if [ -d "${pgDataDir}" ]; then
      ${pg}/bin/pg_ctl -D ${pgDataDir} stop -m fast 2>/dev/null || true
      echo "✓ PostgreSQL stopped"
    fi

    # Remove services flag
    rm -f ${servicesFlag}

    echo "✓ Services stopped successfully"
  '';

  # Pre-commit hooks configuration
  pre-commit-check = pre-commit-hooks.run {
    src = ../.;
    hooks = {
      # Elixir formatting
      mix-format = {
        enable = true;
        name = "mix format";
        entry = "mix format --check-formatted";
        files = "\\.(ex|exs)$";
        pass_filenames = false;
      };

      # Credo linting
      credo = {
        enable = true;
        name = "credo";
        entry = "mix credo --strict";
        files = "\\.(ex|exs)$";
        pass_filenames = false;
      };

      # Nix formatting
      nixfmt = {
        enable = true;
        entry = "${nixfmt}/bin/nixfmt";
      };

      # Shellcheck for shell scripts
      shellcheck.enable = true;
    };
  };

in
mkShell {
  name = "aprsme-dev";

  # Development tools
  buildInputs = [
    # Elixir/Erlang
    elixir

    # Databases (PostgreSQL with PostGIS)
    pg

    # NIF build tools (bcrypt_elixir)
    gcc
    gnumake
    pkg-config

    # LSPs and formatters
    elixir-ls
    nixfmt
    shellcheck

    # Service management scripts
    startServices
    stopServices
  ]
  ++ lib.optionals stdenv.isLinux [
    # Linux-only tools
    inotify-tools # For Mix file watching
  ];

  # Environment variables
  shellHook = ''
    # Color codes
    GREEN='\033[0;32m'
    BLUE='\033[0;34m'
    NC='\033[0m' # No Color

    # Set Mix and Hex home to local directories (avoid polluting $HOME)
    export MIX_HOME="$PWD/.nix-mix"
    export HEX_HOME="$PWD/.nix-hex"
    mkdir -p "$MIX_HOME" "$HEX_HOME"

    # PostgreSQL connection (dev.exs/test.exs connect as postgres:postgres)
    export PGHOST="${pgHost}"
    export PGPORT="${pgPort}"
    export PGUSER="$USER"
    export DATABASE_URL="ecto://postgres:postgres@${pgHost}:${pgPort}/aprsme_dev"

    # Development environment
    export MIX_ENV=dev
    export PHX_HOST=localhost
    export PORT=4000

    # Erlang shell history
    export ERL_AFLAGS="-kernel shell_history enabled"

    # Skip heavy setup when running inside direnv (DIRENV_DIR is set).
    # direnv evaluates the shellHook to load env vars only; running mix,
    # starting services, or installing pre-commit hooks would block the
    # prompt and cause nix profile cleanup hangs on macOS.
    if [ -z "$DIRENV_DIR" ]; then
      # Running in nix develop (interactive shell)

      # Install Hex and Rebar3 if not already installed
      mix local.hex --force --if-missing > /dev/null 2>&1
      mix local.rebar --force --if-missing > /dev/null 2>&1

      # Pre-compile test dependencies so mix test works immediately.
      # The _build/test tree is ephemeral (cleared when nix devShell changes).
      MIX_ENV=test mix deps.compile 2>/dev/null || true

      # Auto-start services on first shell entry.
      # Run in a detached subshell to prevent nix develop from waiting
      # on daemonized PostgreSQL child processes.
      if [ ! -f "${servicesFlag}" ]; then
        ( ${startServices}/bin/start-services </dev/null & )
      fi

      # Install pre-commit hooks
      ${pre-commit-check.shellHook}

      # Welcome message
      echo ""
      echo -e "''${BLUE}╔═══════════════════════════════════════════════════════════╗''${NC}"
      echo -e "''${BLUE}║                                                           ║''${NC}"
      echo -e "''${BLUE}║''${NC}  ''${GREEN}aprs.me Development Environment''${NC}                      ''${BLUE}║''${NC}"
      echo -e "''${BLUE}║                                                           ║''${NC}"
      echo -e "''${BLUE}╚═══════════════════════════════════════════════════════════╝''${NC}"
      echo ""
      echo -e "''${GREEN}Available commands:''${NC}"
      echo "  mix phx.server       - Start Phoenix development server"
      echo "  mix test             - Run test suite"
      echo "  mix format           - Format Elixir code"
      echo "  mix credo --strict   - Run static analysis"
      echo "  start-services       - Start PostgreSQL"
      echo "  stop-services        - Stop PostgreSQL"
      echo "  mix ecto.reset       - Reset database (drop, create, migrate, seed)"
      echo ""
      echo -e "''${GREEN}Services:''${NC}"
      echo "  PostgreSQL: ${pgHost}:${pgPort}"
      echo ""
    fi
  '';
}
