package runtimeevents

import "context"

// EventOption applies optional overrides to an Event before publishing.
type EventOption func(*Event)

// WithData sets the EventData, validating kind match.
func WithData(data EventData) EventOption {
	return func(ev *Event) {
		if data == nil {
			return
		}
		ev.Data = data
	}
}

// WithSeverity sets the Severity field.
func WithSeverity(sev Severity) EventOption {
	return func(ev *Event) { ev.Severity = sev }
}

// WithContext sets the EventContext.
func WithContext(ctx EventContext) EventOption {
	return func(ev *Event) { ev.Context = ctx }
}

// WithTrace sets the EventTrace.
func WithTrace(trace EventTrace) EventOption {
	return func(ev *Event) { ev.Trace = trace }
}

// WithMessageID sets the Trace.MessageID.
func WithMessageID(id string) EventOption {
	return func(ev *Event) { ev.Trace.MessageID = id }
}

// WithCorrelationID sets the Trace.CorrelationID.
func WithCorrelationID(id string) EventOption {
	return func(ev *Event) { ev.Trace.CorrelationID = id }
}

// WithCauseID sets the Trace.CauseID.
func WithCauseID(id string) EventOption {
	return func(ev *Event) { ev.Trace.CauseID = id }
}

// WithTurnID sets the Trace.TurnID.
func WithTurnID(id string) EventOption {
	return func(ev *Event) { ev.Trace.TurnID = id }
}

// WithMapData creates a MapData compatibility wrapper.
func WithMapData(kind Kind, m map[string]any) EventOption {
	return func(ev *Event) { ev.Data = NewMapData(kind, m) }
}

// Publisher is a helper that carries base context fields and provides a
// convenient Publish method. Create one per agent/process/session and pass
// it into callbacks instead of the raw Bus.
type Publisher struct {
	bus         Bus
	baseContext EventContext
	baseTrace   EventTrace
}

// NewPublisher creates a Publisher with the given base context and trace.
func NewPublisher(bus Bus, baseCtx EventContext, baseTrace EventTrace) Publisher {
	return Publisher{
		bus:         bus,
		baseContext: baseCtx,
		baseTrace:   baseTrace,
	}
}

// Bus returns the underlying Bus.
func (p Publisher) Bus() Bus { return p.bus }

// WithAgent returns a new Publisher with agent context set.
func (p Publisher) WithAgent(agentID, serverAgentID string) Publisher {
	np := p
	np.baseContext.AgentID = agentID
	np.baseContext.ServerAgentID = serverAgentID
	return np
}

// WithServer returns a new Publisher with server connection context set.
func (p Publisher) WithServer(serverID string) Publisher {
	np := p
	np.baseContext.ServerID = serverID
	return np
}

// WithSession returns a new Publisher with session context set.
func (p Publisher) WithSession(sessionID string) Publisher {
	np := p
	np.baseContext.SessionID = sessionID
	return np
}

// WithWake returns a new Publisher with wake trace set.
func (p Publisher) WithWake(messageID, wakeToken, target string) Publisher {
	np := p
	np.baseTrace.MessageID = messageID
	np.baseTrace.WakeToken = wakeToken
	np.baseTrace.Target = target
	return np
}

// WithRuntime returns a new Publisher with runtime info set.
func (p Publisher) WithRuntime(runtime, model, workDir string, pid int) Publisher {
	np := p
	np.baseContext.Runtime = runtime
	np.baseContext.Model = model
	np.baseContext.WorkDir = workDir
	np.baseContext.PID = pid
	return np
}

// Publish creates a new Event from the given kind with base context/trace,
// applies opts, and publishes it to the Bus.
func (p Publisher) Publish(ctx context.Context, kind Kind, opts ...EventOption) {
	ev := NewEvent(kind)
	ev.Context = p.baseContext
	ev.Trace = p.baseTrace

	for _, opt := range opts {
		opt(&ev)
	}

	p.bus.Publish(ctx, ev)
}

// PublishData creates a new Event from the given data with base context/trace,
// applies opts, and publishes it to the Bus.
func (p Publisher) PublishData(ctx context.Context, data EventData, opts ...EventOption) {
	ev := NewDataEvent(data)
	ev.Context = p.baseContext
	ev.Trace = p.baseTrace

	for _, opt := range opts {
		opt(&ev)
	}

	p.bus.Publish(ctx, ev)
}
