// Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.

// operator-index.go
package expr

import (
	"strings"

	"git.portale-stac.it/go-pkg/expr/kern"
	"git.portale-stac.it/go-pkg/expr/scan"
	"git.portale-stac.it/go-pkg/expr/sym"
	"git.portale-stac.it/go-pkg/expr/types"
	"git.portale-stac.it/go-pkg/expr/types/array"
	"git.portale-stac.it/go-pkg/expr/types/dict"
	"git.portale-stac.it/go-pkg/expr/types/interval"
	"git.portale-stac.it/go-pkg/expr/types/list"
)

// -------- index term
func newIndexTerm(tk *scan.Token) (inst *scan.Term) {
	return &scan.Term{
		Tk:       *tk,
		Children: make([]*scan.Term, 0, 2),
		Position: scan.PosInfix,
		Priority: scan.PriDot,
		EvalFunc: evalIndex,
	}
}

func verifyKey(indexList *array.ArrayType) (index any, err error) {
	index = (*indexList)[0]
	return
}

func verifyIndex(indexTerm kern.Term, indexList *array.ArrayType, maxValue int64) (index int64, err error) {
	var v int64

	if v, err = types.ToGoInt64((*indexList)[0], "index expression"); err == nil {
		if v < 0 && v >= -maxValue {
			v = maxValue + v
		}
		if v >= 0 && v < maxValue {
			index = v
		} else {
			err = indexTerm.Errorf("index %d out of bounds", v)
		}
	}
	return
}

// func verifyInterval(indexTerm kern.Term, indexList *array.ArrayType, maxValue int64) (startIndex, endIndex int64, err error) {
// 	v, _ := ((*indexList)[0]).(*interval.IntervalType)
// 	startIndex = v.Begin()
// 	endIndex = v.End()
// 	if endIndex == kern.ConstLastIndex {
// 		endIndex = int64(maxValue)
// 	}
// 	if startIndex < 0 && startIndex >= -maxValue {
// 		startIndex = int64(maxValue) + startIndex
// 	}
// 	if endIndex < 0 && endIndex >= -maxValue {
// 		endIndex = int64(maxValue) + endIndex
// 	}
// 	if startIndex < 0 || startIndex > maxValue {
// 		err = indexTerm.Errorf("%s start-index %d is out of bounds", interval.TypeName, startIndex)
// 	} else if endIndex < 0 || endIndex > maxValue {
// 		err = indexTerm.Errorf("%s end-index %d is out of bounds", interval.TypeName, endIndex)
// 	} else if startIndex > endIndex {
// 		err = indexTerm.Errorf("%s start-index %d must not be greater than end-index %d", interval.TypeName, startIndex, endIndex)
// 	}
// 	return
// }

func evalIndex(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
	var leftValue, rightValue any
	var indexList *array.ArrayType
	var ok bool

	if leftValue, rightValue, err = opTerm.EvalInfix(ctx); err != nil {
		return
	}

	indexTerm := opTerm.Children[1]
	if indexList, ok = rightValue.(*array.ArrayType); !ok {
		err = opTerm.Errorf("invalid index expression")
		return
	} else if len(*indexList) != 1 {
		err = indexTerm.Errorf("one index only is allowed")
		return
	}

	if types.IsInteger((*indexList)[0]) {
		v, err = collectionItemByIndex(opTerm, leftValue, rightValue, indexList)
	} else if interval.IsInterval((*indexList)[0]) {
		v, err = subCollectionByIndex(opTerm, leftValue, rightValue, indexList)
	} else if dict.IsDict(leftValue) {
		d := leftValue.(*dict.DictType)
		v, err = getDictItem(d, indexTerm, indexList, rightValue)
	} else {
		rightChild := opTerm.Children[1]
		err = rightChild.Errorf("invalid index type: %v", (*indexList)[0])
	}
	return
}

