# syntax=docker/dockerfile:1.7
#
# Image Docker pour le frontend DigitalKin Hub (React + Vite SPA).
# Bundle Magicbox — copié de l'image production Vercel.
#
# Stratégie : multi-stage
#   1. deps    : install pnpm + dépendances (cache friendly)
#   2. build   : tsc -b && vite build → dist/
#   3. runtime : nginx non-privilégié qui sert /dist en statique
#
# Rebuilds rapides (le build ne re-tourne QUE si un input change — cf. le plan
# incrémental de `dk prepare`, cli/compose/build_state.py) :
#   · `Dockerfile.dockerignore` (à côté de ce fichier) réduit le contexte au
#     strict nécessaire — pas de .git/node_modules/dist du clone ;
#   · les ARG VITE_* sont déclarés APRÈS les COPY → changer une valeur ne
#     recopie ni node_modules ni le code, seul le build re-tourne ;
#   · cache mounts BuildKit qui survivent aux rebuilds : store pnpm (stage
#     deps) + .tsbuildinfo du type-check tsc (stage build).
#
# Les variables VITE_* sont inlinées dans le bundle au build time.
# Magicbox les passe via `docker build --build-arg VITE_API_URL=...` depuis
# le bloc managed de `<profile>/env/horizon.env` (cf. cli/compose.py
# `generate_horizon_compose`).
# Pour les variables runtime (changer sans rebuild) il faut une autre approche
# (script entrypoint qui réécrit le bundle ou injecte /env-config.js) —
# pas le cas ici puisqu'on reste sur le même modèle que Vercel.

# ─────────────────────────────────────────────────────────────
# Stage 1 — deps : install des dépendances via pnpm
# ─────────────────────────────────────────────────────────────
FROM node:24-alpine AS deps

ENV PNPM_HOME=/pnpm
ENV PATH="$PNPM_HOME:$PATH"
# Skip husky : .git n'est pas dans le contexte, et le hook prepare lancerait husky
ENV HUSKY=0
# Pin pnpm à la branche 10 (cohérent avec le format pnpm.onlyBuiltDependencies
# du package.json — pnpm 11 ne le lit plus). À supprimer si tu migres tout en
# ajoutant `"packageManager": "pnpm@10.x.x"` dans package.json.
RUN corepack enable && corepack prepare pnpm@10 --activate

WORKDIR /app

# Copy uniquement les manifestes pour maximiser le cache de cette étape.
# .npmrc est crucial : il déclare `public-hoist-pattern[]=rxjs` (et autres) pour
# que les deps transitives utilisées en direct soient hoistées au top-level.
COPY package.json pnpm-lock.yaml .npmrc ./

# pnpm 10+ erreure sur les builds ignorés (postinstall scripts non-approuvés via
# onlyBuiltDependencies). On désactive ce strict mode pour le build Docker —
# les builds critiques (esbuild, oxide, sentry-cli…) sont déjà approuvés dans
# package.json `pnpm.onlyBuiltDependencies`, les autres deviennent juste des warnings.
ENV npm_config_strict_dep_builds=false

# Mount cache du store pnpm pour accélérer les rebuilds (BuildKit)
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
    pnpm config set strict-dep-builds false && \
    pnpm install --frozen-lockfile

# ─────────────────────────────────────────────────────────────
# Stage 2 — build : compile l'app (tsc + vite build)
# ─────────────────────────────────────────────────────────────
FROM node:24-alpine AS build

ENV PNPM_HOME=/pnpm
ENV PATH="$PNPM_HOME:$PATH"
ENV HUSKY=0
# Plus de heap pour Vite/tsc sur les gros bundles
ENV NODE_OPTIONS=--max-old-space-size=4096
# Même version de pnpm qu'au stage deps
RUN corepack enable && corepack prepare pnpm@10 --activate

WORKDIR /app

# Récup les node_modules du stage deps (pas de réinstall). Placé AVANT les
# ARG VITE_* pour que ce COPY — et le COPY du code juste après — restent en
# cache même quand une valeur VITE_* change (autre profil, autre API URL) :
# BuildKit n'invalide qu'à la PREMIÈRE UTILISATION d'un ARG (le bloc ENV plus
# bas), pas à sa déclaration.
COPY --from=deps /app/node_modules ./node_modules
# Puis le code applicatif. `Dockerfile.dockerignore` (à côté de ce Dockerfile,
# résolu par BuildKit sur le contexte = clone Horizon) exclut
# node_modules/.git/dist… : contexte minimal, pas de recopie de node_modules
# par-dessus celui du stage deps, et couche invalidée uniquement par un vrai
# changement de source (plus par le churn de `.git`).
COPY . .

