package evidencecontext

import (
	"fmt"
	"sort"

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

type expandedRange struct {
	startPos int
	endPos   int
}

// Build validates projected source references, expands them by source-document
// position, and returns their ordered union as an owned source-unit excerpt.
func Build(request BuildRequest) (Document, error) {
	if request.WindowUnits < 0 {
		return nil, fmt.Errorf("window_units must not be negative")
	}
	if err := source.ValidateDocument(request.Source); err != nil {
		return nil, fmt.Errorf("validate source document: %w", err)
	}
	digest, err := source.DigestDocument(request.Source)
	if err != nil {
		return nil, fmt.Errorf("digest source document: %w", err)
	}
	if digest != request.Source.Digest {
		return nil, fmt.Errorf("source digest does not match source document digest")
	}

	index := source.NewDocumentIndex(request.Source)
	ranges := make([]expandedRange, 0, len(request.SourceRefs))
	for refIndex, ref := range request.SourceRefs {
		if err := index.ValidateRef(ref); err != nil {
			return nil, fmt.Errorf("source reference[%d]: %w", refIndex, err)
		}
		startPos, _ := index.Position(ref.StartUnitID)
		endPos, _ := index.Position(ref.EndUnitID)
		ranges = append(ranges, expandedRange{
			startPos: expandStart(startPos, request.WindowUnits),
			endPos:   expandEnd(endPos, len(request.Source.Units), request.WindowUnits),
		})
	}

	merged := mergeRanges(ranges)
	unitCount := 0
	for _, value := range merged {
		unitCount += value.endPos - value.startPos + 1
	}
	document := make(Document, 0, unitCount)
	for _, value := range merged {
		for position := value.startPos; position <= value.endPos; position++ {
			unit, err := cloneSourceUnit(request.Source.Units[position])
			if err != nil {
				return nil, fmt.Errorf("clone source unit at position %d: %w", position, err)
			}
			document = append(document, unit)
		}
	}
	return document, nil
}

func expandStart(position, window int) int {
	if window > position {
		return 0
	}
	return position - window
}

func expandEnd(position, length, window int) int {
	last := length - 1
	if window > last-position {
		return last
	}
	return position + window
}

func mergeRanges(values []expandedRange) []expandedRange {
	if len(values) == 0 {
		return nil
	}
	sort.Slice(values, func(i, j int) bool {
		if values[i].startPos != values[j].startPos {
			return values[i].startPos < values[j].startPos
		}
		return values[i].endPos < values[j].endPos
	})
	merged := make([]expandedRange, 0, len(values))
	for _, value := range values {
		if len(merged) == 0 || value.startPos > merged[len(merged)-1].endPos+1 {
			merged = append(merged, value)
			continue
		}
		if value.endPos > merged[len(merged)-1].endPos {
			merged[len(merged)-1].endPos = value.endPos
		}
	}
	return merged
}
