//go:build crm

package crm

import (
	"context"
	"strings"

	"github.com/nexuscrm/backend/internal/features/streaming"
	recordEvents "github.com/nexuscrm/events"
	"github.com/nexuscrm/shared/pkg/constants"
	sharedEvents "github.com/nexuscrm/shared/pkg/events"
)

func StreamingEventChannelMappings() []streaming.EventChannelMapping {
	recordFilter := publicRecordTopicFilter()
	return []streaming.EventChannelMapping{
		{EventType: sharedEvents.RecordCreated, Channel: "/topic/records/created", Filter: recordFilter},
		{EventType: sharedEvents.RecordUpdated, Channel: "/topic/records/updated", Filter: recordFilter},
		{EventType: sharedEvents.RecordDeleted, Channel: "/topic/records/deleted", Filter: recordFilter},
		{EventType: sharedEvents.ObjectCreated, Channel: "/topic/metadata/object_created"},
		{EventType: sharedEvents.ObjectUpdated, Channel: "/topic/metadata/object_updated"},
		{EventType: sharedEvents.ObjectDeleted, Channel: "/topic/metadata/object_deleted"},
		{EventType: sharedEvents.FieldCreated, Channel: "/topic/metadata/field_created"},
		{EventType: sharedEvents.FieldUpdated, Channel: "/topic/metadata/field_updated"},
		{EventType: sharedEvents.FieldDeleted, Channel: "/topic/metadata/field_deleted"},
		{EventType: sharedEvents.ActionCreated, Channel: "/topic/metadata/action_created"},
		{EventType: sharedEvents.ActionUpdated, Channel: "/topic/metadata/action_updated"},
		{EventType: sharedEvents.ActionDeleted, Channel: "/topic/metadata/action_deleted"},
	}
}

// publicRecordTopicFilter keeps Notification rows off the public record
// topics. Those topics broadcast full record payloads to every subscribed
// CometD session with no per-recipient scoping, and Notification is
// per-recipient private content (the REST surface reads it back
// recipient-filtered). The Flow sendNotification action is the first writer to
// route Notification through the full persistence pipeline, so without this
// drop its rows would reach every subscriber. Scope is deliberately
// Notification-only: every other object's broadcast behaviour predates this
// change and must not be altered here, so an undeterminable payload is passed
// through -- only a positive Notification match is dropped. If a second
// per-recipient-private system table ever needs this, lift the rule to an
// opt-in TableDefinition flag consulted here rather than adding a second
// name literal.
func publicRecordTopicFilter() func(context.Context, interface{}) bool {
	return func(_ context.Context, payload interface{}) bool {
		return recordPayloadObjectAPIName(payload) != constants.TableNotification
	}
}

// recordPayloadObjectAPIName extracts the object API name from the two
// payload shapes record events travel as (mirrors FlowTriggerListener's
// decoding): the typed in-process payload and the JSON-deserialized map form
// delivered by the outbox dispatcher.
func recordPayloadObjectAPIName(payload interface{}) string {
	switch p := payload.(type) {
	case recordEvents.RecordEventPayload:
		return strings.TrimSpace(p.ObjectAPIName)
	case map[string]interface{}:
		if s, ok := p["object_api_name"].(string); ok {
			return strings.TrimSpace(s)
		}
	}
	return ""
}
