package config

import (
	"context"
	"encoding/json"
	"regexp"
	"time"

	"github.com/mudler/LocalAI/pkg/system"
	"github.com/mudler/LocalAI/pkg/xsysinfo"
	"github.com/mudler/xlog"
)

type ApplicationConfig struct {
	Context                             context.Context
	ConfigFile                          string
	SystemState                         *system.SystemState
	ExternalBackends                    []string
	UploadLimitMB, Threads, ContextSize int
	F16                                 bool
	Debug                               bool
	EnableTracing                       bool
	TracingMaxItems                     int
	GeneratedContentDir                 string

	UploadDir string
	DataPath  string // Persistent data directory for collectiondb, agents, etc.

	DynamicConfigsDir             string
	DynamicConfigsDirPollInterval time.Duration
	CORS                          bool
	CSRF                          bool
	PreloadJSONModels             string
	PreloadModelsFromPath         string
	CORSAllowOrigins              string
	ApiKeys                       []string
	P2PToken                      string
	P2PNetworkID                  string
	Federated                     bool

	DisableWebUI                       bool
	EnforcePredownloadScans            bool
	OpaqueErrors                       bool
	UseSubtleKeyComparison             bool
	DisableApiKeyRequirementForHttpGet bool
	DisableMetrics                     bool
	HttpGetExemptedEndpoints           []*regexp.Regexp
	DisableGalleryEndpoint             bool
	DisableMCP                         bool
	LoadToMemory                       []string

	Galleries        []Gallery
	BackendGalleries []Gallery

	ExternalGRPCBackends map[string]string

	AutoloadGalleries, AutoloadBackendGalleries bool

	SingleBackend           bool // Deprecated: use MaxActiveBackends = 1 instead
	MaxActiveBackends       int  // Maximum number of active backends (0 = unlimited, 1 = single backend mode)
	ParallelBackendRequests bool

	WatchDogIdle bool
	WatchDogBusy bool
	WatchDog     bool

	// Memory Reclaimer settings (works with GPU if available, otherwise RAM)
	MemoryReclaimerEnabled   bool    // Enable memory threshold monitoring
	MemoryReclaimerThreshold float64 // Threshold 0.0-1.0 (e.g., 0.95 = 95%)

	// Eviction settings
	ForceEvictionWhenBusy    bool          // Force eviction even when models have active API calls (default: false for safety)
	LRUEvictionMaxRetries    int           // Maximum number of retries when waiting for busy models to become idle (default: 30)
	LRUEvictionRetryInterval time.Duration // Interval between retries when waiting for busy models (default: 1s)

	ModelsURL []string

	WatchDogBusyTimeout, WatchDogIdleTimeout time.Duration
	WatchDogInterval                         time.Duration // Interval between watchdog checks

	MachineTag string

	APIAddress string

	LlamaCPPTunnelCallback    func(tunnels []string)
	MLXTunnelCallback func(tunnels []string)

	DisableRuntimeSettings bool

	AgentJobRetentionDays int // Default: 30 days

	OpenResponsesStoreTTL time.Duration // TTL for Open Responses store (0 = no expiration)

	PathWithoutAuth []string

	// Agent Pool (LocalAGI integration)
	AgentPool AgentPoolConfig
}

// AgentPoolConfig holds configuration for the LocalAGI agent pool integration.
type AgentPoolConfig struct {
	Enabled                bool   // default: true (disabled by LOCALAI_DISABLE_AGENTS=true)
	StateDir               string // default: DynamicConfigsDir (LocalAI configuration folder)
	APIURL                 string // default: self-referencing LocalAI (http://127.0.0.1:<port>)
	APIKey                 string // default: first API key from LocalAI config
	DefaultModel           string
	MultimodalModel        string
	TranscriptionModel     string
	TranscriptionLanguage  string
	TTSModel               string
	Timeout                string // default: "5m"
	EnableSkills     bool
	EnableLogs       bool
	CustomActionsDir string
	CollectionDBPath string
	VectorEngine     string // default: "chromem"
	EmbeddingModel   string // default: "granite-embedding-107m-multilingual"
	MaxChunkingSize  int    // default: 400
	ChunkOverlap     int    // default: 0
	DatabaseURL      string
	AgentHubURL      string // default: "https://agenthub.localai.io"
}

type AppOption func(*ApplicationConfig)

