package openapi

// Schema represents a JSON Schema for OpenAPI.
type Schema map[string]any

// String creates a string schema.
func String() Schema {
	return Schema{"type": "string"}
}

// StringWithFormat creates a string schema with a format.
// Common formats: email, uri, uuid, date, date-time, password
func StringWithFormat(format string) Schema {
	return Schema{"type": "string", "format": format}
}

// StringEnum creates a string schema restricted to specific values.
func StringEnum(values ...string) Schema {
	return Schema{"type": "string", "enum": values}
}

// StringWithMinMax creates a string schema with length constraints.
func StringWithMinMax(min, max int) Schema {
	s := Schema{"type": "string"}
	if min > 0 {
		s["minLength"] = min
	}
	if max > 0 {
		s["maxLength"] = max
	}
	return s
}

// Int creates an integer schema.
func Int() Schema {
	return Schema{"type": "integer"}
}

// IntWithMinMax creates an integer schema with constraints.
func IntWithMinMax(min, max int) Schema {
	s := Schema{"type": "integer"}
	if min != 0 {
		s["minimum"] = min
	}
	if max != 0 {
		s["maximum"] = max
	}
	return s
}

// Int64 creates a 64-bit integer schema.
func Int64() Schema {
	return Schema{"type": "integer", "format": "int64"}
}

// Number creates a number (float) schema.
func Number() Schema {
	return Schema{"type": "number"}
}

// Bool creates a boolean schema.
func Bool() Schema {
	return Schema{"type": "boolean"}
}

// Array creates an array schema with the given item type.
func Array(items Schema) Schema {
	return Schema{
		"type":  "array",
		"items": items,
	}
}

// Object creates an object schema with the given properties.
// Required fields can be specified separately.
func Object(props map[string]Schema, required ...string) Schema {
	properties := make(map[string]any, len(props))
	for k, v := range props {
		properties[k] = v
	}

	s := Schema{
		"type":       "object",
		"properties": properties,
	}

	if len(required) > 0 {
		s["required"] = required
	}

	return s
}

// Ref creates a $ref to a schema in components/schemas.
func Ref(name string) Schema {
	return Schema{"$ref": "#/components/schemas/" + name}
}

// RefArray creates an array of $ref items.
func RefArray(name string) Schema {
	return Array(Ref(name))
}

// Nullable makes a schema nullable using oneOf pattern.
func Nullable(s Schema) Schema {
	return Schema{
		"oneOf": []Schema{
			s,
			{"type": "null"},
		},
	}
}

// WithDescription adds a description to a schema.
func (s Schema) WithDescription(desc string) Schema {
	s["description"] = desc
	return s
}

// WithExample adds an example to a schema.
func (s Schema) WithExample(example any) Schema {
	s["example"] = example
	return s
}

// WithDefault adds a default value to a schema.
func (s Schema) WithDefault(value any) Schema {
	s["default"] = value
	return s
}

// WithPattern adds a regex pattern to a string schema.
func (s Schema) WithPattern(pattern string) Schema {
	s["pattern"] = pattern
	return s
}

// Format sets the format for a schema.
func (s Schema) Format(format string) Schema {
	s["format"] = format
	return s
}

// Description is an alias for WithDescription for cleaner chaining.
func (s Schema) Description(desc string) Schema {
	return s.WithDescription(desc)
}

// Example is an alias for WithExample for cleaner chaining.
func (s Schema) Example(example any) Schema {
	return s.WithExample(example)
}

// Default is an alias for WithDefault for cleaner chaining.
func (s Schema) Default(value any) Schema {
	return s.WithDefault(value)
}

// Pattern is an alias for WithPattern for cleaner chaining.
func (s Schema) Pattern(pattern string) Schema {
	return s.WithPattern(pattern)
}

// UUID creates a UUID string schema.
func UUID() Schema {
	return StringWithFormat("uuid").WithExample("550e8400-e29b-41d4-a716-446655440000")
}

// Email creates an email string schema.
func Email() Schema {
	return StringWithFormat("email").WithExample("user@example.com")
}

// URL creates a URL string schema.
func URL() Schema {
	return StringWithFormat("uri").WithExample("https://example.com")
}

// DateTime creates a date-time string schema.
func DateTime() Schema {
	return StringWithFormat("date-time").WithExample("2024-01-15T10:30:00Z")
}

// Password creates a password string schema (hidden in docs).
func Password() Schema {
	return StringWithFormat("password")
}

// Pagination creates a common pagination object schema.
func Pagination() Schema {
	return Object(map[string]Schema{
		"page":        Int().WithDescription("Current page number").WithExample(1),
		"per_page":    Int().WithDescription("Items per page").WithExample(20),
		"total":       Int().WithDescription("Total number of items").WithExample(100),
		"total_pages": Int().WithDescription("Total number of pages").WithExample(5),
	})
}

// ResponseSchema creates the standard response envelope schema.
func ResponseSchema(dataSchema Schema) Schema {
	return Object(map[string]Schema{
		"data": dataSchema,
		"meta": Object(map[string]Schema{
			"request_id": String().WithDescription("Request correlation ID"),
			"timestamp":  DateTime().WithDescription("Response timestamp"),
		}),
	})
}

// ErrorResponseSchema creates the standard error response schema.
func ErrorResponseSchema() Schema {
	return Object(map[string]Schema{
		"error": Object(map[string]Schema{
			"code":    String().WithDescription("Machine-readable error code").WithExample("BAD_REQUEST"),
			"message": String().WithDescription("Human-readable error message").WithExample("Invalid request"),
			"details": Schema{"type": "object"}.WithDescription("Additional error details"),
		}, "code", "message"),
		"meta": Object(map[string]Schema{
			"request_id": String().WithDescription("Request correlation ID"),
			"timestamp":  DateTime().WithDescription("Response timestamp"),
		}),
	})
}

// ValidationErrorSchema creates a validation error details schema.
func ValidationErrorSchema() Schema {
	return Array(Object(map[string]Schema{
		"field":   String().WithDescription("Field that failed validation").WithExample("email"),
		"message": String().WithDescription("Validation error message").WithExample("is required"),
	}, "field", "message"))
}
