#!/bin/bash
# Copyright 2026 Rhett Creighton - Apache License 2.0
# z — LLM-optimized CRUD interface for zclassic23 node.
#
# Designed for AI/LLM tool calls: minimal input, clean output.
# CRUD pattern: z <resource> [action] [params...]
#
# RESOURCES:
#   z block <height>              Read block at height
#   z block <height> <count>      Read N blocks
#   z blocks                      Chain tip height
#   z tx <txid>                   Read transaction UTXO detail
#   z balance                     Read all balances
#   z utxos                       List unspent transparent
#   z notes                       List unspent shielded
#   z addrs                       List all addresses
#   z zaddrs                      List shielded addresses
#   z mempool                     Mempool status
#   z peers                       Connected peers
#   z chain                       Chain summary stats
#   z tree                        Sapling tree state
#   z scan <start> <end>          Scan for commitments
#
# SEND:
#   z send <addr> <amount>        Send transparent
#   z zsend <from> <to> <amount>  Send shielded
#   z shield <from> <to> <amount> Shield (t→z)
#
# RAW:
#   z rpc <method> [args...]      Raw RPC call

set -e
CLI="$(dirname "$0")/../build/bin/zclassic-cli"
[ -x "$CLI" ] || CLI="./build/bin/zclassic-cli"
[ -x "$CLI" ] || { echo "zclassic-cli not found" >&2; exit 1; }

rpc() { "$CLI" "$@" 2>/dev/null; }

json_field() {
    printf '%s\n' "$1" | sed -n "s/.*\"$2\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p; s/.*\"$2\"[[:space:]]*:[[:space:]]*\([-0-9][0-9]*\).*/\1/p; s/.*\"$2\"[[:space:]]*:[[:space:]]*\(true\|false\).*/\1/p" | head -1
}

json_put_string() {
    local json="$1" key="$2" value="$3"
    if printf '%s\n' "$json" | grep -q "\"$key\"[[:space:]]*:"; then
        printf '%s\n' "$json" | sed "s/\"$key\"[[:space:]]*:[[:space:]]*\"[^\"]*\"/\"$key\":\"$value\"/g"
    else
        printf '%s\n' "$json" | sed "s/}$/,\"$key\":\"$value\"}/"
    fi
}

json_put_number() {
    local json="$1" key="$2" value="$3"
    if printf '%s\n' "$json" | grep -q "\"$key\"[[:space:]]*:"; then
        printf '%s\n' "$json" | sed "s/\"$key\"[[:space:]]*:[[:space:]]*[-0-9][0-9]*/\"$key\":$value/g"
    else
        printf '%s\n' "$json" | sed "s/}$/,\"$key\":$value}/"
    fi
}

normalize_mirror_status_json() {
    local json="$1" lag blocker
    lag="$(json_field "$json" candidate_lag)"
    [ -n "$lag" ] || lag="$(json_field "$json" lag)"
    [ -n "$lag" ] || lag=0
    blocker="$(json_field "$json" candidate_blocker)"
    [ -n "$blocker" ] || blocker="$(json_field "$json" last_blocker_code)"
    [ -n "$blocker" ] || blocker="$(json_field "$json" activation_blocker)"

    json="$(json_put_string "$json" consensus_authority local_consensus_validation)"
    json="$(json_put_string "$json" candidate_source legacy_advisory)"
    json="$(json_put_string "$json" candidate_trust bounded_advisory_fallback)"
    json="$(json_put_number "$json" candidate_lag "$lag")"
    json="$(json_put_string "$json" candidate_blocker "$blocker")"
    printf '%s\n' "$json"
}

legacy_conf_value() {
    awk -F= -v k="$1" '
        $0 !~ /^[[:space:]]*[#;]/ {
            key=$1; val=$0; sub(/^[^=]*=/, "", val)
            gsub(/^[[:space:]]+|[[:space:]]+$/, "", key)
            gsub(/^[[:space:]]+|[[:space:]]+$/, "", val)
            if (key == k) { print val; exit }
        }' "${LEGACY_CONF:-$HOME/.zclassic/zclassic.conf}" 2>/dev/null
}

legacy_rpc() {
    local method="$1" params="${2:-[]}" user pass port body
    user="$(legacy_conf_value rpcuser)"
    pass="$(legacy_conf_value rpcpassword)"
    port="${LEGACY_RPCPORT:-$(legacy_conf_value rpcport)}"
    port="${port:-8232}"
    [ -n "$user" ] && [ -n "$pass" ] || return 1
    body="{\"jsonrpc\":\"1.0\",\"id\":\"z-mirror\",\"method\":\"$method\",\"params\":$params}"
    curl -s --max-time 8 --user "$user:$pass" \
        -H 'content-type: application/json' \
        --data "$body" "http://127.0.0.1:$port/" 2>/dev/null
}

rpc_result() {
    local out
    out="$(rpc "$@" 2>/dev/null || true)"
    if printf '%s' "$out" | grep -q '"result"'; then
        json_field "$out" result
    else
        printf '%s' "$out"
    fi
}

