package programchecker

import (
	"fmt"
	"strings"
)

// Severity classifies checker diagnostics for fail-closed pack verification.
type Severity string

const (
	SeverityError   Severity = "error"
	SeverityWarning Severity = "warning"

	// DiagnosticCodeReaderError identifies an operational reader failure, not
	// authored metadata drift. Consumers must keep these failures fail-closed.
	DiagnosticCodeReaderError = "metadata_program.reader_error"
)

// Diagnostic is the stable, structured error shape emitted by the metadata
// program checker. The checker owns diagnostics only; referenced metadata facts
// remain owned by the reader interfaces in this package.
type Diagnostic struct {
	Code          string
	Severity      Severity
	ComponentPath string
	JSONPointer   string
	ElementName   string
	OwnerPack     string
	Reference     string
	ExpectedType  string
	ActualType    string
	Message       string
}

func (d Diagnostic) IsError() bool {
	return d.Severity == SeverityError
}

func (d Diagnostic) summary() string {
	if msg := strings.TrimSpace(d.Message); msg != "" {
		return msg
	}
	code := strings.TrimSpace(d.Code)
	if code == "" {
		code = "metadata_program.diagnostic"
	}
	ref := strings.TrimSpace(d.Reference)
	if ref == "" {
		return code
	}
	return fmt.Sprintf("%s: %s", code, ref)
}

// Diagnostics is a batch of independent checker diagnostics.
type Diagnostics []Diagnostic

func (ds Diagnostics) HasErrors() bool {
	for _, d := range ds {
		if d.IsError() {
			return true
		}
	}
	return false
}

func (ds Diagnostics) ErrorDiagnostics() Diagnostics {
	out := make(Diagnostics, 0, len(ds))
	for _, d := range ds {
		if d.IsError() {
			out = append(out, d)
		}
	}
	return out
}

// HasReaderErrors reports whether validation was incomplete because a source
// reader failed. Such a batch cannot safely prove metadata drift even when it
// also contains authored-content diagnostics.
func (ds Diagnostics) HasReaderErrors() bool {
	for _, d := range ds {
		if d.Code == DiagnosticCodeReaderError {
			return true
		}
	}
	return false
}

func (ds Diagnostics) Error() string {
	errors := ds.ErrorDiagnostics()
	if len(errors) == 0 {
		return ""
	}
	first := errors[0].summary()
	if len(errors) == 1 {
		return first
	}
	return fmt.Sprintf("%s; %d more error(s)", first, len(errors)-1)
}
