// Copyright 2026 The Erigon Authors
// This file is part of Erigon.
//
// Erigon is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Erigon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Erigon. If not, see <http://www.gnu.org/licenses/>.

package component

import (
	"context"
	"errors"
	"fmt"
	"path/filepath"
	"reflect"
	"strings"
	"sync"
	"unsafe"

	"github.com/urfave/cli/v3"

	"github.com/erigontech/erigon/common/dbg"
	liblog "github.com/erigontech/erigon/common/log/v3"
	"github.com/erigontech/erigon/node/app"
	"github.com/erigontech/erigon/node/app/event"
	"github.com/erigontech/erigon/node/app/util"
)

// actorMsg is a message sent to a component's actor goroutine.
// All state-mutating operations are serialized through the actor.
type actorMsg struct {
	kind       actorMsgKind
	ctx        context.Context
	onActivity onActivity
	event      *ComponentStateChanged
	reply      chan error // optional: caller blocks on this for synchronous error return
}

type actorMsgKind int

const (
	msgActivate actorMsgKind = iota
	msgDeactivate
	msgStateChanged
)

type relation interface {
	Id() app.Id
	Name() string

	State() State
	AwaitState(ctx context.Context, state State) (State, error)

	Context() context.Context

	Lock()
	Unlock()

	GetDependency(context context.Context, selector app.TypedSelector) relation
	AddDependency(component relation) relation
	RemoveDependency(component relation)

	Dependents() relations
	HasDependent(component relation) bool

	Domain() ComponentDomain
	Flags() []cli.Flag

	isConfigurable() bool
	isInitializable() bool
}

type ActivityHandler[P any] interface {
	OnActivity(ctx context.Context, c Component[P], state State, err error)
}

type ActivityHandlerFunc[P any] func(ctx context.Context, c Component[P], state State, err error)

func (f ActivityHandlerFunc[P]) OnActivity(ctx context.Context, c Component[P], state State, err error) {
	f(ctx, c, state, err)
}

type Component[P any] interface {
	relation

	Configure(ctx context.Context, options ...app.Option) error
	Initialize(ctx context.Context, options ...app.Option) error
	Activate(ctx context.Context, handler ...ActivityHandler[P]) error
	Deactivate(ctx context.Context, handler ...ActivityHandler[P]) error

	Provider() *P

	EventBus(key any) *event.ManagedEventBus
}

type typedComponent[P any] struct {
	*component
}

func (t typedComponent[P]) AddDependency(dependency relation) relation {
	t.component.AddDependency(dependency)
	return t
}

func (t typedComponent[P]) unwrap() *component {
	return t.component
}

func (c typedComponent[P]) Provider() *P {
	return c.provider.(*P)
}

func (c typedComponent[P]) Activate(ctx context.Context, handler ...ActivityHandler[P]) error {
	return c.activate(ctx, func(ctx context.Context, _ *component, err error) {
		if len(handler) > 0 {
			handler[0].OnActivity(ctx, c, c.state, err)
		}
	})
}

func (c typedComponent[P]) Deactivate(ctx context.Context, handler ...ActivityHandler[P]) error {
	return c.deactivate(ctx, func(ctx context.Context, _ *component, err error) {
		if len(handler) > 0 {
			handler[0].OnActivity(ctx, c, c.state, err)
		}
	})
}

var deactivatoinWaiters = struct {
	sync.Mutex
	*util.ChannelGroup
	cmap   map[any]*component
	cancel func()
}{
	cmap: map[any]*component{},
}

// relations is stored as []*component (not []relation) so pointer identity
// can be compared directly without interface-header allocation or reflect.
// The relation interface is still used as the public parameter type for
// Add/Remove/Contains — callers pass any relation and we unwrap to *component.
type relations []*component

func cmp(p0 *component, p1 *component) bool {
	return uintptr(unsafe.Pointer(p0)) < uintptr(unsafe.Pointer(p1))
}

func samePtr(p0 *component, p1 *component) bool {
	return p0 == p1
}

func (c relations) Add(r relation) relations {
	component := asComponent(r)
	if component == nil {
		return c
	}
	l := len(c)

	if l == 0 {
		return relations{component}
	}

	var i = l
	for index := range l {
		if cmp(c[index], component) && (index == l-1 || !cmp(c[index+1], component)) {
			i = index
			break
		}
	}

	if i == l { // not found = new value is the smallest
		if samePtr(c[0], component) {
			return c
		}
		return append(relations{component}, c...)
	}

	if i == l-1 { // new value is the biggest
		return append(c[0:l], component)
	}

	if samePtr(c[i+1], component) {
		return c
	}

	c = append(c, nil)
	copy(c[i+2:], c[i+1:])
	c[i+1] = component

	return c
}

func (c relations) Remove(r relation) relations {
	component := asComponent(r)
	if component == nil {
		return c
	}
	l := len(c)

	if l == 0 {
		return c
	}

	var i int
	for i = 0; i < l; i++ {
		if cmp(c[i], component) && (i == l-1 || !cmp(c[i+1], component)) {
			break
		}
	}

	if i == l {
		if !samePtr(c[0], component) {
			return c
		}
		return c[1:]
	}

	if i == l-1 { // new value is the biggest
		return c
	}

	if !samePtr(c[i+1], component) {
		return c
	}

	if i < l-2 {
		copy(c[i+1:], c[i+2:])
	}
	c[l-1] = nil
	return c[:l-1]
}

func (c relations) Contains(r relation) bool {
	component := asComponent(r)
	if component == nil {
		return false
	}
	l := len(c)

	if l == 0 {
		return false
	}

	var i = l
	for index := range l {
		if cmp(c[index], component) && (index == l-1 || !cmp(c[index+1], component)) {
			i = index + 1
			break
		}
	}

	if i == l {
		return samePtr(c[0], component)
	}
	return samePtr(c[i], component)
}

func (c relations) String() string {
	result := ""

	for _, component := range c {
		if len(result) == 0 {
			result = "["
		} else {
			result = result + ","
		}

		result = result + component.Name()
	}

	if len(result) == 0 {
		result = "[]"
	} else {
		result = result + "]"
	}

	return result
}

func Provider[T any](c *component) T {
	return c.provider.(T)
}

type component struct {
	sync.RWMutex
	componentDomain *componentDomain
	options         []app.Option
	context         context.Context
	contextCancel   context.CancelFunc
	id              app.Id
	name            string
	state           State
	dependents      relations
	dependencies    relations
	provider        any
	log             app.Logger
	flags           []cli.Flag
	inbox           chan actorMsg // serializes activate/deactivate/stateChanged — prevents lock-ordering deadlocks
}

// runActor is the per-component actor goroutine. It processes all
// state-mutating lifecycle operations serially, eliminating lock-ordering
// deadlocks between concurrent activate/deactivate/stateChanged paths.
//
// The actor runs for the lifetime of the component. Callers send messages
// to c.inbox; the actor processes them one at a time. External state reads
// (via State()) still use RLock for now — the actor holds the write lock
// when mutating state, same as before, but no two lifecycle operations
// can interleave on the same component.
func (c *component) runActor() {
	for msg := range c.inbox {
		switch msg.kind {
		case msgActivate:
			err := c.doActivate(msg.ctx, msg.onActivity)
			if msg.reply != nil {
				msg.reply <- err
			}
		case msgDeactivate:
			c.doDeactivate(msg.ctx, msg.onActivity)
		case msgStateChanged:
			c.doOnComponentStateChanged(msg.event)
		}
	}
}

func WithFlag[F cli.Flag, T any](flag F, setter func(f F, t *T) bool) app.Option {
	return app.WithOption[any](
		func(a *any) bool {
			switch t := (*a).(type) {
			case *component:
				t.flags = append(t.flags, flag)
				return false
			case *T:
				return setter(flag, t)
			default:
				return false
			}
		})
}

func WithName(name string) app.Option {
	return app.WithOption[component](
		func(c *component) bool {
			c.name = name
			return true
		})
}

type ProviderFactory[P any] interface {
	New() *P
}

type ProviderFactoryFunc[P any] func() *P

func (f ProviderFactoryFunc[P]) New() *P {
	return f()
}

func WithProvider[P any](p *P) app.Option {
	return app.WithOption[component](
		func(c *component) bool {
			c.provider = p
			return true
		})
}

type componentOptions struct {
	dependencies relations
	dependents   relations
	id           string
	logLabels    []string
	logLvl       liblog.Lvl
	logCtx       []any
}

func WithLogLevel(lvl liblog.Lvl) app.Option {
	return app.WithOption[componentOptions](
		func(c *componentOptions) bool {
			c.logLvl = lvl
			return true
		})
}

func WithLogLabels(labels ...string) app.Option {
	return app.WithOption[componentOptions](
		func(c *componentOptions) bool {
			c.logLabels = labels
			return true
		})
}

func WithLogCtx(ctx ...any) app.Option {
	return app.WithOption[componentOptions](
		func(c *componentOptions) bool {
			c.logCtx = ctx
			return true
		})
}

func WithId(id string) app.Option {
	return app.WithOption[componentOptions](
		func(c *componentOptions) bool {
			c.id = id
			return true
		})
}

