package exec

import "encoding/json"

// compactModelSchema removes only annotations and implicit defaults from the
// model-facing projection. The fully expanded descriptor remains the source
// used for validation and OpenAPI generation.
func compactModelSchema(raw json.RawMessage, output bool) json.RawMessage {
	document, err := decodeStrict(raw)
	if err != nil {
		return raw
	}
	compactSchemaNode(document)
	encoded, err := json.Marshal(document)
	if err != nil {
		return raw
	}
	return encoded
}

// compactSchemaNode strips JSON Schema annotations and implicit defaults from a
// schema subtree for the model-facing projection. It reuses the Draft 2020-12
// keyword groups (namedSchemaMapKeywords / singleSchemaKeywords /
// schemaArrayKeywords, the same positions walkDraft2020 descends) so it only
// ever enters schema-valued positions. Data-valued keywords (const/enum/
// default/examples, and the property names inside named maps) are never
// descended into: doing so would both delete business properties named after
// annotations (e.g. description) and corrupt literal const/default data.
// Annotations are stripped only at a schema node, where these keys are reserved
// and never collide with property names.
func compactSchemaNode(node any) {
	value, ok := node.(map[string]any)
	if !ok {
		return
	}
	for _, keyword := range namedSchemaMapKeywords {
		if subschemas, ok := value[keyword].(map[string]any); ok {
			for _, subschema := range subschemas {
				compactSchemaNode(subschema) // name -> subschema: compact values, keep keys
			}
		}
	}
	for _, keyword := range singleSchemaKeywords {
		if subschema, ok := value[keyword].(map[string]any); ok {
			compactSchemaNode(subschema) // bool schemas (additionalProperties: true) have nothing inside
		}
	}
	for _, keyword := range schemaArrayKeywords {
		if subschemas, ok := value[keyword].([]any); ok {
			for _, subschema := range subschemas {
				compactSchemaNode(subschema)
			}
		}
	}
	for _, annotation := range []string{"$schema", "title", "description", "examples", "default", "readOnly", "writeOnly"} {
		delete(value, annotation)
	}
	if additional, ok := value["additionalProperties"].(bool); ok && additional {
		delete(value, "additionalProperties")
	}
	if _, exists := value["const"]; exists {
		delete(value, "type")
	} else if _, exists := value["enum"]; exists {
		delete(value, "type")
	}
	if value["type"] == "object" && unionGuaranteesType(value["oneOf"], "object") {
		delete(value, "type")
	}
}

func unionGuaranteesType(raw any, want string) bool {
	branches, ok := raw.([]any)
	if !ok || len(branches) == 0 {
		return false
	}
	for _, rawBranch := range branches {
		branch, ok := rawBranch.(map[string]any)
		if !ok || branch["type"] != want {
			return false
		}
	}
	return true
}
