#!/usr/bin/env bash

# ============================================================
# Prototype runtime: canonical @in / @out authoring surface
#   @in.type  → declare typed inputs at load time
#   @in name  → retrieve raw bound input at runtime
#   @out.type → declare output fields at load time / build them at runtime
# ============================================================

declare -A __reg_inputs
declare -A __reg_outputs
declare -A __reg_notes
declare -A __reg_contracts
declare -A __dispatch
declare -A __in_values
declare -A __type_validators
declare -A __type_defaults

__cur_op=""
__phase=""  # "register" or "dispatch"
__current_result='{}'
__result_owner_subshell=0

# --- Built-in types ---

@string() {
    if (( $# == 0 )); then
        printf '%s\n' '""'
        return 0
    fi

    jq -Rn --arg value "$1" '$value'
}

@string.is() {
    return 0
}

@int() {
    if (( $# == 0 )); then
        printf '%s\n' '0'
        return 0
    fi

    [[ "$1" =~ ^-?[0-9]+$ ]] || {
        @throw "$1 is not an integer"
        return 1
    }

    printf '%s\n' "$1"
}

@int.is() {
    @int "$1" >/dev/null
}

@bool() {
    if (( $# == 0 )); then
        printf '%s\n' 'false'
        return 0
    fi

    case "$1" in
        true|false)
            printf '%s\n' "$1"
            ;;
        *)
            @throw "$1 is not a bool"
            return 1
            ;;
    esac
}

@bool.is() {
    @bool "$1" >/dev/null
}

@object() {
    local first=true
    local json

    if (( $# == 1 )); then
        jq -ce 'type == "object"' <<< "$1" >/dev/null || {
            @throw "'$1' is not a valid JSON object"
            return 1
        }

        jq -c . <<< "$1"
        return 0
    fi

    json="{"
    while (( $# >= 2 )); do
        $first && first=false || json+=","
        json+="\"$1\":$2"
        shift 2
    done
    json+="}"

    printf '%s\n' "$json"
}

@object.is() {
    @object "$1" >/dev/null
}

@result() {
    __ensure_output_scope
    printf '%s\n' "$__current_result"
}

@result.get() {
    local path="$1"

    __ensure_output_scope
    jq -cr ".$path" <<< "$__current_result"
}

@in() {
    printf '%s\n' "${__in_values[$1]}"
}

@return() {
    printf '%s\n' "$@"
}

@return.out() {
    __ensure_output_scope
    @return "$__current_result"
}

@throw() {
    __current_result='{}'
    printf 'ERROR: %s\n' "$*" >&2
    return 1
}

# --- Metadata calls ---

@note() {
    __reg_notes["$__cur_op"]+="$1 "
}

@symbol() { :; }
@remote() { :; }
@dangerous() { :; }
@infix() { :; }
@imperative() { :; }

# --- Type registration ---

@type_register() {
    local name="$1"
    local is_fn="$2"
    local default_fn="${3:-}"

    __type_validators["$name"]="$is_fn"
    __type_defaults["$name"]="$default_fn"

    eval "@${name}() {
        if (( \$# == 0 )); then
            if [[ -n \"$default_fn\" ]]; then
                $default_fn
                return \$?
            fi

            @throw \"type ${name} has no default\"
            return 1
        fi

        $is_fn \"\$@\"
    }"

    eval "@${name}.is() {
        $is_fn \"\$1\" >/dev/null
    }"

    __register_typed_helpers "$name"
}

# --- Helpers ---

_snap() {
    declare -F | awk '{print $3}'
}

_discover_new() {
    local before="$1"
    local after="$2"
    local fn

    while IFS= read -r fn; do
        [[ -n "$fn" ]] && echo "$before" | grep -qxF "$fn" || echo "$fn"
    done <<< "$after"
}

__ensure_output_scope() {
    if [[ "$__result_owner_subshell" != "$BASH_SUBSHELL" ]]; then
        __current_result='{}'
        __result_owner_subshell="$BASH_SUBSHELL"
    fi
}

__parse_type_call() {
    local full_name="$1"

    __typed_verb=""
    __typed_spec=""
    __typed_base=""
    __typed_is_array=false
    __typed_is_optional=false

    case "$full_name" in
        @in.*)
            __typed_verb="@in"
            __typed_spec="${full_name#@in.}"
            ;;
        @out.*)
            __typed_verb="@out"
            __typed_spec="${full_name#@out.}"
            ;;
        *)
            return 1
            ;;
    esac

    __typed_base="$__typed_spec"
    if [[ "$__typed_base" == *'[]?' ]]; then
        __typed_base="${__typed_base%'[]?'}"
        __typed_is_array=true
        __typed_is_optional=true
    elif [[ "$__typed_base" == *'[]' ]]; then
        __typed_base="${__typed_base%'[]'}"
        __typed_is_array=true
    elif [[ "$__typed_base" == *'?' ]]; then
        __typed_base="${__typed_base%?}"
        __typed_is_optional=true
    fi
}

__register_input() {
    local field_name="$1"

    __reg_inputs["$__cur_op"]+="${field_name}=${__typed_spec} "
}

__register_output() {
    local field_name="$1"

    __reg_outputs["$__cur_op"]+="${field_name}=${__typed_spec} "
}

__call_type() {
    local type_name="$1"
    local fn_name="@${type_name}"

    if ! declare -F "$fn_name" >/dev/null; then
        @throw "unknown type ${type_name}"
        return 1
    fi

    "$fn_name" "${@:2}"
}

__json_array() {
    local first=true
    local item
    local json="["

    for item in "$@"; do
        $first && first=false || json+=","
        json+="$item"
    done
    json+="]"

    printf '%s\n' "$json"
}

__json_value_for_out() {
    local value

    if [[ "$__typed_is_array" == true ]]; then
        if (( $# == 0 )); then
            if [[ "$__typed_is_optional" == true ]]; then
                printf '%s\n' 'null'
                return 0
            fi

            printf '%s\n' '[]'
            return 0
        fi

        local values=()
        while (( $# > 0 )); do
            if [[ "$__typed_base" == "object" ]]; then
                value=$(@object "$1") || return 1
            else
                value=$(__call_type "$__typed_base" "$1") || return 1
            fi
            values+=("$value")
            shift
        done

        __json_array "${values[@]}"
        return 0
    fi

    if (( $# == 0 )); then
        if [[ "$__typed_is_optional" == true ]]; then
            printf '%s\n' 'null'
            return 0
        fi

        __call_type "$__typed_base"
        return $?
    fi

    if [[ "$__typed_base" == "object" ]]; then
        @object "$1"
        return $?
    fi

    __call_type "$__typed_base" "$1"
}

__set_output_field() {
    local field_name="$1"
    local field_value="$2"

    __ensure_output_scope
    __current_result=$(
        jq -c \
            --arg path "$field_name" \
            --argjson value "$field_value" \
            'setpath(($path | split(".")); $value)' \
            <<< "$__current_result"
    ) || return 1
}

__handle_typed_call() {
    local full_name="$1"
    shift

    __parse_type_call "$full_name" || return 1

    if [[ "$__typed_verb" == "@in" ]]; then
        [[ "$__phase" == "register" ]] || {
            @throw "${full_name} is only valid during load-time input declaration"
            return 1
        }

        __register_input "$1"
        return 0
    fi

    if [[ "$__phase" == "register" ]]; then
        __register_output "$1"
        return 0
    fi

    local field_name="$1"
    local field_value
    shift

    field_value=$(__json_value_for_out "$@") || return 1
    __set_output_field "$field_name" "$field_value"
}

__register_typed_helpers() {
    local type_name="$1"
    local spec

    for spec in "$type_name" "${type_name}?" "${type_name}[]" "${type_name}[]?"; do
        eval "@in.${spec}() { __handle_typed_call '@in.${spec}' \"\$@\"; }"
        eval "@out.${spec}() { __handle_typed_call '@out.${spec}' \"\$@\"; }"
    done
}

command_not_found_handle() {
    local full_name="$1"
    shift

    __parse_type_call "$full_name" || {
        printf '%s: command not found\n' "$full_name" >&2
        return 127
    }

    __handle_typed_call "$full_name" "$@"
}

__register_typed_helpers string
__register_typed_helpers int
__register_typed_helpers bool
__register_typed_helpers object

# --- load_ops framework ---

load_op() {
    local outer="$1"
    local name="${outer#@operation.}"
    local before
    local after
    local new_fns
    local contracts=""
    local prefix="${name//\./__}"
    local fn

    __cur_op="$name"
    __phase="register"
    before=$(_snap)
    "$outer"
    after=$(_snap)
    new_fns=$(_discover_new "$before" "$after")

    while IFS= read -r fn; do
        [[ -z "$fn" ]] && continue

        case "$fn" in
            @do|@undo|@is-done)
                local cname="${fn#@}"
                local mangled="__op__${prefix}__${cname//-/_}"
                eval "$(declare -f "$fn" | sed "1s/.*/${mangled} ()/")"
                __dispatch["${name}.${cname}"]="$mangled"
                contracts+="${cname} "
                unset -f "$fn"
                ;;
        esac
    done <<< "$new_fns"

    __reg_contracts["$name"]="$contracts"
    __cur_op=""
    __phase=""
}

load_type() {
    local outer="$1"
    local name="${outer#@type.}"
    local before
    local after
    local new_fns
    local prefix="type__${name//-/_}"
    local fn
    local is_fn=""
    local default_fn=""
    local contracts=""

    __cur_op="type:$name"
    __phase="register"
    before=$(_snap)
    "$outer"
    after=$(_snap)
    new_fns=$(_discover_new "$before" "$after")

    while IFS= read -r fn; do
        [[ -z "$fn" ]] && continue

        case "$fn" in
            @is|@default)
                local cname="${fn#@}"
                local mangled="__${prefix}__${cname//-/_}"
                eval "$(declare -f "$fn" | sed "1s/.*/${mangled} ()/")"
                __dispatch["type:${name}.${cname}"]="$mangled"
                contracts+="${cname} "
                if [[ "$cname" == "is" ]]; then
                    is_fn="$mangled"
                else
                    default_fn="$mangled"
                fi
                unset -f "$fn"
                ;;
        esac
    done <<< "$new_fns"

    [[ -n "$is_fn" ]] && @type_register "$name" "$is_fn" "$default_fn"

    __reg_contracts["type:$name"]="$contracts"
    __cur_op=""
    __phase=""
}

dispatch() {
    local op="$1"
    local contract="$2"
    local fn
    local i=1
    local input_defs
    local pair
    local input_name

    shift 2

    fn="${__dispatch["${op}.${contract}"]}"
    if [[ -z "$fn" ]]; then
        printf 'no dispatch for %s.%s\n' "$op" "$contract" >&2
        return 1
    fi

    __phase="dispatch"
    __current_result='{}'
    __result_owner_subshell="$BASH_SUBSHELL"
    __in_values=()
    input_defs="${__reg_inputs[$op]:-}"

    for pair in $input_defs; do
        input_name="${pair%%=*}"
        __in_values["$input_name"]="${!i:-}"
        (( i++ ))
    done

    "$fn" "$@"
    local exit_code=$?
    __phase=""
    return $exit_code
}

# --- Reporting ---

dump_registry() {
    local op
    local type_name

    printf '=== Operations ===\n'
    for op in $(printf '%s\n' "${!__reg_contracts[@]}" | sort); do
        [[ "$op" == type:* ]] && continue
        printf '  %s\n' "$op"
        [[ -n "${__reg_inputs[$op]:-}" ]] && printf '    inputs:    %s\n' "${__reg_inputs[$op]}"
        [[ -n "${__reg_outputs[$op]:-}" ]] && printf '    outputs:   %s\n' "${__reg_outputs[$op]}"
        [[ -n "${__reg_notes[$op]:-}" ]] && printf '    notes:     %s\n' "${__reg_notes[$op]}"
        printf '    contracts: [%s]\n' "${__reg_contracts[$op]}"
    done

    printf '\n=== Types ===\n'
    for type_name in $(printf '%s\n' "${!__type_validators[@]}" | sort); do
        printf '  %s\n' "$type_name"
        [[ -n "${__type_defaults[$type_name]:-}" ]] && printf '    has default\n'
    done
}