func WithDependencies(dependencies ...relation) app.Option {
	return app.WithOption[componentOptions](
		func(c *componentOptions) bool {
			for _, d := range dependencies {
				if unwrapped := asComponent(d); unwrapped != nil {
					c.dependencies = append(c.dependencies, unwrapped)
				}
			}
			return true
		})
}

func WithDependent(dependent relation) app.Option {
	return app.WithOption[componentOptions](
		func(c *componentOptions) bool {
			if unwrapped := asComponent(dependent); unwrapped != nil {
				c.dependents = append(c.dependents, unwrapped)
			}
			return true
		})
}

func WithDomain(dependent ComponentDomain) app.Option {
	return app.WithOption[componentOptions](
		func(c *componentOptions) bool {
			c.dependents = append(c.dependents, dependent.(*componentDomain).component)
			return true
		})
}

func WithProviderFactory[P any](p ProviderFactory[P]) app.Option {
	return app.WithOption[component](
		func(c *component) bool {
			c.provider = p.New()
			return true
		})
}

func NewComponent[P any](parentCtx context.Context, options ...app.Option) (Component[P], error) {
	ctx, cancel := context.WithCancel(parentCtx)
	c := &component{
		RWMutex:       sync.RWMutex{},
		context:       ctx,
		contextCancel: cancel,
		state:         Instantiated,
		log:           log,
		inbox:         make(chan actorMsg, 16),
	}
	go c.runActor()

	opts := componentOptions{
		logLvl: -1,
	}
	options = app.ApplyOptions(&opts, options)
	var domainOpts domainOptions
	options = app.ApplyOptions(&domainOpts, options)
	c.options = app.ApplyOptions(c, options)

	if c.provider == nil {
		var p P
		c.provider = &p
	}

	if len(c.name) == 0 {
		t := reflect.TypeOf(c.provider)
		if t.Kind() == reflect.Pointer {
			t = t.Elem()
		}
		c.name = t.String()
	}

	if domainOpts.dependent != nil {
		opts.dependents = append(opts.dependents, domainOpts.dependent.component)
	}

	if len(opts.dependents) > 0 {
		for _, dependent := range opts.dependents {
			if err := c.addDependent(asComponent(dependent), false); err != nil {
				return nil, err
			}
		}
	}

	if c.componentDomain == nil {
		if c.provider == rootComponentDomain {
			if len(opts.id) == 0 {
				opts.id = "root"
			}
			c.id, _ = rootComponentDomain.NewId(parentCtx, opts.id)
		} else {
			if err := c.setDomain(rootComponentDomain, false); err != nil {
				return nil, err
			}
		}
	}

	if c.id == nil {
		if len(opts.id) == 0 {
			opts.id = strings.ToLower(filepath.Ext(reflect.TypeOf(c.provider).String())[1:])
		}
		c.id, _ = c.componentDomain.NewId(parentCtx, opts.id)
	}

	if err := c.registerSubscriptions(); err != nil {
		return nil, err
	}

	// don't do this for component domains - they will not
	// be fully initialized yet - they handle this themselves
	if _, ok := c.provider.(*componentDomain); !ok {
		for _, dependency := range opts.dependencies {
			c.AddDependency(dependency)
		}
	}

	c.log = app.NewLogger(opts.logLvl, nil, opts.logCtx)
	if len(opts.logLabels) > 0 {
		c.log.SetLabels(opts.logLabels...)
	} else {
		c.log.SetLabels(c.Id().String())
	}
	if opts.logLvl >= 0 {
		c.log.SetLevel(opts.logLvl)
	}

	return typedComponent[P]{c}, nil
}

func (c *component) isConfigurable() bool {
	_, ok := c.provider.(Configurable)
	return ok
}

func (c *component) isInitializable() bool {
	_, ok := c.provider.(Initializable)
	return ok
}

func (c *component) String() string {
	if provider, ok := c.provider.(interface{ String() string }); ok {
		return provider.String()
	}

	return fmt.Sprintf("%p:%T [id=%s, name=%s, dependents=%s, state=%s]",
		c,
		c.provider,
		c.Id(),
		c.Name(),
		c.dependents,
		c.State())
}

func (c *component) Context() context.Context {
	if c.context != nil {
		return c.context
	}

	return context.Background()
}

func (c *component) State() State {
	c.RLock()
	state := c.state
	c.RUnlock()
	return state
}

func (c *component) Domain() ComponentDomain {
	return c.componentDomain
}

func (c *component) setState(state State, locked bool) {
	if !locked {
		c.Lock()
		defer c.Unlock()
	}

	if state != c.state {
		previousState := c.state
		c.state = state

		if c.log.DebugEnabled() {
			c.log.Debug("", "provider", app.LogInstance(c.provider), "state", state.String())
		}
		c.serviceBus().Post(newComponentStateChanged(c, state, previousState))
	}
}