func NewApplicationConfig(o ...AppOption) *ApplicationConfig {
	opt := &ApplicationConfig{
		Context:                  context.Background(),
		UploadLimitMB:            15,
		Debug:                    true,
		AgentJobRetentionDays:    30,                     // Default: 30 days
		LRUEvictionMaxRetries:    30,                     // Default: 30 retries
		LRUEvictionRetryInterval: 1 * time.Second,        // Default: 1 second
		WatchDogInterval:         500 * time.Millisecond, // Default: 500ms
		TracingMaxItems:          1024,
		AgentPool: AgentPoolConfig{
			Enabled:        true,
			Timeout:        "5m",
			VectorEngine:   "chromem",
			EmbeddingModel: "granite-embedding-107m-multilingual",
			MaxChunkingSize: 400,
			AgentHubURL:    "https://agenthub.localai.io",
		},
		PathWithoutAuth: []string{
			"/static/",
			"/generated-audio/",
			"/generated-images/",
			"/generated-videos/",
			"/favicon.svg",
			"/readyz",
			"/healthz",
		},
	}
	for _, oo := range o {
		oo(opt)
	}
	return opt
}

func WithModelsURL(urls ...string) AppOption {
	return func(o *ApplicationConfig) {
		o.ModelsURL = urls
	}
}

func WithSystemState(state *system.SystemState) AppOption {
	return func(o *ApplicationConfig) {
		o.SystemState = state
	}
}

func WithExternalBackends(backends ...string) AppOption {
	return func(o *ApplicationConfig) {
		o.ExternalBackends = backends
	}
}

func WithMachineTag(tag string) AppOption {
	return func(o *ApplicationConfig) {
		o.MachineTag = tag
	}
}

func WithCors(b bool) AppOption {
	return func(o *ApplicationConfig) {
		o.CORS = b
	}
}

func WithP2PNetworkID(s string) AppOption {
	return func(o *ApplicationConfig) {
		o.P2PNetworkID = s
	}
}

func WithCsrf(b bool) AppOption {
	return func(o *ApplicationConfig) {
		o.CSRF = b
	}
}

func WithP2PToken(s string) AppOption {
	return func(o *ApplicationConfig) {
		o.P2PToken = s
	}
}

var EnableWatchDog = func(o *ApplicationConfig) {
	o.WatchDog = true
}

var EnableTracing = func(o *ApplicationConfig) {
	o.EnableTracing = true
}

var EnableWatchDogIdleCheck = func(o *ApplicationConfig) {
	o.WatchDog = true
	o.WatchDogIdle = true
}

var DisableGalleryEndpoint = func(o *ApplicationConfig) {
	o.DisableGalleryEndpoint = true
}

var DisableMCP = func(o *ApplicationConfig) {
	o.DisableMCP = true
}

var EnableWatchDogBusyCheck = func(o *ApplicationConfig) {
	o.WatchDog = true
	o.WatchDogBusy = true
}

var DisableWebUI = func(o *ApplicationConfig) {
	o.DisableWebUI = true
}

var DisableRuntimeSettings = func(o *ApplicationConfig) {
	o.DisableRuntimeSettings = true
}

func SetWatchDogBusyTimeout(t time.Duration) AppOption {
	return func(o *ApplicationConfig) {
		o.WatchDogBusyTimeout = t
	}
}

func SetWatchDogIdleTimeout(t time.Duration) AppOption {
	return func(o *ApplicationConfig) {
		o.WatchDogIdleTimeout = t
	}
}

func SetWatchDogInterval(t time.Duration) AppOption {
	return func(o *ApplicationConfig) {
		o.WatchDogInterval = t
	}
}

// EnableMemoryReclaimer enables memory threshold monitoring.
// When enabled, the watchdog will evict backends if memory usage exceeds the threshold.
// Works with GPU VRAM if available, otherwise uses system RAM.
var EnableMemoryReclaimer = func(o *ApplicationConfig) {
	o.MemoryReclaimerEnabled = true
	o.WatchDog = true // Memory reclaimer requires watchdog infrastructure
}

// SetMemoryReclaimerThreshold sets the memory usage threshold (0.0-1.0).
// When memory usage exceeds this threshold, backends will be evicted using LRU strategy.
func SetMemoryReclaimerThreshold(threshold float64) AppOption {
	return func(o *ApplicationConfig) {
		if threshold > 0 && threshold <= 1.0 {
			o.MemoryReclaimerThreshold = threshold
			o.MemoryReclaimerEnabled = true
			o.WatchDog = true // Memory reclaimer requires watchdog infrastructure
		}
	}
}

// WithMemoryReclaimer configures the memory reclaimer with the given settings
func WithMemoryReclaimer(enabled bool, threshold float64) AppOption {
	return func(o *ApplicationConfig) {
		o.MemoryReclaimerEnabled = enabled
		if threshold > 0 && threshold <= 1.0 {
			o.MemoryReclaimerThreshold = threshold
		}
		if enabled {
			o.WatchDog = true // Memory reclaimer requires watchdog infrastructure
		}
	}
}

// EnableSingleBackend is deprecated: use SetMaxActiveBackends(1) instead.
// This is kept for backward compatibility.
var EnableSingleBackend = func(o *ApplicationConfig) {
	o.SingleBackend = true
	o.MaxActiveBackends = 1
}

// SetMaxActiveBackends sets the maximum number of active backends.
// 0 = unlimited, 1 = single backend mode (replaces EnableSingleBackend)
func SetMaxActiveBackends(n int) AppOption {
	return func(o *ApplicationConfig) {
		o.MaxActiveBackends = n
		// For backward compatibility, also set SingleBackend if n == 1
		if n == 1 {
			o.SingleBackend = true
		}
	}
}

// GetEffectiveMaxActiveBackends returns the effective max active backends limit.
// It considers both MaxActiveBackends and the deprecated SingleBackend setting.
// If MaxActiveBackends is set (> 0), it takes precedence.
// If SingleBackend is true and MaxActiveBackends is 0, returns 1.
// Otherwise returns 0 (unlimited).
func (o *ApplicationConfig) GetEffectiveMaxActiveBackends() int {
	if o.MaxActiveBackends > 0 {
		return o.MaxActiveBackends
	}
	if o.SingleBackend {
		return 1
	}
	return 0
}

// WithForceEvictionWhenBusy sets whether to force eviction even when models have active API calls
func WithForceEvictionWhenBusy(enabled bool) AppOption {
	return func(o *ApplicationConfig) {
		o.ForceEvictionWhenBusy = enabled
	}
}

// WithLRUEvictionMaxRetries sets the maximum number of retries when waiting for busy models to become idle
func WithLRUEvictionMaxRetries(maxRetries int) AppOption {
	return func(o *ApplicationConfig) {
		if maxRetries > 0 {
			o.LRUEvictionMaxRetries = maxRetries
		}
	}
}

// WithLRUEvictionRetryInterval sets the interval between retries when waiting for busy models
func WithLRUEvictionRetryInterval(interval time.Duration) AppOption {
	return func(o *ApplicationConfig) {
		if interval > 0 {
			o.LRUEvictionRetryInterval = interval
		}
	}
}

var EnableParallelBackendRequests = func(o *ApplicationConfig) {
	o.ParallelBackendRequests = true
}

var EnableGalleriesAutoload = func(o *ApplicationConfig) {
	o.AutoloadGalleries = true
}

var EnableBackendGalleriesAutoload = func(o *ApplicationConfig) {
	o.AutoloadBackendGalleries = true
}

var EnableFederated = func(o *ApplicationConfig) {
	o.Federated = true
}

func WithExternalBackend(name string, uri string) AppOption {
	return func(o *ApplicationConfig) {
		if o.ExternalGRPCBackends == nil {
			o.ExternalGRPCBackends = make(map[string]string)
		}
		o.ExternalGRPCBackends[name] = uri
	}
}

func WithCorsAllowOrigins(b string) AppOption {
	return func(o *ApplicationConfig) {
		o.CORSAllowOrigins = b
	}
}

func WithStringGalleries(galls string) AppOption {
	return func(o *ApplicationConfig) {
		if galls == "" {
			o.Galleries = []Gallery{}
			return
		}
		var galleries []Gallery
		if err := json.Unmarshal([]byte(galls), &galleries); err != nil {
			xlog.Error("failed loading galleries", "error", err)
		}
		o.Galleries = append(o.Galleries, galleries...)
	}
}

func WithBackendGalleries(galls string) AppOption {
	return func(o *ApplicationConfig) {
		if galls == "" {
			o.BackendGalleries = []Gallery{}
			return
		}
		var galleries []Gallery
		if err := json.Unmarshal([]byte(galls), &galleries); err != nil {
			xlog.Error("failed loading galleries", "error", err)
		}
		o.BackendGalleries = append(o.BackendGalleries, galleries...)
	}
}

