# =============================================================================
# docker-compose.dev.yml — Development Orchestration
# =============================================================================
# Startup order (enforced via depends_on + healthchecks):
#   postgres → redis → app → worker → nginx
#
# WHY depends_on with condition: service_healthy is not enough alone:
#   - It only gates container START, not application readiness
#   - A container passes its healthcheck once, then Docker considers it done
#   - Entrypoint wait-loops are second-layer defence (belt + suspenders)
# =============================================================================

name: taskco-dev

services:

  # ═══════════════════════════════════════════════════════════════════════════
  # TIER 1 — Data Stores (no upstream dependencies)
  # ═══════════════════════════════════════════════════════════════════════════

  postgres:
    image: postgres:16-alpine
    container_name: taskco-postgres
    restart: unless-stopped
    environment:
      POSTGRES_DB: ${DB_DATABASE:-taskco_landlord}
      POSTGRES_USER: ${DB_USERNAME:-taskco_admin}
      POSTGRES_PASSWORD: ${DB_PASSWORD:-change_me}
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - taskco-postgres-data:/var/lib/postgresql/data
      - ./.docker/postgres/postgresql.conf:/etc/postgresql/postgresql.conf:ro
      - ./.docker/postgres/pg_hba.conf:/etc/postgresql/pg_hba.conf:ro
      - ./.docker/postgres/init-script:/docker-entrypoint-initdb.d:ro
    command: ["postgres", "-c", "config_file=/etc/postgresql/postgresql.conf"]
    ports:
      - "${DB_EXTERNAL_PORT:-5442}:5432"
    networks:
      - taskco_net
    healthcheck:
      # WHY pg_isready and not TCP: pg_isready proves postgres is accepting
      # queries. Raw TCP passes as soon as the socket opens — before postgres
      # finishes initialisation — causing false-positive healthy state.
      test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U ${DB_USERNAME:-taskco_admin} -d ${DB_DATABASE:-taskco_landlord}"]
      interval: 5s
      timeout: 5s
      retries: 10
      start_period: 15s

  redis:
    image: redis:7-alpine
    container_name: taskco-redis
    restart: unless-stopped
    # WHY --save "" --appendonly no in dev: no disk persistence needed,
    # removes BGSAVE blocking, eliminates unnecessary disk I/O in dev.
    command: ["redis-server", "--save", "", "--appendonly", "no"]
    ports:
      - "${REDIS_EXTERNAL_PORT:-6382}:6379"
    volumes:
      - taskco-redis-data:/data
    networks:
      - taskco_net
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 5s

  # ═══════════════════════════════════════════════════════════════════════════
  # TIER 2 — Application Services
  # ═══════════════════════════════════════════════════════════════════════════

  app:
    build:
      context: .
      dockerfile: .docker/app/dev/Dockerfile
      args:
        HOST_UID: ${HOST_UID:-1000}
        HOST_GID: ${HOST_GID:-1000}
    image: taskco-app-dev:latest
    container_name: taskco-app
    restart: unless-stopped
    env_file: .env
    environment:
      APP_ENV: local
      APP_DEBUG: "true"
      LOG_CHANNEL: stderr
    volumes:
      # Bind mount for hot reload. storage/ and bootstrap/cache get named
      # volumes so they are NOT overwritten by the host bind mount on restart.
      - ./app:/var/www/html
      - taskco-storage:/var/www/html/storage
      - taskco-bootstrap-cache:/var/www/html/bootstrap/cache
      # WHY named volume for node_modules: host has macOS-native binaries
      # (e.g. @rollup/rollup-darwin-arm64). Container needs Linux binaries.
      # Named volume masks host modules; entrypoint runs pnpm install.
      - taskco-app-modules:/var/www/html/node_modules
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - taskco_net
    healthcheck:
      # Real HTTP to /health proves: Octane/artisan-serve is up, app
      # bootstrapped, DB connection working. TCP-only check would pass
      # even if PHP crashed mid-boot.
      test: ["CMD-SHELL", "curl -sf http://127.0.0.1:8000/health || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  nextjs:
    build:
      context: .
      dockerfile: .docker/web/dev/Dockerfile
    image: taskco-web-dev:latest
    container_name: taskco-nextjs
    restart: unless-stopped
    environment:
      NODE_ENV: development
      PORT: "3000"
      NEXT_TELEMETRY_DISABLED: "1"
      # WHY no NEXT_PUBLIC_API_URL here: web/.env sets it to
      # http://demo.localhost:8000 (tenant subdomain, no /api suffix).
      # Overriding it here with localhost:8000/api caused TWO bugs:
      #   1) Double /api/ — axios baseURL already had /api, API paths
      #      also start with /api/ → http://…/api/api/publish/meta → 404
      #   2) No tenant context — bare localhost hits the non-tenant
      #      nginx server block, so Laravel can't identify the tenant → 404
    volumes:
      - ./web:/app
      # WHY named volumes for node_modules and .next:
      # macOS↔Linux filesystem differences (case sensitivity, inotify limits)
      # break host-mounted node_modules. Named volumes stay in the Linux layer.
      - taskco-nextjs-modules:/app/node_modules
      - taskco-nextjs-next:/app/.next
    ports:
      - "3000:3000"
    networks:
      - taskco_net
    healthcheck:
      test: ["CMD-SHELL", "wget -qO /dev/null http://127.0.0.1:3000/ || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 60s

  worker:
    # Reuses app-dev image (same PHP + extensions + supervisor).
    # Eliminates duplicate 500s PHP extension build.
    image: taskco-app-dev:latest
    container_name: taskco-worker
    restart: unless-stopped
    env_file: .env
    entrypoint: ["/bin/sh", "-c"]
    command:
      - |
        chown -R appuser:appgroup /var/log/supervisor /var/run/supervisor 2>/dev/null || true
        chown -R appuser:appgroup /var/www/html/storage /var/www/html/bootstrap/cache 2>/dev/null || true
        exec /usr/bin/supervisord -c /etc/supervisord.conf
    volumes:
      - ./app:/var/www/html
      - taskco-storage:/var/www/html/storage
      # Supervisor configs mounted from host (not baked in app-dev image)
      - ./.docker/worker/supervisord.conf:/etc/supervisord.conf:ro
      - ./.docker/worker/worker.conf:/etc/supervisor.d/worker.conf:ro
    # WHY depends on app:healthy: app runs migrations (if AUTO_MIGRATE=true).
    # Workers must start AFTER migrations. Otherwise queue jobs reference
    # tables that don't exist and fail permanently (not retryably).
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      app:
        condition: service_healthy
    networks:
      - taskco_net
    healthcheck:
      # Checks supervisorctl: proves supervisord is running AND workers are
      # in RUNNING state. FATAL state means auto-restart exhausted → unhealthy.
      test: ["CMD-SHELL", "supervisorctl -c /etc/supervisord.conf status 2>/dev/null | grep -q RUNNING || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s

  # ═══════════════════════════════════════════════════════════════════════════
  # TIER 3 — Edge
  # ═══════════════════════════════════════════════════════════════════════════

  nginx:
    image: nginx:1.27-alpine
    container_name: taskco-nginx
    restart: unless-stopped
    ports:
      - "8000:80"
    volumes:
      # WHY mount both nginx.conf AND site conf:
      # nginx.conf has global settings (gzip, buffers, DNS resolver 127.0.0.11).
      # Mounting only the site conf leaves nginx with Docker-default globals
      # that differ between versions and may not include the Docker DNS resolver.
      - ./.docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./.docker/nginx/app/dev.conf:/etc/nginx/conf.d/default.conf:ro
      - ./app/public:/var/www/html/public:ro
    extra_hosts:
      # On Linux, host.docker.internal is not auto-populated → explicit entry.
      # Needed because dev.conf routes /vite/ to host:5173 (Vite on host).
      - "host.docker.internal:host-gateway"
    depends_on:
      app:
        condition: service_healthy
      nextjs:
        condition: service_healthy
    networks:
      - taskco_net
    healthcheck:
      # nginx -t validates config. HTTP /health hits default_server which
      # returns 200. Using / would hit 444 (catch-all reject).
      test: ["CMD-SHELL", "nginx -t 2>/dev/null && wget -qO /dev/null http://127.0.0.1:80/health || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s

  # ═══════════════════════════════════════════════════════════════════════════
  # DEV TOOLS
  # ═══════════════════════════════════════════════════════════════════════════

  mailpit:
    image: axllent/mailpit:latest
    container_name: taskco-mailpit
    restart: unless-stopped
    ports:
      - "${MAILPIT_SMTP_PORT:-1026}:1025"
      - "${MAILPIT_UI_PORT:-8026}:8025"
    networks:
      - taskco_net

networks:
  taskco_net:
    driver: bridge
    name: taskco_net

volumes:
  taskco-postgres-data:
  taskco-redis-data:
  taskco-storage:
  taskco-bootstrap-cache:
  taskco-app-modules:      # Linux-native node_modules for app container
  taskco-nextjs-modules:
  taskco-nextjs-next:
