// Package openapi provides an OpenAPI 3.0 specification builder and documentation endpoints.
//
// It includes:
//   - OpenAPISpec: A builder for constructing OpenAPI 3.0 specifications
//   - Schema helpers: Typed schema constructors (String, Int, Object, Array, etc.)
//   - Parameter helpers: Path, query, header parameter builders
//   - Documentation: Scalar UI and JSON spec serving via EnableDocs
//
// Example:
//
//	spec := openapi.NewOpenAPISpec("My Service", "1.0.0").
//	    WithDescription("Service API documentation").
//	    WithBearerSecurity("bearer", "JWT authentication")
//
//	spec.AddPath("/api/v1/items", "get", map[string]any{
//	    "summary": "List items",
//	    "tags":    []string{"Items"},
//	    "responses": map[string]any{
//	        "200": openapi.OpResponse("Success", openapi.RefArray("Item")),
//	    },
//	})
//
//	application.EnableDocs(spec)
package openapi

import (
	"encoding/json"
	"sync"
)

// OpenAPIInfo contains metadata about the API.
type OpenAPIInfo struct {
	Title       string `json:"title"`
	Description string `json:"description,omitempty"`
	Version     string `json:"version"`
}

// OpenAPIServer describes a server endpoint.
type OpenAPIServer struct {
	URL         string `json:"url"`
	Description string `json:"description,omitempty"`
}

// OpenAPIComponents contains reusable schema definitions.
type OpenAPIComponents struct {
	Schemas         map[string]any `json:"schemas,omitempty"`
	SecuritySchemes map[string]any `json:"securitySchemes,omitempty"`
}

// OpenAPISpec represents a minimal OpenAPI 3.0 specification.
type OpenAPISpec struct {
	OpenAPI    string                    `json:"openapi"`
	Info       OpenAPIInfo               `json:"info"`
	Servers    []OpenAPIServer           `json:"servers,omitempty"`
	Paths      map[string]map[string]any `json:"paths"`
	Tags       []OpenAPITag              `json:"tags,omitempty"`
	Components *OpenAPIComponents        `json:"components,omitempty"`
	Security   []map[string][]string     `json:"security,omitempty"`

	mu sync.RWMutex
}

// OpenAPITag groups operations together.
type OpenAPITag struct {
	Name        string `json:"name"`
	Description string `json:"description,omitempty"`
}

// NewOpenAPISpec creates a new OpenAPI specification builder.
func NewOpenAPISpec(title, version string) *OpenAPISpec {
	return &OpenAPISpec{
		OpenAPI: "3.0.3",
		Info: OpenAPIInfo{
			Title:   title,
			Version: version,
		},
		Paths: make(map[string]map[string]any),
	}
}

// WithDescription sets the API description.
func (s *OpenAPISpec) WithDescription(desc string) *OpenAPISpec {
	s.Info.Description = desc
	return s
}

// WithServer adds a server to the spec.
func (s *OpenAPISpec) WithServer(url, description string) *OpenAPISpec {
	s.Servers = append(s.Servers, OpenAPIServer{
		URL:         url,
		Description: description,
	})
	return s
}

// WithTag adds a tag for grouping operations.
func (s *OpenAPISpec) WithTag(name, description string) *OpenAPISpec {
	s.Tags = append(s.Tags, OpenAPITag{
		Name:        name,
		Description: description,
	})
	return s
}

// AddPath adds an operation to the spec.
// method should be lowercase (get, post, put, patch, delete).
func (s *OpenAPISpec) AddPath(path, method string, operation map[string]any) *OpenAPISpec {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.Paths[path] == nil {
		s.Paths[path] = make(map[string]any)
	}
	s.Paths[path][method] = operation
	return s
}

// ensureComponents initializes the Components field if nil.
// Must be called while holding s.mu.
func (s *OpenAPISpec) ensureComponents() {
	if s.Components == nil {
		s.Components = &OpenAPIComponents{
			Schemas:         make(map[string]any),
			SecuritySchemes: make(map[string]any),
		}
	}
}

// WithSchema adds a reusable schema to components/schemas.
func (s *OpenAPISpec) WithSchema(name string, schema Schema) *OpenAPISpec {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.ensureComponents()
	if s.Components.Schemas == nil {
		s.Components.Schemas = make(map[string]any)
	}
	s.Components.Schemas[name] = schema
	return s
}

// WithAPIKeySecurity adds API key security scheme.
func (s *OpenAPISpec) WithAPIKeySecurity(name, headerName, description string) *OpenAPISpec {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.ensureComponents()
	if s.Components.SecuritySchemes == nil {
		s.Components.SecuritySchemes = make(map[string]any)
	}
	s.Components.SecuritySchemes[name] = map[string]any{
		"type":        "apiKey",
		"in":          "header",
		"name":        headerName,
		"description": description,
	}
	return s
}

// WithBearerSecurity adds Bearer token security scheme.
func (s *OpenAPISpec) WithBearerSecurity(name, description string) *OpenAPISpec {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.ensureComponents()
	if s.Components.SecuritySchemes == nil {
		s.Components.SecuritySchemes = make(map[string]any)
	}
	s.Components.SecuritySchemes[name] = map[string]any{
		"type":         "http",
		"scheme":       "bearer",
		"bearerFormat": "JWT",
		"description":  description,
	}
	return s
}

// WithGlobalSecurity sets global security requirements.
func (s *OpenAPISpec) WithGlobalSecurity(schemeName string) *OpenAPISpec {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.Security = append(s.Security, map[string][]string{
		schemeName: {},
	})
	return s
}

// JSON returns the spec as JSON bytes.
func (s *OpenAPISpec) JSON() ([]byte, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return json.MarshalIndent(s, "", "  ")
}

// Op creates an OpenAPI operation helper.
func Op(summary, description string, tags ...string) map[string]any {
	return map[string]any{
		"summary":     summary,
		"description": description,
		"tags":        tags,
		"responses": map[string]any{
			"200": map[string]any{"description": "Success"},
		},
	}
}

// OpWithBody creates an OpenAPI operation with a request body.
func OpWithBody(summary, description string, tags ...string) map[string]any {
	return map[string]any{
		"summary":     summary,
		"description": description,
		"tags":        tags,
		"requestBody": map[string]any{
			"required": true,
			"content": map[string]any{
				"application/json": map[string]any{
					"schema": map[string]any{
						"type": "object",
					},
				},
			},
		},
		"responses": map[string]any{
			"200": map[string]any{"description": "Success"},
			"201": map[string]any{"description": "Created"},
		},
	}
}
