package parser

import (
	"encoding/binary"
	"eq2emu/internal/common"
	"strconv"
	"strings"
	"unsafe"
)

type ParseContext struct {
	data       []byte
	offset     int
	version    uint32
	flags      uint64
	vars       map[string]any
	arrayStack []int
}

func NewContext(data []byte, version uint32, flags uint64) *ParseContext {
	return &ParseContext{
		data:    data,
		version: version,
		flags:   flags,
		vars:    make(map[string]any),
	}
}

// Unsigned integer readers
func (ctx *ParseContext) readUint8() uint8 {
	val := ctx.data[ctx.offset]
	ctx.offset++
	return val
}

func (ctx *ParseContext) readUint16() uint16 {
	val := binary.LittleEndian.Uint16(ctx.data[ctx.offset:])
	ctx.offset += 2
	return val
}

func (ctx *ParseContext) readUint32() uint32 {
	val := binary.LittleEndian.Uint32(ctx.data[ctx.offset:])
	ctx.offset += 4
	return val
}

func (ctx *ParseContext) readUint64() uint64 {
	val := binary.LittleEndian.Uint64(ctx.data[ctx.offset:])
	ctx.offset += 8
	return val
}

// Signed integer readers
func (ctx *ParseContext) readSint8() int8 {
	val := int8(ctx.data[ctx.offset])
	ctx.offset++
	return val
}

func (ctx *ParseContext) readSint16() int16 {
	val := int16(binary.LittleEndian.Uint16(ctx.data[ctx.offset:]))
	ctx.offset += 2
	return val
}

func (ctx *ParseContext) readSint32() int32 {
	val := int32(binary.LittleEndian.Uint32(ctx.data[ctx.offset:]))
	ctx.offset += 4
	return val
}

func (ctx *ParseContext) readSint64() int64 {
	val := int64(binary.LittleEndian.Uint64(ctx.data[ctx.offset:]))
	ctx.offset += 8
	return val
}

// Oversized readers
func (ctx *ParseContext) readOversizedUint8(threshold int) uint8 {
	if ctx.data[ctx.offset] == byte(threshold) {
		ctx.offset++
		return ctx.readUint8()
	}
	return ctx.readUint8()
}

func (ctx *ParseContext) readOversizedUint16(threshold int) uint16 {
	if ctx.data[ctx.offset] == byte(threshold) {
		ctx.offset++
		return ctx.readUint16()
	}
	return uint16(ctx.readUint8())
}

func (ctx *ParseContext) readOversizedUint32(threshold int) uint32 {
	if ctx.data[ctx.offset] == byte(threshold) {
		ctx.offset++
		return ctx.readUint32()
	}
	return uint32(ctx.readUint16())
}

func (ctx *ParseContext) readOversizedSint16(threshold int) int16 {
	val := int8(ctx.data[ctx.offset])
	if val == int8(threshold) || val == int8(-threshold) {
		ctx.offset++
		return ctx.readSint16()
	}
	return int16(ctx.readSint8())
}

func (ctx *ParseContext) readEQ2String8() common.EQ2String8 {
	size := ctx.readSint8()
	data := string(ctx.data[ctx.offset : ctx.offset+int(size)])
	ctx.offset += int(size)
	return common.EQ2String8{
		Size: size,
		Data: data,
	}
}

func (ctx *ParseContext) readEQ2String16() common.EQ2String16 {
	size := ctx.readSint16()
	data := string(ctx.data[ctx.offset : ctx.offset+int(size)])
	ctx.offset += int(size)
	return common.EQ2String16{
		Size: size,
		Data: data,
	}
}

func (ctx *ParseContext) readEQ2String32() common.EQ2String32 {
	size := ctx.readSint32()
	data := string(ctx.data[ctx.offset : ctx.offset+int(size)])
	ctx.offset += int(size)
	return common.EQ2String32{
		Size: size,
		Data: data,
	}
}

func (ctx *ParseContext) readEQ2Color() common.EQ2Color {
	return common.EQ2Color{
		Red:   ctx.readSint8(),
		Green: ctx.readSint8(),
		Blue:  ctx.readSint8(),
	}
}

func (ctx *ParseContext) readEQ2Equipment() common.EQ2EquipmentItem {
	return common.EQ2EquipmentItem{
		Type:      ctx.readUint16(),
		Color:     ctx.readEQ2Color(),
		Highlight: ctx.readEQ2Color(),
	}
}

