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

ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
LINUXCNC_ROOT="${LINUXCNC_ROOT:-$ROOT_DIR/../linuxcnc}"
FIXTURE_FAMILY="axis/remap/stop-lookahead/nc_files"
FIXTURE_DIR="$LINUXCNC_ROOT/configs/sim/axis/remap/stop-lookahead"
FIXTURE_INI="$FIXTURE_DIR/demo.ini"
RUN_DIR="$ROOT_DIR/build/native/python-remap-runtime"
PYTHON_RUNTIME_STDOUT="$RUN_DIR/python_lifecycle.stdout.log"
PYTHON_RUNTIME_STDERR="$RUN_DIR/python_lifecycle.stderr.log"

required_commands=(python3 linuxcnc)
missing_requirements=()

command_path() {
  command -v "$1" 2>/dev/null || true
}

csv_join() {
  local IFS=,
  printf '%s' "$*"
}

print_kv() {
  printf '%s=%s\n' "$1" "$2"
}

runtime_requirements=()
for command_name in "${required_commands[@]}"; do
  path="$(command_path "$command_name")"
  if [[ -n "$path" ]]; then
    runtime_requirements+=("$command_name:1")
    print_kv "python_remap_runtime_${command_name}_path" "$path"
  else
    runtime_requirements+=("$command_name:0")
    missing_requirements+=("$command_name")
  fi
done

source_ready=1
for rel in \
  src/emc/rs274ngc/interp_python.cc \
  src/emc/pythonplugin/python_plugin.cc \
  configs/sim/axis/remap/stop-lookahead/demo.ini \
  configs/sim/axis/remap/stop-lookahead/python/remap.py \
  configs/sim/axis/remap/stop-lookahead/python/toplevel.py; do
  if [[ ! -f "$LINUXCNC_ROOT/$rel" ]]; then
    source_ready=0
    missing_requirements+=("$rel")
    print_kv "python_remap_missing_source_${rel//[^A-Za-z0-9]/_}" "$LINUXCNC_ROOT/$rel"
  fi
done

runtime_ready=0
if [[ "${#missing_requirements[@]}" -eq 0 ]]; then
  runtime_ready=1
fi

print_kv python_remap_runtime_fixture_family "$FIXTURE_FAMILY"
print_kv python_remap_runtime_fixture_id "stop_lookahead_python_runtime_lifecycle"
print_kv python_remap_runtime_fixture_scope "no_python_callables_no_ngc_only_subpaths"
print_kv python_remap_runtime_phases "initialize_python,apply_ini_python_path,execute_toplevel,callable_lookup,pycall_dispatch,callable_invoke,remap_phase_dispatch,generator_finish,execute_python_runtime,reload_on_change"
print_kv python_remap_runtime_modules "axis/remap/stop-lookahead/python/remap.py,axis/remap/stop-lookahead/python/toplevel.py"
print_kv python_remap_runtime_requirements "$(csv_join "${runtime_requirements[@]}")"
if [[ "${#missing_requirements[@]}" -eq 0 ]]; then
  print_kv python_remap_missing_requirements "-"
else
  print_kv python_remap_missing_requirements "$(csv_join "${missing_requirements[@]}")"
fi
print_kv python_remap_source_proof_ready "$source_ready"
print_kv python_remap_runtime_ready "$runtime_ready"
print_kv python_remap_execution_enabled 0
print_kv python_remap_promotion_allowed 0

if [[ "$source_ready" != "1" ]]; then
  print_kv python_remap_runtime_probe_status "blocked_missing_source"
  print_kv python_remap_runtime_probe_note "missing_source_requirements_keep_python_remap_blocked"
  exit 0
fi

if [[ "$runtime_ready" != "1" ]]; then
  print_kv python_remap_runtime_probe_status "skipped_missing_host_runtime"
  print_kv python_remap_runtime_probe_note "missing_host_runtime_requirements_keep_python_remap_blocked"
  exit 0
fi

if [[ "${ENABLE_PYTHON_REMAP_RUNTIME_PROBE:-0}" != "1" ]]; then
  print_kv python_remap_runtime_probe_status "ready_disabled_by_default"
  print_kv python_remap_runtime_probe_note "set_ENABLE_PYTHON_REMAP_RUNTIME_PROBE_1_to_run_python_lifecycle_probe"
  exit 0
fi

mkdir -p "$RUN_DIR"
rm -f "$PYTHON_RUNTIME_STDOUT" "$PYTHON_RUNTIME_STDERR"

