server {
    listen 80;

    # The nginx image ships with gzip off, so the bundle went out raw: 555KB of
    # JS where gzip sends 167KB. Only text formats are listed — images, fonts
    # (woff2) and audio are already compressed and would only burn CPU.
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    # The public URL sits behind another proxy, which adds a Via header. With
    # the default gzip_proxied off, nginx refuses to compress those responses.
    gzip_proxied any;
    # That same proxy speaks HTTP/1.0 upstream (nginx proxy_pass defaults to it),
    # and gzip_http_version defaults to 1.1 — so without this nothing here is
    # ever compressed, however the client asks.
    gzip_http_version 1.0;
    gzip_comp_level 6;
    gzip_types
        application/javascript
        application/json
        application/manifest+json
        image/svg+xml
        text/css
        text/plain;

    location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
        try_files $uri $uri/ /index.html;
    }

    # A cached service worker or shell would pin the app to an old build, since
    # both are the files that point at every hashed asset. Assets themselves are
    # content-hashed by Vite, so they can be held forever.
    # nginx's stock mime.types has no .webmanifest entry, so it would otherwise
    # go out as application/octet-stream.
    location = /manifest.webmanifest {
        root /usr/share/nginx/html;
        default_type application/manifest+json;
        add_header Cache-Control "no-cache, must-revalidate";
    }

    location = /sw.js {
        root /usr/share/nginx/html;
        add_header Cache-Control "no-cache, must-revalidate";
    }

    location = /index.html {
        root /usr/share/nginx/html;
        add_header Cache-Control "no-cache, must-revalidate";
    }

    location /assets/ {
        root /usr/share/nginx/html;
        add_header Cache-Control "public, max-age=31536000, immutable";
    }

    # Admin endpoints need the admin key, not the regular API key. This prefix
    # location is longer than "/api", and nginx picks the longest matching
    # prefix location, so it wins for /api/admin/* while /api handles the rest.
    location /api/admin/ {
        proxy_pass http://backend:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header Authorization "Bearer ${MUZICK_ADMIN_KEY}";
        proxy_cache_bypass $http_upgrade;
    }

    # The cross-device event stream is long-lived and must arrive unbuffered:
    # with proxy buffering on, nginx holds each event until it has a chunk worth
    # forwarding, which is exactly the latency this channel exists to avoid.
    location /api/playback/stream {
        proxy_pass http://backend:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header Authorization "Bearer ${MUZICK_API_KEY}";
        proxy_set_header Connection '';
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 1h;
    }

    location /api {
        proxy_pass http://backend:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header Authorization "Bearer ${MUZICK_API_KEY}";
        proxy_cache_bypass $http_upgrade;
    }
}
