/*
 * AxonASP Server
 * Copyright (C) 2026 G3pix Ltda. All rights reserved.
 *
 * Developed by Lucas Guimarães - G3pix Ltda
 * Contact: https://g3pix.com.br
 * Project URL: https://g3pix.com.br/axonasp
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 *
 * Attribution Notice:
 * If this software is used in other projects, the name "AxonASP Server"
 * must be cited in the documentation or "About" section.
 *
 * Contribution Policy:
 * Modifications to the core source code of AxonASP Server must be
 * made available under this same license terms.
 */
package axonvm

import (
	"fmt"
	"math"
	"math/big"
	"strconv"
	"strings"
	"time"
)

type ValueType byte

const (
	VTEmpty ValueType = iota
	VTNull
	VTBool
	VTInteger
	VTDouble
	VTString
	VTDate
	VTArray
	VTObject
	VTNativeObject
	VTBuiltin
	VTUserSub
	VTNothing
	// VTJSUndefined represents the JavaScript undefined primitive.
	VTJSUndefined
	// VTJSObject points to one dynamic JS object ID in VM jsObjectItems.
	VTJSObject
	// VTJSFunction points to one dynamic JS closure ID in VM jsFunctionItems.
	VTJSFunction
	// VTJSUninitialized represents uninitialized 'this' in derived constructors.
	VTJSUninitialized
	// VTJSFunctionTemplate is a compile-time constant describing one JS function body.
	VTJSFunctionTemplate
	// VTJSArrowFunctionTemplate is a compile-time constant describing one JS arrow function body.
	// Arrow functions capture the enclosing 'this' value at closure creation time.
	VTJSArrowFunctionTemplate
	// VTArgRef is a synthetic value type emitted only at call sites to carry a ByRef
	// slot reference alongside the argument value for post-call write-back.
	VTArgRef
	// VTSymbol represents the JavaScript Symbol primitive.
	VTSymbol
	// VTJSBigInt represents the JavaScript BigInt primitive.
	VTJSBigInt
	// VTJSPromise points to one dynamic Promise ID in VM jsPromiseItems.
	VTJSPromise
	// VTJSGenerator points to one dynamic Generator ID in VM jsGeneratorItems.
	VTJSGenerator
	// VTJSProxy points to one dynamic Proxy ID in VM jsProxyItems.
	VTJSProxy
	// VTRecord represents a User-Defined Type (UDT) instance.
	VTRecord
)

type Value struct {
	Type      ValueType
	Interface string    // Phase 5: Optional Class/Interface name for VTObject
	Num       int64     // Used for Bool (0/1), Integer, Date, NativeObject ID, and Builtin Index
	Flt       float64   // Used for Double
	Str       string    // Strings in Go are lightweight pointers
	Arr       *VBArray  // Used for VBScript arrays
	Rec       *VBRecord // Used for User-Defined Types (UDT)
	Names     []string  // Stores local names for VTUserSub or field names for VTObject
	Big       *big.Int  // Used for JavaScript BigInt
}

// VBRecord stores data for a User-Defined Type (UDT) instance.
type VBRecord struct {
	DefIdx  int
	Members []Value
	// releaseMark prevents recursive/double release when record graphs contain
	// aliases or cycles.
	releaseMark bool
}