func WithGalleries(galleries []Gallery) AppOption {
	return func(o *ApplicationConfig) {
		o.Galleries = append(o.Galleries, galleries...)
	}
}

func WithContext(ctx context.Context) AppOption {
	return func(o *ApplicationConfig) {
		o.Context = ctx
	}
}

func WithYAMLConfigPreload(configFile string) AppOption {
	return func(o *ApplicationConfig) {
		o.PreloadModelsFromPath = configFile
	}
}

func WithJSONStringPreload(configFile string) AppOption {
	return func(o *ApplicationConfig) {
		o.PreloadJSONModels = configFile
	}
}
func WithConfigFile(configFile string) AppOption {
	return func(o *ApplicationConfig) {
		o.ConfigFile = configFile
	}
}

func WithUploadLimitMB(limit int) AppOption {
	return func(o *ApplicationConfig) {
		o.UploadLimitMB = limit
	}
}

func WithThreads(threads int) AppOption {
	return func(o *ApplicationConfig) {
		if threads == 0 { // 0 is not allowed
			threads = xsysinfo.CPUPhysicalCores()
		}
		o.Threads = threads
	}
}

func WithContextSize(ctxSize int) AppOption {
	return func(o *ApplicationConfig) {
		o.ContextSize = ctxSize
	}
}

func WithLlamaCPPTunnelCallback(callback func(tunnels []string)) AppOption {
	return func(o *ApplicationConfig) {
		o.LlamaCPPTunnelCallback = callback
	}
}

func WithMLXTunnelCallback(callback func(tunnels []string)) AppOption {
	return func(o *ApplicationConfig) {
		o.MLXTunnelCallback = callback
	}
}

func WithF16(f16 bool) AppOption {
	return func(o *ApplicationConfig) {
		o.F16 = f16
	}
}

func WithDebug(debug bool) AppOption {
	return func(o *ApplicationConfig) {
		o.Debug = debug
	}
}

func WithTracingMaxItems(items int) AppOption {
	return func(o *ApplicationConfig) {
		o.TracingMaxItems = items
	}
}

func WithGeneratedContentDir(generatedContentDir string) AppOption {
	return func(o *ApplicationConfig) {
		o.GeneratedContentDir = generatedContentDir
	}
}

func WithUploadDir(uploadDir string) AppOption {
	return func(o *ApplicationConfig) {
		o.UploadDir = uploadDir
	}
}

func WithDataPath(dataPath string) AppOption {
	return func(o *ApplicationConfig) {
		o.DataPath = dataPath
	}
}

func WithDynamicConfigDir(dynamicConfigsDir string) AppOption {
	return func(o *ApplicationConfig) {
		o.DynamicConfigsDir = dynamicConfigsDir
	}
}

func WithDynamicConfigDirPollInterval(interval time.Duration) AppOption {
	return func(o *ApplicationConfig) {
		o.DynamicConfigsDirPollInterval = interval
	}
}

func WithApiKeys(apiKeys []string) AppOption {
	return func(o *ApplicationConfig) {
		o.ApiKeys = apiKeys
	}
}

func WithAgentJobRetentionDays(days int) AppOption {
	return func(o *ApplicationConfig) {
		o.AgentJobRetentionDays = days
	}
}

func WithOpenResponsesStoreTTL(ttl time.Duration) AppOption {
	return func(o *ApplicationConfig) {
		o.OpenResponsesStoreTTL = ttl
	}
}

func WithEnforcedPredownloadScans(enforced bool) AppOption {
	return func(o *ApplicationConfig) {
		o.EnforcePredownloadScans = enforced
	}
}

func WithOpaqueErrors(opaque bool) AppOption {
	return func(o *ApplicationConfig) {
		o.OpaqueErrors = opaque
	}
}

func WithLoadToMemory(models []string) AppOption {
	return func(o *ApplicationConfig) {
		o.LoadToMemory = models
	}
}

func WithSubtleKeyComparison(subtle bool) AppOption {
	return func(o *ApplicationConfig) {
		o.UseSubtleKeyComparison = subtle
	}
}

func WithDisableApiKeyRequirementForHttpGet(required bool) AppOption {
	return func(o *ApplicationConfig) {
		o.DisableApiKeyRequirementForHttpGet = required
	}
}

func WithAPIAddress(address string) AppOption {
	return func(o *ApplicationConfig) {
		o.APIAddress = address
	}
}

