#!/bin/sh
# ============================================================
# hiperiso-lib.sh -- Shared functions for hiperiso initramfs
# POSIX sh compatible (dash/ash/busybox sh)
# ============================================================

# ── Mount points (from INTERFACES.sh) ────────────────────────
EFI_MOUNT="/mnt/efi"
DATA_MOUNT="/mnt/usb"

# ── Paths on EFI partition (from INTERFACES.sh) ──────────────
EFI_PAYLOAD_DIR="/EFI/hiperiso"
KERNEL_PATH="${EFI_PAYLOAD_DIR}/vmlinuz"
INITRAMFS_PATH="${EFI_PAYLOAD_DIR}/initramfs.cpio.gz"
OVMF_PATH="${EFI_PAYLOAD_DIR}/OVMF.fd"
TRACE_EVENTS_DIR="${EFI_PAYLOAD_DIR}/trace"

# ── hiperiso_strip_quotes() ──────────────────────────────────
# Remove surrounding double or single quotes from a string
hiperiso_strip_quotes() {
    _s="$1"
    _s="${_s#\"}"   # strip leading double-quote
    _s="${_s%\"}"   # strip trailing double-quote
    _s="${_s#\'}"   # strip leading single-quote
    _s="${_s%\'}"   # strip trailing single-quote
    printf '%s' "$_s"
}

# ── hiperiso_parse_cmdline() ─────────────────────────────────
# Extract hiperiso_* parameters from /proc/cmdline.
# Sets and exports: HIPERISO_ISO_PATH, HIPERISO_LOG_DIR,
#   HIPERISO_TRACE_LEVEL, HIPERISO_GUEST_RAM, HIPERISO_GUEST_CPUS,
#   HIPERISO_DISPLAY, HIPERISO_VGA, HIPERISO_FALLBACK
hiperiso_parse_cmdline() {
    HIPERISO_ISO_PATH=""
    HIPERISO_LOG_DIR=""
    HIPERISO_TRACE_LEVEL=""
    HIPERISO_GUEST_RAM=""
    HIPERISO_GUEST_CPUS=""
    HIPERISO_DISPLAY=""
    HIPERISO_VGA=""
    HIPERISO_FALLBACK=""
    HIPERISO_AUTO_INSTALL=""
    HIPERISO_PERSISTENCE=""
    HIPERISO_DUD=""
    HIPERISO_INJECTION=""
    HIPERISO_CONF_REPLACE=""
    HIPERISO_SECURE_BOOT=""
    HIPERISO_TPM=""
    HIPERISO_CPU_FEATURES=""
    HIPERISO_BOOT_MODE=""
    HIPERISO_NET_DUMP=""

    for _token in $(cat /proc/cmdline 2>/dev/null); do
        case "$_token" in
            hiperiso_iso=*)          HIPERISO_ISO_PATH="${_token#hiperiso_iso=}" ;;
            hiperiso_log=*)          HIPERISO_LOG_DIR="${_token#hiperiso_log=}" ;;
            hiperiso_trace_level=*)  HIPERISO_TRACE_LEVEL="${_token#hiperiso_trace_level=}" ;;
            hiperiso_ram=*)          HIPERISO_GUEST_RAM="${_token#hiperiso_ram=}" ;;
            hiperiso_cpus=*)         HIPERISO_GUEST_CPUS="${_token#hiperiso_cpus=}" ;;
            hiperiso_display=*)      HIPERISO_DISPLAY="${_token#hiperiso_display=}" ;;
            hiperiso_vga=*)          HIPERISO_VGA="${_token#hiperiso_vga=}" ;;
            hiperiso_fallback=*)     HIPERISO_FALLBACK="${_token#hiperiso_fallback=}" ;;
            hiperiso_auto_install=*) HIPERISO_AUTO_INSTALL="${_token#hiperiso_auto_install=}" ;;
            hiperiso_persistence=*)  HIPERISO_PERSISTENCE="${_token#hiperiso_persistence=}" ;;
            hiperiso_dud=*)          HIPERISO_DUD="${_token#hiperiso_dud=}" ;;
            hiperiso_injection=*)    HIPERISO_INJECTION="${_token#hiperiso_injection=}" ;;
            hiperiso_conf_replace=*) HIPERISO_CONF_REPLACE="${_token#hiperiso_conf_replace=}" ;;
            hiperiso_secure_boot=*)  HIPERISO_SECURE_BOOT="${_token#hiperiso_secure_boot=}" ;;
            hiperiso_tpm=*)          HIPERISO_TPM="${_token#hiperiso_tpm=}" ;;
            hiperiso_cpu_features=*) HIPERISO_CPU_FEATURES="${_token#hiperiso_cpu_features=}" ;;
            hiperiso_boot_mode=*)    HIPERISO_BOOT_MODE="${_token#hiperiso_boot_mode=}" ;;
            hiperiso_net_dump=*)     HIPERISO_NET_DUMP="${_token#hiperiso_net_dump=}" ;;
        esac
    done

    HIPERISO_ISO_PATH=$(hiperiso_strip_quotes "$HIPERISO_ISO_PATH")
    HIPERISO_LOG_DIR=$(hiperiso_strip_quotes "$HIPERISO_LOG_DIR")
    HIPERISO_TRACE_LEVEL=$(hiperiso_strip_quotes "$HIPERISO_TRACE_LEVEL")
    HIPERISO_GUEST_RAM=$(hiperiso_strip_quotes "$HIPERISO_GUEST_RAM")
    HIPERISO_GUEST_CPUS=$(hiperiso_strip_quotes "$HIPERISO_GUEST_CPUS")
    HIPERISO_DISPLAY=$(hiperiso_strip_quotes "$HIPERISO_DISPLAY")
    HIPERISO_VGA=$(hiperiso_strip_quotes "$HIPERISO_VGA")
    HIPERISO_FALLBACK=$(hiperiso_strip_quotes "$HIPERISO_FALLBACK")
    HIPERISO_AUTO_INSTALL=$(hiperiso_strip_quotes "$HIPERISO_AUTO_INSTALL")
    HIPERISO_PERSISTENCE=$(hiperiso_strip_quotes "$HIPERISO_PERSISTENCE")
    HIPERISO_DUD=$(hiperiso_strip_quotes "$HIPERISO_DUD")
    HIPERISO_INJECTION=$(hiperiso_strip_quotes "$HIPERISO_INJECTION")
    HIPERISO_CONF_REPLACE=$(hiperiso_strip_quotes "$HIPERISO_CONF_REPLACE")
    HIPERISO_SECURE_BOOT=$(hiperiso_strip_quotes "$HIPERISO_SECURE_BOOT")
    HIPERISO_TPM=$(hiperiso_strip_quotes "$HIPERISO_TPM")
    HIPERISO_CPU_FEATURES=$(hiperiso_strip_quotes "$HIPERISO_CPU_FEATURES")
    HIPERISO_BOOT_MODE=$(hiperiso_strip_quotes "$HIPERISO_BOOT_MODE")
    HIPERISO_NET_DUMP=$(hiperiso_strip_quotes "$HIPERISO_NET_DUMP")

    export HIPERISO_ISO_PATH HIPERISO_LOG_DIR HIPERISO_TRACE_LEVEL
    export HIPERISO_GUEST_RAM HIPERISO_GUEST_CPUS
    export HIPERISO_DISPLAY HIPERISO_VGA HIPERISO_FALLBACK
    export HIPERISO_AUTO_INSTALL HIPERISO_PERSISTENCE HIPERISO_DUD
    export HIPERISO_INJECTION HIPERISO_CONF_REPLACE HIPERISO_SECURE_BOOT
    export HIPERISO_TPM HIPERISO_CPU_FEATURES HIPERISO_BOOT_MODE
    export HIPERISO_NET_DUMP
}