func collectionItemByIndex(opTerm kern.Term, leftValue, rightValue any, indexList *array.ArrayType) (v any, err error) {
	indexTerm := opTerm.GetChild(1)
	switch unboxedValue := leftValue.(type) {
	case *array.ArrayType:
		var index int64
		if index, err = verifyIndex(indexTerm, indexList, int64(len(*unboxedValue))); err == nil {
			v = (*unboxedValue)[index]
		}
	case *list.LinkedList:
		var index int64
		if index, err = verifyIndex(indexTerm, indexList, int64(unboxedValue.Len())); err == nil {
			v, err = unboxedValue.At(index)
		}
	case string:
		var index int64
		if index, err = verifyIndex(indexTerm, indexList, int64(len(unboxedValue))); err == nil {
			v = string(unboxedValue[index])
		}
	case *dict.DictType:
		v, err = getDictItem(unboxedValue, indexTerm, indexList, rightValue)
	default:
		err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
	}
	return
}

func subCollectionByIndex(opTerm kern.Term, leftValue, rightValue any, indexList *array.ArrayType) (v any, err error) {
	triple := (*indexList)[0].(*interval.IntervalType)
	switch unboxedValue := leftValue.(type) {
	case *array.ArrayType:
		v = intervalSubArray(unboxedValue, triple)
	case *list.LinkedList:
		v = intervalSubLinkedList(unboxedValue, triple)
	case string:
		v = intervalSubString(unboxedValue, triple)
	default:
		err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
	}
	return
}

func fixInterval(p *interval.IntervalType, maxLength int) (b, e, s int) {
	b, e, s = p.ToIntTriple()
	if b < 0 {
		b = maxLength + b
	}
	if e < 0 {
		e = maxLength + e //+ 1
	}
	return
}

func intervalSubString(source string, p *interval.IntervalType) (result string) {
	b, e, s := fixInterval(p, len(source))
	if b <= e {
		e = min(e, len(source))
		if s == 1 {
			result = source[b:e]
		} else {
			var sb strings.Builder
			for i := b; i < e; i += s {
				sb.WriteByte(source[i])
			}
			result = sb.String()
		}
	} else {
		var sb strings.Builder
		for i := b; i >= e; i -= s {
			sb.WriteByte(source[i])
		}
		result = sb.String()
	}
	return
}

func intervalSubArray(source *array.ArrayType, p *interval.IntervalType) (result *array.ArrayType) {
	var a array.ArrayType
	b, e, s := fixInterval(p, len(*source))
	if b <= e {
		e = min(e, len(*source))
		if s == 1 {
			a = (*source)[b:e]
		} else {
			size := (e-b)/s + 1
			a = make(array.ArrayType, 0, size)
			for i := b; i < e; i += s {
				a = append(a, (*source)[i])
			}
		}
	} else {
		size := (b-e)/s + 1
		a = make(array.ArrayType, 0, size)
		for i := b; i > e; i -= s {
			a = append(a, (*source)[i])
		}
	}
	result = &a
	return
}

func intervalSubLinkedList(source *list.LinkedList, p *interval.IntervalType) (result *list.LinkedList) {
	b, e, s := fixInterval(p, source.Len())
	revert := b > e
	if revert {
		b, e = e, b
		source = source.Revert()
	}
	e = min(e, source.Len())
	if s == 1 {
		result = source.Sub(int64(b), int64(e))
	} else {
		result = source.SubStep(int64(b), int64(e), int64(s))
	}
	if revert {
		source = source.Revert()
	}
	return
}

func getDictItem(d *dict.DictType, indexTerm kern.Term, indexList *array.ArrayType, rightValue any) (v any, err error) {
	var ok bool
	var indexValue any

	if indexValue, err = verifyKey(indexList); err == nil {
		if v, ok = (*d)[indexValue]; !ok {
			err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
		}
	}
	return
}

// init
func init() {
	scan.RegisterTermConstructor(sym.SymIndex, newIndexTerm)
}