mirror_fallback_json() {
    local c23_h c23_hash zd_resp zd_h zd_hash lag reachable
    c23_h="$(rpc_result getblockcount)"
    c23_hash=""
    if [ -n "$c23_h" ]; then
        c23_hash="$(rpc_result getblockhash "$c23_h")"
    fi
    zd_resp="$(legacy_rpc getblockchaininfo || true)"
    zd_h="$(json_field "$zd_resp" blocks)"
    if [ -n "$zd_h" ]; then
        zd_hash="$(json_field "$(legacy_rpc getblockhash "[$zd_h]" || true)" result)"
        reachable=true
    else
        zd_hash=""
        reachable=false
    fi
    if [ -n "$c23_h" ] && [ -n "$zd_h" ]; then
        lag=$((zd_h - c23_h))
    else
        lag=0
    fi
    printf '{"zclassic23_height":%s,"zclassic23_hash":"%s","zclassicd_height":%s,"zclassicd_hash":"%s","lag":%s,"reachable":%s,"mirror_running":false,"last_catchup":0,"last_error":"fallback: getmirrorstatus unavailable","headers_added":0,"blocks_applied":0,"target_height":0,"authority_rewind_target":0,"last_advanced_height":0,"last_progress_blocks":0,"local_recovery_active":false,"legacy_advisory_gated_by_native_retries":false,"mirror_repair_gated_by_local_retries":false,"local_retries_exhausted":false,"local_missing_height":0,"local_retry_count":0,"local_distinct_peer_count":0,"local_peer_rotation_count":0,"stuck_height":0,"stuck_status_flags":0,"stuck_reason":"","stalls_total":0,"last_blocker_code":"","consensus_authority":"local_consensus_validation","candidate_source":"legacy_advisory","candidate_trust":"bounded_advisory_fallback","candidate_lag":%s,"candidate_blocker":"","override_active":false,"overrides_total":0,"unsafe_overrides_total":0,"blockers_total":0,"last_override_height":0,"last_override_safe":false,"last_override_scope":"","last_override_reason":"","activation_blocker":"","csr_sqlite_rc":0,"csr_failure_reason":""}\n' \
        "${c23_h:-0}" "$c23_hash" "${zd_h:-0}" "$zd_hash" "$lag" "$reachable" "$lag"
}

mirror_status_json() {
    local out
    out="$(rpc getmirrorstatus 2>/dev/null || true)"
    if [ -n "$out" ] && printf '%s' "$out" | grep -q 'zclassic23_height'; then
        normalize_mirror_status_json "$out"
    else
        mirror_fallback_json
    fi
}

case "${1:-help}" in

# ── READ: Chain ──────────────────────────────────────────────────
blocks|height|tip)
    rpc getblockcount
    ;;

block)
    if [ -z "$3" ]; then
        rpc chainview "$2" 1
    else
        rpc chainview "$2" "$3"
    fi
    ;;

chain|stats)
    rpc chainstats ${2:+"$2"} ${3:+"$3"}
    ;;

# ── READ: Transactions ──────────────────────────────────────────
tx)
    rpc gettxdetail "$2"
    ;;

mempool)
    rpc getmempoolinfo
    ;;

# ── READ: Wallet ────────────────────────────────────────────────
balance|bal)
    rpc z_gettotalbalance 0
    ;;

utxos)
    rpc listunspent ${2:-0}
    ;;

notes)
    rpc z_listunspent
    ;;

addrs)
    rpc listunspent 0
    ;;

zaddrs)
    rpc z_listaddresses
    ;;

# ── READ: Network ───────────────────────────────────────────────
peers)
    rpc getpeerinfo
    ;;

network|net)
    rpc getnetworkinfo
    ;;

status|health)
    rpc healthcheck
    ;;

advance|chain-advance)
    rpc dumpstate chain_advance_coordinator
    ;;

peerlife|peer-lifecycle)
    rpc dumpstate peer_lifecycle
    ;;

