// AVM ISA v1 — Go Port (Strict Functional Execution)
package avm

import "errors"

// ── Constants ────────────────────────────────────────────────────
const (
	AVMClampMin = -2147483647
	AVMClampMax = 2147483647
	AVMQ0Min    = -32767
	AVMQ0Max    = 32767
	Q16Scale    = 65536
	AVMMaxStack = 1024
)

func avmClamp(x int64) int32 {
	if x > AVMClampMax { return AVMClampMax }
	if x < AVMClampMin { return AVMClampMin }
	return int32(x)
}

func avmQ0Clamp(x int64) int32 {
	if x > AVMQ0Max { return AVMQ0Max }
	if x < AVMQ0Min { return AVMQ0Min }
	return int32(x)
}

func floorDiv(a, b int64) int64 {
	if b == 0 { return 0 }
	q := a / b
	r := a % b
	if r != 0 && ((a ^ b) < 0) { q-- }
	return q
}

// euclideanDiv matches Lean Int.ediv (remainder >= 0).
func euclideanDiv(a, b int64) int64 {
	if b == 0 { return 0 }
	q := a / b
	r := a % b
	if r < 0 {
		if b > 0 { q-- } else { q++ }
	}
	return q
}

func ltQ16V6(a, b int32) bool {
	sa, sb := a < 0, b < 0
	if sa != sb { return sa }
	return a < b
}

// ── Types ──────────────────────────────────────────────────────
type Ty uint8
const (
	Q0_16  Ty = 0
	Q16_16 Ty = 1
	Bool   Ty = 2
)

type Val struct {
	Ty   Ty
	Q    int32
	Bval bool
}

// ── Primitives ────────────────────────────────────────────────
type Prim uint8
const (
	AddSatQ0 Prim = iota; SubSatQ0; AddSatQ16; SubSatQ16
	MulSatQ16; DivSatQ16; LtQ16; EqQ16
	And; Or; Not
)

func primArity(p Prim) int {
	if p == Not { return 1 }
	return 2
}

// ── Instructions ──────────────────────────────────────────────
type Op uint8
const (
	PushQ16 Op = iota; PushBool; PushQ0; Pop; Dup; Swap
	Load; Store; Jump; JumpIf; Primitive; Halt
)

type Instr struct {
	Op   Op
	Arg  int32
	Arg2 bool
}

// ── Primitive execution ──────────────────────────────────────
func execPrim(p Prim, a, b Val) (Val, error) {
	check := func(v Val, t Ty) error {
		if v.Ty != t { return errors.New("type mismatch") }
		return nil
	}
	switch p {
	case AddSatQ0:
		if err := check(a, Q0_16); err != nil { return Val{}, err }
		if err := check(b, Q0_16); err != nil { return Val{}, err }
		return Val{Ty: Q0_16, Q: avmQ0Clamp(int64(a.Q) + int64(b.Q))}, nil
	case SubSatQ0:
		if err := check(a, Q0_16); err != nil { return Val{}, err }
		if err := check(b, Q0_16); err != nil { return Val{}, err }
		return Val{Ty: Q0_16, Q: avmQ0Clamp(int64(a.Q) - int64(b.Q))}, nil
	case AddSatQ16:
		if err := check(a, Q16_16); err != nil { return Val{}, err }
		if err := check(b, Q16_16); err != nil { return Val{}, err }
		return Val{Ty: Q16_16, Q: avmClamp(int64(a.Q) + int64(b.Q))}, nil
	case SubSatQ16:
		if err := check(a, Q16_16); err != nil { return Val{}, err }
		if err := check(b, Q16_16); err != nil { return Val{}, err }
		return Val{Ty: Q16_16, Q: avmClamp(int64(a.Q) - int64(b.Q))}, nil
	case MulSatQ16:
		if err := check(a, Q16_16); err != nil { return Val{}, err }
		if err := check(b, Q16_16); err != nil { return Val{}, err }
		return Val{Ty: Q16_16, Q: avmClamp(euclideanDiv(int64(a.Q)*int64(b.Q), Q16Scale))}, nil
	case DivSatQ16:
		if err := check(a, Q16_16); err != nil { return Val{}, err }
		if err := check(b, Q16_16); err != nil { return Val{}, err }
		if b.Q == 0 { return Val{}, errors.New("division by zero") }
		return Val{Ty: Q16_16, Q: avmClamp(euclideanDiv(int64(a.Q)*Q16Scale, int64(b.Q)))}, nil
	case LtQ16:
		if err := check(a, Q16_16); err != nil { return Val{}, err }
		if err := check(b, Q16_16); err != nil { return Val{}, err }
		return Val{Ty: Bool, Bval: ltQ16V6(a.Q, b.Q)}, nil
	case EqQ16:
		if err := check(a, Q16_16); err != nil { return Val{}, err }
		if err := check(b, Q16_16); err != nil { return Val{}, err }
		return Val{Ty: Bool, Bval: a.Q == b.Q}, nil
	case And:
		if err := check(a, Bool); err != nil { return Val{}, err }
		if err := check(b, Bool); err != nil { return Val{}, err }
		return Val{Ty: Bool, Bval: a.Bval && b.Bval}, nil
	case Or:
		if err := check(a, Bool); err != nil { return Val{}, err }
		if err := check(b, Bool); err != nil { return Val{}, err }
		return Val{Ty: Bool, Bval: a.Bval || b.Bval}, nil
	case Not:
		if err := check(a, Bool); err != nil { return Val{}, err }
		return Val{Ty: Bool, Bval: !a.Bval}, nil
	}
	return Val{}, errors.New("unknown prim")
}