// String returns the string representation of the VBScript value.
func (v Value) String() string {
	switch v.Type {
	case VTEmpty:
		return ""
	case VTNull:
		return "Null"
	case VTBool:
		if v.Num != 0 {
			return "True"
		}
		return "False"
	case VTInteger:
		return fmt.Sprintf("%d", v.Num)
	case VTDouble:
		if math.IsNaN(v.Flt) {
			return "NaN"
		}
		if math.IsInf(v.Flt, 1) {
			return "Infinity"
		}
		if math.IsInf(v.Flt, -1) {
			return "-Infinity"
		}
		if v.Flt == 0 {
			return "0"
		}
		return fmt.Sprintf("%g", v.Flt)
	case VTString:
		return v.Str
	case VTArray:
		return "[Array]"
	case VTObject:
		if v.Num == 0 {
			return "" // Nothing has no string value
		}
		return fmt.Sprintf("[Object:%d]", v.Num)
	case VTNativeObject:
		if v.Num == nativeObjectConsole {
			return "[object console]"
		}
		return fmt.Sprintf("[NativeObject:%d]", v.Num)
	case VTBuiltin:
		return fmt.Sprintf("[Builtin:%d]", v.Num)
	case VTDate:
		defaults := loadBuiltinDefaults()
		return localizedDateString(time.Unix(0, v.Num).In(defaults.location), builtinLocaleProfileForLCID(defaults.mslcid))
	case VTUserSub:
		return "[UserSub]"
	case VTJSUndefined:
		return "undefined"
	case VTJSUninitialized:
		return "[Uninitialized]"
	case VTJSObject:
		return fmt.Sprintf("[JSObject:%d]", v.Num)
	case VTJSFunction:
		return fmt.Sprintf("[JSFunction:%d]", v.Num)
	case VTJSFunctionTemplate:
		return fmt.Sprintf("[JSFunctionTemplate:%d]", v.Num)
	case VTArgRef:
		return "[ArgRef]"
	case VTSymbol:
		if v.Str == "" {
			return "Symbol()"
		}
		return "Symbol(" + v.Str + ")"
	case VTJSBigInt:
		if v.Big == nil {
			return "0"
		}
		return v.Big.String()
	case VTJSPromise:
		return "[object Promise]"
	case VTJSGenerator:
		return "[object Generator]"
	case VTJSProxy:
		return "[object Proxy]"
	case VTRecord:
		return "[Record]"
	default:
		return "Unknown"
	}
}

func NewInteger(v int64) Value {
	return Value{Type: VTInteger, Num: v}
}

func NewDouble(v float64) Value {
	return Value{Type: VTDouble, Flt: v}
}

func NewString(v string) Value {
	return Value{Type: VTString, Str: v}
}

func NewBool(v bool) Value {
	val := int64(0)
	if v {
		val = 1
	}
	return Value{Type: VTBool, Num: val}
}

func NewBigInt(v *big.Int) Value {
	return Value{Type: VTJSBigInt, Big: v}
}

// NewDate creates a VM date value from a Go time instance.
func NewDate(v time.Time) Value {
	return Value{Type: VTDate, Num: v.UTC().UnixNano()}
}

// NewNull returns a VM Null value. Any operation on Null propagates Null.
func NewNull() Value {
	return Value{Type: VTNull}
}

// NewEmpty returns a VM Empty value.
func NewEmpty() Value {
	return Value{Type: VTEmpty}
}

// NewUserSub creates a VM value for a user-defined Sub or Function.
// entryPoint is the bytecode offset where the body begins.
// paramCount is the number of declared parameters.
// localCount is the total number of local slots (params + locals + function return slot when applicable).
// isFunc is true for Function and false for Sub.
// ParamMeta stores extended parameter metadata for a VTUserSub function.
// Used by the VM during call setup for Optional/ParamArray/ByVal handling.
type ParamMeta struct {
	Name            string    // Parameter name
	Flags           byte      // bit 0: Optional, bit 1: ParamArray
	DeclaredType    ValueType // VB6 As Type (VTEmpty = Variant)
	DeclaredUDTName string    // UDT name if DeclaredType is VTRecord
	DefaultValueIdx int       // Constant pool index for default value, -1 if none
}

// ParamMeta flag bits.
const (
	ParamFlagOptional   byte = 1 << iota // Parameter is Optional
	ParamFlagParamArray                  // Parameter is ParamArray
	ParamFlagByVal                       // Parameter is explicit ByVal
)

// byRefMask encodes which parameters are ByRef: bit i set = param i is ByRef.
// localNames is the list of symbol names for all local variable slots.
// optionalMask encodes which parameters are Optional: bit i set = param i is Optional.
// paramArrayIdx is the index of the ParamArray parameter, or -1 if none.
func NewUserSub(entryPoint int, paramCount int, localCount int, isFunc bool, byRefMask uint64, localNames []string) Value {
	return NewUserSubEx(entryPoint, paramCount, localCount, isFunc, byRefMask, 0, -1, localNames)
}

