// Package diagnostics provides bounded local grouping for producer and
// validator diagnostic results.
package diagnostics

import (
	"errors"
	"fmt"

	"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
)

// Collector merges local producer diagnostics by their semantic identity. Its
// zero value is ready for use.
type Collector struct {
	diagnostics []contracts.ProducerDiagnostic
	indices     map[key]int
}

// NewCollector returns an empty local diagnostic collector.
func NewCollector() *Collector {
	return &Collector{}
}

// Add validates and merges one producer diagnostic. Every occurrence remains
// counted, while the first three distinct samples in input order are retained.
func (collector *Collector) Add(diagnostic contracts.ProducerDiagnostic) error {
	if err := diagnostic.Validate(); err != nil {
		return fmt.Errorf("producer diagnostic: %w", err)
	}
	if collector.indices == nil {
		collector.indices = make(map[key]int)
	}
	diagnosticKey := key{disposition: diagnostic.Disposition, category: diagnostic.Category, reasonCode: diagnostic.ReasonCode}
	index, exists := collector.indices[diagnosticKey]
	if !exists {
		if len(collector.diagnostics) >= contracts.MaxProducerDiagnosticGroups {
			return errors.New("producer diagnostics exceed maximum group count")
		}
		collector.indices[diagnosticKey] = len(collector.diagnostics)
		collector.diagnostics = append(collector.diagnostics, contracts.CloneProducerDiagnostics([]contracts.ProducerDiagnostic{diagnostic})[0])
		return nil
	}

	current := &collector.diagnostics[index]
	if diagnostic.OccurrenceCount > maximumInt()-current.OccurrenceCount {
		return errors.New("producer diagnostic occurrence count overflow")
	}
	current.OccurrenceCount += diagnostic.OccurrenceCount
	for _, sample := range diagnostic.Samples {
		if len(current.Samples) == contracts.MaxDiagnosticSamples || containsSample(current.Samples, sample) {
			continue
		}
		current.Samples = append(current.Samples, cloneSample(sample))
	}
	current.OmittedSampleCount = current.OccurrenceCount - len(current.Samples)
	return nil
}

// Diagnostics returns an independently owned snapshot in first-occurrence
// order.
func (collector *Collector) Diagnostics() []contracts.ProducerDiagnostic {
	if collector == nil {
		return nil
	}
	return contracts.CloneProducerDiagnostics(collector.diagnostics)
}

func containsSample(samples []contracts.DiagnosticSample, candidate contracts.DiagnosticSample) bool {
	for _, sample := range samples {
		if sample.Scope == candidate.Scope && sample.Message == candidate.Message {
			return true
		}
	}
	return false
}

func cloneSample(sample contracts.DiagnosticSample) contracts.DiagnosticSample {
	if sample.ChunkIndex != nil {
		chunkIndex := *sample.ChunkIndex
		sample.ChunkIndex = &chunkIndex
	}
	return sample
}

func maximumInt() int {
	return int(^uint(0) >> 1)
}

type key struct {
	disposition contracts.DiagnosticDisposition
	category    contracts.DiagnosticCategory
	reasonCode  string
}
