Service Registry
===============

Overview
--------

The ServiceRegistry provides centralized service and widget management for pyqt-reactive applications. Services register themselves at creation time, making them available throughout the application without manual tracking or circular dependencies.

**Module**: ``pyqt_reactive.services.service_registry``

Core Concepts
-------------

Service Registration
~~~~~~~~~~~~~~~~~~

Services register themselves via the ``AutoRegisterServiceMixin``:

.. code-block:: python

    from pyqt_reactive.services import ServiceRegistry, AutoRegisterServiceMixin

    class PlateManagerWidget(QWidget, AutoRegisterServiceMixin):
        """Widget that auto-registers with ServiceRegistry."""

        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            # AutoRegisterServiceMixin registers this instance automatically

Service Resolution
~~~~~~~~~~~~~~~~~~

Services are retrieved by type:

.. code-block:: python

    from pyqt_reactive.services import ServiceRegistry
    from my_widgets import PlateManagerWidget

    # Get plate manager widget
    plate_manager = ServiceRegistry.get(PlateManagerWidget)

    if plate_manager:
        # Use the widget
        plate_manager.refresh_plate_list()

AutoRegisterServiceMixin
------------------------

Mixin for automatic service registration.

**Usage**:

.. code-block:: python

    class MyWidget(QWidget, AutoRegisterServiceMixin):
        """Widget that auto-registers with ServiceRegistry."""

        def __init__(self):
            super().__init__()
            # ServiceRegistry.set(MyWidget, self) called automatically

**Implementation**:

.. code-block:: python

    class AutoRegisterServiceMixin:
        """Mixin to auto-register widgets with ServiceRegistry."""

        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            ServiceRegistry.set(type(self), self)

ServiceRegistry API
-------------------

Register Services
~~~~~~~~~~~~~~~~~

.. code-block:: python

    from pyqt_reactive.services import ServiceRegistry

    # Register a service instance
    ServiceRegistry.set(MyService, my_service_instance)

    # Register with explicit service key
    ServiceRegistry.set("my_service_key", my_service_instance)

Retrieve Services
~~~~~~~~~~~~~~~~~~

.. code-block:: python

    # Get by type (preferred)
    plate_manager = ServiceRegistry.get(PlateManagerWidget)

    # Get by key
    service = ServiceRegistry.get("my_service_key")

    # Get with default fallback
    manager = ServiceRegistry.get(PlateManagerWidget, None)

Check Registration
~~~~~~~~~~~~~~~~~~

.. code-block:: python

    # Check if service exists
    if ServiceRegistry.has(PlateManagerWidget):
        # Use it
        pass

    # Check with key
    if ServiceRegistry.has("my_service"):
        pass

Clear Services
~~~~~~~~~~~~~~

.. code-block:: python

    # Clear a specific service
    ServiceRegistry.clear(MyService)

    # Clear all services (use with caution)
    ServiceRegistry.clear_all()

Common Use Cases
----------------

Widget-to-Widget Communication
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Previously required traversing widget trees or maintaining manual references:

.. code-block:: python

    # Before: Traversal through all windows
    for widget in QApplication.topLevelWidgets():
        if hasattr(widget, 'plate_manager'):
            plate_manager = widget.plate_manager
            break

Now use ServiceRegistry:

.. code-block:: python

    # After: Direct lookup
    from pyqt_reactive.services import ServiceRegistry
    from my_widgets import PlateManagerWidget

    plate_manager = ServiceRegistry.get(PlateManagerWidget)
    if plate_manager:
        # Connect signals
        self.plate_selected.connect(plate_manager.set_current_plate)

Window Handler Registration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Handlers access widgets from ServiceRegistry:

.. code-block:: python

    def create_plate_config_window(scope_id: str, object_state=None):
        from pyqt_reactive.services import ServiceRegistry
        from my_widgets import PlateManagerWidget

        # Get plate manager from ServiceRegistry
        plate_manager = ServiceRegistry.get(PlateManagerWidget)
        if not plate_manager:
            logger.warning("Could not find PlateManager for plate config window")
            return None

        orchestrator = ObjectStateRegistry.get_object(scope_id)
        if not orchestrator:
            return None

        window = ConfigWindow(
            config_class=PipelineConfig,
            current_config=orchestrator.pipeline_config,
            scope_id=scope_id,
        )
        window.show()
        return window

Pipeline-to-Plate Manager Connection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Connect pipeline editor to plate manager via ServiceRegistry:

.. code-block:: python

    def connect_pipeline_to_plate_manager(pipeline_widget):
        from my_widgets import PlateManagerWidget
        from pyqt_reactive.services import ServiceRegistry

        # Get plate manager from ServiceRegistry
        plate_manager_widget = ServiceRegistry.get(PlateManagerWidget)

        if plate_manager_widget:
            # Connect plate selection signal to pipeline editor
            plate_manager_widget.plate_selected.connect(
                pipeline_widget.set_current_plate
            )

            # Set current plate if already selected
            if plate_manager_widget.selected_plate_path:
                pipeline_widget.set_current_plate(
                    plate_manager_widget.selected_plate_path
                )

            logger.debug("Connected pipeline editor to plate manager")
        else:
            logger.warning("Could not find plate manager widget to connect")

Service Lifecycle
-----------------

Registration Timing
~~~~~~~~~~~~~~~~~~

