package store

// settings_cascade.go — v0.2.0
//
// Pure functions for three-tier settings resolution:
//   global (admin) → team → user
//
// Two orthogonal axes:
//   - RBAC controls who can write at each scope
//   - user_overridable (per setting key) controls whether lower scopes
//     can override higher scope values
//
// The kernel enforces the cascade without knowing setting semantics.

import "encoding/json"

// SettingSchema represents one entry from the manifest "settings" array.
type SettingSchema struct {
	Key             string `json:"key"`
	UserOverridable *bool  `json:"user_overridable,omitempty"`
}

// IsUserOverridable returns whether a setting key can be overridden at
// lower scopes (team, user). Defaults to true when the field is absent
// for backward compatibility with existing manifests.
func (s SettingSchema) IsUserOverridable() bool {
	return s.UserOverridable == nil || *s.UserOverridable
}

// ParseSettingsSchema extracts []SettingSchema from a package manifest.
func ParseSettingsSchema(manifest map[string]any) []SettingSchema {
	arr, ok := manifest["settings"].([]any)
	if !ok {
		return nil
	}
	result := make([]SettingSchema, 0, len(arr))
	for _, item := range arr {
		m, ok := item.(map[string]any)
		if !ok {
			continue
		}
		s := SettingSchema{}
		s.Key, _ = m["key"].(string)
		if s.Key == "" {
			continue
		}
		if uo, ok := m["user_overridable"].(bool); ok {
			s.UserOverridable = &uo
		}
		result = append(result, s)
	}
	return result
}

// ResolveSettings implements the settings cascade:
//
//	user override → team override → global default
//
// For each key declared in the schema with user_overridable: false,
// team and user values are ignored — only the global value is used.
// Keys not declared in schema default to overridable.
//
// Parameters:
//   - globalSettings: admin-set values from packages.package_settings
//   - teamSettings:   team-scoped overrides (nil if no team context)
//   - userSettings:   per-user overrides (nil if no user context)
//   - schema:         parsed manifest settings array
func ResolveSettings(
	globalSettings json.RawMessage,
	teamSettings json.RawMessage,
	userSettings json.RawMessage,
	schema []SettingSchema,
) map[string]any {
	global := jsonToMap(globalSettings)
	team := jsonToMap(teamSettings)
	user := jsonToMap(userSettings)

	// Build overridable lookup from schema
	overridable := make(map[string]bool, len(schema))
	for _, s := range schema {
		overridable[s.Key] = s.IsUserOverridable()
	}

	// Start with global as base
	merged := make(map[string]any, len(global))
	for k, v := range global {
		merged[k] = v
	}

	// Overlay team values (respecting user_overridable)
	for k, v := range team {
		canOverride, declared := overridable[k]
		if !declared {
			canOverride = true // unknown keys default to overridable
		}
		if canOverride {
			merged[k] = v
		}
	}

	// Overlay user values (respecting user_overridable)
	for k, v := range user {
		canOverride, declared := overridable[k]
		if !declared {
			canOverride = true
		}
		if canOverride {
			merged[k] = v
		}
	}

	return merged
}

// FilterOverridableKeys strips non-overridable keys from a settings map.
// Used by handlers to enforce user_overridable before saving.
// Returns the filtered map and the list of rejected keys.
func FilterOverridableKeys(settings map[string]any, schema []SettingSchema) (map[string]any, []string) {
	overridable := make(map[string]bool, len(schema))
	for _, s := range schema {
		overridable[s.Key] = s.IsUserOverridable()
	}

	filtered := make(map[string]any, len(settings))
	var rejected []string
	for k, v := range settings {
		canOverride, declared := overridable[k]
		if !declared {
			canOverride = true
		}
		if canOverride {
			filtered[k] = v
		} else {
			rejected = append(rejected, k)
		}
	}
	return filtered, rejected
}

func jsonToMap(raw json.RawMessage) map[string]any {
	if len(raw) == 0 {
		return nil
	}
	var m map[string]any
	json.Unmarshal(raw, &m)
	return m
}
