# syntax=docker/dockerfile:1

# ── x-chat: single image (SPA + FastAPI) ────────────────────────────────────
# Three stages:
#   web      - Vite/React build → static SPA bundle
#   builder  - uv resolves the locked deps into a self-contained virtualenv
#   runtime  - slim Python that serves the API *and* the SPA from one uvicorn
#
# FastAPI serves the built SPA via StaticFiles (XCHAT_STATIC_DIR), so there is
# no nginx and no second container — pull one image, run one process, one port.
#
# pywinpty is marked `sys_platform == 'win32'` in pyproject.toml, so it is
# skipped here (Linux) — the browser-driven pty terminal is a Windows-desktop
# feature and is not wired on Linux yet.

# ── web build ────────────────────────────────────────────────────────────────
FROM node:20-slim AS web

WORKDIR /web

# Install deps from the lockfile first (cached unless package*.json changes).
COPY frontend/package.json frontend/package-lock.json ./
RUN npm ci

COPY frontend/ ./
RUN npm run build


# ── python deps ──────────────────────────────────────────────────────────────
FROM python:3.11-slim AS builder

# uv: fast, lockfile-faithful installs. Pinned to match the dev toolchain.
COPY --from=ghcr.io/astral-sh/uv:0.11.21 /uv /uvx /bin/

ENV UV_COMPILE_BYTECODE=1 \
    UV_LINK_MODE=copy \
    UV_PYTHON_DOWNLOADS=never

WORKDIR /app

# Install deps first (cached layer) using only the lock + manifest, so a code
# change doesn't bust the dependency cache. --no-install-project: app code
# isn't copied yet. --frozen: fail rather than silently re-resolve the lock.
RUN --mount=type=cache,target=/root/.cache/uv \
    --mount=type=bind,source=uv.lock,target=uv.lock \
    --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
    uv sync --frozen --no-install-project --no-dev

# Now the manifest + source, then install the project itself into the venv.
COPY pyproject.toml uv.lock ./
COPY backend ./backend
COPY README.md ./README.md
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --frozen --no-dev


# ── runtime ──────────────────────────────────────────────────────────────────
FROM python:3.11-slim AS runtime

# curl: HEALTHCHECK probe only.
RUN apt-get update \
    && apt-get install -y --no-install-recommends curl \
    && rm -rf /var/lib/apt/lists/*

# Drop privileges — the app never needs root at runtime.
RUN useradd --create-home --uid 10001 appuser

WORKDIR /app

# The venv + backend source from the builder, and the built SPA from the web
# stage. PATH points at the venv so `uvicorn`/`python` resolve to it.
COPY --from=builder --chown=appuser:appuser /app/.venv /app/.venv
COPY --from=builder --chown=appuser:appuser /app/backend /app/backend
COPY --from=builder --chown=appuser:appuser /app/pyproject.toml /app/pyproject.toml
COPY --from=web --chown=appuser:appuser /web/dist /app/frontend

ENV PATH="/app/.venv/bin:$PATH" \
    PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    XCHAT_ENVIRONMENT=prod \
    XCHAT_STATIC_DIR=/app/frontend

USER appuser

EXPOSE 8000

# On Linux uvicorn[standard] uses uvloop and streams SSE natively — the
# Windows-only `--loop selector` workaround from CLAUDE.md is NOT needed, and
# there's no nginx in the way, so SSE flows without buffering tweaks.
# No --reload in the image: the live watcher / pty sessions are dev-only.
HEALTHCHECK --interval=10s --timeout=3s --start-period=20s --retries=5 \
    CMD curl -fsS http://localhost:8000/health || exit 1

CMD ["uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000"]