func (ctx *ParseContext) readBytes(count int) []byte {
	val := make([]byte, count)
	copy(val, ctx.data[ctx.offset:ctx.offset+count])
	ctx.offset += count
	return val
}

func (ctx *ParseContext) readFloat32() float32 {
	val := ctx.readUint32()
	return *(*float32)(unsafe.Pointer(&val))
}

func (ctx *ParseContext) readFloat64() float64 {
	val := ctx.readUint64()
	return *(*float64)(unsafe.Pointer(&val))
}

func (ctx *ParseContext) setVar(name string, value any) {
	ctx.vars[name] = value
}

func (ctx *ParseContext) pushArrayIndex(index int) {
	ctx.arrayStack = append(ctx.arrayStack, index)
}

func (ctx *ParseContext) popArrayIndex() {
	if len(ctx.arrayStack) > 0 {
		ctx.arrayStack = ctx.arrayStack[:len(ctx.arrayStack)-1]
	}
}

// Condition evaluation methods
func (ctx *ParseContext) checkCondition(condition string) bool {
	if condition == "" {
		return true
	}

	// Handle comma-separated OR conditions
	if strings.Contains(condition, ",") {
		parts := strings.Split(condition, ",")
		for _, part := range parts {
			if ctx.evaluateCondition(strings.TrimSpace(part)) {
				return true
			}
		}
		return false
	}

	// Handle AND conditions with &
	if strings.Contains(condition, "&") && !strings.Contains(condition, "0x") {
		parts := strings.Split(condition, "&")
		for _, part := range parts {
			if !ctx.evaluateCondition(strings.TrimSpace(part)) {
				return false
			}
		}
		return true
	}

	return ctx.evaluateCondition(condition)
}

func (ctx *ParseContext) evaluateCondition(condition string) bool {
	// Flag conditions: flag:name or !flag:name
	if strings.HasPrefix(condition, "flag:") {
		flagName := condition[5:]
		return (ctx.flags & ctx.getFlagValue(flagName)) != 0
	}
	if strings.HasPrefix(condition, "!flag:") {
		flagName := condition[6:]
		return (ctx.flags & ctx.getFlagValue(flagName)) == 0
	}

	// Variable conditions: var:name or !var:name (with %i support)
	if strings.HasPrefix(condition, "var:") {
		varName := ctx.resolveVariableName(condition[4:])
		return ctx.hasVar(varName)
	}
	if strings.HasPrefix(condition, "!var:") {
		varName := ctx.resolveVariableName(condition[5:])
		return !ctx.hasVar(varName)
	}

	// Version comparisons
	if strings.HasPrefix(condition, "version") {
		return ctx.evaluateVersionCondition(condition)
	}

	// Bitwise AND: header_flag&0x01 (with %i support)
	if strings.Contains(condition, "&0x") {
		parts := strings.SplitN(condition, "&", 2)
		varName := ctx.resolveVariableName(parts[0])
		hexValue, _ := strconv.ParseUint(parts[1], 0, 64)
		varValue := ctx.getVarValue(varName)
		return (varValue & hexValue) != 0
	}

	// String length operators: name!>5, name!<=10 (with %i support)
	stringOps := []string{"!>=", "!<=", "!>", "!<", "!="}
	for _, op := range stringOps {
		if idx := strings.Index(condition, op); idx > 0 {
			varName := ctx.resolveVariableName(condition[:idx])
			valueStr := condition[idx+len(op):]
			return ctx.evaluateStringLength(varName, valueStr, op)
		}
	}

	// Comparison operators: >=, <=, >, <, ==, != (with %i support)
	compOps := []string{">=", "<=", ">", "<", "==", "!="}
	for _, op := range compOps {
		if idx := strings.Index(condition, op); idx > 0 {
			varName := ctx.resolveVariableName(condition[:idx])
			valueStr := condition[idx+len(op):]
			return ctx.evaluateComparison(varName, valueStr, op)
		}
	}

	// Simple variable existence (with %i support)
	resolvedName := ctx.resolveVariableName(condition)
	return ctx.hasVar(resolvedName)
}

