package plugin

import (
	"encoding/json"
	"fmt"
)

// MergeJSONConfig merges override on top of base at the top-level
// (binding fields win, base fields fill the rest). Both inputs are
// expected to be JSON objects ("{}" when empty); arrays or scalars are
// rejected because trigger and binding configs are always objects.
//
// Always returns a freshly allocated slice so callers may freely mutate
// the result without affecting base. The fast-path (override empty)
// returns a defensive copy of base for the same reason.
func MergeJSONConfig(base, override json.RawMessage) (json.RawMessage, error) {
	if len(override) == 0 || string(override) == "{}" {
		if len(base) == 0 {
			return json.RawMessage("{}"), nil
		}
		return append(json.RawMessage(nil), base...), nil
	}
	if len(base) == 0 || string(base) == "{}" {
		return append(json.RawMessage(nil), override...), nil
	}
	baseMap := map[string]json.RawMessage{}
	if err := json.Unmarshal(base, &baseMap); err != nil {
		return nil, fmt.Errorf("merge config: base is not a JSON object: %w", err)
	}
	overMap := map[string]json.RawMessage{}
	if err := json.Unmarshal(override, &overMap); err != nil {
		return nil, fmt.Errorf("merge config: override is not a JSON object: %w", err)
	}
	for k, v := range overMap {
		baseMap[k] = v
	}
	out, err := json.Marshal(baseMap)
	if err != nil {
		return nil, fmt.Errorf("merge config: re-marshal: %w", err)
	}
	return out, nil
}

// WithEffectiveTrigger returns a copy of w with TriggerKind and
// TriggerConfig set from a resolved (trigger, binding) pair. The
// existing Trigger.Match contract reads w.TriggerConfig via
// TriggerConfigOf[T], so this is the seam that lets the trigger plugins
// stay unchanged after the trigger-split refactor.
func WithEffectiveTrigger(w Workload, kind string, triggerConfig, bindingConfig json.RawMessage) (Workload, error) {
	merged, err := MergeJSONConfig(triggerConfig, bindingConfig)
	if err != nil {
		return Workload{}, err
	}
	w.TriggerKind = kind
	w.TriggerConfig = merged
	return w, nil
}