func (c *component) AwaitState(ctx context.Context, state State) (State, error) {
	statechan := make(chan State, 1)
	c.RLock()

	if state == c.state {
		c.RUnlock()
		return state, nil
	}

	var subscriber func(event *ComponentStateChanged)

	subscriber = func(event *ComponentStateChanged) {
		sourceComponent, isComponent := event.Source().(relation)

		if !isComponent {
			return
		}

		if sourceComponent == c && event.Current() == state {
			//fmt.Printf("%p Await Received Component State Changed [%v->%v]\n",
			//	c, event.Previous(), event.Current())
			_ = c.serviceBus().Unregister(c, subscriber)
			statechan <- state
		}
	}

	err := c.serviceBus().Register(c, subscriber)

	if err != nil {
		return Unknown, err
	}

	c.RUnlock()
	//fmt.Printf("%T:%p (%v) Await: %v\n", component, component, component.State(), state)
	result, ok := <-statechan

	if !ok {
		return Unknown, fmt.Errorf("subscription channel unexpectedly closed")
	}

	return result, nil
}

func asComponent(r relation) *component {
	switch r := r.(type) {
	case *component:
		return r
	case interface{ unwrap() *component }:
		return r.unwrap()
	}

	return nil
}

func (c *component) AddDependency(dependency relation) relation {
	asComponent(dependency).addDependent(c, false)
	return c
}

func (c *component) RemoveDependency(dependency relation) {
	c.removeDependency(asComponent(dependency), false)
}

func (c *component) Id() app.Id {
	return c.id
}

func (c *component) Name() string {
	if c == nil {
		return "nil"
	}

	if len(c.name) > 0 {
		return c.name
	}

	if c.id == nil || len(c.id.String()) == 0 {
		if c.provider != nil {
			return fmt.Sprintf("%T(%p)", c, c)
		}
		return reflect.TypeFor[*component]().String()
	}

	return c.id.String()
}

func (c *component) InstanceId() string {
	return app.LogInstance(c)
}

func (c *component) Flags() []cli.Flag {
	c.RLock()
	defer c.RUnlock()

	flags := make([]cli.Flag, 0, len(c.flags))
	flags = append(flags, c.flags...)

	for _, dependency := range c.dependencies {
		flags = append(flags, dependency.Flags()...)
	}

	return flags
}

// lock to ensure that only one component collects and activates
// at a time - to avoid multiple activations of subsidiary components
var activationMutex = sync.Mutex{}

func (c *component) Configure(ctx context.Context, options ...app.Option) error {
	return c.configure(ctx, true, false, noopHanlder, options...)
}

func (c *component) configure(ctx context.Context, force bool, activationLocked bool, onActivity onActivity, options ...app.Option) error {
	if !activationLocked {
		activationMutex.Lock()
		defer activationMutex.Unlock()
	}

	err := func() error {
		c.RLock()
		defer c.RUnlock()

		var errs []error

		for _, dependency := range c.dependencies {
			if force || !dependency.State().IsConfigured() {
				if force || dependency.State() == Instantiated ||
					dependency.State() == Unknown {
					if dependency.isConfigurable() {
						err := func() (err error) {
							defer func() {
								if r := recover(); r != nil {
									var ok bool
									if err, ok = r.(error); ok {
										err = fmt.Errorf("%T configure panicked with error: %s, stack: %s", dependency, err, dbg.Stack())
									} else {
										err = fmt.Errorf("%T configure panicked: %v, stack: %s", dependency, r, dbg.Stack())
									}
								}
							}()

							return asComponent(dependency).configure(ctx, force, true, noopHanlder, options...)
						}()

						if err != nil {
							errs = append(errs, err)
							continue
						}
					}
				}
			}
		}

		if len(errs) > 0 {
			return errors.Join(append(
				[]error{fmt.Errorf("depenccy configure failed for: %s", c.id)}, errs...)...)
		}

		return nil
	}()

	if err != nil {
		return err
	}

	if force || !c.State().IsConfigured() {
		if err := c.configureProvider(ctx, onActivity, options...); err != nil {
			return err
		}
	}

	return nil
}

func (c *component) configureProvider(ctx context.Context, onActivity onActivity, options ...app.Option) error {
	c.Lock()
	defer c.Unlock()

	if configurable, ok := c.provider.(Configurable); ok {
		if _, ok := c.provider.(*componentDomain); !ok {
			if err := configurable.Configure(
				withComponent(ctx, c), append(c.options, options...)...); err != nil {
				return err
			}
		}
	}

	if c.state == Instantiated || c.state == Unknown {
		c.setState(Configured, true)
		onActivity(ctx, c, nil)
	}

	return nil
}