print_kv python_remap_runtime_probe_fixture "$FIXTURE_DIR"
print_kv python_remap_runtime_probe_ini "$FIXTURE_INI"
print_kv python_remap_runtime_probe_stdout "$PYTHON_RUNTIME_STDOUT"
print_kv python_remap_runtime_probe_stderr "$PYTHON_RUNTIME_STDERR"

if python3 - "$LINUXCNC_ROOT" "$FIXTURE_DIR" "$FIXTURE_INI" "$RUN_DIR" \
    >"$PYTHON_RUNTIME_STDOUT" 2>"$PYTHON_RUNTIME_STDERR" <<'PY'
import configparser
import importlib
import inspect
import os
from pathlib import Path
import re
import sys
import tempfile

linuxcnc_root = Path(sys.argv[1])
fixture_dir = Path(sys.argv[2])
fixture_ini = Path(sys.argv[3])
run_dir = Path(sys.argv[4])

def emit(name, value):
    print(f"{name}={value}")

def require(condition, message):
    if not condition:
        raise RuntimeError(message)

interp_return = linuxcnc_root / "src/emc/nml_intf/interp_return.hh"
match = re.search(
    r"INTERP_EXECUTE_FINISH\s*=\s*([0-9]+)",
    interp_return.read_text(encoding="utf-8"),
)
require(match, "missing_INTERP_EXECUTE_FINISH_source_value")
execute_finish = int(match.group(1))
emit("python_remap_lifecycle_execute_finish_source_value", execute_finish)

stub_dir = Path(tempfile.mkdtemp(prefix="python-remap-interpreter-", dir=run_dir))
(stub_dir / "interpreter.py").write_text(
    f"INTERP_EXECUTE_FINISH = {execute_finish}\n",
    encoding="utf-8",
)

parser = configparser.ConfigParser(strict=False)
parser.optionxform = str
parser.read(fixture_ini)
require(parser.has_section("PYTHON"), "missing_PYTHON_section")
path_prepend = parser.get("PYTHON", "PATH_PREPEND", fallback="")
toplevel = parser.get("PYTHON", "TOPLEVEL", fallback="")
require(path_prepend == "python", f"PATH_PREPEND_drift:{path_prepend}")
require(toplevel == "python/toplevel.py", f"TOPLEVEL_drift:{toplevel}")
emit("python_remap_lifecycle_ini_path_prepend", path_prepend)
emit("python_remap_lifecycle_ini_toplevel", toplevel)

sys.path.insert(0, str(stub_dir))
sys.path.insert(0, str(fixture_dir / path_prepend))
sys.path.insert(0, str(linuxcnc_root / "lib/python"))

import interpreter  # noqa: E402
require(interpreter.INTERP_EXECUTE_FINISH == execute_finish, "interpreter_sentinel_drift")
emit("python_remap_lifecycle_interpreter_sentinel_ok", 1)

toplevel_module = importlib.import_module(Path(toplevel).stem)
emit("python_remap_lifecycle_toplevel_imported", int(toplevel_module.__name__ == "toplevel"))
remap_module = importlib.import_module("remap")
emit("python_remap_lifecycle_remap_imported", int(remap_module.__name__ == "remap"))
require(hasattr(remap_module, "queuebuster"), "missing_queuebuster_callable")
require(callable(remap_module.queuebuster), "queuebuster_not_callable")
emit("python_remap_lifecycle_callable_lookup_ok", 1)

result = remap_module.queuebuster(object())
require(inspect.isgenerator(result), "queuebuster_not_generator")
emit("python_remap_lifecycle_generator_returned", 1)
first_yield = next(result)
require(first_yield == execute_finish, f"queuebuster_yield_drift:{first_yield}")
emit("python_remap_lifecycle_generator_first_yield", first_yield)
try:
    next(result)
    raise RuntimeError("queuebuster_generator_did_not_finish")
except StopIteration:
    emit("python_remap_lifecycle_generator_finish_ok", 1)

emit("python_remap_runtime_lifecycle_probe_ok", 1)
PY
then
  cat "$PYTHON_RUNTIME_STDOUT"
  print_kv python_remap_runtime_probe_status "runtime_lifecycle_probe_passed"
  print_kv python_remap_runtime_probe_note "linuxcnc_stop_lookahead_python_lifecycle_probe_passed_without_promotion"
  exit 0
fi

cat "$PYTHON_RUNTIME_STDOUT" || true
print_kv python_remap_runtime_probe_status "runtime_lifecycle_probe_failed"
print_kv python_remap_runtime_probe_note "linuxcnc_stop_lookahead_python_lifecycle_probe_failed"
exit 1