# ── hiperiso_find_usb_partition() ────────────────────────────
# Scan block devices for the hiperiso USB data partition.
# Outputs the device path (e.g. /dev/sdb2) and returns 0,
# or outputs nothing and returns 1 if not found.
# Strategy: blkid label "HIPERISO" -> known partition 2 devices
#           -> sysfs removable scan
hiperiso_find_usb_partition() {
    # Strategy 1: blkid by label HIPERISO
    if command -v blkid >/dev/null 2>&1; then
        _dev=$(blkid -L HIPERISO 2>/dev/null)
        if [ -n "$_dev" ] && [ -e "$_dev" ]; then
            printf '%s' "$_dev"
            return 0
        fi
        _dev=$(blkid -t LABEL=HIPERISO -o device 2>/dev/null | head -n 1)
        if [ -n "$_dev" ] && [ -e "$_dev" ]; then
            printf '%s' "$_dev"
            return 0
        fi
    fi

    # Strategy 2: try known partition-1 device nodes (sd[a-f] covers
    # most USB storage; the data partition is partition 1).
    for _dev in /dev/sda1 /dev/sdb1 /dev/sdc1 /dev/sdd1 /dev/sde1 /dev/sdf1; do
        [ -e "$_dev" ] || continue
        if command -v blkid >/dev/null 2>&1; then
            # Verify it has a filesystem
            if blkid "$_dev" >/dev/null 2>&1; then
                printf '%s' "$_dev"
                return 0
            fi
        else
            # No blkid available -- accept device if it exists
            printf '%s' "$_dev"
            return 0
        fi
    done

    # Strategy 3: scan sysfs for removable block devices with partition 1.
    # Only returns a device if sysfs marks it as removable to avoid
    # accidentally selecting the system disk.
    for _blk in /sys/block/sd[a-z] /sys/block/nvme[0-9]n[0-9] /sys/block/mmcblk[0-9]; do
        [ -e "$_blk" ] || continue
        _name=$(basename "$_blk")
        # NVMe (nvme0n1) and MMC (mmcblk0) use 'p' separator before partition number
        case "$_name" in
            nvme*|mmcblk*) _part="/dev/${_name}p1" ;;
            *)             _part="/dev/${_name}1" ;;
        esac
        [ -e "$_part" ] || continue
        _removable=$(cat "${_blk}/removable" 2>/dev/null)
        if [ "$_removable" = "1" ]; then
            printf '%s' "$_part"
            return 0
        fi
    done

    return 1
}

