package channel import ( "context" "errors" "fmt" "strings" "sync" ) // Registry holds all registered channel adapters and provides dispatch methods // for configuration normalization, target resolution, and binding operations. // It replaces the former global registry, and must be created via NewRegistry // and passed explicitly to components that need it. type Registry struct { mu sync.RWMutex adapters map[ChannelType]Adapter } // NewRegistry creates an empty Registry. func NewRegistry() *Registry { return &Registry{ adapters: map[ChannelType]Adapter{}, } } // Register adds an adapter to the registry. func (r *Registry) Register(adapter Adapter) error { if adapter == nil { return errors.New("adapter is nil") } ct := normalizeChannelType(adapter.Type().String()) if ct == "" { return errors.New("channel type is required") } r.mu.Lock() defer r.mu.Unlock() if _, exists := r.adapters[ct]; exists { return fmt.Errorf("channel type already registered: %s", ct) } r.adapters[ct] = adapter return nil } // MustRegister calls Register and panics on error. func (r *Registry) MustRegister(adapter Adapter) { if err := r.Register(adapter); err != nil { panic(err) } } // Unregister removes a channel type from the registry. func (r *Registry) Unregister(channelType ChannelType) bool { ct := normalizeChannelType(channelType.String()) if ct == "" { return false } r.mu.Lock() defer r.mu.Unlock() if _, exists := r.adapters[ct]; !exists { return false } delete(r.adapters, ct) return true } // Get returns the adapter for the given channel type. func (r *Registry) Get(channelType ChannelType) (Adapter, bool) { ct := normalizeChannelType(channelType.String()) r.mu.RLock() defer r.mu.RUnlock() adapter, ok := r.adapters[ct] return adapter, ok } // DirectoryAdapter returns the directory adapter for the given channel type if it implements ChannelDirectoryAdapter. func (r *Registry) DirectoryAdapter(channelType ChannelType) (ChannelDirectoryAdapter, bool) { adapter, ok := r.Get(channelType) if !ok { return nil, false } dir, ok := adapter.(ChannelDirectoryAdapter) return dir, ok } // List returns all registered adapters. func (r *Registry) List() []Adapter { r.mu.RLock() defer r.mu.RUnlock() items := make([]Adapter, 0, len(r.adapters)) for _, a := range r.adapters { items = append(items, a) } return items } // Types returns all registered channel types. func (r *Registry) Types() []ChannelType { r.mu.RLock() defer r.mu.RUnlock() items := make([]ChannelType, 0, len(r.adapters)) for ct := range r.adapters { items = append(items, ct) } return items } // --- Descriptor accessors --- // GetDescriptor returns the descriptor for the given channel type. func (r *Registry) GetDescriptor(channelType ChannelType) (Descriptor, bool) { adapter, ok := r.Get(channelType) if !ok { return Descriptor{}, false } return adapter.Descriptor(), true } // ListDescriptors returns descriptors for all registered channel types. func (r *Registry) ListDescriptors() []Descriptor { adapters := r.List() items := make([]Descriptor, 0, len(adapters)) for _, a := range adapters { items = append(items, a.Descriptor()) } return items } // ParseChannelType validates and normalizes a raw string into a registered ChannelType. func (r *Registry) ParseChannelType(raw string) (ChannelType, error) { ct := normalizeChannelType(raw) if ct == "" { return "", fmt.Errorf("unsupported channel type: %s", raw) } if _, ok := r.Get(ct); !ok { return "", fmt.Errorf("unsupported channel type: %s", raw) } return ct, nil } // --- Capability accessors --- // GetCapabilities returns the capability matrix for the given channel type. func (r *Registry) GetCapabilities(channelType ChannelType) (ChannelCapabilities, bool) { desc, ok := r.GetDescriptor(channelType) if !ok { return ChannelCapabilities{}, false } return desc.Capabilities, true } // GetOutboundPolicy returns the outbound policy for the given channel type. func (r *Registry) GetOutboundPolicy(channelType ChannelType) (OutboundPolicy, bool) { desc, ok := r.GetDescriptor(channelType) if !ok { return OutboundPolicy{}, false } return desc.OutboundPolicy, true } // GetConfigSchema returns the configuration schema for the given channel type. func (r *Registry) GetConfigSchema(channelType ChannelType) (ConfigSchema, bool) { desc, ok := r.GetDescriptor(channelType) if !ok { return ConfigSchema{}, false } return desc.ConfigSchema, true } // GetUserConfigSchema returns the user-binding configuration schema. func (r *Registry) GetUserConfigSchema(channelType ChannelType) (ConfigSchema, bool) { desc, ok := r.GetDescriptor(channelType) if !ok { return ConfigSchema{}, false } return desc.UserConfigSchema, true } // IsConfigless reports whether the channel type operates without per-bot configuration. func (r *Registry) IsConfigless(channelType ChannelType) bool { desc, ok := r.GetDescriptor(channelType) if !ok { return false } return desc.Configless } // --- Sender / Receiver accessors --- // GetSender returns the Sender for the given channel type, or nil if unsupported. func (r *Registry) GetSender(channelType ChannelType) (Sender, bool) { adapter, ok := r.Get(channelType) if !ok { return nil, false } sender, ok := adapter.(Sender) return sender, ok } // GetStreamSender returns the StreamSender for the given channel type, or nil if unsupported. func (r *Registry) GetStreamSender(channelType ChannelType) (StreamSender, bool) { adapter, ok := r.Get(channelType) if !ok { return nil, false } streamSender, ok := adapter.(StreamSender) return streamSender, ok } // GetMessageEditor returns the MessageEditor for the given channel type, or nil if unsupported. func (r *Registry) GetMessageEditor(channelType ChannelType) (MessageEditor, bool) { adapter, ok := r.Get(channelType) if !ok { return nil, false } editor, ok := adapter.(MessageEditor) return editor, ok } // GetReactor returns the Reactor for the given channel type, or nil if unsupported. func (r *Registry) GetReactor(channelType ChannelType) (Reactor, bool) { adapter, ok := r.Get(channelType) if !ok { return nil, false } reactor, ok := adapter.(Reactor) return reactor, ok } // GetReceiver returns the Receiver for the given channel type, or nil if unsupported. func (r *Registry) GetReceiver(channelType ChannelType) (Receiver, bool) { adapter, ok := r.Get(channelType) if !ok { return nil, false } receiver, ok := adapter.(Receiver) return receiver, ok } // GetProcessingStatusNotifier returns the ProcessingStatusNotifier for the given channel type, or nil if unsupported. func (r *Registry) GetProcessingStatusNotifier(channelType ChannelType) (ProcessingStatusNotifier, bool) { adapter, ok := r.Get(channelType) if !ok { return nil, false } notifier, ok := adapter.(ProcessingStatusNotifier) return notifier, ok } // GetAttachmentResolver returns the AttachmentResolver for the given channel // type, or nil if unsupported. func (r *Registry) GetAttachmentResolver(channelType ChannelType) (AttachmentResolver, bool) { adapter, ok := r.Get(channelType) if !ok { return nil, false } resolver, ok := adapter.(AttachmentResolver) return resolver, ok } // DiscoverSelf calls the SelfDiscoverer for the given channel type if supported. func (r *Registry) DiscoverSelf(ctx context.Context, channelType ChannelType, credentials map[string]any) (map[string]any, string, error) { adapter, ok := r.Get(channelType) if !ok { return nil, "", fmt.Errorf("unsupported channel type: %s", channelType) } discoverer, ok := adapter.(SelfDiscoverer) if !ok { return nil, "", nil } return discoverer.DiscoverSelf(ctx, credentials) } // --- Dispatch methods (replace former global functions in config.go / target.go) --- // NormalizeConfig validates and normalizes a channel configuration map. func (r *Registry) NormalizeConfig(channelType ChannelType, raw map[string]any) (map[string]any, error) { if raw == nil { raw = map[string]any{} } adapter, ok := r.Get(channelType) if !ok { return nil, fmt.Errorf("unsupported channel type: %s", channelType) } if normalizer, ok := adapter.(ConfigNormalizer); ok { return normalizer.NormalizeConfig(raw) } return raw, nil } // NormalizeUserConfig validates and normalizes a user-channel binding configuration. func (r *Registry) NormalizeUserConfig(channelType ChannelType, raw map[string]any) (map[string]any, error) { if raw == nil { raw = map[string]any{} } adapter, ok := r.Get(channelType) if !ok { return nil, fmt.Errorf("unsupported channel type: %s", channelType) } if normalizer, ok := adapter.(ConfigNormalizer); ok { return normalizer.NormalizeUserConfig(raw) } return raw, nil } // ResolveTargetFromUserConfig derives a delivery target from a user-channel binding. func (r *Registry) ResolveTargetFromUserConfig(channelType ChannelType, config map[string]any) (string, error) { adapter, ok := r.Get(channelType) if !ok { return "", fmt.Errorf("unsupported channel type: %s", channelType) } if resolver, ok := adapter.(TargetResolver); ok { return resolver.ResolveTarget(config) } return "", fmt.Errorf("channel type %s does not support target resolution", channelType) } // NormalizeTarget applies the channel-specific target normalization. func (r *Registry) NormalizeTarget(channelType ChannelType, raw string) (string, bool) { adapter, ok := r.Get(channelType) if !ok { return strings.TrimSpace(raw), false } if resolver, ok := adapter.(TargetResolver); ok { normalized := strings.TrimSpace(resolver.NormalizeTarget(raw)) if normalized == "" { return "", false } return normalized, true } return strings.TrimSpace(raw), false } // MatchUserBinding reports whether the given binding config matches the criteria. func (r *Registry) MatchUserBinding(channelType ChannelType, config map[string]any, criteria BindingCriteria) bool { adapter, ok := r.Get(channelType) if !ok { return false } if matcher, ok := adapter.(BindingMatcher); ok { return matcher.MatchBinding(config, criteria) } return false } // BuildUserBindingConfig constructs a user-channel binding config from an Identity. func (r *Registry) BuildUserBindingConfig(channelType ChannelType, identity Identity) map[string]any { adapter, ok := r.Get(channelType) if !ok { return map[string]any{} } if matcher, ok := adapter.(BindingMatcher); ok { return matcher.BuildUserConfig(identity) } return map[string]any{} } func normalizeChannelType(raw string) ChannelType { normalized := strings.TrimSpace(strings.ToLower(raw)) if normalized == "" { return "" } return ChannelType(normalized) }