// Package npcoccurrences owns canonical ordering and exact-identity rules for
// D&D NPC occurrence artifacts.
package npcoccurrences

import (
	"strconv"
	"strings"

	"gitea.maximumdirect.net/eric/notarius/internal/core/source"
	"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
	"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
)

// SourceRefsEqual reports whether two source-reference lists have identical
// representations and values.
func SourceRefsEqual(left, right []source.SourceRef) bool {
	if (left == nil) != (right == nil) || len(left) != len(right) {
		return false
	}
	for index := range left {
		if left[index] != right[index] {
			return false
		}
	}
	return true
}

// Less defines the canonical order for NPC occurrences.
func Less(order shared.SourceRefOrder, left, right dnd.NPCOccurrence) bool {
	leftPosition, leftHasEvidence := order.EarliestValid(left.SourceRefs)
	rightPosition, rightHasEvidence := order.EarliestValid(right.SourceRefs)
	if leftHasEvidence != rightHasEvidence {
		return leftHasEvidence
	}
	if leftHasEvidence && leftPosition != rightPosition {
		return leftPosition < rightPosition
	}
	leftKey := left.NPCID
	rightKey := right.NPCID
	if leftKey != rightKey {
		return leftKey < rightKey
	}
	if left.Name != right.Name {
		return left.Name < right.Name
	}
	if left.Kind != right.Kind {
		return left.Kind < right.Kind
	}
	return sourceRefsLess(order, left.SourceRefs, right.SourceRefs)
}

// ValidSourceRefs reports whether an occurrence has non-empty, valid
// current-document evidence.
func ValidSourceRefs(index source.DocumentIndex, refs []source.SourceRef) bool {
	if len(refs) == 0 {
		return false
	}
	for _, ref := range refs {
		if index.ValidateRef(ref) != nil {
			return false
		}
	}
	return true
}

// ExactIdentity returns a collision-safe key over every durable occurrence
// field. Callers decide whether the record is eligible for duplicate handling.
func ExactIdentity(occurrence dnd.NPCOccurrence) string {
	var key strings.Builder
	writeKeyString(&key, occurrence.NPCID)
	writeKeyString(&key, occurrence.Name)
	writeKeyString(&key, string(occurrence.Kind))
	for _, ref := range occurrence.SourceRefs {
		writeKeyString(&key, ref.SourceID)
		writeKeyInt(&key, ref.StartUnitID)
		writeKeyInt(&key, ref.EndUnitID)
	}
	return key.String()
}

func sourceRefsLess(order shared.SourceRefOrder, left, right []source.SourceRef) bool {
	for index := 0; index < len(left) && index < len(right); index++ {
		if left[index] == right[index] {
			continue
		}
		return order.Less(left[index], right[index])
	}
	return len(left) < len(right)
}

func writeKeyString(builder *strings.Builder, value string) {
	builder.WriteString(strconv.Itoa(len(value)))
	builder.WriteByte(':')
	builder.WriteString(value)
}

func writeKeyInt(builder *strings.Builder, value int) {
	builder.WriteString(strconv.Itoa(value))
	builder.WriteByte(';')
}