# ── hiperiso_log() ───────────────────────────────────────────
# Log a message to console and env.txt (if LOG_DIR is set).
hiperiso_log() {
    _msg="$1"
    printf '[hiperiso] %s\n' "$_msg"
    if [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ]; then
        printf '[hiperiso] %s\n' "$_msg" >> "${LOG_DIR}/env.txt"
    fi
}

# ── hiperiso_write_env() ─────────────────────────────────────
# Write full environment snapshot to ${LOG_DIR}/env.txt.
# Caller must set LOG_DIR and have /proc, /sys mounted.
hiperiso_write_env() {
    _envfile="${LOG_DIR}/env.txt"
    : > "$_envfile"

    {
        printf '=== hiperiso Environment Snapshot ===\n'
        printf 'Uptime: %s\n' "$(cut -d' ' -f1 /proc/uptime 2>/dev/null)"
        printf '\n'
        printf -- '--- Host Kernel ---\n'
        cat /proc/version 2>/dev/null
        printf 'Cmdline: %s\n' "$(cat /proc/cmdline 2>/dev/null)"
        printf '\n'
        printf -- '--- Memory ---\n'
        grep '^MemTotal\|^MemFree\|^MemAvailable\|^SwapTotal' /proc/meminfo 2>/dev/null
        printf '\n'
        printf -- '--- CPU ---\n'
        grep '^model name' /proc/cpuinfo 2>/dev/null | head -n 1
        grep '^cpu cores' /proc/cpuinfo 2>/dev/null | head -n 1
        grep '^siblings'  /proc/cpuinfo 2>/dev/null | head -n 1
        printf 'Logical processors: %s\n' \
            "$(grep -c '^processor' /proc/cpuinfo 2>/dev/null)"
        if grep -q -E '(vmx|svm)' /proc/cpuinfo 2>/dev/null; then
            printf 'Hardware virtualization: supported (vmx/svm)\n'
        else
            printf 'Hardware virtualization: not detected\n'
        fi
        printf '\n'
        printf -- '--- Block Devices ---\n'
        ls /dev/sd* 2>/dev/null
        ls /dev/nvme* 2>/dev/null
        printf '\n'
        printf -- '--- hiperiso Configuration ---\n'
        printf 'ISO path:      %s\n' "${HIPERISO_ISO_PATH:-}"
        printf 'Log dir:       %s\n' "${HIPERISO_LOG_DIR:-}"
        printf 'Trace level:   %s\n' "${HIPERISO_TRACE_LEVEL:-standard}"
        printf 'Guest RAM:     %s MB\n' "${HIPERISO_GUEST_RAM:-2048}"
        printf 'Guest CPUs:    %s\n' "${HIPERISO_GUEST_CPUS:-2}"
        printf 'Display:       %s\n' "${HIPERISO_DISPLAY:-none}"
        printf 'VGA:           %s\n' "${HIPERISO_VGA:-std}"
        printf 'Fallback:      %s\n' "${HIPERISO_FALLBACK:-0}"
        printf 'Data mount:    %s\n' "$DATA_MOUNT"
        printf 'EFI mount:     %s\n' "$EFI_MOUNT"
        printf 'Log dir (abs): %s\n' "$LOG_DIR"
        printf 'Session ID:    %s\n' "$(hiperiso_session_id)"
        printf '\n'
    } >> "$_envfile"
}

