package product

import module "bijection.dev/module"

const (
	StreamEventObserved = "observed"
	StreamEventIdle     = "idle"

	StreamCoalesceEvery  = "every"
	StreamCoalesceLatest = "latest"

	MisfireFireOnce = "fire_once"
	MisfireSkip     = "skip"

	CausationExternalRoot     = "external_root"
	CausationOutboundRequired = "outbound_required"
	CausationActionRequired   = "action_required"

	// The closed set of operations one Automation occurrence may start.
	TargetAction   = "action"
	TargetWorkflow = "workflow"
	TargetAgent    = "agent"

	// The closed set of values an occurrence carries into the operation it
	// starts. Each names one thing the immutable occurrence fact already knows;
	// a condition supplies only the fields its own fact records.
	OccurrenceSubjectKey         = "subject_key"
	OccurrenceDatasetTransaction = "dataset_transaction"
	OccurrenceFileVersion        = "file_version"
	OccurrenceSince              = "since"
	OccurrenceDate               = "date"
	OccurrenceInstant            = "instant"
)

// ActionTarget selects one governed operation. A targeted Action declares its
// subject with the same literal/occurrence source type as every other argument.
func ActionTarget(name string, subject ...Argument) AutomationTarget {
	target := AutomationTarget{Kind: TargetAction, Name: name}
	if len(subject) > 1 {
		panic("ActionTarget accepts at most one subject")
	}
	if len(subject) == 1 {
		copy := cloneArgument(subject[0])
		target.Subject = &copy
	}
	return target
}
func WorkflowTarget(name string) AutomationTarget {
	return AutomationTarget{Kind: TargetWorkflow, Name: name}
}
func AgentTarget(name string) AutomationTarget {
	return AutomationTarget{Kind: TargetAgent, Name: name}
}
func Literal(value module.Value) Argument { return Argument{Literal: &LiteralArgument{Value: value}} }
func Occurrence(field string) Argument {
	return Argument{Occurrence: &OccurrenceArgument{Field: field}}
}
func Publishers(principalKinds []string, causationPolicy string, outboundConnections ...string) PublisherCondition {
	return PublisherCondition{PrincipalKinds: append([]string(nil), principalKinds...), CausationPolicy: causationPolicy, OutboundConnections: append([]string(nil), outboundConnections...)}
}
func OnTime(cron, timeZone, misfirePolicy string) Trigger {
	return Trigger{Time: &TimeTrigger{Cron: cron, TimeZone: timeZone, MisfirePolicy: misfirePolicy}}
}
func OnObjectCreated(object string) Trigger {
	return Trigger{ObjectCreated: &ObjectCreatedTrigger{Object: object}}
}
func OnObjectPropertiesSet(object string, properties []string, where map[string]ValuePredicate) Trigger {
	return Trigger{ObjectPropertiesSet: &ObjectPropertiesSetTrigger{Object: object, Properties: append([]string(nil), properties...), Where: clonePredicates(where)}}
}
func OnDatasetChanged(dataset string) Trigger {
	return Trigger{DatasetChanged: &DatasetChangedTrigger{Dataset: dataset}}
}
func OnStreamChanged(dataset, event string) Trigger {
	return Trigger{StreamChanged: &StreamChangedTrigger{Dataset: dataset, Event: event}}
}
func OnFileCreated(namePrefix, nameSuffix string) Trigger {
	return Trigger{FileCreated: &FileCreatedTrigger{NamePrefix: namePrefix, NameSuffix: nameSuffix}}
}
func OnEvent(name string) Trigger  { return Trigger{Event: &EventTrigger{Name: name}} }
func OnSignal(name string) Trigger { return Trigger{Signal: &SignalTrigger{Name: name}} }

func Predicate(op string, value module.Value) ValuePredicate {
	return ValuePredicate{Op: op, Value: value}
}

// EscalationSignal constructs the one closed publisher shape used by agent
// escalation occurrences.
func EscalationSignal(name string) Signal {
	return NewSignal(name, "", Publishers([]string{"service"}, CausationExternalRoot))
}

func clonePredicates(values map[string]ValuePredicate) map[string]ValuePredicate {
	if values == nil {
		return nil
	}
	out := make(map[string]ValuePredicate, len(values))
	for name, value := range values {
		out[name] = value
	}
	return out
}

func cloneStringsMap(values map[string]string) map[string]string {
	if values == nil {
		return nil
	}
	out := make(map[string]string, len(values))
	for name, value := range values {
		out[name] = value
	}
	return out
}

func cloneValuesMap(values map[string]module.Value) map[string]module.Value {
	if values == nil {
		return nil
	}
	out := make(map[string]module.Value, len(values))
	for name, value := range values {
		out[name] = value
	}
	return out
}

func NewSignal(name, subject string, publishers PublisherCondition) Signal {
	publishers.PrincipalKinds = append([]string(nil), publishers.PrincipalKinds...)
	publishers.OutboundConnections = append([]string(nil), publishers.OutboundConnections...)
	return Signal{Name: name, Subject: subject, Publishers: publishers}
}

func NewEvent(name, subject string, payload ...EventProperty) Event {
	return Event{Name: name, Subject: subject, Payload: append([]EventProperty(nil), payload...)}
}

func cloneArgument(value Argument) Argument {
	if value.Literal != nil {
		copy := *value.Literal
		value.Literal = &copy
	}
	if value.Occurrence != nil {
		copy := *value.Occurrence
		value.Occurrence = &copy
	}
	return value
}
func NewAutomation(value Automation) Automation {
	value.Tags = append([]string(nil), value.Tags...)
	value.Authority = cloneStringsMap(value.Authority)
	if value.Arguments != nil {
		result := make(map[string]Argument, len(value.Arguments))
		for name, source := range value.Arguments {
			result[name] = cloneArgument(source)
		}
		value.Arguments = result
	}
	if value.Target.Subject != nil {
		copy := cloneArgument(*value.Target.Subject)
		value.Target.Subject = &copy
	}
	if value.Delivery != nil {
		copy := *value.Delivery
		value.Delivery = &copy
	}
	if value.ReadScope != nil {
		copy := *value.ReadScope
		value.ReadScope = &copy
	}
	if value.Trigger.Time != nil {
		copy := *value.Trigger.Time
		value.Trigger.Time = &copy
	}
	if value.Trigger.ObjectCreated != nil {
		copy := *value.Trigger.ObjectCreated
		value.Trigger.ObjectCreated = &copy
	}
	if value.Trigger.ObjectPropertiesSet != nil {
		copy := *value.Trigger.ObjectPropertiesSet
		copy.Properties = append([]string(nil), copy.Properties...)
		copy.Where = clonePredicates(copy.Where)
		value.Trigger.ObjectPropertiesSet = &copy
	}
	if value.Trigger.DatasetChanged != nil {
		copy := *value.Trigger.DatasetChanged
		value.Trigger.DatasetChanged = &copy
	}
	if value.Trigger.StreamChanged != nil {
		copy := *value.Trigger.StreamChanged
		value.Trigger.StreamChanged = &copy
	}
	if value.Trigger.FileCreated != nil {
		copy := *value.Trigger.FileCreated
		value.Trigger.FileCreated = &copy
	}
	if value.Trigger.Event != nil {
		copy := *value.Trigger.Event
		value.Trigger.Event = &copy
	}
	if value.Trigger.Signal != nil {
		copy := *value.Trigger.Signal
		value.Trigger.Signal = &copy
	}
	return value
}
