# cache-setup.sh — Package cache symlinks and directory cache seeding.
# shellcheck shell=bash
# Requires: TARGET_UID, CURRENT_UID (from uid-remap.sh), warn (from helpers.sh).
# Called in a subshell (Group A) for parallelism — no side effects on parent state.
# Copyright (c) 2026 Progralink. Licensed under the DevCage License — see LICENSE.

# _setup_cache: set up /cache symlinks for package managers and seed dircache overlays.
_setup_cache() {
    # ── Package cache symlinks (/cache → home directory paths) ────────
    # If the /cache volume is mounted (by the launcher), create subdirectories
    # and symlink standard package manager locations into /cache/<name>.
    # Gated on [ -d /cache ] so --no-cache-volumes is a graceful no-op.
    if [ -d /cache ]; then
        local _cache_map=(
            "/home/devcage/.m2:maven"
            "/home/devcage/.npm:npm"
            "/home/devcage/.npm-global:npm-global"
            "/home/devcage/.local/share/pnpm/store:pnpm"
            "/home/devcage/.cache:user"
            "/home/devcage/.gradle:gradle"
            "/home/devcage/.sdkman:sdkman"
            "/home/devcage/.semgrep:semgrep"
            "/home/devcage/go:go"
            "/home/devcage/.cargo:cargo"
            "/home/devcage/.nuget:nuget"
        )

        # Fast-path: if UID unchanged and all symlinks already correct, skip setup
        local _cache_skip=1 _entry _src _dst
        if [ "$TARGET_UID" != "$CURRENT_UID" ]; then
            _cache_skip=0
        else
            for _entry in "${_cache_map[@]}"; do
                _src="${_entry%%:*}"; _dst="/cache/${_entry##*:}"
                if [ ! -L "$_src" ] || [ "$(readlink "$_src")" != "$_dst" ]; then
                    _cache_skip=0; break
                fi
            done
        fi

        if [ "$_cache_skip" = "0" ]; then
            for _entry in "${_cache_map[@]}"; do
                _src="${_entry%%:*}"
                _dst="/cache/${_entry##*:}"
                mkdir -p "$_dst"
                chown devcage:devcage "$_dst"
                # Seed cache from image content on first run (e.g. SDKMAN install)
                if [ -d "$_src" ] && [ ! -L "$_src" ]; then
                    if [ "$(ls -A "$_dst" 2>/dev/null)" = "" ] && [ "$(ls -A "$_src" 2>/dev/null)" != "" ]; then
                        # Stream via tar pipe: data passes through a small (64 KB) kernel
                        # pipe buffer instead of bloating the page cache the way cp -a can.
                        if ! (cd "$_src" && tar cf - .) | (cd "$_dst" && tar xpf -); then
                            warn "failed to seed cache: $_dst"
                        fi
                        # Free page cache accumulated during the copy to prevent OOM in
                        # memory-constrained containers.  We are still root here.
                        if [ -w /proc/sys/vm/drop_caches ]; then
                            sync
                            echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true
                        fi
                    fi
                    rm -rf "$_src"
                elif [ -L "$_src" ]; then
                    rm -f "$_src"
                fi
                mkdir -p "$(dirname "$_src")"
                if ln -s "$_dst" "${_src}.devcage_tmp" 2>/dev/null \
                    && mv -fT "${_src}.devcage_tmp" "$_src"; then
                    :  # atomic symlink swap succeeded
                else
                    rm -f "${_src}.devcage_tmp" 2>/dev/null || true
                    ln -sf "$_dst" "$_src"
                fi
            done
            # Only re-chown cache if UID changed AND not already chowned to target UID
            if [ "$TARGET_UID" != "$CURRENT_UID" ]; then
                local _cache_uid_marker="/cache/.devcage-uid"
                if [ ! -f "$_cache_uid_marker" ] || [ "$(cat "$_cache_uid_marker" 2>/dev/null)" != "$TARGET_UID" ]; then
                    chown -Rh devcage:devcage /cache
                    printf '%s\n' "$TARGET_UID" > "$_cache_uid_marker"
                fi
            fi
        fi
        # /cache/apps: writable by devcage for build scripts to store app-specific
        # persistent caches (e.g. /cache/apps/remotion for Chrome Headless Shell)
        mkdir -p /cache/apps
        chown devcage:devcage /cache/apps
    fi

    # ── Directory cache seeding (cache-dirs) ────────────────────────
    # Seeding from the host workspace (mounted read-only at /workspace-host)
    # can be very slow on WSL2/VirtioFS (large node_modules = minutes of I/O).
    # Strategy: create mount-point directories synchronously so sessions can
    # attach immediately, then seed from host content in a background job.
    # The marker file (.devcage-dircache-seeded) prevents re-seeding on restart.
    if [ "${DIR_CACHE_PATHS:-}" != "" ]; then
        local _dc_paths _dc_path _dc_resolved
        IFS=',' read -ra _dc_paths <<< "$DIR_CACHE_PATHS"

        # Phase 1 (fast, blocking): validate paths and create directories.
        # Sessions need the mount points to exist before they start.
        for _dc_path in "${_dc_paths[@]}"; do
            [ "$_dc_path" = "" ] && continue
            case "$_dc_path" in /*|*..*) continue ;; esac
            _dc_resolved="$(realpath -m "/workspace/${_dc_path}")"
            case "$_dc_resolved" in /workspace/*) ;; *) continue ;; esac
            mkdir -p "/workspace/${_dc_path}"
            chown devcage:devcage "/workspace/${_dc_path}"
        done

        # Phase 2 (slow, background): seed overlay volumes from host content.
        # Runs after the entrypoint signals ready so it never blocks startup.
        (
            local _dc_path _dc_resolved _dc_actual _dc_marker
            IFS=',' read -ra _dc_paths <<< "$DIR_CACHE_PATHS"
            for _dc_path in "${_dc_paths[@]}"; do
                [ "$_dc_path" = "" ] && continue
                case "$_dc_path" in
                    /*|*..*) warn "rejecting dircache path (traversal): ${_dc_path}"; continue ;;
                esac
                _dc_resolved="$(realpath -m "/workspace/${_dc_path}")"
                case "$_dc_resolved" in
                    /workspace/*)  ;;
                    *)  warn "rejecting dircache path (escapes workspace): ${_dc_path}"; continue ;;
                esac
                _dc_actual="$(realpath "/workspace/${_dc_path}" 2>/dev/null || echo "")"
                case "$_dc_actual" in
                    /workspace/*) ;;
                    *) warn "dircache path resolved outside workspace after mkdir: ${_dc_path} -> ${_dc_actual}"; continue ;;
                esac
                _dc_marker="/workspace/${_dc_path}/.devcage-dircache-seeded"
                [ -f "$_dc_marker" ] && continue
                if [ -d "/workspace-host/${_dc_path}" ] && \
                   [ "$(ls -A "/workspace-host/${_dc_path}" 2>/dev/null)" != "" ]; then
                    _entrypoint_log "seeding dircache (background): ${_dc_path}"
                    if ! (cd "/workspace-host/${_dc_path}" && tar cf - .) | \
                         (cd "/workspace/${_dc_path}" && tar xpf -); then
                        warn "failed to seed dircache: ${_dc_path}"
                    fi
                    if [ -w /proc/sys/vm/drop_caches ]; then
                        sync
                        echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true
                    fi
                fi
                touch "$_dc_marker"
                chown -Rh devcage:devcage "/workspace/${_dc_path}" 2>/dev/null || true
            done
        ) &
    fi
}