# ── hiperiso_check_ram() ─────────────────────────────────────
# Verify enough RAM for the guest VM.
# Args: $1 = guest RAM in MB (default 2048)
# Returns: 0 if sufficient, 1 if not
hiperiso_check_ram() {
    _guest_ram="${1:-2048}"
    _host_overhead=512       # host kernel + initramfs working set
    _needed=$(( _guest_ram + _host_overhead ))
    _total_kb=$(awk '/^MemTotal:/ { print $2 }' /proc/meminfo 2>/dev/null)
    if [ -z "$_total_kb" ]; then
        return 0            # cannot determine -- assume OK
    fi
    _total_mb=$(( _total_kb / 1024 ))
    if [ "$_total_mb" -lt "$_needed" ]; then
        return 1
    fi
    return 0
}

# ── hiperiso_utc_stamp() ──────────────────────────────────────
# Output a compact UTC timestamp suitable for directory names:
#   YYYYMMDDTHHMMSSZ
# Falls back to uptime-based identifier if date(1) is unavailable.
hiperiso_utc_stamp() {
    _ts=$(date -u +%Y%m%dT%H%M%SZ 2>/dev/null)
    if [ -z "$_ts" ]; then
        _up=$(cut -d. -f1 /proc/uptime 2>/dev/null)
        _ts="boot${_up:-0}"
    fi
    printf '%s' "$_ts"
}

# ── hiperiso_utc_iso() ────────────────────────────────────────
# Output ISO 8601 UTC timestamp for JSON metadata:
#   YYYY-MM-DDTHH:MM:SSZ
# Falls back to "unknown" if date(1) is unavailable.
hiperiso_utc_iso() {
    _ts=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null)
    if [ -z "$_ts" ]; then
        printf '%s' "unknown"
    else
        printf '%s' "$_ts"
    fi
}

# ── hiperiso_json_escape() ────────────────────────────────────
# Escape backslashes and double quotes for JSON string safety.
# Uses sed if available; falls back to raw value.
hiperiso_json_escape() {
    printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' 2>/dev/null \
        || printf '%s' "$1"
}

# ── hiperiso_session_id() ─────────────────────────────────────
# Derive the session identifier (directory basename) from the
# current HIPERISO_LOG_DIR.  E.g. "/hiperiso/logs/ubuntu_Ts/" →
# "ubuntu_Ts".
hiperiso_session_id() {
    _sid="${HIPERISO_LOG_DIR%/}"
    printf '%s' "${_sid##*/}"
}

# ── hiperiso_derive_session_dir() ─────────────────────────────
# Given a base log dir from GRUB (e.g. /hiperiso/logs/ubuntu/),
# append a UTC timestamp so every boot gets a unique directory.
# Input:  $1 = base log dir (from hiperiso_log= cmdline param)
# Output: /hiperiso/logs/<basename>_<UTC-timestamp>/
hiperiso_derive_session_dir() {
    _base="${1%/}"
    _ts=$(hiperiso_utc_stamp)
    _name="${_base##*/}"
    _parent="${_base%/*}"
    printf '%s/%s_%s/' "$_parent" "$_name" "$_ts"
}

# ── hiperiso_write_session_meta() ─────────────────────────────
# Write session.json into LOG_DIR with structured session metadata
# for agent consumption.  Caller must have LOG_DIR set and created.
hiperiso_write_session_meta() {
    [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ] || return 1

    _file="${LOG_DIR}/session.json"
    _ts=$(hiperiso_utc_iso)
    _sid=$(hiperiso_session_id)

    # Capture the session start time on first invocation so the
    # final manifest rewrite can preserve it.
    if [ -z "${HIPERISO_SESSION_START:-}" ]; then
        HIPERISO_SESSION_START="$_ts"
        export HIPERISO_SESSION_START
    fi

    _iso_basename="${HIPERISO_ISO_PATH##*/}"
    _iso_esc=$(hiperiso_json_escape "${HIPERISO_ISO_PATH:-}")
    _log_esc=$(hiperiso_json_escape "${HIPERISO_LOG_DIR%/}")

    {
        printf '{\n'
        printf '  "session_id": "%s",\n' "$_sid"
        printf '  "status": "%s",\n' "${1:-started}"
        printf '  "iso_path": "%s",\n' "$_iso_esc"
        printf '  "iso_basename": "%s",\n' "$_iso_basename"
        printf '  "log_dir": "%s",\n' "$_log_esc"
        printf '  "start_time_utc": "%s",\n' "$_ts"
        printf '  "kvm": "%s",\n' "${KVM_STATUS:-unknown}"
        printf '  "trace_level": "%s",\n' "${HIPERISO_TRACE_LEVEL:-standard}"
        printf '  "guest_ram_mb": %s,\n' "${HIPERISO_GUEST_RAM:-2048}"
        printf '  "guest_cpus": %s\n' "${HIPERISO_GUEST_CPUS:-2}"
        printf '}\n'
    } > "$_file"
    sync "$_file" 2>/dev/null || sync
}

