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

// dict-iterator.go
package expr

import (
	"fmt"
	"io"
	"slices"
	"strings"

	"git.portale-stac.it/go-pkg/expr/kern"
	"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/str"
)

type dictIterMode int

const (
	dictIterModeKeys dictIterMode = iota
	dictIterModeValues
	dictIterModeItems
)

type DictIterator struct {
	a        *dict.DictType
	count    int64
	index    int64
	keys     []any
	iterMode dictIterMode
}

type sortType int

const (
	sortTypeNone sortType = iota
	sortTypeAsc
	sortTypeDesc
	sortTypeDefault = sortTypeAsc
)

func (it *DictIterator) makeKeys(m map[any]any, sort sortType) {
	it.keys = make([]any, 0, len(m))

	if sort == sortTypeNone {
		for keyAny := range m {
			it.keys = append(it.keys, keyAny)
		}
	} else {
		scalarMap := make(map[string]any, len(m))
		scalerKeys := make([]string, 0, len(m))
		for keyAny := range m {
			keyStr := fmt.Sprint(keyAny)
			scalarMap[keyStr] = keyAny
			scalerKeys = append(scalerKeys, keyStr)
		}

		switch sort {
		case sortTypeAsc:
			slices.Sort(scalerKeys)
		case sortTypeDesc:
			slices.Sort(scalerKeys)
			slices.Reverse(scalerKeys)
		}
		for _, keyStr := range scalerKeys {
			it.keys = append(it.keys, scalarMap[keyStr])
		}
	}
}

func NewDictIterator(dict *dict.DictType, args []any) (it *DictIterator, err error) {
	var sortType = sortTypeNone
	var s string
	var argAny any

	dictIt := &DictIterator{a: dict, count: 0, index: -1, keys: nil, iterMode: dictIterModeKeys}
	if len(args) > 0 {
		argAny = args[0]
	} else {
		argAny = "default"
	}
	if s, err = str.ToGoString(argAny, "sort type"); err == nil {
		switch strings.ToLower(s) {
		case "a", "asc":
			sortType = sortTypeAsc
		case "d", "desc":
			sortType = sortTypeDesc
		case "n", "none", "nosort", "no-sort":
			sortType = sortTypeNone
		case "", "default":
			sortType = sortTypeDefault
		default:
			err = fmt.Errorf("invalid sort type %q", s)
		}

		if err == nil {
			if len(args) > 1 {
				argAny = args[1]
			} else {
				argAny = "default"
			}

			if s, err = str.ToGoString(argAny, "iteration mode"); err == nil {
				switch strings.ToLower(s) {
				case "k", "key", "keys":
					dictIt.iterMode = dictIterModeKeys
				case "v", "value", "values":
					dictIt.iterMode = dictIterModeValues
				case "i", "item", "items":
					dictIt.iterMode = dictIterModeItems
				case "", "default":
					dictIt.iterMode = dictIterModeKeys
				default:
					err = fmt.Errorf("invalid iteration mode %q", s)
				}
			}
		}
	}

	if err == nil {
		dictIt.makeKeys(*dict, sortType)
		it = dictIt
	}
	return
}

func NewMapIterator(m map[any]any) (it *DictIterator) {
	it = &DictIterator{a: (*dict.DictType)(&m), count: 0, index: -1, keys: nil}
	it.makeKeys(m, sortTypeNone)
	return
}

func (it *DictIterator) String() string {
	var l = int64(0)
	if it.a != nil {
		l = int64(len(*it.a))
	}
	return fmt.Sprintf("$({#%d})", l)
}

func (it *DictIterator) TypeName() string {
	return "DictIterator"
}

func (it *DictIterator) HasOperation(name string) bool {
	yes := slices.Contains([]string{kern.NextName, kern.ResetName, kern.IndexName, kern.CountName, kern.CurrentName, kern.CleanName, kern.KeyName, kern.ValueName}, name)
	return yes
}

func (it *DictIterator) CallOperation(name string, args map[string]any) (v any, err error) {
	switch name {
	case kern.NextName:
		v, err = it.Next()
	case kern.ResetName:
		err = it.Reset()
	case kern.CleanName:
		err = it.Clean()
	case kern.IndexName:
		v = int64(it.Index())
	case kern.CurrentName:
		v, err = it.Current()
	case kern.CountName:
		v = it.count
	case kern.KeyName:
		if it.index >= 0 && it.index < int64(len(it.keys)) {
			v = it.keys[it.index]
		} else {
			err = io.EOF
		}
	case kern.ValueName:
		if it.index >= 0 && it.index < int64(len(it.keys)) {
			a := *(it.a)
			v = a[it.keys[it.index]]
		} else {
			err = io.EOF
		}
	default:
		err = kern.ErrNoOperation(name)
	}
	return
}

func (it *DictIterator) Current() (item any, err error) {
	if it.index >= 0 && it.index < int64(len(it.keys)) {
		switch it.iterMode {
		case dictIterModeKeys:
			item = it.keys[it.index]
		case dictIterModeValues:
			a := *(it.a)
			item = a[it.keys[it.index]]
		case dictIterModeItems:
			a := *(it.a)
			pair := []any{it.keys[it.index], a[it.keys[it.index]]}
			item = array.NewArray(pair)
		}
	} else {
		err = io.EOF
	}
	return
}

func (it *DictIterator) Next() (item any, err error) {
	it.index++
	if item, err = it.Current(); err != io.EOF {
		it.count++
	}
	return
}

func (it *DictIterator) Index() int64 {
	return it.index
}

func (it *DictIterator) Count() int64 {
	return it.count
}

func (it *DictIterator) Reset() error {
	it.index = -1
	it.count = 0
	return nil
}

func (it *DictIterator) Clean() error {
	return nil
}
