package query

import (
	"errors"
	"fmt"
)

// CalculateOffsetAndLength calculates the offset and length of a given path within the SSZ object.
// By walking the given path, it accumulates the offsets based on SszInfo.
func CalculateOffsetAndLength(sszInfo *SszInfo, path Path) (*SszInfo, uint64, uint64, error) {
	if sszInfo == nil {
		return nil, 0, 0, errors.New("sszInfo is nil")
	}

	if len(path.Elements) == 0 {
		return nil, 0, 0, errors.New("path is empty")
	}

	walk := sszInfo
	offset := uint64(0)

	for pathIndex, elem := range path.Elements {
		containerInfo, err := walk.ContainerInfo()
		if err != nil {
			return nil, 0, 0, fmt.Errorf("could not get field infos: %w", err)
		}

		fieldInfo, exists := containerInfo.fields[elem.Name]
		if !exists {
			return nil, 0, 0, fmt.Errorf("field %s not found in containerInfo", elem.Name)
		}

		offset += fieldInfo.offset
		walk = fieldInfo.sszInfo

		// Check for accessing List/Vector elements by index
		if elem.Index != nil {
			switch walk.sszType {
			case List:
				index := *elem.Index
				listInfo := walk.listInfo
				if index >= listInfo.length {
					return nil, 0, 0, fmt.Errorf("index %d out of bounds for field %s with size %d", index, elem.Name, listInfo.length)
				}

				walk = listInfo.element
				if walk.isVariable {
					// Cumulative sum of sizes of previous elements to get the offset.
					for i := range index {
						offset += listInfo.elementSizes[i]
					}

					// NOTE: When populating recursively, the shared element template is updated for each
					// list item, causing it to retain the size information of the last processed element.
					// This wouldn't be an issue if this is in the middle of the path, as the walk would be updated
					// to the next field's sszInfo, which would have the correct size information.
					// However, if this is the last element in the path, we need to ensure we return the correct size
					// for the indexed element. Hence, we return the size from elementSizes.
					if pathIndex == len(path.Elements)-1 {
						return walk, offset, listInfo.elementSizes[index], nil
					}
				} else {
					offset += index * listInfo.element.Size()
				}

			case Vector:
				index := *elem.Index
				vectorInfo := walk.vectorInfo
				if index >= vectorInfo.length {
					return nil, 0, 0, fmt.Errorf("index %d out of bounds for field %s with size %d", index, elem.Name, vectorInfo.length)
				}

				offset += index * vectorInfo.element.Size()
				walk = vectorInfo.element

			default:
				return nil, 0, 0, fmt.Errorf("field %s of type %s does not support index access", elem.Name, walk.sszType)
			}
		}
	}

	return walk, offset, walk.Size(), nil
}