# ── hiperiso_write_status() ───────────────────────────────────
# Update status.json in LOG_DIR with current session state.
# Args: $1 = status  (started|running|complete|failed)
#       $2 = stage   (mounted|kvm_ok|tcg_emulation|launching_qemu|
#                     qemu_running|report_generated|session_complete|
#                     fallback_no_kvm|iso_not_found|fatal_error|...)
#       $3 = qemu_exit_code (optional, integer)
hiperiso_write_status() {
    [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ] || return 1

    _status="$1"
    _stage="$2"
    _qexit="${3:-}"
    _file="${LOG_DIR}/status.json"
    _ts=$(hiperiso_utc_iso)
    _sid=$(hiperiso_session_id)

    {
        printf '{\n'
        printf '  "session_id": "%s",\n' "$_sid"
        printf '  "status": "%s",\n' "$_status"
        printf '  "stage": "%s",\n' "$_stage"
        if [ -n "$_qexit" ]; then
            printf '  "qemu_exit_code": %s,\n' "$_qexit"
        fi
        printf '  "timestamp_utc": "%s"\n' "$_ts"
        printf '}\n'
    } > "$_file"
    sync "$_file" 2>/dev/null || sync
}

# ── hiperiso_run_report() ─────────────────────────────────────
# Run the hiperiso-log analysis tool on LOG_DIR to auto-generate
# report.json and report.txt.  Returns 0 on success, 1 if the tool
# is missing or fails.
hiperiso_run_report() {
    [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ] || return 1

    _tool=""
    for _p in /usr/bin/hiperiso-log /bin/hiperiso-log hiperiso-log; do
        if command -v "$_p" >/dev/null 2>&1; then
            _tool="$_p"
            break
        fi
    done

    if [ -z "$_tool" ]; then
        hiperiso_log "report: hiperiso-log not found -- skipping report generation"
        return 1
    fi

    hiperiso_log "report: generating report.json and report.txt..."
    if "$_tool" analyze "$LOG_DIR" >>"${LOG_DIR}/report.log" 2>&1; then
        hiperiso_log "report: generated successfully"
        return 0
    else
        _rc=$?
        hiperiso_log "report: generation failed (exit $_rc)"
        return 1
    fi
}

# ── hiperiso_mark_session_complete() ──────────────────────────
# Write the session-complete sentinel and do a final sync so agents
# can detect that the boot session finished cleanly.
hiperiso_mark_session_complete() {
    [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ] || return 1

    _ts=$(hiperiso_utc_iso)
    printf '%s\n' "$_ts" > "${LOG_DIR}/SESSION_COMPLETE"
    sync "${LOG_DIR}/SESSION_COMPLETE" 2>/dev/null || sync
}

# ── hiperiso_read_analysis_meta() ─────────────────────────────
# Parse ${LOG_DIR}/analysis.meta (flat KEY=VALUE written by the
# hiperiso-log report tool) into HIPERISO_A_* shell variables.
# Missing file leaves all values empty.  POSIX sh: no arrays, no
# $(<file) -- uses a while-read loop.
hiperiso_read_analysis_meta() {
    HIPERISO_A_BOOT_RESULT=""
    HIPERISO_A_FAILURE_DOMAIN=""
    HIPERISO_A_BOOT_STAGE=""
    HIPERISO_A_REACHED_LOGIN=""
    HIPERISO_A_KERNEL_PANIC=""
    HIPERISO_A_ERROR_COUNT=""
    HIPERISO_A_HAS_SERIAL=""
    HIPERISO_A_HAS_TRACE=""
    HIPERISO_A_GRAPHICS_DRIVERS=""
    HIPERISO_A_GRAPHICS_RES=""
    HIPERISO_A_DURATION_MS=""

    _meta="${LOG_DIR}/analysis.meta"
    [ -f "$_meta" ] || return 0

    while IFS='=' read -r _k _v || [ -n "$_k" ]; do
        [ -n "$_k" ] || continue
        case "$_k" in
            BOOT_RESULT)         HIPERISO_A_BOOT_RESULT="$_v" ;;
            FAILURE_DOMAIN)      HIPERISO_A_FAILURE_DOMAIN="$_v" ;;
            BOOT_STAGE_FINAL)    HIPERISO_A_BOOT_STAGE="$_v" ;;
            REACHED_LOGIN)       HIPERISO_A_REACHED_LOGIN="$_v" ;;
            KERNEL_PANIC_COUNT)  HIPERISO_A_KERNEL_PANIC="$_v" ;;
            ERROR_COUNT)         HIPERISO_A_ERROR_COUNT="$_v" ;;
            HAS_SERIAL)          HIPERISO_A_HAS_SERIAL="$_v" ;;
            HAS_TRACE)           HIPERISO_A_HAS_TRACE="$_v" ;;
            GRAPHICS_DRIVERS)    HIPERISO_A_GRAPHICS_DRIVERS="$_v" ;;
            GRAPHICS_RESOLUTION) HIPERISO_A_GRAPHICS_RES="$_v" ;;
            BOOT_DURATION_MS)    HIPERISO_A_DURATION_MS="$_v" ;;
        esac
    done < "$_meta"
}

