# syntax=docker/dockerfile:1
#
# Multi-stage build: Node builds the editor, uv builds the Python environment,
# and the runtime image contains only the virtualenv (no Node, no uv, no source).
#
#   docker build -t noodlelab .                                   # the full tier
#   docker build -t noodlelab:maths --build-arg NOODLELAB_TIER=maths .
#   docker build -t noodlelab --build-arg UV_EXTRAS="--extra s3 --extra sftp" .
#
# NOODLELAB_TIER is one of basic (the editor alone, the app extra), maths, science,
# engineering, geo or full (see "Installation" in the README); UV_EXTRAS adds remote
# storage backends.
#   docker run -p 8000:8000 -v noodlelab-data:/data -e NOODLELAB_ADMIN_TOKEN=change-me noodlelab
#   Sign in with the admin token, then add users and workspaces at /admin.

ARG PYTHON_VERSION=3.12

# --- 1. editor -------------------------------------------------------------------------
# The editor is static files: build it natively, not under emulation, for every platform
FROM --platform=$BUILDPLATFORM node:22-slim AS web
WORKDIR /build/frontend
COPY frontend/package*.json ./
COPY frontend/scripts ./scripts
RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi
COPY frontend/ ./
# vite writes to ../src/noodlelab/static, i.e. /build/src/noodlelab/static
RUN npm run build

# --- 2. python environment ---------------------------------------------------------------
FROM python:${PYTHON_VERSION}-slim AS build
# Pin this tag (e.g. uv:0.9.x) for reproducible builds.
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
ENV UV_COMPILE_BYTECODE=1 \
    UV_LINK_MODE=copy \
    UV_PYTHON_DOWNLOADS=0
# The installation tier, and remote storage mounts with S3 (MinIO, R2...); add gcs,
# azure or sftp as needed.
ARG NOODLELAB_TIER=full
ARG UV_EXTRAS="--extra s3"
WORKDIR /app

# Dependencies first: this layer is cached until pyproject.toml or uv.lock change.
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 --locked --no-install-project --no-dev \
        $([ "$NOODLELAB_TIER" = basic ] && echo "--extra app" || echo "--extra $NOODLELAB_TIER") ${UV_EXTRAS}

# The build context has no .git, so the version cannot come from the tag: the
# release workflow passes it (--build-arg NOODLELAB_VERSION=0.3.0), and so does
# `make docker-up`, and render.yaml sets it for the demo. Set after the dependencies,
# so a new version keeps their layer.
ARG NOODLELAB_VERSION=0.0.0
ENV SETUPTOOLS_SCM_PRETEND_VERSION=${NOODLELAB_VERSION}
COPY . .
COPY --from=web /build/src/noodlelab/static ./src/noodlelab/static
# --no-editable installs a real wheel (editor included) so only .venv is needed later.
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --locked --no-dev --no-editable \
        $([ "$NOODLELAB_TIER" = basic ] && echo "--extra app" || echo "--extra $NOODLELAB_TIER") ${UV_EXTRAS}

# --- 3. runtime --------------------------------------------------------------------------
FROM python:${PYTHON_VERSION}-slim
# Claude Code for the editor's Agent panel (docs/server.md) is left out unless asked
# for: docker build --build-arg CLAUDE_CODE=1 (docker-compose.yml does).
ARG CLAUDE_CODE=0
# The rasterio wheel bundles GDAL but links against the system libexpat, which the
# slim image leaves out; without it reading a GeoTIFF fails with an ImportError.
RUN apt-get update \
    && apt-get install -y --no-install-recommends libexpat1 \
        $([ "$CLAUDE_CODE" = 1 ] && echo "curl ca-certificates git") \
    && rm -rf /var/lib/apt/lists/*
RUN useradd --create-home --uid 1000 noodle \
    && mkdir -p /data/workspaces/default /data/.noodlelab \
    && chown -R noodle:noodle /data
# Same path as the build stage: virtualenv scripts hard-code their interpreter path.
COPY --from=build --chown=noodle:noodle /app/.venv /app/.venv

ENV PATH="/app/.venv/bin:$PATH" \
    PYTHONUNBUFFERED=1 \
    MPLCONFIGDIR=/tmp/matplotlib \
    NOODLELAB_HOST=0.0.0.0 \
    NOODLELAB_PORT=8000 \
    NOODLELAB_HOME=/data/.noodlelab \
    NOODLELAB_WORKSPACE=/data/workspaces/default \
    NOODLELAB_AUTH=users \
    NOODLELAB_OPEN_BROWSER=0

USER noodle
# Its login lives on the volume, so a rebuild does not sign you out.
ENV PATH="/home/noodle/.local/bin:$PATH" \
    CLAUDE_CONFIG_DIR=/data/.claude
RUN if [ "$CLAUDE_CODE" = 1 ]; then \
        curl -fsSL https://claude.ai/install.sh | bash && claude --version; \
    fi
WORKDIR /data
VOLUME ["/data"]
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \
    CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/health')"
CMD ["noodlelab", "serve"]