func (c *component) Initialize(ctx context.Context, options ...app.Option) error {
	return c.initialize(ctx, false, noopHanlder, options...)
}

func (c *component) initialize(ctx context.Context, activationLocked bool, onActivity onActivity, options ...app.Option) error {
	if !activationLocked {
		activationMutex.Lock()
		defer activationMutex.Unlock()
	}

	err := func() error {
		c.RLock()
		defer c.RUnlock()

		var errs []error

		for _, dependency := range c.dependencies {
			if !dependency.State().IsInitialized() {
				if dependency.State() == Instantiated ||
					dependency.State() == Configured ||
					dependency.State() == Unknown {
					if dependency.isInitializable() {
						err := func() (err error) {
							defer func() {
								if r := recover(); r != nil {
									var ok bool
									if err, ok = r.(error); ok {
										err = fmt.Errorf("%T initialize panicked with error: %s, stack: %s", dependency, err, dbg.Stack())
									} else {
										err = fmt.Errorf("%T initialize panicked: %v, stack: %s", dependency, r, dbg.Stack())
									}
								}
							}()

							return asComponent(dependency).initialize(ctx, true, noopHanlder, options...)
						}()

						if err != nil {
							errs = append(errs, err)
							continue
						}
					}
				}
			}
		}

		if len(errs) > 0 {
			return errors.Join(append(
				[]error{fmt.Errorf("dependency initialize failed for: %s", c.id)}, errs...)...)
		}

		return nil
	}()

	if err != nil {
		return err
	}

	if !c.State().IsInitialized() {
		if err := c.initializeProvider(ctx, onActivity, options...); err != nil {
			return err
		}
	}

	return nil
}

func (c *component) initializeProvider(ctx context.Context, onActivity onActivity, options ...app.Option) error {
	c.Lock()
	defer c.Unlock()

	if configurable, ok := c.provider.(Initializable); ok {
		if _, ok := c.provider.(*componentDomain); !ok {
			if err := configurable.Initialize(withComponent(ctx, c), append(c.options, options...)...); err != nil {
				return err
			}
		}
	}

	if c.state == Configured || c.state == Instantiated || c.state == Unknown {
		c.setState(Initialised, true)
		onActivity(ctx, c, nil)
	}

	return nil
}

type onActivity func(ctx context.Context, c *component, err error)

var noopHanlder = func(context.Context, *component, error) {}

// activate sends an activation message to the actor. Blocks until
// configure/initialize complete (synchronous errors returned to caller).
// Dependency activation proceeds asynchronously after the reply.
func (c *component) activate(ctx context.Context, onActivity onActivity) error {
	reply := make(chan error, 1)
	c.inbox <- actorMsg{kind: msgActivate, ctx: ctx, onActivity: onActivity, reply: reply}
	return <-reply
}

// doActivate is the actual activation logic, run inside the actor goroutine.
// Returns an error from the synchronous configure/initialize phase.
// Dependency activation is fire-and-forget (completion via stateChanged events).
func (c *component) doActivate(ctx context.Context, onActivity onActivity) error {
	activationList := make([]*component, 0, len(c.dependencies))

	err := func() error {
		activationMutex.Lock()
		defer activationMutex.Unlock()

		if err := c.configure(ctx, false, true, onActivity); err != nil {
			return err
		}

		if err := c.initialize(ctx, true, onActivity); err != nil {
			return err
		}

		c.RLock()
		defer c.RUnlock()

		for _, dependency := range c.dependencies {
			if !dependency.State().IsActivated() {
				activationList = append(activationList, asComponent(dependency))
			}
		}

		return nil
	}()

	if err != nil {
		return err
	}

	if c.State().IsActivated() {
		if len(activationList) == 0 {
			return nil
		} else {
			c.activateDependencies(ctx, activationList, onActivity)
		}
	} else {
		c.setState(Activating, false)
		onActivity(ctx, c, err)
		c.activateDependencies(ctx, activationList, onActivity)
	}
	return nil
}

func (c *component) activateDependencies(ctx context.Context, activationList []*component, onActivity onActivity) {
	if len(activationList) == 0 {
		if c.log.TraceEnabled() {
			c.log.Trace("Activated", "component", app.LogInstance(c))
		}
		c.onDependenciesActive(ctx, onActivity)
	} else {
		// Fire-and-forget: send activate to each dependency's actor.
		// Don't block this actor waiting for them — completion is signaled
		// via ComponentStateChanged events which arrive as msgStateChanged
		// messages in our inbox. doOnComponentStateChanged checks whether
		// all dependencies are active and calls onDependenciesActive.
		for _, dependency := range activationList {
			if c.log.TraceEnabled() {
				c.log.Trace("Activating",
					"component", app.LogInstance(c),
					"dependency", app.LogInstance(dependency))
			}
			dependency.activate(ctx, noopHanlder)
		}
	}
}