Services are registered at widget creation time:

.. code-block:: python

    # In main window
    self.plate_manager_widget = PlateManagerWidget(...)
    # PlateManagerWidget.__init__ calls ServiceRegistry.set(PlateManagerWidget, self)

    # Plate manager is now available immediately
    other_widget.connect_to_plate_manager()

Unregistration
~~~~~~~~~~~~~~

Widgets are unregistered automatically when destroyed (if subclassing QObject):

.. code-block:: python

    # ServiceRegistry hooks into object destruction
    def on_object_destroyed(self):
        service_type = self._registered_service_type
        ServiceRegistry.clear(service_type)

Singleton Pattern
~~~~~~~~~~~~~~~~~~

ServiceRegistry enforces one instance per service type:

.. code-block:: python

    # First registration
    service1 = MyService()
    ServiceRegistry.set(MyService, service1)

    # Second registration overwrites first
    service2 = MyService()
    ServiceRegistry.set(MyService, service2)  # Replaces service1

    # Only service2 is available
    retrieved = ServiceRegistry.get(MyService)
    assert retrieved is service2  # True

Thread Safety
-------------

ServiceRegistry is **not thread-safe** by default. All service operations should occur on the main GUI thread:

.. code-block:: python

    # CORRECT: Main thread
    def main_thread_function():
        service = ServiceRegistry.get(MyService)
        service.do_something()

    # INCORRECT: Background thread
    def background_thread_function():
        # This can cause race conditions
        service = ServiceRegistry.get(MyService)
        service.do_something()

If thread-safe access is needed, implement a wrapper:

.. code-block:: python

    from threading import Lock

    class ThreadSafeServiceRegistry:
        def __init__(self):
            self._services = {}
            self._lock = Lock()

        def get(self, service_type, default=None):
            with self._lock:
                return self._services.get(service_type, default)

        def set(self, service_type, instance):
            with self._lock:
                self._services[service_type] = instance

Best Practices
--------------

Use Type-Based Keys
~~~~~~~~~~~~~~~~~~

Prefer class types over string keys:

.. code-block:: python

    # PREFERRED: Type-based
    ServiceRegistry.set(PlateManagerWidget, widget)
    manager = ServiceRegistry.get(PlateManagerWidget)

    # AVOID: String-based (unless necessary)
    ServiceRegistry.set("plate_manager", widget)
    manager = ServiceRegistry.get("plate_manager")

Check Before Use
~~~~~~~~~~~~~~~~

Always check if service exists:

.. code-block:: python

    manager = ServiceRegistry.get(PlateManagerWidget)
    if manager:
        manager.refresh()
    else:
        logger.warning("PlateManager not available")

Auto-Register Widgets
~~~~~~~~~~~~~~~~~~~~~

Use ``AutoRegisterServiceMixin`` for widgets:

.. code-block:: python

    # DO: Auto-register
    class PlateManagerWidget(QWidget, AutoRegisterServiceMixin):
        def __init__(self):
            super().__init__()
            # Registered automatically

    # AVOID: Manual registration
    class PlateManagerWidget(QWidget):
        def __init__(self):
            super().__init__()
            ServiceRegistry.set(PlateManagerWidget, self)  # Boilerplate

Avoid Circular Dependencies
~~~~~~~~~~~~~~~~~~~~~~~~~~

ServiceRegistry breaks dependency chains:

.. code-block:: python

    # BEFORE: Circular dependency
    # PipelineEditor needs PlateManager
    # PlateManager needs PipelineEditor
    # Both import each other → circular

    # AFTER: ServiceRegistry breaks the cycle
    # PipelineEditor imports PlateManagerWidget (type only)
    # PlateManagerWidget imports PipelineEditorWidget (type only)
    # Both resolve via ServiceRegistry.get() at runtime

Service Keys Design
~~~~~~~~~~~~~~~~~~

Design service keys with clear semantics:

.. code-block:: python

    # Use concrete widget/service types
    ServiceRegistry.set(PlateManagerWidget, widget)

    # For multiple instances, use distinct service classes
    class MainPlateManagerWidget(PlateManagerWidget): pass
    class SecondaryPlateManagerWidget(PlateManagerWidget): pass

    ServiceRegistry.set(MainPlateManagerWidget, widget1)
    ServiceRegistry.set(SecondaryPlateManagerWidget, widget2)

Integration Points
------------------

Window Handlers
~~~~~~~~~~~~~~

Window handlers use ServiceRegistry to access widgets:

.. code-block:: python

    def create_step_editor_window(scope_id: str, object_state=None):
        plate_manager = ServiceRegistry.get(PlateManagerWidget)
        orchestrator = ObjectStateRegistry.get_object(plate_path)

        window = DualEditorWindow(
            step_data=step,
            orchestrator=orchestrator,
        )
        return window

Main Window
~~~~~~~~~~~~

Main window removes widget tracking:

.. code-block:: python

    class MainWindow(QMainWindow):
        def __init__(self):
            # Before: self.floating_windows = {}
            # After: No tracking needed

            self.plate_manager_widget = PlateManagerWidget()
            # Auto-registered, accessible everywhere

See Also
--------

- :doc:`scope_window_factory` - Handler-based window creation
- :doc:`ui_services_architecture` - Service layer architecture
- :doc:`widget_protocol_system` - Widget interaction patterns