# ── Variables VITE_* (inlinées dans le bundle au build) ──
# Déclarées ici, juste avant le build et APRÈS les COPY, pour préserver le cache
# des couches ci-dessus. Toutes optionnelles : si non passées, Vite mettra
# `undefined` côté code.
ARG VITE_API_URL
ARG VITE_CLERK_PUBLISHABLE_KEY
ARG VITE_POSTHOG_HOST
ARG VITE_POSTHOG_KEY
ARG VITE_SENTRY_DSN
ARG VITE_INCIDENT_IO_WIDGET_URL
ARG VITE_VERCEL_ENV=production
ARG VITE_REACT_QUERY_DEVTOOLS=false
ARG VITE_SCAN=false
ARG VITE_LOG_I=false
# ── Upload sourcemaps Sentry (optionnel) ──
ARG SENTRY_ORG
ARG SENTRY_PROJECT
ARG SENTRY_AUTH_TOKEN

ENV VITE_API_URL=$VITE_API_URL \
    VITE_CLERK_PUBLISHABLE_KEY=$VITE_CLERK_PUBLISHABLE_KEY \
    VITE_POSTHOG_HOST=$VITE_POSTHOG_HOST \
    VITE_POSTHOG_KEY=$VITE_POSTHOG_KEY \
    VITE_SENTRY_DSN=$VITE_SENTRY_DSN \
    VITE_INCIDENT_IO_WIDGET_URL=$VITE_INCIDENT_IO_WIDGET_URL \
    VITE_VERCEL_ENV=$VITE_VERCEL_ENV \
    VITE_REACT_QUERY_DEVTOOLS=$VITE_REACT_QUERY_DEVTOOLS \
    VITE_SCAN=$VITE_SCAN \
    VITE_LOG_I=$VITE_LOG_I \
    SENTRY_ORG=$SENTRY_ORG \
    SENTRY_PROJECT=$SENTRY_PROJECT \
    SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN

# Build = `tsc -b && vite build && node scripts/generate-sitemap.mjs`.
# `tsc -b` (type-check incrémental, noEmit) écrit ses .tsbuildinfo dans
# node_modules/.tmp (cf. tsconfig.app/node.json → `tsBuildInfoFile`). Le cache
# mount BuildKit les fait persister d'un build à l'autre → seuls les fichiers
# TS modifiés sont re-checkés (ce cache survit à l'invalidation de couche
# provoquée par `COPY . .`). vite/rollup n'ont pas de cache disque persistant
# au build : rien à monter pour eux.
RUN --mount=type=cache,id=horizon-tsbuildinfo,target=/app/node_modules/.tmp \
    pnpm run build

# ─────────────────────────────────────────────────────────────
# Stage 3 — runtime : nginx non-privilégié qui sert le statique
# ─────────────────────────────────────────────────────────────
# nginx-unprivileged tourne déjà en uid 101 (nginx) sur port 8080,
# sans bidouille de permissions/PID — le bon défaut pour Kubernetes.
FROM nginxinc/nginx-unprivileged:1.27-alpine AS runtime

# Installer dumb-init + curl (healthcheck) → besoin root le temps du apk add
USER root
RUN apk add --no-cache dumb-init curl
USER nginx

# Conf nginx custom Magicbox (reproduit vercel.json : SPA fallback, sécurité,
# proxy PostHog). Le fichier est apporté par le contexte nommé `magicbox-bundle`
# (cf. `additional_contexts` du compose override généré par `dk prepare`) — il
# vit à `services/horizon/Dockerfile/nginx.conf` côté Magicbox et reste
# indépendant du clone Horizon (qui peut, ou non, en avoir un à la racine).
COPY --from=magicbox-bundle --chown=nginx:nginx nginx.conf /etc/nginx/conf.d/default.conf

# Bundle statique
COPY --from=build --chown=nginx:nginx /app/dist /usr/share/nginx/html

EXPOSE 8080

# Healthcheck Docker (Kubernetes utilisera plutôt liveness/readiness probes sur /healthz)
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD curl -fsS http://127.0.0.1:8080/healthz || exit 1

# dumb-init = PID 1 propre (forward signaux, reap zombies)
ENTRYPOINT ["dumb-init", "--"]
CMD ["nginx", "-g", "daemon off;"]