func (c *component) activateProvider(ctx context.Context, onActivity onActivity) {
	//fmt.Println("activateProvider", c.Id())
	if c.context.Err() != nil {
		c.setState(Deactivated, false)
		onActivity(ctx, c, c.context.Err())
		return
	}

	// TODO - not sure if this is usefukl - as
	// it can leave things hanging - needs invetigation
	//if ctx.Err() != nil {
	//	onActivity(ctx, c, c.context.Err())
	//	return
	//}

	if activatable, ok := c.provider.(Activatable); ok {
		err := activatable.Activate(withComponent(ctx, c))

		if err != nil {
			c.setState(Failed, false)
			onActivity(ctx, c, err)
			return
		}
	}
	c.setState(Active, false)

	deactivatoinWaiters.Lock()
	deactivatoinWaiters.cmap[c.context.Done()] = c
	deactivatoinWaiters.Unlock()
	awaitDeactivationChannels()

	onActivity(ctx, c, nil)
}

func awaitDeactivationChannels() {
	deactivatoinWaiters.Lock()
	defer deactivatoinWaiters.Unlock()

	// Cancel the previous watcher goroutine's ChannelGroup.
	// The goroutine captures its own reference and exits when cancelled.
	if deactivatoinWaiters.ChannelGroup != nil {
		deactivatoinWaiters.cancel()
		deactivatoinWaiters.ChannelGroup = nil
		deactivatoinWaiters.cancel = nil
	}

	if len(deactivatoinWaiters.cmap) == 0 {
		return
	}

	ctx, cancel := context.WithCancel(context.Background())
	channels := util.NewChannelGroup(ctx)
	deactivatoinWaiters.ChannelGroup = channels
	deactivatoinWaiters.cancel = cancel

	for dc := range deactivatoinWaiters.cmap {
		channels.Add(dc)
	}

	// Each goroutine captures its own ChannelGroup reference.
	// When awaitDeactivationChannels is called again, the old ctx is cancelled
	// and the goroutine exits — it never touches the new ChannelGroup.
	myChannels := channels
	waiters := &deactivatoinWaiters
	go func() {
		myChannels.Wait(
			func(ch any, _ any, _ bool) (bool, bool) {
				waiters.Lock()
				c, ok := waiters.cmap[ch]
				delete(waiters.cmap, ch)
				waiters.Unlock()

				if ok {
					c.deactivate(context.Background(), noopHanlder)
				}
				return false, false
			}, nil)
	}()
}

// deactivate sends a deactivation message to the actor. Returns immediately.
func (c *component) deactivate(ctx context.Context, onActivity onActivity) error {
	c.RLock()
	alreadyDeactivated := c.state.IsDeactivated()
	c.RUnlock()

	if alreadyDeactivated {
		onActivity(ctx, c, nil)
		return nil
	}

	c.inbox <- actorMsg{kind: msgDeactivate, ctx: ctx, onActivity: onActivity}
	return nil
}

// doDeactivate is the actual deactivation logic, run inside the actor goroutine.
func (c *component) doDeactivate(ctx context.Context, onActivity onActivity) {
	if err := c.deactivateProvider(ctx, onActivity); err == nil {
		c.deactivateDependencies(ctx, onActivity)
	}
}

func (c *component) deactivateDependencies(ctx context.Context, onActivity onActivity) {
	deactivationList := make([]*component, 0, len(c.dependencies))

	c.RLock()
DEPENDENCIES:
	for _, dependency := range c.dependencies {
		dependency := asComponent(dependency)
		for _, dependent := range dependency.dependents {
			dependent := asComponent(dependent)
			// Skip domain components — they manage membership, not
			// explicit dependencies. A domain shouldn't block its
			// children from deactivating their dependencies.
			if _, isDomain := dependent.provider.(*componentDomain); isDomain {
				continue
			}
			if !dependent.State().IsDeactivated() {
				continue DEPENDENCIES
			}
		}
		deactivationList = append(deactivationList, dependency)
	}
	c.RUnlock()

	if len(deactivationList) == 0 {
		c.onDependenciesDeactivated(ctx)
		onActivity(ctx, c, nil)
	} else {
		// Fire-and-forget: send deactivate to each dependency's actor.
		// Completion is signaled via ComponentStateChanged events which
		// arrive as msgStateChanged in our inbox. doOnComponentStateChanged
		// checks whether all dependencies are deactivated.
		for _, dependency := range deactivationList {
			if dependency.State().IsDeactivated() {
				continue
			}

			if dependency.contextCancel != nil {
				dependency.contextCancel()
			}
			deactivatoinWaiters.Lock()
			delete(deactivatoinWaiters.cmap, dependency.context.Done())
			deactivatoinWaiters.Unlock()
			awaitDeactivationChannels()

			dependency.deactivate(ctx, noopHanlder)
		}
	}
}

