#!/usr/bin/env bash
# build.sh — Compile and run native-hello on macOS.
#
# Usage:
#   ./build.sh           # compile and run
#   ./build.sh compile   # compile only
#   ./build.sh clean     # remove build artefacts
#   ./build.sh platforms # detect available platform bridges on this machine
#
# Requirements:
#   - elc in PATH (or ../../../lang/dist/platform/elc)
#   - clang with Xcode Command Line Tools (for Cocoa framework)
#   - macOS 12+
#
# Multi-file compilation strategy:
#   elc does not resolve cross-directory imports (../../vessels/...) at bundle
#   time. The el-native vessel is compiled as a separate translation unit.
#
#   el_seed.c and el_runtime.c MUST both be linked:
#     - el_seed.c  = OS-boundary layer (__-prefixed primitives) + thin wrappers
#     - el_runtime.c = high-level el builtins (str_len, json_get_string, etc.)
#   They share many __-prefixed symbols. The macOS linker rejects duplicate
#   definitions, so we use nmedit to hide the el_runtime.c copies of the __
#   symbols (keeping el_seed.c's versions canonical).
#
#   el_native_target.h is a conflict-free header that adds:
#     - float_div / float_mul etc. (missing from current el_runtime.h)
#     - Native widget builtin declarations (#ifdef EL_TARGET_MACOS)
#   without redefining el_to_float / el_from_float from el_runtime.h.
#
#   Vessel header generation: elc --emit-header includes function BODIES.
#   We extract only the forward declarations using awk (stop at first body).
#   NOTE: macOS awk does not support \s — use [[:space:]] instead.
#
#   Vessel main(): the vessel's compiled .c file has its own main(). We use
#   nmedit to hide it from the vessel object before linking.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
EL_LANG_ROOT="${SCRIPT_DIR}/../../../lang"
EL_UI_ROOT="${SCRIPT_DIR}/../.."
EL_RUNTIME="${EL_LANG_ROOT}/runtime"
EL_NATIVE_VESSEL="${EL_UI_ROOT}/vessels/el-native/src/main.el"
BUILD_DIR="${SCRIPT_DIR}/build"

# Locate elc
if command -v elc &>/dev/null; then
    ELC="elc"
elif [ -x "${EL_LANG_ROOT}/dist/platform/elc" ]; then
    ELC="${EL_LANG_ROOT}/dist/platform/elc"
else
    echo "Error: elc not found. Add it to PATH or place it at:"
    echo "  ${EL_LANG_ROOT}/dist/platform/elc"
    exit 1
fi

CLANG_FLAGS_COMMON=(
    -std=c11
    -DEL_TARGET_MACOS
    -I "${EL_RUNTIME}"
)

clean() {
    rm -rf "${BUILD_DIR}"
    echo "Cleaned."
}

