"""
    AVM ISA v1 — Julia Port (Strict Functional Execution)
"""
module AVM

using ..Q16_16

export Prim, Instr, State, step, run, prim_add, q16_val

# ── Primitives ──────────────────────────────────────────────────────

@enum Prim begin
    ADD_SAT_Q0
    SUB_SAT_Q0
    ADD_SAT_Q16
    SUB_SAT_Q16
    MUL_SAT_Q16
    DIV_SAT_Q16
    LT_Q16
    EQ_Q16
    AND_OP
    OR_OP
    NOT_OP
end

const prim_add   = ADD_SAT_Q16
const prim_sub   = SUB_SAT_Q16
const prim_mul   = MUL_SAT_Q16
const prim_div   = DIV_SAT_Q16
const prim_lt    = LT_Q16
const prim_eq    = EQ_Q16

# ── Instructions ────────────────────────────────────────────────────

struct Instr
    op::Int8          # 0=push_val, 1=push_q16, 2=pop, 3=dup, 4=swap,
                      # 5=load, 6=store, 7=jump, 8=jump_if, 9=prim, 10=halt
    arg::Int32        # payload: Q16_16 raw, local index, jump target, or Prim
    arg2::Bool        # for push_val: Bool value
end

# Constructors
push_q16(x::Integer) = Instr(1, Int32(x), false)
push_val(x::Bool)    = Instr(0, 0, x)
pop()                = Instr(2, 0, false)
dup()                = Instr(3, 0, false)
swap()               = Instr(4, 0, false)
load(i::Integer)     = Instr(5, i, false)
store(i::Integer)    = Instr(6, i, false)
jump(t::Integer)     = Instr(7, t, false)
jump_if(t::Integer)  = Instr(8, t, false)
prim(p::Prim)        = Instr(9, Int32(p), false)
halt()               = Instr(10, 0, false)

# ── Step Error ─────────────────────────────────────────────────────

@enum StepError begin
    EMPTY_STACK
    STACK_UNDERFLOW
    TYPE_MISMATCH
    MISSING_LOCAL
    DIVISION_BY_ZERO
    JUMP_OUT_OF_BOUNDS
    STACK_OVERFLOW
    HALTED
    UNKNOWN_INSTR
end

# ── AVM constants ───────────────────────────────────────────────────
const AVM_CLAMP_MIN = -2147483647
const AVM_CLAMP_MAX = 2147483647
const AVM_Q0_MIN = -32767
const AVM_Q0_MAX = 32767
const AVM_MAX_STACK = 1024

# Type tags: 0=Bool, 1=Q16_16, 2=Q0_16
const TYPE_BOOL  = Int8(0)
const TYPE_Q16   = Int8(1)
const TYPE_Q0    = Int8(2)

# ── State ──────────────────────────────────────────────────────────

struct State
    pc::Int
    stack::Vector{Union{Int32, Bool}}
    types::Vector{Int8}    # 0=Bool, 1=Q16_16, 2=Q0_16
    locals::Vector{Union{Nothing, Union{Int32, Bool}}}
    local_types::Vector{Int8}
    halted::Bool
end

function State(n_locals::Int=0)
    State(1, Union{Int32, Bool}[], Int8[],
          [nothing for _ in 1:n_locals], zeros(Int8, n_locals),
          false)
end

q16_val(s::State, i::Int) = s.stack[i]::Int32
bool_val(s::State, i::Int) = s.stack[i]::Bool
q16_val(v::Int32) = v

# ── Primitive execution ────────────────────────────────────────────

avm_clamp(x::Int64) = Int32(min(AVM_CLAMP_MAX, max(AVM_CLAMP_MIN, x)))
avm_q0_clamp(x::Int64) = Int32(min(AVM_Q0_MAX, max(AVM_Q0_MIN, x)))

lt_q16_v6(a::Int32, b::Int32) = (a < 0) != (b < 0) ? (a < 0) : (a < b)

