#!/usr/bin/env bash
set -euo pipefail

# TODO this is a complete vibe-coded hack, but the header patching at least is crucial

# Generate Haskell FFI bindings from Rust source code.
#
# Pipeline: cargo build -> cbindgen -> patch header -> hs-bindgen
#
# Prerequisites: run inside the Nix dev shell (provides gcc, cabal, cbindgen, hs-bindgen-cli).

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
RUST_DIR="$SCRIPT_DIR/rust"
HASKELL_DIR="$SCRIPT_DIR"
HEADER_NAME="garnet_rs.h"
HEADER="$RUST_DIR/$HEADER_NAME"

# --- Step 1: Build Rust static library ---
echo "=== Building Rust library ==="
cargo build --manifest-path "$RUST_DIR/Cargo.toml"

# --- Step 2: Generate C header with cbindgen ---
echo "=== Running cbindgen ==="
cbindgen \
  --lang c \
  --crate garnet-rs \
  --output "$HEADER" \
  "$RUST_DIR"

echo "  Raw header written to $HEADER"

# --- Step 3: Patch the header for hs-bindgen compatibility ---
echo "=== Patching header ==="

# Two patches are needed:
#
# Patch 1: Replace cbindgen's enum pattern with typedef + #defines.
#   cbindgen emits:  enum Shape_Tag { Circle, Rectangle, }; typedef uint8_t Shape_Tag;
#   hs-bindgen needs: typedef uint8_t Shape_Tag; #define Circle 0 ...
#
# Patch 2: Name anonymous unions inside structs.
#   cbindgen emits:  union { ... };
#   hs-bindgen needs: union { ... } body;

awk '
# State machine for enum->typedef+define transformation
/^enum [A-Za-z_][A-Za-z0-9_]* \{$/ {
    in_enum = 1
    enum_name = $2
    variant_count = 0
    delete variants
    next
}
in_enum && /^\};$/ {
    # Next line should be: typedef <type> <enum_name>;
    in_enum = 0
    pending_enum = 1
    next
}
in_enum {
    # Collect variant names (strip trailing comma and whitespace)
    v = $0
    gsub(/^[[:space:]]+/, "", v)
    gsub(/,[[:space:]]*$/, "", v)
    if (v != "") {
        variants[variant_count] = v
        variant_count++
    }
    next
}
pending_enum && /^typedef [A-Za-z0-9_]+ / {
    # Emit: typedef <type> <name>; then #define for each variant
    print $0
    for (i = 0; i < variant_count; i++) {
        printf "#define %s  %d\n", variants[i], i
    }
    pending_enum = 0
    next
}

# Name anonymous unions: }; at end of union block inside struct -> } body;
/^  \};$/ && saw_union {
    print "  } body;"
    saw_union = 0
    next
}
/^  union \{$/ {
    saw_union = 1
}

{ print }
' "$HEADER" > "${HEADER}.tmp" && mv "${HEADER}.tmp" "$HEADER"

echo "  Patched header at $HEADER"

# --- Step 4: Derive system include paths for hs-bindgen's libclang ---
echo "=== Detecting system include paths ==="

# hs-bindgen uses libclang directly, which doesn't know about NixOS's
# non-standard include locations. We extract them from cpp -v and pass
# all of them — extra paths are harmless.
CLANG_OPTIONS=()
while IFS= read -r dir; do
  CLANG_OPTIONS+=("--clang-option" "-isystem$dir")
done < <(echo | cpp -v 2>&1 | awk '/#include <\.\.\.> search starts here:/{f=1;next}/End of search list/{f=0}f{gsub(/^ +/,"");print}')

if [ ${#CLANG_OPTIONS[@]} -eq 0 ]; then
  echo "  WARNING: No system include paths detected. hs-bindgen may fail."
else
  echo "  Found ${#CLANG_OPTIONS[@]} clang options:"
  for ((i=0; i<${#CLANG_OPTIONS[@]}; i+=2)); do
    echo "    ${CLANG_OPTIONS[i+1]}"
  done
fi

# --- Step 5: Run hs-bindgen ---
echo "=== Running hs-bindgen ==="
hs-bindgen-cli preprocess \
  --overwrite-files --create-output-dirs \
  --unique-id com.garnet --enable-record-dot \
  --hs-output-dir "$HASKELL_DIR/generated" --module GarnetRs \
  "${CLANG_OPTIONS[@]}" \
  -I "$RUST_DIR" "$HEADER_NAME"

echo "=== Done ==="
echo "Generated Haskell bindings in $HASKELL_DIR/generated/"
echo "Run 'cabal run' to test."
