package locationregistry

import (
	"fmt"

	"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
	"gitea.maximumdirect.net/eric/notarius/internal/framework/semanticreconcile"
	"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
	"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/locations/identity"
	"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
	"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared/diagnostics"
)

func reconciliationInputs(records []normalizedRecord) ([]semanticreconcile.Candidate, []semanticreconcile.Record[dnd.Location], error) {
	candidates := make([]semanticreconcile.Candidate, len(records))
	envelopes := make([]semanticreconcile.Record[dnd.Location], len(records))
	for index, record := range records {
		candidates[index] = semanticreconcile.Candidate{
			Label:      record.location.Name,
			SourceRefs: cloneSourceRefs(record.location.SourceRefs),
		}
		envelope, err := semanticreconcile.NewRecord(record.location, record.inputIndexes, record.earliest, cloneLocation)
		if err != nil {
			return nil, nil, fmt.Errorf("record %d: %w", index, err)
		}
		envelopes[index] = envelope
	}
	return candidates, envelopes, nil
}

func applyReconciliationPlan(plan semanticreconcile.Plan, records []normalizedRecord, envelopes []semanticreconcile.Record[dnd.Location], order shared.SourceRefOrder) ([]normalizedRecord, []contracts.Warning, error) {
	application, err := semanticreconcile.ApplyPlan(plan, envelopes, semanticreconcile.ApplicationPolicy[dnd.Location]{
		CloneValue: cloneLocation,
		ConsolidateGroup: func(members []dnd.Location, canonical dnd.Location) (dnd.Location, error) {
			output := cloneLocation(canonical)
			output.SourceRefs = nil
			for _, member := range members {
				output.SourceRefs = append(output.SourceRefs, member.SourceRefs...)
			}
			output.SourceRefs = order.Canonicalize(output.SourceRefs)
			output.ID = identity.DeriveID(output.Name, output.SourceRefs)
			return output, nil
		},
	})
	if err != nil {
		return nil, nil, err
	}

	applied := application.Records()
	output := make([]normalizedRecord, len(applied))
	for index, record := range applied {
		output[index] = normalizedRecord{
			location:     record.Value(),
			inputIndexes: record.OriginalInputIndexes(),
			earliest:     record.EarliestInputPosition(),
		}
	}
	warnings := make([]contracts.Warning, 0, len(application.AppliedGroups()))
	for _, event := range application.AppliedGroups() {
		provenance := event.Provenance()
		warnings = append(warnings, semanticDuplicateWarning(provenance, records[provenance.CanonicalPosition()]))
	}
	return output, warnings, nil
}

func semanticDuplicateWarning(provenance semanticreconcile.GroupProvenance, canonical normalizedRecord) contracts.Warning {
	inputIndexes := provenance.OriginalInputIndexes()
	details := make([]string, 0, len(inputIndexes)+1)
	for _, inputIndex := range inputIndexes {
		details = append(details, fmt.Sprintf("input index %d", inputIndex))
	}
	if canonical.earliest != provenance.EarliestInputPosition() {
		details = append(details, fmt.Sprintf("canonical display name from input index %d", canonical.earliest))
	}
	return contracts.Warning{
		Scope:      locationScope(provenance.EarliestInputPosition()),
		ReasonCode: ReasonCodeDuplicateLocationCollapsed,
		Message:    diagnostics.Aggregate("semantic duplicate consolidation", details),
	}
}