mirror)
    JSON="$(mirror_status_json)"
    if [ "${2:-}" = "--json" ] || [ "${2:-}" = "-j" ]; then
        printf '%s\n' "$JSON"
    else
        printf 'zclassic23=%s zclassicd=%s lag=%s reachable=%s mirror_running=%s\n' \
            "$(json_field "$JSON" zclassic23_height)" \
            "$(json_field "$JSON" zclassicd_height)" \
            "$(json_field "$JSON" lag)" \
            "$(json_field "$JSON" reachable)" \
            "$(json_field "$JSON" mirror_running)"
        printf 'authority=%s trust=%s candidate_source=%s candidate_lag=%s overrides=%s unsafe_overrides=%s blockers=%s last_override_height=%s last_override_safe=%s activation_blocker=%s\n' \
            "$(json_field "$JSON" consensus_authority)" \
            "$(json_field "$JSON" candidate_trust)" \
            "$(json_field "$JSON" candidate_source)" \
            "$(json_field "$JSON" candidate_lag)" \
            "$(json_field "$JSON" overrides_total)" \
            "$(json_field "$JSON" unsafe_overrides_total)" \
            "$(json_field "$JSON" blockers_total)" \
            "$(json_field "$JSON" last_override_height)" \
            "$(json_field "$JSON" last_override_safe)" \
            "$(json_field "$JSON" activation_blocker)"
        if [ "$(json_field "$JSON" local_recovery_active)" = "true" ]; then
            legacy_gate="$(json_field "$JSON" legacy_advisory_gated_by_native_retries)"
            [ -n "$legacy_gate" ] || legacy_gate="$(json_field "$JSON" mirror_repair_gated_by_local_retries)"
            printf 'local_recovery missing_height=%s retries=%s peers=%s rotations=%s legacy_advisory_gated=%s\n' \
                "$(json_field "$JSON" local_missing_height)" \
                "$(json_field "$JSON" local_retry_count)" \
                "$(json_field "$JSON" local_distinct_peer_count)" \
                "$(json_field "$JSON" local_peer_rotation_count)" \
                "$legacy_gate"
        fi
        blocker="$(json_field "$JSON" last_blocker_code)"
        if [ -n "$blocker" ]; then
            printf 'last_blocker_code=%s stuck_height=%s stuck_flags=%s stuck_reason=%s stalls=%s\n' \
                "$blocker" \
                "$(json_field "$JSON" stuck_height)" \
                "$(json_field "$JSON" stuck_status_flags)" \
                "$(json_field "$JSON" stuck_reason)" \
                "$(json_field "$JSON" stalls_total)"
            printf 'authority_rewind_target=%s csr_sqlite_rc=%s csr_failure_reason=%s\n' \
                "$(json_field "$JSON" authority_rewind_target)" \
                "$(json_field "$JSON" csr_sqlite_rc)" \
                "$(json_field "$JSON" csr_failure_reason)"
        fi
    fi
    ;;

info)
    rpc getinfo
    ;;

# ── READ: Sapling ───────────────────────────────────────────────
tree)
    rpc saplingtreeinfo
    ;;

scan)
    rpc scancommitments "$2" "$3"
    ;;

verify)
    rpc verifychainroots "$2" "$3"
    ;;

hodl)
    CHARTBIN="$(dirname "$0")/../hodl_chart"
    [ -x "$CHARTBIN" ] || { echo "Run: make hodl_chart" >&2; exit 1; }
    "$CHARTBIN"
    ;;

# ── SEND: Transactions ──────────────────────────────────────────
send)
    rpc sendtoaddress "$2" "$3"
    ;;

zsend)
    # zsend <from_zaddr> <to_zaddr> <amount>
    rpc z_sendmany "$2" "[{\"address\":\"$3\",\"amount\":$4}]"
    ;;

shield)
    # shield <from_taddr> <to_zaddr> <amount>
    rpc z_sendmany "$2" "[{\"address\":\"$3\",\"amount\":$4}]" 0
    ;;

unshield)
    # unshield <from_zaddr> <to_taddr> <amount>
    rpc z_sendmany "$2" "[{\"address\":\"$3\",\"amount\":$4}]"
    ;;

# ── CREATE: Addresses ───────────────────────────────────────────
newaddr)
    rpc getnewaddress
    ;;

newzaddr)
    rpc z_getnewaddress
    ;;

multisig)
    # multisig <nrequired> <key1> <key2> ...
    N="$2"; shift 2
    KEYS=$(printf '"%s",' "$@" | sed 's/,$//')
    rpc createmultisig "$N" "[$KEYS]"
    ;;

# ── RAW RPC ─────────────────────────────────────────────────────
rpc)
    shift
    rpc "$@"
    ;;

# ── HELP ─────────────────────────────────────────────────────────
help|--help|-h)
    cat <<'HELP'
z — LLM-optimized CRUD interface for zclassic23

READ:
  z blocks              Chain height
  z block 100           Block at height 100
  z block 100 5         5 blocks from height 100
  z chain               Full chain statistics
  z tx <txid>           Transaction UTXO detail
  z balance             All balances (transparent + shielded)
  z utxos               Transparent unspent outputs
  z notes               Shielded unspent notes
  z addrs               Transparent addresses with balances
  z zaddrs              Shielded addresses
  z mempool             Mempool status
  z peers               Connected peers
  z network             P2P reachability and handshake summary
  z status              Healthcheck with chain advance diagnostics
  z advance             Chain advance source scoring and selection blockers
  z peerlife            Peer lifecycle attempts, handshakes, failures by source
  z mirror [--json]     zclassic23/zclassicd mirror status
  z tree                Sapling commitment tree state
  z scan 476969 486969  Scan blocks for commitments
  z info                Node info

SEND:
  z send <taddr> <amt>                  Transparent send
  z zsend <zfrom> <zto> <amt>           Shielded send (z→z)
  z shield <tfrom> <zto> <amt>          Shield funds (t→z)
  z unshield <zfrom> <tto> <amt>        Unshield funds (z→t)

CREATE:
  z newaddr             New transparent address
  z newzaddr            New shielded address
  z multisig 2 pk1 pk2  Create multisig

RAW:
  z rpc <method> [args...]  Direct RPC call
HELP
    ;;

*)
    echo "Unknown: $1 (try: z help)" >&2
    exit 1
    ;;
esac