# ── hiperiso_write_final_manifest() ───────────────────────────
# Rewrite ${LOG_DIR}/session.json as the canonical final manifest,
# folding in end time, qemu exit code, report availability, final
# status, and the derived analysis from analysis.meta.
# Args: $1=status  $2=qemu_exit_code(may be empty)  $3=start_utc
#       $4=end_utc  $5=report_available(0|1)
hiperiso_write_final_manifest() {
    [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ] || return 1

    _status="$1"
    _qexit="${2:-}"
    _start_ts="${3:-unknown}"
    _end_ts="${4:-unknown}"
    _report_avail="${5:-0}"

    _file="${LOG_DIR}/session.json"
    _sid=$(hiperiso_session_id)
    _iso_basename="${HIPERISO_ISO_PATH##*/}"
    _iso_esc=$(hiperiso_json_escape "${HIPERISO_ISO_PATH:-}")
    _log_esc=$(hiperiso_json_escape "${HIPERISO_LOG_DIR%/}")
    _gdrv_esc=$(hiperiso_json_escape "${HIPERISO_A_GRAPHICS_DRIVERS:-}")
    _gres_esc=$(hiperiso_json_escape "${HIPERISO_A_GRAPHICS_RES:-}")

    # result: prefer the report-derived boot_result; fall back to status.
    _result="${HIPERISO_A_BOOT_RESULT:-}"
    [ -n "$_result" ] || _result="$_status"

    {
        printf '{\n'
        printf '  "session_id": "%s",\n' "$_sid"
        printf '  "status": "%s",\n' "$_status"
        printf '  "result": "%s",\n' "$_result"
        printf '  "iso_path": "%s",\n' "$_iso_esc"
        printf '  "iso_basename": "%s",\n' "$_iso_basename"
        printf '  "log_dir": "%s",\n' "$_log_esc"
        printf '  "start_time_utc": "%s",\n' "$_start_ts"
        printf '  "end_time_utc": "%s",\n' "$_end_ts"
        printf '  "kvm": "%s",\n' "${KVM_STATUS:-unknown}"
        printf '  "trace_level": "%s",\n' "${HIPERISO_TRACE_LEVEL:-standard}"
        printf '  "guest_ram_mb": %s,\n' "${HIPERISO_GUEST_RAM:-2048}"
        printf '  "guest_cpus": %s,\n' "${HIPERISO_GUEST_CPUS:-2}"
        if [ -n "$_qexit" ]; then
            printf '  "qemu_exit_code": %s,\n' "$_qexit"
        else
            printf '  "qemu_exit_code": null,\n'
        fi
        printf '  "report_available": %s,\n' "$_report_avail"

        # Host metadata: non-identifying fields from /proc, already
        # captured in env.txt — surfaced here for structured ingestion.
        _host_kernel=$(head -n 1 /proc/version 2>/dev/null)
        _host_cpu_model=$(grep '^model name' /proc/cpuinfo 2>/dev/null | head -n 1 | cut -d: -f2- | sed 's/^[[:space:]]*//')
        _host_cpu_logical=$(grep -c '^processor' /proc/cpuinfo 2>/dev/null)
        _host_ram_kb=$(awk '/^MemTotal:/ { print $2 }' /proc/meminfo 2>/dev/null)
        _host_ram_mb=$(( _host_ram_kb / 1024 ))
        if grep -q -E '(vmx|svm)' /proc/cpuinfo 2>/dev/null; then
            _host_virt="true"
        else
            _host_virt="false"
        fi

        printf '  "host": {\n'
        printf '    "kernel_version": "%s",\n' "$(hiperiso_json_escape "${_host_kernel:-}")"
        printf '    "cpu_model": "%s",\n'      "$(hiperiso_json_escape "${_host_cpu_model:-}")"
        printf '    "cpu_logical_cores": %s,\n' "${_host_cpu_logical:-0}"
        printf '    "total_ram_mb": %s,\n'       "${_host_ram_mb:-0}"
        printf '    "virt_supported": %s\n'     "$_host_virt"
        printf '  },\n'
        printf '  "analysis": {\n'
        printf '    "boot_result": "%s",\n' "${HIPERISO_A_BOOT_RESULT:-unknown}"
        printf '    "boot_stage_final": "%s",\n' "${HIPERISO_A_BOOT_STAGE:-unknown}"
        printf '    "failure_domain": "%s",\n' "${HIPERISO_A_FAILURE_DOMAIN:-unknown}"
        printf '    "reached_login": %s,\n' "${HIPERISO_A_REACHED_LOGIN:-0}"
        printf '    "kernel_panic_count": %s,\n' "${HIPERISO_A_KERNEL_PANIC:-0}"
        printf '    "error_count": %s,\n' "${HIPERISO_A_ERROR_COUNT:-0}"
        printf '    "has_serial": %s,\n' "${HIPERISO_A_HAS_SERIAL:-0}"
        printf '    "has_trace": %s,\n' "${HIPERISO_A_HAS_TRACE:-0}"
        printf '    "boot_duration_ms": %s,\n' "${HIPERISO_A_DURATION_MS:-0}"
        printf '    "graphics_drivers": "%s",\n' "$_gdrv_esc"
        printf '    "graphics_resolution": "%s"\n' "$_gres_esc"
        printf '  },\n'

        # hw_inventory: list which hardware data files exist so agents
        # know what's available without scanning the directory.
        _hw_dir="${LOG_DIR}/hw"
        _hw_files=""
        if [ -d "$_hw_dir" ]; then
            _hw_files=$(ls "$_hw_dir" 2>/dev/null | tr '\n' ',' | sed 's/,$//')
        fi

        printf '  "hw_inventory": {\n'
        printf '    "available": %s,\n' $([ -n "$_hw_files" ] && echo true || echo false)
        printf '    "files": "%s",\n' "$(hiperiso_json_escape "$_hw_files")"
        printf '    "has_timing": %s,\n' $([ -f "${LOG_DIR}/timing.json" ] && echo true || echo false)
        printf '    "has_network_pcap": %s\n' $([ -f "${LOG_DIR}/network.pcap" ] && echo true || echo false)
        printf '  }\n'
        printf '}\n'
    } > "$_file"
    sync "$_file" 2>/dev/null || sync
}