// ── State ─────────────────────────────────────────────────────
type State struct {
	Pc     int
	Stack  []Val
	Locals []Val
	Halted bool
}

func NewState(nLocals int) State {
	return State{Pc: 0, Stack: []Val{}, Locals: make([]Val, nLocals), Halted: false}
}

// ── Step ─────────────────────────────────────────────────────
func Step(s State, prog []Instr) (*State, error) {
	if s.Halted { return &s, nil }
	if s.Pc < 0 || s.Pc >= len(prog) {
		return nil, errors.New("invalid jump")
	}

	instr := prog[s.Pc]
	stack := s.Stack
	npc := s.Pc + 1

	growing := instr.Op == PushQ16 || instr.Op == PushBool || instr.Op == PushQ0 || instr.Op == Dup || instr.Op == Load
	if growing && len(stack) >= AVMMaxStack { return nil, errors.New("stack overflow") }

	switch instr.Op {
	case PushQ16:
		stack = append(stack, Val{Ty: Q16_16, Q: avmClamp(int64(instr.Arg))})
		case PushBool:
			stack = append(stack, Val{Ty: Bool, Bval: instr.Arg2})
		case PushQ0:
			stack = append(stack, Val{Ty: Q0_16, Q: avmQ0Clamp(int64(instr.Arg))})
	case Pop:
		if len(stack) == 0 { return nil, errors.New("empty stack") }
		stack = stack[:len(stack)-1]
	case Dup:
		if len(stack) == 0 { return nil, errors.New("empty stack") }
		stack = append(stack, stack[len(stack)-1])
	case Swap:
		if len(stack) < 2 { return nil, errors.New("stack underflow") }
		stack[len(stack)-1], stack[len(stack)-2] = stack[len(stack)-2], stack[len(stack)-1]
	case Load:
		i := int(instr.Arg)
		if i >= len(s.Locals) { return nil, errors.New("missing local") }
		stack = append(stack, s.Locals[i])
	case Store:
		if len(stack) == 0 { return nil, errors.New("empty stack") }
		i := int(instr.Arg)
		if i >= len(s.Locals) { return nil, errors.New("missing local") }
		s.Locals[i] = stack[len(stack)-1]
		stack = stack[:len(stack)-1]
	case Jump:
		if instr.Arg < 0 || int(instr.Arg) >= len(prog) { return nil, errors.New("jump OOB") }
		npc = int(instr.Arg)
	case JumpIf:
		if len(stack) == 0 { return nil, errors.New("empty stack") }
		v := stack[len(stack)-1]; stack = stack[:len(stack)-1]
		if v.Ty != Bool { return nil, errors.New("type mismatch") }
		if v.Bval {
			if instr.Arg < 0 || int(instr.Arg) >= len(prog) { return nil, errors.New("jump OOB") }
			npc = int(instr.Arg)
		}
	case Primitive:
		p := Prim(instr.Arg)
		arity := primArity(p)
		if len(stack) < arity { return nil, errors.New("stack underflow") }
		var b Val
		if arity >= 2 { b = stack[len(stack)-1]; stack = stack[:len(stack)-1] }
		a := stack[len(stack)-1]; stack = stack[:len(stack)-1]
		r, execErr := execPrim(p, a, b)
		if execErr != nil { return nil, execErr }
		stack = append(stack, r)
	case Halt:
		s.Halted = true
		npc = s.Pc  // Lean: Halt does not increment PC
	}
	return &State{Pc: npc, Stack: stack, Locals: s.Locals, Halted: s.Halted}, nil
}

// ── Run (fuel-bounded) ───────────────────────────────────────
func Run(init State, prog []Instr, fuel int) (*State, error) {
	s := init
	for i := 0; i < fuel; i++ {
		if s.Halted { return &s, nil }
		next, err := Step(s, prog)
		if err != nil { return nil, err }
		s = *next
	}
	return &s, nil
}
