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

// operator-prod.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/float"
	"git.portale-stac.it/go-pkg/expr/types/fract"
	"git.portale-stac.it/go-pkg/expr/types/str"
)

//-------- multiply term

func newMultiplyTerm(tk *scan.Token) (inst *scan.Term) {
	return &scan.Term{
		Tk:       *tk,
		Children: make([]*scan.Term, 0, 2),
		Position: scan.PosInfix,
		Priority: scan.PriProduct,
		EvalFunc: evalMultiply,
	}
}

func mulValues(opTerm *scan.Term, leftValue, rightValue any) (v any, err error) {
	if str.IsString(leftValue) && types.IsInteger(rightValue) {
		s, _ := leftValue.(string)
		n, _ := rightValue.(int64)
		v = strings.Repeat(s, int(n))
	} else if types.IsNumOrFract(leftValue) && types.IsNumOrFract(rightValue) {
		if float.IsFloat(leftValue) || float.IsFloat(rightValue) {
			v = types.NumAsFloat(leftValue) * types.NumAsFloat(rightValue)
		} else if fract.IsFraction(leftValue) || fract.IsFraction(rightValue) {
			v, err = fract.MulAnyFract(leftValue, rightValue)
		} else {
			leftInt, _ := leftValue.(int64)
			rightInt, _ := rightValue.(int64)
			v = leftInt * rightInt
		}
	} else {
		err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
	}
	return
}

func evalMultiply(ctx kern.ExprContext, prodTerm *scan.Term) (v any, err error) {
	var leftValue, rightValue any

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

	return mulValues(prodTerm, leftValue, rightValue)
}

//-------- divide term

func newDivideTerm(tk *scan.Token) (inst *scan.Term) {
	return &scan.Term{
		Tk:       *tk,
		Children: make([]*scan.Term, 0, 2),
		Position: scan.PosInfix,
		Priority: scan.PriProduct,
		EvalFunc: evalDivide,
	}
}

func divValues(opTerm *scan.Term, leftValue, rightValue any) (v any, err error) {
	if types.IsNumOrFract(leftValue) && types.IsNumOrFract(rightValue) {
		if float.IsFloat(leftValue) || float.IsFloat(rightValue) {
			d := types.NumAsFloat(rightValue)
			if d == 0.0 {
				err = opTerm.ErrDivisionByZero()
			} else {
				v = types.NumAsFloat(leftValue) / d
			}
		} else if fract.IsFraction(leftValue) || fract.IsFraction(rightValue) {
			v, err = fract.DivAnyFract(leftValue, rightValue)
		} else {
			leftInt, _ := leftValue.(int64)
			if rightInt, _ := rightValue.(int64); rightInt == 0 {
				err = opTerm.ErrDivisionByZero()
			} else {
				v = leftInt / rightInt
			}
		}
	} else if str.IsString(leftValue) && str.IsString(rightValue) {
		source := leftValue.(string)
		sep := rightValue.(string)
		v = array.ArrayFromStrings(strings.Split(source, sep))
	} else if str.IsString(leftValue) && types.IsInteger(rightValue) {
		source := leftValue.(string)
		partSize := int(rightValue.(int64))
		if partSize == 0 {
			err = opTerm.ErrDivisionByZero()
		} else {
			partCount := len(source) / partSize
			remainder := len(source) % partSize
			listSize := partCount
			if remainder > 0 {
				listSize++
			}
			parts := make([]any, 0, listSize)
			for i := 0; i < partCount; i++ {
				parts = append(parts, source[i*partSize:(i+1)*partSize])
			}
			if remainder > 0 {
				parts = append(parts, source[len(source)-remainder:])
			}
			v = array.NewArray(parts)
		}
	} else {
		err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
	}
	return
}

func evalDivide(ctx kern.ExprContext, opTerm *scan.Term) (v any, err error) {
	var leftValue, rightValue any

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

	return divValues(opTerm, leftValue, rightValue)
}

//-------- divide as float term

func newDivideAsFloatTerm(tk *scan.Token) (inst *scan.Term) {
	return &scan.Term{
		Tk:       *tk,
		Children: make([]*scan.Term, 0, 2),
		Position: scan.PosInfix,
		Priority: scan.PriProduct,
		EvalFunc: evalDivideAsFloat,
	}
}

func evalDivideAsFloat(ctx kern.ExprContext, floatDivTerm *scan.Term) (v any, err error) {
	var leftValue, rightValue any

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

	if types.IsNumOrFract(leftValue) && types.IsNumOrFract(rightValue) {
		d := types.NumAsFloat(rightValue)
		if d == 0.0 {
			err = floatDivTerm.ErrDivisionByZero()
		} else {
			v = types.NumAsFloat(leftValue) / d
		}
	} else {
		err = floatDivTerm.ErrIncompatibleTypes(leftValue, rightValue)
	}
	return
}

//-------- reminder term

func newRemainderTerm(tk *scan.Token) (inst *scan.Term) {
	return &scan.Term{
		Tk:       *tk,
		Children: make([]*scan.Term, 0, 2),
		Position: scan.PosInfix,
		Priority: scan.PriProduct,
		EvalFunc: evalRemainder,
	}
}
func remainderValues(opTerm *scan.Term, leftValue, rightValue any) (v any, err error) {
	if types.IsInteger(leftValue) && types.IsInteger(rightValue) {
		rightInt, _ := rightValue.(int64)
		if rightInt == 0 {
			err = opTerm.ErrDivisionByZero()
		} else {
			leftInt, _ := leftValue.(int64)
			v = leftInt % rightInt
		}
	} else {
		err = opTerm.ErrIncompatibleTypes(leftValue, rightValue)
	}
	return
}

func evalRemainder(ctx kern.ExprContext, remainderTerm *scan.Term) (v any, err error) {
	var leftValue, rightValue any

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

	return remainderValues(remainderTerm, leftValue, rightValue)
}

// init
func init() {
	scan.RegisterTermConstructor(sym.SymStar, newMultiplyTerm)
	scan.RegisterTermConstructor(sym.SymSlash, newDivideTerm)
	scan.RegisterTermConstructor(sym.SymDotSlash, newDivideAsFloatTerm)
	scan.RegisterTermConstructor(sym.SymPercent, newRemainderTerm)
}