# ── hiperiso_append_global_index() ────────────────────────────
# Append one JSONL record to /hiperiso/logs/index.jsonl — the
# append-only corpus-level index.  Creates the file if absent.
# One compact JSON object per line so failed + successful sessions
# are both indexed for downstream ingestion.
# Args: $1=status  $2=qemu_exit_code  $3=start_utc  $4=end_utc
#       $5=report_available(0|1)
hiperiso_append_global_index() {
    [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ] || return 1

    _status="$1"
    _qexit="${2:-}"
    _start_ts="${3:-unknown}"
    _end_ts="${4:-unknown}"
    _report_avail="${5:-0}"

    # LOG_DIR = .../hiperiso/logs/<session>/  ->  parent = .../hiperiso/logs
    _idx_parent=$(dirname "$LOG_DIR")
    _idx="${_idx_parent}/index.jsonl"
    _sid=$(hiperiso_session_id)
    _iso_basename="${HIPERISO_ISO_PATH##*/}"
    _iso_esc=$(hiperiso_json_escape "${HIPERISO_ISO_PATH:-}")
    _log_esc=$(hiperiso_json_escape "${HIPERISO_LOG_DIR%/}")
    _gdrv_esc=$(hiperiso_json_escape "${HIPERISO_A_GRAPHICS_DRIVERS:-}")

    _result="${HIPERISO_A_BOOT_RESULT:-}"
    [ -n "$_result" ] || _result="$_status"

    {
        printf '{"session_id":"%s","status":"%s","result":"%s",' \
            "$_sid" "$_status" "$_result"
        printf '"iso_basename":"%s","iso_path":"%s","log_dir":"%s",' \
            "$(hiperiso_json_escape "$_iso_basename")" "$_iso_esc" "$_log_esc"
        printf '"start_time_utc":"%s","end_time_utc":"%s",' \
            "$_start_ts" "$_end_ts"
        printf '"kvm":"%s","trace_level":"%s",' \
            "${KVM_STATUS:-unknown}" "${HIPERISO_TRACE_LEVEL:-standard}"
        if [ -n "$_qexit" ]; then
            printf '"qemu_exit_code":%s,' "$_qexit"
        else
            printf '"qemu_exit_code":null,'
        fi
        printf '"report_available":%s,' "$_report_avail"
        printf '"boot_result":"%s","boot_stage_final":"%s","failure_domain":"%s",' \
            "${HIPERISO_A_BOOT_RESULT:-unknown}" \
            "${HIPERISO_A_BOOT_STAGE:-unknown}" \
            "${HIPERISO_A_FAILURE_DOMAIN:-unknown}"
        printf '"graphics_drivers":"%s"}\n' "$_gdrv_esc"
    } >> "$_idx"
    sync "$_idx" 2>/dev/null || sync
}