func (c *component) deactivateProvider(ctx context.Context, onActivity onActivity) error {
	c.RLock()
	isActive := !(c.State().IsDeactivated())
	c.RUnlock()

	if isActive {
		c.setState(Deactivating, false)
		onActivity(ctx, c, nil)

		//fmt.Printf("%T:%p Deactivating: %v\n", component, component, component.State())

		if deactivatable, ok := c.provider.(Deactivatable); ok {
			err := deactivatable.Deactivate(withComponent(ctx, c))

			if err != nil {
				c.setState(Failed, false)
				onActivity(ctx, c, err)
				return err
			}

			// Cancel the component's context so the global deactivation
			// watcher sees it and doesn't block on this component's
			// Done channel. Don't restart the watcher — the dependency
			// cascade (deactivateDependencies) handles explicit deactivation.
			if c.contextCancel != nil {
				c.contextCancel()
			}

			deactivatoinWaiters.Lock()
			delete(deactivatoinWaiters.cmap, c.context.Done())
			deactivatoinWaiters.Unlock()
		}
	}
	return nil
}

func (c *component) EventBus(key any) *event.ManagedEventBus {
	return c.serviceBus().GetEventBus(key)
}

func (c *component) serviceBus() *event.ServiceBus {
	if domain, isDomain := (c.provider.(ComponentDomain)); isDomain {
		return domain.serviceBus()
	}
	if c.componentDomain != nil {
		return c.componentDomain.serviceBus()
	}
	return nil
}

func (c *component) setDomain(cm *componentDomain, domainLocked bool) error {
	if c.componentDomain != cm {
		var registrations []any

		if c.componentDomain != nil {
			registrations = c.serviceBus().Registrations(c)
			if err := c.serviceBus().UnregisterAll(c); err != nil {
				return err
			}
			c.componentDomain.removeDependency(c, domainLocked)
		}
		c.componentDomain = cm
		if cm != nil {
			if c.id != nil {
				c.id, _ = cm.NewId(context.Background(), c.id.Value())
				if c.log != nil {
					c.log.SetLabels(c.id.String())
				}
			}

			if len(c.dependents) == 0 {
				cm.addDependency(c, domainLocked)
			}

			if err := cm.serviceBus().Register(c, registrations...); err != nil {
				return err
			}

			if c.HasDependencies() {
				for _, dependent := range c.Dependencies() {
					if err := asComponent(dependent).setDomain(cm, domainLocked); err != nil {
						return err
					}
				}
			}
		}
	}

	return nil
}

func (c *component) hasDomain() bool {
	return c.componentDomain != nil
}

func (c *component) HasDependent(dependent relation) bool {
	return c.dependents.Contains(asComponent(dependent))
}

func (c *component) Dependents() relations {
	return c.dependents
}

func (c *component) addDependent(dependent *component, parentLocked bool) error {
	c.dependents = c.dependents.Add(dependent)

	dependent.addDependency(c, parentLocked)

	if domain, ok := dependent.provider.(*componentDomain); ok {
		if c.Domain() != domain {
			return c.setDomain(domain, parentLocked)
		}
	} else {
		if c.Domain() != dependent.Domain() {
			return c.setDomain(dependent.Domain().(*componentDomain), false)
		}
	}

	depState := dependent.State() // thread-safe read via RLock
	if c.state != depState {
		switch {
		case depState.IsActivated():
			if err := c.activate(c.context, noopHanlder); err != nil {
				return err
			}
		case depState.IsInitialized():
			if err := c.initialize(c.context, false, noopHanlder); err != nil {
				return err
			}
		case depState.IsConfigured():
			if err := c.configure(c.context, false, false, noopHanlder); err != nil {
				return err
			}
		}
	}

	return nil
}

func (c *component) removeDependent(dependent *component, dependentLocked bool) error {
	c.dependents = c.dependents.Remove(dependent)
	dependent.removeDependency(c, dependentLocked)
	return nil
}

func (component *component) registerSubscriptions() error {
	if domain, ok := component.provider.(*componentDomain); ok {
		if serviceBus := domain.serviceBus(); serviceBus != nil {
			return serviceBus.Register(component, component.onComponentStateChanged)
		}
	}

	if !component.hasDomain() {
		return nil
	}

	if serviceBus := component.Domain().serviceBus(); serviceBus != nil {
		return serviceBus.Register(component, component.onComponentStateChanged)
	}

	return fmt.Errorf("expected domain (%T) to have non nil service bus", component.Domain())
}