func (ctx *ParseContext) evaluateVersionCondition(condition string) bool {
	if strings.Contains(condition, ">=") {
		versionStr := condition[strings.Index(condition, ">=")+2:]
		targetVersion, _ := strconv.ParseUint(versionStr, 10, 32)
		return ctx.version >= uint32(targetVersion)
	}
	if strings.Contains(condition, "<=") {
		versionStr := condition[strings.Index(condition, "<=")+2:]
		targetVersion, _ := strconv.ParseUint(versionStr, 10, 32)
		return ctx.version <= uint32(targetVersion)
	}
	if strings.Contains(condition, ">") {
		versionStr := condition[strings.Index(condition, ">")+1:]
		targetVersion, _ := strconv.ParseUint(versionStr, 10, 32)
		return ctx.version > uint32(targetVersion)
	}
	if strings.Contains(condition, "<") {
		versionStr := condition[strings.Index(condition, "<")+1:]
		targetVersion, _ := strconv.ParseUint(versionStr, 10, 32)
		return ctx.version < uint32(targetVersion)
	}
	return false
}

func (ctx *ParseContext) evaluateStringLength(varName, valueStr, op string) bool {
	resolvedName := ctx.resolveVariableName(varName)
	str := ctx.getStringVar(resolvedName)
	targetLen, _ := strconv.Atoi(valueStr)
	strLen := len(str)

	switch op {
	case "!>":
		return strLen > targetLen
	case "!<":
		return strLen < targetLen
	case "!>=":
		return strLen >= targetLen
	case "!<=":
		return strLen <= targetLen
	case "!=":
		return strLen != targetLen
	}
	return false
}

func (ctx *ParseContext) evaluateComparison(varName, valueStr, op string) bool {
	resolvedName := ctx.resolveVariableName(varName)
	varValue := ctx.getVarValue(resolvedName)
	targetValue, _ := strconv.ParseUint(valueStr, 0, 64)

	switch op {
	case ">=":
		return varValue >= targetValue
	case "<=":
		return varValue <= targetValue
	case ">":
		return varValue > targetValue
	case "<":
		return varValue < targetValue
	case "==":
		return varValue == targetValue
	case "!=":
		return varValue != targetValue
	}
	return false
}

func (ctx *ParseContext) hasVar(name string) bool {
	if val, exists := ctx.vars[name]; exists {
		switch v := val.(type) {
		case uint8, uint16, uint32, uint64, int8, int16, int32, int64:
			return ctx.getVarValue(name) != 0
		case string:
			return v != ""
		case common.EQ2String8:
			return v.Data != ""
		case common.EQ2String16:
			return v.Data != ""
		case common.EQ2String32:
			return v.Data != ""
		default:
			return true
		}
	}
	return false
}

func (ctx *ParseContext) getVarValue(name string) uint64 {
	if val, exists := ctx.vars[name]; exists {
		switch v := val.(type) {
		case uint8:
			return uint64(v)
		case uint16:
			return uint64(v)
		case uint32:
			return uint64(v)
		case uint64:
			return v
		case int8:
			return uint64(v)
		case int16:
			return uint64(v)
		case int32:
			return uint64(v)
		case int64:
			return uint64(v)
		}
	}
	return 0
}

func (ctx *ParseContext) getStringVar(name string) string {
	if val, exists := ctx.vars[name]; exists {
		switch v := val.(type) {
		case string:
			return v
		case common.EQ2String8:
			return v.Data
		case common.EQ2String16:
			return v.Data
		case common.EQ2String32:
			return v.Data
		}
	}
	return ""
}

func (ctx *ParseContext) getFlagValue(flagName string) uint64 {
	flagMap := map[string]uint64{
		"loot":          0x01,
		"has_equipment": 0x02,
		"no_colors":     0x04,
	}
	if val, exists := flagMap[flagName]; exists {
		return val
	}
	return 0
}

func (ctx *ParseContext) getArraySize(condition string) int {
	if strings.HasPrefix(condition, "var:") {
		varName := condition[4:]
		return int(ctx.getVarValue(varName))
	}
	return 0
}

func (ctx *ParseContext) resolveVariableName(name string) string {
	// Handle %i substitution for array contexts
	if strings.Contains(name, "%i") && len(ctx.arrayStack) > 0 {
		currentIndex := ctx.arrayStack[len(ctx.arrayStack)-1]
		return strings.ReplaceAll(name, "%i", strconv.Itoa(currentIndex))
	}
	return name
}

func (ctx *ParseContext) setVarWithArrayIndex(name string, value any) {
	// Always set the base variable name
	ctx.vars[name] = value

	// If we're in an array context, also set the indexed variable
	if len(ctx.arrayStack) > 0 {
		currentIndex := ctx.arrayStack[len(ctx.arrayStack)-1]
		indexedName := name + "_" + strconv.Itoa(currentIndex)
		ctx.vars[indexedName] = value
	}
}
