package npcregistry

import (
	"fmt"

	"gitea.maximumdirect.net/eric/notarius/internal/framework/semanticreconcile"
	"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
	"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/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.NPC], error) {
	candidates := make([]semanticreconcile.Candidate, len(records))
	envelopes := make([]semanticreconcile.Record[dnd.NPC], len(records))
	for index, record := range records {
		candidates[index] = semanticreconcile.Candidate{
			Label:      record.npc.Name,
			SourceRefs: cloneSourceRefs(record.npc.SourceRefs),
		}
		envelope, err := semanticreconcile.NewRecord(record.npc, record.inputIndexes, record.earliest, cloneNPC)
		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.NPC], order shared.SourceRefOrder) ([]normalizedRecord, []diagnostics.Finding, error) {
	application, err := semanticreconcile.ApplyPlan(plan, envelopes, semanticreconcile.ApplicationPolicy[dnd.NPC]{
		CloneValue: cloneNPC,
		ConsolidateGroup: func(members []dnd.NPC, canonical dnd.NPC) (dnd.NPC, error) {
			output := cloneNPC(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)
			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{
			npc:          record.Value(),
			inputIndexes: record.OriginalInputIndexes(),
			earliest:     record.EarliestInputPosition(),
		}
	}
	findings := make([]diagnostics.Finding, 0, len(application.AppliedGroups()))
	for _, event := range application.AppliedGroups() {
		provenance := event.Provenance()
		findings = append(findings, semanticDuplicateFinding(provenance, records[provenance.CanonicalPosition()]))
	}
	return output, findings, nil
}

func semanticDuplicateFinding(provenance semanticreconcile.GroupProvenance, canonical normalizedRecord) diagnostics.Finding {
	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 diagnostics.Finding{
		Scope:      npcScope(provenance.EarliestInputPosition()),
		ReasonCode: ReasonCodeDuplicateNPCCollapsed,
		Message:    diagnostics.Aggregate("semantic duplicate consolidation", details),
	}
}
