package publicerr

import (
	"encoding/json"
	"reflect"
	"strings"
)

func diagnosticsFromReflect(raw any) []Diagnostic {
	value := reflect.ValueOf(raw)
	if value.Kind() != reflect.Slice && value.Kind() != reflect.Array {
		return nil
	}
	diagnostics := make([]Diagnostic, 0, value.Len())
	for i := 0; i < value.Len(); i++ {
		if diagnostic, ok := diagnosticFromReflect(value.Index(i)); ok {
			diagnostics = append(diagnostics, diagnostic)
		}
	}
	return diagnostics
}

func diagnosticFromReflect(value reflect.Value) (Diagnostic, bool) {
	if value.Kind() == reflect.Interface {
		value = value.Elem()
	}
	if value.Kind() == reflect.Pointer {
		if value.IsNil() {
			return Diagnostic{}, false
		}
		value = value.Elem()
	}
	if value.Kind() != reflect.Struct {
		return Diagnostic{}, false
	}
	field := func(name string) string {
		value := value.FieldByName(name)
		if !value.IsValid() || value.Kind() != reflect.String {
			return ""
		}
		return strings.TrimSpace(value.String())
	}
	code := field("Code")
	if code == "" {
		return Diagnostic{}, false
	}
	diagnostic := Diagnostic{
		Severity: Severity(field("Severity")), Code: code,
		Path: field("Path"), Field: field("Field"), Message: field("Message"),
		Line: intField(value, "Line"), Column: intField(value, "Column"),
		NodeID: field("NodeID"), EdgeID: field("EdgeID"),
	}
	if indexValue := value.FieldByName("Index"); indexValue.IsValid() &&
		indexValue.Kind() == reflect.Pointer && !indexValue.IsNil() &&
		indexValue.Elem().Kind() == reflect.Int {
		index := int(indexValue.Elem().Int())
		diagnostic.Index = &index
	}
	return diagnostic, true
}

// intField reads one exported integer-valued field of a typed diagnostic
// struct. It is used to preserve deterministic compiler/validator source
// locations (Line/Column) and graph identities are kept as strings.
func intField(value reflect.Value, name string) int {
	field := value.FieldByName(name)
	if !field.IsValid() || field.Kind() != reflect.Int {
		return 0
	}
	return int(field.Int())
}

func diagnosticFromAny(value any) (Diagnostic, bool) {
	switch typed := value.(type) {
	case Diagnostic:
		return typed, true
	case map[string]any:
		severity, _ := typed["severity"].(string)
		code, _ := typed["code"].(string)
		path, _ := typed["path"].(string)
		field, _ := typed["field"].(string)
		message, _ := typed["message"].(string)
		nodeID, _ := typed["nodeId"].(string)
		edgeID, _ := typed["edgeId"].(string)
		var index *int
		if rawIndex, ok := typed["index"].(float64); ok {
			value := int(rawIndex)
			index = &value
		}
		if code == "" {
			return Diagnostic{}, false
		}
		return Diagnostic{
			Severity: Severity(severity), Code: code, Path: path,
			Field: field, Index: index, Message: message,
			Line: intFromMap(typed, "line"), Column: intFromMap(typed, "column"),
			NodeID: nodeID, EdgeID: edgeID,
		}, true
	default:
		return Diagnostic{}, false
	}
}

func intFromMap(values map[string]any, key string) int {
	switch raw := values[key].(type) {
	case float64:
		return int(raw)
	case int:
		return raw
	case int64:
		return int(raw)
	case json.Number:
		value, err := raw.Int64()
		if err != nil {
			return 0
		}
		return int(value)
	default:
		return 0
	}
}