// NewUserSubEx creates a VTUserSub value with full VB6 advanced signature metadata.
// optionalMask encodes which parameters are Optional: bit i set = param i is Optional.
// paramArrayIdx is the index of the ParamArray parameter, or -1 if none.
func NewUserSubEx(entryPoint int, paramCount int, localCount int, isFunc bool, byRefMask uint64, optionalMask uint64, paramArrayIdx int, localNames []string) Value {
	if paramCount < 0 {
		paramCount = 0
	}
	if localCount < paramCount {
		localCount = paramCount
	}

	packed := (localCount << 12) | (paramCount & 0x0FFF)
	marker := float64(packed)
	if isFunc {
		marker += 0.5
	}
	v := Value{Type: VTUserSub, Num: int64(entryPoint), Flt: marker, Names: localNames}

	// Encode byRefMask, optionalMask, and paramArrayIdx into Str.
	// Format: "byRefMask|optionalMask|paramArrayIdx"
	var sb strings.Builder
	if byRefMask != 0 || optionalMask != 0 || paramArrayIdx >= 0 {
		sb.WriteString(strconv.FormatUint(byRefMask, 10))
		sb.WriteByte('|')
		sb.WriteString(strconv.FormatUint(optionalMask, 10))
		sb.WriteByte('|')
		sb.WriteString(strconv.Itoa(paramArrayIdx))
		v.Str = sb.String()
	}
	return v
}

// parseUserSubStr decodes the Str metadata of a VTUserSub value into its components.
func parseUserSubStr(s string) (byRefMask uint64, optionalMask uint64, paramArrayIdx int) {
	paramArrayIdx = -1 // default: no ParamArray
	if s == "" {
		return 0, 0, -1
	}
	parts := strings.SplitN(s, "|", 3)
	if len(parts) >= 1 && parts[0] != "" {
		byRefMask, _ = strconv.ParseUint(parts[0], 10, 64)
	}
	if len(parts) >= 2 && parts[1] != "" {
		optionalMask, _ = strconv.ParseUint(parts[1], 10, 64)
	}
	if len(parts) >= 3 && parts[2] != "" {
		paIdx, err := strconv.Atoi(parts[2])
		if err == nil {
			paramArrayIdx = paIdx
		}
	}
	return
}

// UserSubIsFunc reports whether a VTUserSub value represents a Function.
func (v Value) UserSubIsFunc() bool {
	return v.Flt != float64(int(v.Flt))
}

// UserSubParamCount returns the declared parameter count for a VTUserSub value.
func (v Value) UserSubParamCount() int {
	packed := int(v.Flt)
	if packed <= 0x0FFF {
		return packed
	}
	return packed & 0x0FFF
}

// UserSubLocalCount returns the total number of local slots for a VTUserSub value.
func (v Value) UserSubLocalCount() int {
	packed := int(v.Flt)
	if packed <= 0x0FFF {
		return packed
	}
	return packed >> 12
}

// UserSubByRefMask returns the ByRef parameter bitmask for a VTUserSub value.
// Bit i set means parameter i is declared ByRef.
func (v Value) UserSubByRefMask() uint64 {
	mask, _, _ := parseUserSubStr(v.Str)
	return mask
}

// UserSubOptionalMask returns the Optional parameter bitmask for a VTUserSub value.
// Bit i set means parameter i is Optional.
func (v Value) UserSubOptionalMask() uint64 {
	_, mask, _ := parseUserSubStr(v.Str)
	return mask
}

// UserSubParamArrayIdx returns the index of the ParamArray parameter, or -1 if none.
func (v Value) UserSubParamArrayIdx() int {
	_, _, idx := parseUserSubStr(v.Str)
	return idx
}

// ArgRefIsGlobal reports whether this VTArgRef references a global slot.
func (v Value) ArgRefIsGlobal() bool { return v.Flt == 1.0 }

// ArgRefIsLocal reports whether this VTArgRef references a local slot.
func (v Value) ArgRefIsLocal() bool { return v.Flt == 0.0 }

// ArgRefIsClassMember reports whether this VTArgRef references a class member slot.
func (v Value) ArgRefIsClassMember() bool { return v.Flt == 2.0 }

// ArgRefIdx returns the slot index encoded in a VTArgRef value.
func (v Value) ArgRefIdx() int { return int(v.Num) }

// IsObjectReferenceValue reports whether a value is a valid object reference for Is/Is Not.
func IsObjectReferenceValue(v Value) bool {
	return v.Type == VTObject || v.Type == VTNativeObject || v.Type == VTNothing
}