# ── hiperiso_finalize_session() ───────────────────────────────
# Finalize the boot session: rewrite the canonical session.json
# manifest with full end/result metadata, update status.json,
# append to the global JSONL index, and write the completion
# sentinel.  Called on BOTH success and failure paths so every
# session — including failed ones — is indexed.
# Args: $1=status (complete|failed)
#       $2=qemu_exit_code (optional)
#       $3=stage label (optional, e.g. session_complete|iso_not_found|
#                       fallback_no_kvm)
hiperiso_finalize_session() {
    [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ] || return 1

    _status="${1:-complete}"
    _qexit="${2:-}"
    _stage="${3:-session_complete}"

    _end_ts=$(hiperiso_utc_iso)
    _start_ts="${HIPERISO_SESSION_START:-unknown}"

    hiperiso_read_analysis_meta

    if [ -f "${LOG_DIR}/report.json" ]; then
        _report_avail=1
    else
        _report_avail=0
    fi

    hiperiso_write_final_manifest  "$_status" "$_qexit" \
        "$_start_ts" "$_end_ts" "$_report_avail"
    hiperiso_write_status          "$_status" "$_stage" "$_qexit"
    hiperiso_append_global_index   "$_status" "$_qexit" \
        "$_start_ts" "$_end_ts" "$_report_avail"
    hiperiso_mark_session_complete

    hiperiso_log "session finalized: status=$_status result=${HIPERISO_A_BOOT_RESULT:-n/a}"
    hiperiso_log "global index: $(dirname "$LOG_DIR")/index.jsonl"
}

# ── hiperiso_timing_mark() ────────────────────────────────────
# Record a boot phase timing mark.  Uses /proc/uptime (seconds.microseconds
# since kernel boot) as a monotonic clock.  Appends to ${LOG_DIR}/timing.dat
# as raw TSV: phase<TAB>uptime<TAB>iso8601
# Args: $1 = phase name (e.g. kernel_start, data_mounted, qemu_started)
hiperiso_timing_mark() {
    [ -n "${LOG_DIR:-}" ] && [ -d "$LOG_DIR" ] || return 0

    _phase="$1"
    _up=$(cut -d' ' -f1 /proc/uptime 2>/dev/null)
    _ts=$(hiperiso_utc_iso)

    printf '%s\t%s\t%s\n' "$_phase" "${_up:-0}" "$_ts" >> "${LOG_DIR}/timing.dat"
}

# ── hiperiso_timing_write_json() ──────────────────────────────
# Convert timing.dat into structured timing.json for agent consumption.
# Calculates per-phase deltas and total duration.  Called at session end.
hiperiso_timing_write_json() {
    _dat="${LOG_DIR}/timing.dat"
    _out="${LOG_DIR}/timing.json"
    [ -f "$_dat" ] || return 0

    # First and last uptime values for total duration
    _first_up=$(head -n1 "$_dat" | cut -f2)
    _last_up=$(tail -n1 "$_dat" | cut -f2)
    _duration=""
    if [ -n "$_first_up" ] && [ -n "$_last_up" ]; then
        _duration=$(awk "BEGIN { printf \"%.3f\", $_last_up - $_first_up }" 2>/dev/null)
    fi

    {
        printf '{\n'
        printf '  "total_duration_s": %s,\n' "${_duration:-null}"
        printf '  "phases": [\n'

        _prev_up=""
        _first=1
        while IFS="$(printf '\t')" read -r _phase _up _ts || [ -n "$_phase" ]; do
            [ -n "$_phase" ] || continue

            if [ "$_first" -eq 1 ]; then
                _first=0
            else
                printf ',\n'
            fi

            # Delta from previous phase
            _delta=""
            if [ -n "$_prev_up" ] && [ -n "$_up" ]; then
                _delta=$(awk "BEGIN { printf \"%.3f\", $_up - $_prev_up }" 2>/dev/null)
            fi

            printf '    {"phase": "%s", "uptime_s": %s, "timestamp_utc": "%s", "delta_s": %s}' \
                "$_phase" "${_up:-0}" "$_ts" "${_delta:-null}"

            _prev_up="$_up"
        done < "$_dat"

        printf '\n  ]\n'
        printf '}\n'
    } > "$_out"
    sync "$_out" 2>/dev/null || sync
}