var DisableMetricsEndpoint AppOption = func(o *ApplicationConfig) {
	o.DisableMetrics = true
}

func WithHttpGetExemptedEndpoints(endpoints []string) AppOption {
	return func(o *ApplicationConfig) {
		o.HttpGetExemptedEndpoints = []*regexp.Regexp{}
		for _, epr := range endpoints {
			r, err := regexp.Compile(epr)
			if err == nil && r != nil {
				o.HttpGetExemptedEndpoints = append(o.HttpGetExemptedEndpoints, r)
			} else {
				xlog.Warn("Error while compiling HTTP Get Exemption regex, skipping this entry.", "error", err, "regex", epr)
			}
		}
	}
}

// Agent Pool options

var DisableAgentPool = func(o *ApplicationConfig) {
	o.AgentPool.Enabled = false
}

func WithAgentPoolAPIURL(url string) AppOption {
	return func(o *ApplicationConfig) {
		o.AgentPool.APIURL = url
	}
}

func WithAgentPoolAPIKey(key string) AppOption {
	return func(o *ApplicationConfig) {
		o.AgentPool.APIKey = key
	}
}

func WithAgentPoolDefaultModel(model string) AppOption {
	return func(o *ApplicationConfig) {
		o.AgentPool.DefaultModel = model
	}
}

func WithAgentPoolMultimodalModel(model string) AppOption {
	return func(o *ApplicationConfig) {
		o.AgentPool.MultimodalModel = model
	}
}

func WithAgentPoolTranscriptionModel(model string) AppOption {
	return func(o *ApplicationConfig) {
		o.AgentPool.TranscriptionModel = model
	}
}

func WithAgentPoolTranscriptionLanguage(lang string) AppOption {
	return func(o *ApplicationConfig) {
		o.AgentPool.TranscriptionLanguage = lang
	}
}

func WithAgentPoolTTSModel(model string) AppOption {
	return func(o *ApplicationConfig) {
		o.AgentPool.TTSModel = model
	}
}

func WithAgentPoolStateDir(dir string) AppOption {
	return func(o *ApplicationConfig) {
		o.AgentPool.StateDir = dir
	}
}

func WithAgentPoolTimeout(timeout string) AppOption {
	return func(o *ApplicationConfig) {
		o.AgentPool.Timeout = timeout
	}
}

var EnableAgentPoolSkills = func(o *ApplicationConfig) {
	o.AgentPool.EnableSkills = true
}

func WithAgentPoolVectorEngine(engine string) AppOption {
	return func(o *ApplicationConfig) {
		o.AgentPool.VectorEngine = engine
	}
}

func WithAgentPoolEmbeddingModel(model string) AppOption {
	return func(o *ApplicationConfig) {
		o.AgentPool.EmbeddingModel = model
	}
}

func WithAgentPoolCustomActionsDir(dir string) AppOption {
	return func(o *ApplicationConfig) {
		o.AgentPool.CustomActionsDir = dir
	}
}

func WithAgentPoolDatabaseURL(url string) AppOption {
	return func(o *ApplicationConfig) {
		o.AgentPool.DatabaseURL = url
	}
}

func WithAgentPoolMaxChunkingSize(size int) AppOption {
	return func(o *ApplicationConfig) {
		o.AgentPool.MaxChunkingSize = size
	}
}

func WithAgentPoolChunkOverlap(overlap int) AppOption {
	return func(o *ApplicationConfig) {
		o.AgentPool.ChunkOverlap = overlap
	}
}

var EnableAgentPoolLogs = func(o *ApplicationConfig) {
	o.AgentPool.EnableLogs = true
}

func WithAgentPoolCollectionDBPath(path string) AppOption {
	return func(o *ApplicationConfig) {
		o.AgentPool.CollectionDBPath = path
	}
}

func WithAgentHubURL(url string) AppOption {
	return func(o *ApplicationConfig) {
		o.AgentPool.AgentHubURL = url
	}
}

// ToConfigLoaderOptions returns a slice of ConfigLoader Option.
// Some options defined at the application level are going to be passed as defaults for
// all the configuration for the models.
// This includes for instance the context size or the number of threads.
// If a model doesn't set configs directly to the config model file
// it will use the defaults defined here.
func (o *ApplicationConfig) ToConfigLoaderOptions() []ConfigLoaderOption {
	return []ConfigLoaderOption{
		LoadOptionContextSize(o.ContextSize),
		LoadOptionDebug(o.Debug),
		LoadOptionF16(o.F16),
		LoadOptionThreads(o.Threads),
		ModelPath(o.SystemState.Model.ModelsPath),
	}
}

