// Package httpcontext provides type-safe context keys and helpers for HTTP request contexts.
//
// This package standardizes how context values are stored and retrieved across all services.
// Using unexported types for context keys prevents collisions with other packages.
//
// Usage in middleware:
//
//	func AuthMiddleware() func(http.Handler) http.Handler {
//	    return func(next http.Handler) http.Handler {
//	        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//	            user := extractUserFromAuth(r)
//	            ctx := httpcontext.SetUser(r.Context(), user)
//	            ctx = httpcontext.SetOrgID(ctx, user.OrganizationID)
//	            next.ServeHTTP(w, r.WithContext(ctx))
//	        })
//	    }
//	}
//
// Usage in handlers:
//
//	func (h *Handler) GetProfile(w http.ResponseWriter, r *http.Request) {
//	    user, ok := httpcontext.GetUser(r.Context())
//	    if !ok {
//	        http.Error(w, "unauthorized", http.StatusUnauthorized)
//	        return
//	    }
//	    // ... use user
//	}
package httpcontext

import "context"

// contextKey is an unexported type used for context keys to prevent collisions.
// Other packages cannot create values of this type, ensuring our keys are unique.
type contextKey string

// Standard context keys used across services.
const (
	keyUser      contextKey = "user"
	keyOrgID     contextKey = "organization_id"
	keyRequestID contextKey = "request_id"
	keyTraceID   contextKey = "trace_id"
	keyJWTClaims contextKey = "jwt_claims"
)

// SetUser adds a user to the context.
// The user can be any type - typically a domain user struct.
// Returns a new context with the user attached.
func SetUser(ctx context.Context, user any) context.Context {
	if user == nil {
		return ctx
	}
	return context.WithValue(ctx, keyUser, user)
}

// GetUser retrieves the user from context.
// Returns (user, true) if present, (nil, false) if not found.
// Caller should type-assert the returned value to their user type.
//
// Example:
//
//	if val, ok := httpcontext.GetUser(ctx); ok {
//	    user := val.(*domain.User)
//	    // ... use user
//	}
func GetUser(ctx context.Context) (any, bool) {
	val := ctx.Value(keyUser)
	if val == nil {
		return nil, false
	}
	return val, true
}

// SetOrgID adds an organization ID to the context.
// Returns a new context with the organization ID attached.
func SetOrgID(ctx context.Context, orgID string) context.Context {
	if orgID == "" {
		return ctx
	}
	return context.WithValue(ctx, keyOrgID, orgID)
}

// GetOrgID retrieves the organization ID from context.
// Returns (orgID, true) if present, ("", false) if not found.
func GetOrgID(ctx context.Context) (string, bool) {
	val := ctx.Value(keyOrgID)
	if val == nil {
		return "", false
	}
	if orgID, ok := val.(string); ok {
		return orgID, true
	}
	return "", false
}

// SetRequestID adds a request ID to the context.
// Returns a new context with the request ID attached.
func SetRequestID(ctx context.Context, requestID string) context.Context {
	if requestID == "" {
		return ctx
	}
	return context.WithValue(ctx, keyRequestID, requestID)
}

// GetRequestID retrieves the request ID from context.
// Returns (requestID, true) if present, ("", false) if not found.
func GetRequestID(ctx context.Context) (string, bool) {
	val := ctx.Value(keyRequestID)
	if val == nil {
		return "", false
	}
	if requestID, ok := val.(string); ok {
		return requestID, true
	}
	return "", false
}

// SetTraceID adds a trace ID to the context.
// Returns a new context with the trace ID attached.
func SetTraceID(ctx context.Context, traceID string) context.Context {
	if traceID == "" {
		return ctx
	}
	return context.WithValue(ctx, keyTraceID, traceID)
}

// GetTraceID retrieves the trace ID from context.
// Returns (traceID, true) if present, ("", false) if not found.
func GetTraceID(ctx context.Context) (string, bool) {
	val := ctx.Value(keyTraceID)
	if val == nil {
		return "", false
	}
	if traceID, ok := val.(string); ok {
		return traceID, true
	}
	return "", false
}

// SetJWTClaims adds JWT claims to the context.
// The claims can be any type - typically a custom claims struct.
// Returns a new context with the claims attached.
func SetJWTClaims(ctx context.Context, claims any) context.Context {
	if claims == nil {
		return ctx
	}
	return context.WithValue(ctx, keyJWTClaims, claims)
}

// GetJWTClaims retrieves JWT claims from context.
// Returns (claims, true) if present, (nil, false) if not found.
// Caller should type-assert the returned value to their claims type.
//
// Example:
//
//	if val, ok := httpcontext.GetJWTClaims(ctx); ok {
//	    claims := val.(*auth.CustomClaims)
//	    // ... use claims
//	}
func GetJWTClaims(ctx context.Context) (any, bool) {
	val := ctx.Value(keyJWTClaims)
	if val == nil {
		return nil, false
	}
	return val, true
}

// MustGetUser retrieves the user from context and panics if not found.
// Use only when authentication middleware guarantees user presence.
func MustGetUser(ctx context.Context) any {
	user, ok := GetUser(ctx)
	if !ok {
		panic("httpcontext: user not found in context")
	}
	return user
}

// MustGetRequestID retrieves the request ID from context and panics if not found.
// Use only when middleware guarantees request ID presence.
func MustGetRequestID(ctx context.Context) string {
	requestID, ok := GetRequestID(ctx)
	if !ok {
		panic("httpcontext: request_id not found in context")
	}
	return requestID
}