// onComponentStateChanged routes the event through the actor inbox so it's
// processed serially with activate/deactivate. This is the event bus callback.
func (c *component) onComponentStateChanged(event *ComponentStateChanged) {
	c.inbox <- actorMsg{kind: msgStateChanged, event: event}
}

// doOnComponentStateChanged is the actual handler, run inside the actor goroutine.
func (c *component) doOnComponentStateChanged(event *ComponentStateChanged) {
	sourceComponent, isComponent := event.Source().(relation)

	if !isComponent {
		return
	}

	if sourceComponent != c {
		//fmt.Printf("%s Received Component State Changed [%v->%v] from: %s:%p\n",
		//	c.Id(), event.Previous(), event.Current(), sourceComponent.Id(), sourceComponent)

		var allDependenciesActivated bool
		var allDependenciesDeactivated bool
		var state State

		func() {
			c.RLock()
			defer c.RUnlock()
			state = c.state

			if c.dependencies.Contains(sourceComponent) {
				if event.Current() == Active {
					if state.IsActivated() && state != Active {
						allDependenciesActivated = true
						for _, dependent := range c.dependencies {
							if dependent.State() != Active {
								allDependenciesActivated = false
								break
							}
						}
					}
				}

				if event.Current() == Deactivated {
					if state != Deactivated {
						allDependenciesDeactivated = true
					DEPENDENCIES:
						for _, dependency := range c.dependencies {
							dependency := asComponent(dependency)
							for _, dependent := range dependency.dependents {
								if !asComponent(dependent).State().IsDeactivated() {
									continue DEPENDENCIES
								}
							}
							if dependency.State() != Deactivated {
								allDependenciesDeactivated = false
								break
							}
						}
					}
				}
			}
		}()

		if allDependenciesActivated {
			c.onDependenciesActive(c.Context(), noopHanlder)
		}

		if allDependenciesDeactivated && state == Deactivating {
			c.onDependenciesDeactivated(c.Context())
		}
	}
}

func (c *component) onDependenciesActive(ctx context.Context, onActivity onActivity) {
	// this cal get called multiple times as dependencies
	// complete - mke ure that it only activates the
	// provider actions once
	c.Lock()
	activated := c.state == Recovering || c.state == Active
	if !activated {
		c.setState(Recovering, true)
	}
	c.Unlock()

	if activated {
		return
	}

	//fmt.Printf("onDependenciesActive: %s\n", c.Id())

	if recoverable, ok := c.provider.(Recoverable); ok {
		err := recoverable.Recover(withComponent(ctx, c))

		if err != nil {
			c.setState(Failed, false)
			if onActivity != nil {
				onActivity(ctx, c, err)
			}
			return
		}
	}

	c.activateProvider(ctx, onActivity)
}

func (c *component) onDependenciesDeactivated(_ context.Context) {
	c.setState(Deactivated, false)
	if cd, ok := c.provider.(*componentDomain); ok {
		cd.deactivate()
	}
}

func (c *component) addDependency(dependency *component, locked bool) (*component, error) {

	if !locked {
		c.Lock()
		defer c.Unlock()
	}

	if dependency != nil && dependency.provider != nil {
		c.dependencies = c.dependencies.Add(dependency)

		if domain, ok := c.provider.(*componentDomain); ok {
			if dependency.Domain() != domain {
				if err := dependency.setDomain(domain, true); err != nil {
					return nil, err
				}
			}
		}
	}

	return c, nil
}

func (c *component) removeDependency(dependent *component, locked bool) {
	if !locked {
		c.Lock()
		defer c.Unlock()
	}

	if dependent != nil {
		c.dependencies = c.dependencies.Remove(dependent)
	}
}

func (c *component) Dependencies() relations {
	return c.dependencies
}

func (c *component) HasDependencies() bool {
	c.RLock()
	hasDependencies := len(c.dependencies) > 0
	c.RUnlock()
	return hasDependencies
}

func (c *component) GetDependency(context context.Context, selector app.TypedSelector) relation {

	c.RLock()
	defer c.RUnlock()

	for _, dependent := range c.Dependencies() {
		//dependentType := reflect.TypeOf(dependent)
		if /*processorType == dependentType ||*/
		/*(processorType.Kind() == reflect.Interface && dependentType.Implements(processorType)) &&*/ selector.Test(context, dependent) {
			return c
		}
	}

	for _, parent := range c.dependents {
		dependent := parent.GetDependency(context, selector)
		if dependent != nil {
			return dependent
		}
	}

	if c.componentDomain != nil {
		return c.componentDomain.GetDependency(context, selector)
	}

	return nil
}

func (c *component) HasDependency(context context.Context, selector app.TypedSelector) bool {
	return c.GetDependency(context, selector) != nil
}