// ToRuntimeSettings converts ApplicationConfig to RuntimeSettings for API responses and JSON serialization.
// This provides a single source of truth - ApplicationConfig holds the live values,
// and this method creates a RuntimeSettings snapshot for external consumption.
func (o *ApplicationConfig) ToRuntimeSettings() RuntimeSettings {
	// Create local copies for pointer fields
	watchdogEnabled := o.WatchDog
	watchdogIdle := o.WatchDogIdle
	watchdogBusy := o.WatchDogBusy
	singleBackend := o.SingleBackend
	maxActiveBackends := o.MaxActiveBackends
	parallelBackendRequests := o.ParallelBackendRequests
	memoryReclaimerEnabled := o.MemoryReclaimerEnabled
	memoryReclaimerThreshold := o.MemoryReclaimerThreshold
	forceEvictionWhenBusy := o.ForceEvictionWhenBusy
	lruEvictionMaxRetries := o.LRUEvictionMaxRetries
	threads := o.Threads
	contextSize := o.ContextSize
	f16 := o.F16
	debug := o.Debug
	tracingMaxItems := o.TracingMaxItems
	enableTracing := o.EnableTracing
	cors := o.CORS
	csrf := o.CSRF
	corsAllowOrigins := o.CORSAllowOrigins
	p2pToken := o.P2PToken
	p2pNetworkID := o.P2PNetworkID
	federated := o.Federated
	galleries := o.Galleries
	backendGalleries := o.BackendGalleries
	autoloadGalleries := o.AutoloadGalleries
	autoloadBackendGalleries := o.AutoloadBackendGalleries
	apiKeys := o.ApiKeys
	agentJobRetentionDays := o.AgentJobRetentionDays

	// Format timeouts as strings
	var idleTimeout, busyTimeout, watchdogInterval string
	if o.WatchDogIdleTimeout > 0 {
		idleTimeout = o.WatchDogIdleTimeout.String()
	} else {
		idleTimeout = "15m" // default
	}
	if o.WatchDogBusyTimeout > 0 {
		busyTimeout = o.WatchDogBusyTimeout.String()
	} else {
		busyTimeout = "5m" // default
	}
	if o.WatchDogInterval > 0 {
		watchdogInterval = o.WatchDogInterval.String()
	} else {
		watchdogInterval = "2s" // default
	}
	var lruEvictionRetryInterval string
	if o.LRUEvictionRetryInterval > 0 {
		lruEvictionRetryInterval = o.LRUEvictionRetryInterval.String()
	} else {
		lruEvictionRetryInterval = "1s" // default
	}
	var openResponsesStoreTTL string
	if o.OpenResponsesStoreTTL > 0 {
		openResponsesStoreTTL = o.OpenResponsesStoreTTL.String()
	} else {
		openResponsesStoreTTL = "0" // default: no expiration
	}

	// Agent Pool settings
	agentPoolEnabled := o.AgentPool.Enabled
	agentPoolDefaultModel := o.AgentPool.DefaultModel
	agentPoolEmbeddingModel := o.AgentPool.EmbeddingModel
	agentPoolMaxChunkingSize := o.AgentPool.MaxChunkingSize
	agentPoolChunkOverlap := o.AgentPool.ChunkOverlap
	agentPoolEnableLogs := o.AgentPool.EnableLogs
	agentPoolCollectionDBPath := o.AgentPool.CollectionDBPath

	return RuntimeSettings{
		WatchdogEnabled:          &watchdogEnabled,
		WatchdogIdleEnabled:      &watchdogIdle,
		WatchdogBusyEnabled:      &watchdogBusy,
		WatchdogIdleTimeout:      &idleTimeout,
		WatchdogBusyTimeout:      &busyTimeout,
		WatchdogInterval:         &watchdogInterval,
		SingleBackend:            &singleBackend,
		MaxActiveBackends:        &maxActiveBackends,
		ParallelBackendRequests:  &parallelBackendRequests,
		MemoryReclaimerEnabled:   &memoryReclaimerEnabled,
		MemoryReclaimerThreshold: &memoryReclaimerThreshold,
		ForceEvictionWhenBusy:    &forceEvictionWhenBusy,
		LRUEvictionMaxRetries:    &lruEvictionMaxRetries,
		LRUEvictionRetryInterval: &lruEvictionRetryInterval,
		Threads:                  &threads,
		ContextSize:              &contextSize,
		F16:                      &f16,
		Debug:                    &debug,
		TracingMaxItems:          &tracingMaxItems,
		EnableTracing:            &enableTracing,
		CORS:                     &cors,
		CSRF:                     &csrf,
		CORSAllowOrigins:         &corsAllowOrigins,
		P2PToken:                 &p2pToken,
		P2PNetworkID:             &p2pNetworkID,
		Federated:                &federated,
		Galleries:                &galleries,
		BackendGalleries:         &backendGalleries,
		AutoloadGalleries:        &autoloadGalleries,
		AutoloadBackendGalleries: &autoloadBackendGalleries,
		ApiKeys:                  &apiKeys,
		AgentJobRetentionDays:    &agentJobRetentionDays,
		OpenResponsesStoreTTL:    &openResponsesStoreTTL,
		AgentPoolEnabled:          &agentPoolEnabled,
		AgentPoolDefaultModel:     &agentPoolDefaultModel,
		AgentPoolEmbeddingModel:   &agentPoolEmbeddingModel,
		AgentPoolMaxChunkingSize:  &agentPoolMaxChunkingSize,
		AgentPoolChunkOverlap:     &agentPoolChunkOverlap,
		AgentPoolEnableLogs:       &agentPoolEnableLogs,
		AgentPoolCollectionDBPath: &agentPoolCollectionDBPath,
	}
}