function exec_prim(op::Prim, a::Union{Int32, Bool}, b::Union{Int32, Bool, Nothing})
    if op == ADD_SAT_Q0
        a::Int32; b::Int32
        return (avm_q0_clamp(Int64(a) + Int64(b)), TYPE_Q0)
    elseif op == SUB_SAT_Q0
        a::Int32; b::Int32
        return (avm_q0_clamp(Int64(a) - Int64(b)), TYPE_Q0)
    elseif op == ADD_SAT_Q16
        return (avm_clamp(Int64(a::Int32) + Int64(b::Int32)), TYPE_Q16)
    elseif op == SUB_SAT_Q16
        return (avm_clamp(Int64(a::Int32) - Int64(b::Int32)), TYPE_Q16)
    elseif op == MUL_SAT_Q16
        prod = Int64(a::Int32) * Int64(b::Int32)
        result = div(prod, Q16_16.Q16_SCALE)
        return (avm_clamp(result), TYPE_Q16)
    elseif op == DIV_SAT_Q16
        b::Int32 == 0 && error(DIVISION_BY_ZERO)
        num = Int64(a::Int32) * Q16_16.Q16_SCALE
        result = div(num, Int64(b::Int32))
        return (avm_clamp(result), TYPE_Q16)
    elseif op == LT_Q16
        return (lt_q16_v6(a::Int32, b::Int32), TYPE_BOOL)
    elseif op == EQ_Q16
        return (a::Int32 == b::Int32, TYPE_BOOL)
    elseif op == AND_OP
        return (a::Bool && b::Bool, TYPE_BOOL)
    elseif op == OR_OP
        return (a::Bool || b::Bool, TYPE_BOOL)
    elseif op == NOT_OP
        return (!a::Bool, TYPE_BOOL)
    else
        error(TYPE_MISMATCH)
    end
end

# ── Step ───────────────────────────────────────────────────────────

function step(s::State, program::Vector{Instr})::State
    s.halted && error(HALTED)
    s.pc < 1 && return State(s.pc, copy(s.stack), copy(s.types),
                              copy(s.locals), copy(s.local_types), true)
    s.pc > length(program) && return State(s.pc, copy(s.stack), copy(s.types),
                                            copy(s.locals), copy(s.local_types), true)

    instr = program[s.pc]
    stack = copy(s.stack)
    types = copy(s.types)
    locals = copy(s.locals)
    local_types = copy(s.local_types)
    pc = s.pc + 1
    halted = false

    length(stack) >= AVM_MAX_STACK && (instr.op in (0, 1, 3, 5)) && error(STACK_OVERFLOW)

    if instr.op == 0       # push_val (Bool)
        push!(stack, instr.arg2)
        push!(types, TYPE_BOOL)
    elseif instr.op == 1   # push_q16
        push!(stack, instr.arg)
        push!(types, TYPE_Q16)
    elseif instr.op == 2   # pop
        isempty(stack) && error(EMPTY_STACK)
        pop!(stack); pop!(types)
    elseif instr.op == 3   # dup
        isempty(stack) && error(EMPTY_STACK)
        push!(stack, stack[end]); push!(types, types[end])
    elseif instr.op == 4   # swap
        length(stack) < 2 && error(STACK_UNDERFLOW)
        a = pop!(stack); ta = pop!(types)
        b = pop!(stack); tb = pop!(types)
        push!(stack, a); push!(types, ta)
        push!(stack, b); push!(types, tb)
    elseif instr.op == 5   # load
        i = Int(instr.arg) + 1
        i > length(locals) && error(MISSING_LOCAL)
        locals[i] === nothing && error(MISSING_LOCAL)
        push!(stack, locals[i]); push!(types, local_types[i])
    elseif instr.op == 6   # store
        i = Int(instr.arg) + 1
        isempty(stack) && error(EMPTY_STACK)
        i > length(locals) && error(MISSING_LOCAL)
        locals[i] = pop!(stack); local_types[i] = pop!(types)
    elseif instr.op == 7   # jump
        target = Int(instr.arg) + 1
        (target < 1 || target > length(program)) && error(JUMP_OUT_OF_BOUNDS)
        pc = target
    elseif instr.op == 8   # jump_if
        isempty(stack) && error(EMPTY_STACK)
        cond = pop!(stack)::Bool; pop!(types)
        if cond
            target = Int(instr.arg) + 1
            (target < 1 || target > length(program)) && error(JUMP_OUT_OF_BOUNDS)
            pc = target
        end
    elseif instr.op == 9   # prim
        p = Prim(instr.arg)
        arity = (p == NOT_OP) ? 1 : 2
        length(stack) < arity && error(STACK_UNDERFLOW)
        b = arity >= 2 ? pop!(stack) : nothing; if arity >= 2; pop!(types); end
        a = pop!(stack); pop!(types)
        result, type_tag = exec_prim(p, a, b)
        push!(stack, result); push!(types, type_tag)
    elseif instr.op == 10  # halt
        halted = true
    else
        error(UNKNOWN_INSTR)
    end

    State(pc, stack, types, locals, local_types, halted)
end

# ── Run (fuel-bounded) ────────────────────────────────────────────

function run(initial::State, program::Vector{Instr}, fuel::Int)::State
    state = initial
    for _ in 1:fuel
        state.halted && return state
        state = step(state, program)
    end
    state
end

end # module AVM
