# ============================================================
# SmartStack Frontend - Multi-stage build
# Image: smartstack-{project}-frontend:{tag}
# Serves the SPA on port 3000 — the smartstack-infra ingress contract
# (web_port, default 3000). See nginx-frontend.conf.
# ============================================================

# --- Stage 1: Build ---
FROM node:22-alpine AS build
WORKDIR /app

# Copy the manifest AND the lockfile (context = project root): npm ci installs
# the EXACT dependency tree the client validated locally. Without the lock, the
# caret range on @atlashub/smartstack resolves to whatever is published at
# build time — navigation ComponentKeys drift from the pinned NuGet package and
# pages die with MISSING_PAGE/<key>. If this COPY fails, commit the
# package-lock.json that `ss init` generated (it is intentionally NOT
# gitignored) — the explicit failure is wanted.
COPY web/{{ProjectNameLower}}-web/package.json web/{{ProjectNameLower}}-web/package-lock.json ./

# The package.json `postinstall` hook runs `node scripts/patch-smartstack-theme.cjs`
# (a Tailwind v4 workaround that patches node_modules/@atlashub/smartstack), so the
# scripts/ dir must be present BEFORE npm ci fires it. Copied here (scripts
# change rarely) to keep the dependency layer cached.
COPY web/{{ProjectNameLower}}-web/scripts ./scripts

# Reproducible install from the lockfile (cached layer)
RUN npm ci

# Copy source code (node_modules survives: **/node_modules/ is dockerignored)
COPY web/{{ProjectNameLower}}-web/ .

# Build for production
RUN npm run build:prod

# Build metadata served at /version.json (no-store, see nginx conf): the build
# date and the EXACT @atlashub/smartstack version bundled in this image — so a
# deployed environment can be identified without diffing asset hashes.
RUN node -e "require('fs').writeFileSync('dist/version.json', JSON.stringify({ app: require('./package.json').version, smartstack: require('./node_modules/@atlashub/smartstack/package.json').version, builtAt: new Date().toISOString() }, null, 2))"

# --- Stage 2: Runtime ---
FROM nginx:alpine AS runtime

# Copy built files
COPY --from=build /app/dist /usr/share/nginx/html

# Nginx config as an envsubst TEMPLATE: the official nginx entrypoint renders
# /etc/nginx/templates/*.template -> /etc/nginx/conf.d/default.conf at startup,
# substituting ONLY the env vars named by NGINX_ENVSUBST_FILTER below. That
# filter is what protects nginx runtime vars ($uri, $host, $scheme, ...) from
# being clobbered by a stray HOST/SCHEME/URI in the container environment.
COPY docker-images/nginx-frontend.conf /etc/nginx/templates/default.conf.template

# API_FQDN has NO default: it names the API host and must be injected per
# environment (Container App env var, or compose `environment:`). API_SCHEME
# defaults to https (Container Apps re-encrypts to the API); compose overrides
# it to http.
ENV API_SCHEME=https
ENV NGINX_ENVSUBST_FILTER='^(API_FQDN|API_SCHEME|DNS_RESOLVER)$'

# Entrypoint drop-in, sourced by the official entrypoint BEFORE envsubst
# (*.envsh files run in the same shell, so the exports reach the template
# rendering). Two guards against silent boot failures:
#   1. fail fast with an explicit message when API_FQDN is missing — envsubst
#      would otherwise render a broken proxy_pass and nginx dies cryptically;
#   2. autodetect DNS_RESOLVER from /etc/resolv.conf so the nginx `resolver`
#      works on Container Apps (168.63.129.16) AND under compose (127.0.0.11)
#      without per-environment config.
# Generated inline rather than shipped as a repo file: immune to CRLF
# checkouts and lost executable bits, both of which would silently disable it.
RUN printf '%s\n' \
      '#!/bin/sh' \
      '# SmartStack: validate/derive the env consumed by nginx-frontend.conf.' \
      '# Sourced by the entrypoint under `set -eu`: every expansion must be' \
      '# ${VAR:-}-guarded and every branch must end status 0.' \
      'if [ -z "${API_FQDN:-}" ]; then' \
      '  echo >&2 "FATAL: API_FQDN is not set. Set it to the API hostname (e.g. myapi.internal.<env>.azurecontainerapps.io, or backend:5142 under compose)."' \
      '  exit 1' \
      'fi' \
      'export API_SCHEME="${API_SCHEME:-https}"' \
      'if [ -z "${DNS_RESOLVER:-}" ] && [ -f /etc/resolv.conf ]; then' \
      '  while read -r k v _; do if [ "$k" = "nameserver" ]; then DNS_RESOLVER="$v"; break; fi; done < /etc/resolv.conf' \
      'fi' \
      'export DNS_RESOLVER="${DNS_RESOLVER:-127.0.0.11}"' \
      > /docker-entrypoint.d/15-smartstack-env.envsh \
    && chmod +x /docker-entrypoint.d/15-smartstack-env.envsh

# PORT CONTRACT: 3000 = smartstack-infra web_port (see nginx-frontend.conf)
EXPOSE 3000

# 127.0.0.1, not localhost: inside the container localhost resolves to ::1
# first, nginx only listens on IPv4, and busybox wget does not fall back.
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
    CMD wget -qO- http://127.0.0.1:3000/ || exit 1

CMD ["nginx", "-g", "daemon off;"]