// ApplyRuntimeSettings applies RuntimeSettings to ApplicationConfig.
// Only non-nil fields in RuntimeSettings are applied.
// Returns true if watchdog-related settings changed (requiring restart).
func (o *ApplicationConfig) ApplyRuntimeSettings(settings *RuntimeSettings) (requireRestart bool) {
	if settings == nil {
		return false
	}

	if settings.WatchdogEnabled != nil {
		o.WatchDog = *settings.WatchdogEnabled
		requireRestart = true
	}
	if settings.WatchdogIdleEnabled != nil {
		o.WatchDogIdle = *settings.WatchdogIdleEnabled
		if o.WatchDogIdle {
			o.WatchDog = true
		}
		requireRestart = true
	}
	if settings.WatchdogBusyEnabled != nil {
		o.WatchDogBusy = *settings.WatchdogBusyEnabled
		if o.WatchDogBusy {
			o.WatchDog = true
		}
		requireRestart = true
	}
	if settings.WatchdogIdleTimeout != nil {
		if dur, err := time.ParseDuration(*settings.WatchdogIdleTimeout); err == nil {
			o.WatchDogIdleTimeout = dur
			requireRestart = true
		}
	}
	if settings.WatchdogBusyTimeout != nil {
		if dur, err := time.ParseDuration(*settings.WatchdogBusyTimeout); err == nil {
			o.WatchDogBusyTimeout = dur
			requireRestart = true
		}
	}
	if settings.WatchdogInterval != nil {
		if dur, err := time.ParseDuration(*settings.WatchdogInterval); err == nil {
			o.WatchDogInterval = dur
			requireRestart = true
		}
	}
	if settings.MaxActiveBackends != nil {
		o.MaxActiveBackends = *settings.MaxActiveBackends
		o.SingleBackend = (*settings.MaxActiveBackends == 1)
		requireRestart = true
	} else if settings.SingleBackend != nil {
		o.SingleBackend = *settings.SingleBackend
		if *settings.SingleBackend {
			o.MaxActiveBackends = 1
		} else {
			o.MaxActiveBackends = 0
		}
		requireRestart = true
	}
	if settings.ParallelBackendRequests != nil {
		o.ParallelBackendRequests = *settings.ParallelBackendRequests
	}
	if settings.MemoryReclaimerEnabled != nil {
		o.MemoryReclaimerEnabled = *settings.MemoryReclaimerEnabled
		if *settings.MemoryReclaimerEnabled {
			o.WatchDog = true
		}
		requireRestart = true
	}
	if settings.MemoryReclaimerThreshold != nil {
		if *settings.MemoryReclaimerThreshold > 0 && *settings.MemoryReclaimerThreshold <= 1.0 {
			o.MemoryReclaimerThreshold = *settings.MemoryReclaimerThreshold
			requireRestart = true
		}
	}
	if settings.ForceEvictionWhenBusy != nil {
		o.ForceEvictionWhenBusy = *settings.ForceEvictionWhenBusy
		// This setting doesn't require restart, can be updated dynamically
	}
	if settings.LRUEvictionMaxRetries != nil {
		o.LRUEvictionMaxRetries = *settings.LRUEvictionMaxRetries
		// This setting doesn't require restart, can be updated dynamically
	}
	if settings.LRUEvictionRetryInterval != nil {
		if dur, err := time.ParseDuration(*settings.LRUEvictionRetryInterval); err == nil {
			o.LRUEvictionRetryInterval = dur
			// This setting doesn't require restart, can be updated dynamically
		}
	}
	if settings.Threads != nil {
		o.Threads = *settings.Threads
	}
	if settings.ContextSize != nil {
		o.ContextSize = *settings.ContextSize
	}
	if settings.F16 != nil {
		o.F16 = *settings.F16
	}
	if settings.Debug != nil {
		o.Debug = *settings.Debug
	}
	if settings.EnableTracing != nil {
		o.EnableTracing = *settings.EnableTracing
	}
	if settings.TracingMaxItems != nil {
		o.TracingMaxItems = *settings.TracingMaxItems
	}
	if settings.CORS != nil {
		o.CORS = *settings.CORS
	}
	if settings.CSRF != nil {
		o.CSRF = *settings.CSRF
	}
	if settings.CORSAllowOrigins != nil {
		o.CORSAllowOrigins = *settings.CORSAllowOrigins
	}
	if settings.P2PToken != nil {
		o.P2PToken = *settings.P2PToken
	}
	if settings.P2PNetworkID != nil {
		o.P2PNetworkID = *settings.P2PNetworkID
	}
	if settings.Federated != nil {
		o.Federated = *settings.Federated
	}
	if settings.Galleries != nil {
		o.Galleries = *settings.Galleries
	}
	if settings.BackendGalleries != nil {
		o.BackendGalleries = *settings.BackendGalleries
	}
	if settings.AutoloadGalleries != nil {
		o.AutoloadGalleries = *settings.AutoloadGalleries
	}
	if settings.AutoloadBackendGalleries != nil {
		o.AutoloadBackendGalleries = *settings.AutoloadBackendGalleries
	}
	if settings.AgentJobRetentionDays != nil {
		o.AgentJobRetentionDays = *settings.AgentJobRetentionDays
	}
	if settings.OpenResponsesStoreTTL != nil {
		if *settings.OpenResponsesStoreTTL == "0" || *settings.OpenResponsesStoreTTL == "" {
			o.OpenResponsesStoreTTL = 0 // No expiration
		} else if dur, err := time.ParseDuration(*settings.OpenResponsesStoreTTL); err == nil {
			o.OpenResponsesStoreTTL = dur
		}
		// This setting doesn't require restart, can be updated dynamically
	}
	// Agent Pool settings
	if settings.AgentPoolEnabled != nil {
		o.AgentPool.Enabled = *settings.AgentPoolEnabled
		requireRestart = true
	}
	if settings.AgentPoolDefaultModel != nil {
		o.AgentPool.DefaultModel = *settings.AgentPoolDefaultModel
		requireRestart = true
	}
	if settings.AgentPoolEmbeddingModel != nil {
		o.AgentPool.EmbeddingModel = *settings.AgentPoolEmbeddingModel
		requireRestart = true
	}
	if settings.AgentPoolMaxChunkingSize != nil {
		o.AgentPool.MaxChunkingSize = *settings.AgentPoolMaxChunkingSize
		requireRestart = true
	}
	if settings.AgentPoolChunkOverlap != nil {
		o.AgentPool.ChunkOverlap = *settings.AgentPoolChunkOverlap
		requireRestart = true
	}
	if settings.AgentPoolEnableLogs != nil {
		o.AgentPool.EnableLogs = *settings.AgentPoolEnableLogs
		requireRestart = true
	}
	if settings.AgentPoolCollectionDBPath != nil {
		o.AgentPool.CollectionDBPath = *settings.AgentPoolCollectionDBPath
		requireRestart = true
	}

	// Note: ApiKeys requires special handling (merging with startup keys) - handled in caller

	return requireRestart
}

// func WithMetrics(meter *metrics.Metrics) AppOption {
// 	return func(o *StartupOptions) {
// 		o.Metrics = meter
// 	}
// }