compile() {
    mkdir -p "${BUILD_DIR}"

    # ── 1. Vessel forward-declarations header ─────────────────────────────────
    # elc --emit-header emits full function bodies, not just prototypes.
    # Extract only the forward-declaration lines: stop at the first function
    # definition (a line that ends with ') {' opening a body block).
    # macOS awk does not support \s — use POSIX [[:space:]] instead.
    echo "==> Generating el-native vessel header (declarations only)..."
    "${ELC}" --emit-header "${EL_NATIVE_VESSEL}" \
        | awk 'BEGIN{in_body=0} /^[a-zA-Z_].+\)[[:space:]]*\{/ {in_body=1} !in_body{print}' \
        > "${BUILD_DIR}/el_native_vessel.h"

    # ── 2. Compile el-native vessel to C ──────────────────────────────────────
    echo "==> Compiling el-native vessel to C..."
    "${ELC}" "${EL_NATIVE_VESSEL}" \
        > "${BUILD_DIR}/el_native_vessel.c"

    # ── 3. Compile app entry to C ─────────────────────────────────────────────
    echo "==> Compiling app entry to C..."
    EL_MANIFEST="${SCRIPT_DIR}/manifest.el" \
        "${ELC}" "${SCRIPT_DIR}/src/main.el" \
        > "${BUILD_DIR}/native_hello.c"

    # ── 4. Compile el_appkit.m (ObjC, MRC — not ARC) ─────────────────────────
    echo "==> Compiling el_appkit.m (AppKit bridge, -fno-objc-arc)..."
    clang \
        -std=gnu11 \
        -ObjC \
        -fno-objc-arc \
        -DEL_TARGET_MACOS \
        -I "${EL_RUNTIME}" \
        -c "${EL_RUNTIME}/el_appkit.m" \
        -o "${BUILD_DIR}/el_appkit.o"

    # ── 5a. Compile el_seed.c ─────────────────────────────────────────────────
    # el_seed.c provides the OS-boundary __-prefixed primitives + thin wrappers
    # around el_runtime.c's higher-level functions.
    echo "==> Compiling el_seed.c..."
    clang "${CLANG_FLAGS_COMMON[@]}" \
        -c "${EL_RUNTIME}/el_seed.c" \
        -o "${BUILD_DIR}/el_seed.o"

    # ── 5b. Compile el_runtime.c and patch out duplicate __-prefixed symbols ──
    # el_runtime.c provides the high-level builtins (str_len, json_get_string,
    # int_to_str, println, env, exit_program, el_runtime_init_args, etc.) that
    # el_seed.c does NOT provide (they are single-name, no __ prefix).
    #
    # el_runtime.c also defines the same __-prefixed OS wrappers as el_seed.c.
    # We hide those duplicates using nmedit so the linker sees only one copy.
    echo "==> Compiling el_runtime.c..."
    clang "${CLANG_FLAGS_COMMON[@]}" \
        -c "${EL_RUNTIME}/el_runtime.c" \
        -o "${BUILD_DIR}/el_runtime.o"

    echo "==> Patching el_runtime.o (hiding __-prefixed symbols duplicated in el_seed.o)..."
    # Build the set of symbols that el_seed.o defines and el_runtime.o also
    # defines. We will hide el_runtime.o's copies, keeping el_seed.o canonical.
    nm "${BUILD_DIR}/el_seed.o" 2>/dev/null \
        | awk '/^[0-9a-f]+ T _/{print $3}' | sort > "${BUILD_DIR}/.seed_T.txt"
    nm "${BUILD_DIR}/el_runtime.o" 2>/dev/null \
        | awk '/^[0-9a-f]+ T _/{print $3}' | sort > "${BUILD_DIR}/.rt_T.txt"
    # Symbols to keep public in el_runtime.o = all T symbols MINUS the
    # duplicates (those will be supplied by el_seed.o).
    comm -23 "${BUILD_DIR}/.rt_T.txt" "${BUILD_DIR}/.seed_T.txt" \
        > "${BUILD_DIR}/.rt_keep.txt"
    nmedit -s "${BUILD_DIR}/.rt_keep.txt" "${BUILD_DIR}/el_runtime.o"

    # ── 6. Compile el-native vessel object ────────────────────────────────────
    # el_native_target.h adds float_div/mul/etc. + native widget declarations
    # without conflicting with el_runtime.h.
    echo "==> Compiling el-native vessel object..."
    clang "${CLANG_FLAGS_COMMON[@]}" \
        -include "${EL_RUNTIME}/el_native_target.h" \
        -c "${BUILD_DIR}/el_native_vessel.c" \
        -o "${BUILD_DIR}/el_native_vessel.o"

    echo "==> Patching el_native_vessel.o (hiding vessel main)..."
    # The vessel's compiled C has its own main(). Hide it so it does not
    # conflict with the app's main in native_hello.o.
    nm "${BUILD_DIR}/el_native_vessel.o" 2>/dev/null \
        | awk '/^[0-9a-f]+ T _/{print $3}' \
        | grep -v '^_main$' \
        > "${BUILD_DIR}/.vessel_keep.txt"
    nmedit -s "${BUILD_DIR}/.vessel_keep.txt" "${BUILD_DIR}/el_native_vessel.o"

    # ── 7. Compile app object ─────────────────────────────────────────────────
    # Inject el_native_target.h (float ops + native decls) and the vessel's
    # forward-declarations header so the vessel functions are resolvable.
    echo "==> Compiling app object..."
    clang "${CLANG_FLAGS_COMMON[@]}" \
        -include "${EL_RUNTIME}/el_native_target.h" \
        -include "${BUILD_DIR}/el_native_vessel.h" \
        -c "${BUILD_DIR}/native_hello.c" \
        -o "${BUILD_DIR}/native_hello.o"

    # ── 8. Link ───────────────────────────────────────────────────────────────
    # el_seed.o  = OS-boundary primitives (__-prefixed)
    # el_runtime.o (patched) = high-level builtins (str_len, json_*, etc.)
    # el_native_vessel.o (patched) = el-level widget API
    # el_appkit.o = AppKit C bridge (MRC)
    echo "==> Linking native-hello..."
    clang "${CLANG_FLAGS_COMMON[@]}" \
        -framework Cocoa \
        -lpthread \
        -lcurl \
        -ldl \
        "${BUILD_DIR}/native_hello.o" \
        "${BUILD_DIR}/el_native_vessel.o" \
        "${BUILD_DIR}/el_appkit.o" \
        "${BUILD_DIR}/el_runtime.o" \
        "${BUILD_DIR}/el_seed.o" \
        -o "${BUILD_DIR}/native-hello"

    echo "==> Build complete: ${BUILD_DIR}/native-hello"
}

run() {
    compile
    echo "==> Launching native-hello..."
    EL_MANIFEST="${SCRIPT_DIR}/manifest.el" \
        "${BUILD_DIR}/native-hello"
}

case "${1:-run}" in
    clean)     clean   ;;
    compile)   compile ;;
    platforms) "${EL_LANG_ROOT}/runtime/detect-platforms" ;;
    run|*)     run     ;;
esac
