"""Interface for {{ module_name }} liquid handler.

This module provides the hardware interface for communicating with
the {{ module_name }} liquid handler. The interface includes full device lifecycle
management: connect, initialize, shutdown, disconnect, and liquid handling
operations: aspirate, dispense, transfer, pick_up_tips, drop_tips.

The interface is independent of MADSci and can be used directly in
notebooks or scripts.

Generated by: madsci new module --template module/liquid_handler
Template version: {{ template_version }}
"""

import logging
from typing import Any, Optional

from {{ module_name }}_types import {{ module_name | pascal_case }}InterfaceConfig

logger = logging.getLogger(__name__)


class {{ module_name | pascal_case }}Interface:
    """Interface for communicating with the {{ module_name }} liquid handler.

    This class encapsulates all hardware communication logic, including
    device lifecycle management (connect, initialize, shutdown, disconnect)
    and liquid handling operations (aspirate, dispense, transfer, tips).

    It can be used independently of MADSci in notebooks or scripts.

    Example:
        >>> config = {{ module_name | pascal_case }}InterfaceConfig()
        >>> interface = {{ module_name | pascal_case }}Interface(config=config)
        >>> interface.connect()
        >>> interface.initialize()
        >>> interface.pick_up_tips()
        >>> interface.aspirate(100.0, well="A1")
        >>> interface.dispense(100.0, well="B1")
        >>> interface.drop_tips()
        >>> interface.shutdown()
        >>> interface.disconnect()
    """

    def __init__(
        self,
        config: Optional[{{ module_name | pascal_case }}InterfaceConfig] = None,
        **kwargs: Any,
    ) -> None:
        """Initialize the interface.

        Args:
            config: Interface configuration. Uses defaults if None.
            **kwargs: Additional configuration options.
        """
        self._config = config or {{ module_name | pascal_case }}InterfaceConfig(**kwargs)
        self._connected = False
        self._initialized = False
        self._has_tips = False
        self._current_volume_ul = 0.0

    @property
    def is_connected(self) -> bool:
        """Check if the interface is connected to the device."""
        return self._connected

    @property
    def is_initialized(self) -> bool:
        """Check if the device has been initialized."""
        return self._initialized

    @property
    def has_tips(self) -> bool:
        """Check if the liquid handler currently has tips attached."""
        return self._has_tips

    def connect(self) -> None:
        """Establish connection to the liquid handler.

        This method opens the communication channel to the device
        (e.g., serial port, TCP/IP socket, vendor SDK connection).

        Raises:
            ConnectionError: If connection fails after retries.
        """
        # TODO: Implement actual connection logic
        # Example:
        # for attempt in range(self._config.retry_count):
        #     try:
        #         self._connection = LiquidHandlerDriver.connect(
        #             timeout=self._config.connection_timeout
        #         )
        #         break
        #     except ConnectionError:
        #         if attempt == self._config.retry_count - 1:
        #             raise

        logger.info("Connecting to {{ module_name }} liquid handler...")
        self._connected = True
        logger.info("Connected to {{ module_name }} liquid handler")

    def disconnect(self) -> None:
        """Close connection to the liquid handler.

        This method safely closes the communication channel. It should
        be called after shutdown() to ensure the device is in a safe state.
        """
        if self._connected:
            # TODO: Implement actual disconnection logic
            # Example:
            # self._connection.close()

            logger.info("Disconnecting from {{ module_name }} liquid handler...")
            self._connected = False
            self._initialized = False
            self._has_tips = False
            self._current_volume_ul = 0.0
            logger.info("Disconnected from {{ module_name }} liquid handler")

    def initialize(self) -> None:
        """Initialize the liquid handler for operation.

        This method performs device-specific initialization such as
        homing, calibration, self-test, or loading configuration.
        Must be called after connect() and before executing operations.

        Raises:
            RuntimeError: If not connected to the device.
        """
        if not self._connected:
            raise RuntimeError("Not connected to device. Call connect() first.")

        # TODO: Implement actual device initialization
        # Example:
        # self._connection.home()
        # self._connection.calibrate()
        # self._connection.load_deck_layout()

        logger.info("Initializing {{ module_name }} liquid handler...")
        self._initialized = True
        logger.info("{{ module_name | pascal_case }} liquid handler initialized")

    def shutdown(self) -> None:
        """Safely shut down the liquid handler.

        This method puts the device into a safe state before disconnecting.
        It may include parking the pipette head, ejecting tips,
        or releasing locked resources.

        Raises:
            RuntimeError: If not connected to the device.
        """
        if not self._connected:
            raise RuntimeError("Not connected to device. Call connect() first.")

        # TODO: Implement actual shutdown logic
        # Example:
        # if self._has_tips:
        #     self._connection.eject_tips()
        # self._connection.park()
        # self._connection.save_calibration()

        logger.info("Shutting down {{ module_name }} liquid handler...")
        self._initialized = False
        logger.info("{{ module_name | pascal_case }} liquid handler shut down")

    def aspirate(
        self,
        volume_ul: float,
        well: Optional[str] = None,
        speed_ul_per_s: Optional[float] = None,
    ) -> None:
        """Aspirate liquid from a well or current position.

        Args:
            volume_ul: Volume to aspirate in microliters.
            well: Well identifier to aspirate from (e.g., 'A1'). Optional.
            speed_ul_per_s: Aspiration speed in uL/s. Uses default if None.

        Raises:
            RuntimeError: If not connected or not initialized.
        """
        if not self._connected:
            raise RuntimeError("Not connected to device. Call connect() first.")
        if not self._initialized:
            raise RuntimeError("Device not initialized. Call initialize() first.")

        speed = speed_ul_per_s or self._config.default_speed_ul_per_s

        # TODO: Implement actual aspirate logic
        # Example:
        # if well:
        #     self._connection.move_to(well)
        # self._connection.aspirate(volume_ul, speed=speed)

        logger.info(
            f"Aspirating {volume_ul} uL from {well or 'current position'} at {speed} uL/s"
        )
        self._current_volume_ul += volume_ul

    def dispense(
        self,
        volume_ul: float,
        well: Optional[str] = None,
        speed_ul_per_s: Optional[float] = None,
    ) -> None:
        """Dispense liquid into a well or current position.

        Args:
            volume_ul: Volume to dispense in microliters.
            well: Well identifier to dispense into (e.g., 'A1'). Optional.
            speed_ul_per_s: Dispense speed in uL/s. Uses default if None.

        Raises:
            RuntimeError: If not connected or not initialized.
        """
        if not self._connected:
            raise RuntimeError("Not connected to device. Call connect() first.")
        if not self._initialized:
            raise RuntimeError("Device not initialized. Call initialize() first.")

        speed = speed_ul_per_s or self._config.default_speed_ul_per_s

        # TODO: Implement actual dispense logic
        # Example:
        # if well:
        #     self._connection.move_to(well)
        # self._connection.dispense(volume_ul, speed=speed)

        logger.info(
            f"Dispensing {volume_ul} uL into {well or 'current position'} at {speed} uL/s"
        )
        self._current_volume_ul = max(0.0, self._current_volume_ul - volume_ul)

    def transfer(
        self,
        volume_ul: float,
        source_well: str,
        dest_well: str,
        speed_ul_per_s: Optional[float] = None,
    ) -> None:
        """Transfer liquid from one well to another.

        Performs an aspirate from source_well followed by a dispense
        into dest_well.

        Args:
            volume_ul: Volume to transfer in microliters.
            source_well: Well identifier to aspirate from (e.g., 'A1').
            dest_well: Well identifier to dispense into (e.g., 'B1').
            speed_ul_per_s: Pipetting speed in uL/s. Uses default if None.

        Raises:
            RuntimeError: If not connected or not initialized.
        """
        if not self._connected:
            raise RuntimeError("Not connected to device. Call connect() first.")
        if not self._initialized:
            raise RuntimeError("Device not initialized. Call initialize() first.")

        logger.info(f"Transferring {volume_ul} uL from {source_well} to {dest_well}")
        self.aspirate(volume_ul, well=source_well, speed_ul_per_s=speed_ul_per_s)
        self.dispense(volume_ul, well=dest_well, speed_ul_per_s=speed_ul_per_s)

    def pick_up_tips(self, tip_rack: Optional[str] = None) -> None:
        """Pick up disposable tips.

        Args:
            tip_rack: Tip rack identifier. Uses default rack if None.

        Raises:
            RuntimeError: If not connected or not initialized.
        """
        if not self._connected:
            raise RuntimeError("Not connected to device. Call connect() first.")
        if not self._initialized:
            raise RuntimeError("Device not initialized. Call initialize() first.")

        # TODO: Implement actual tip pickup logic
        # Example:
        # if tip_rack:
        #     self._connection.move_to_tip_rack(tip_rack)
        # self._connection.pick_up_tips()

        logger.info(f"Picking up tips from {tip_rack or 'default rack'}")
        self._has_tips = True

    def drop_tips(self) -> None:
        """Drop current tips into the waste.

        Raises:
            RuntimeError: If not connected or not initialized.
        """
        if not self._connected:
            raise RuntimeError("Not connected to device. Call connect() first.")
        if not self._initialized:
            raise RuntimeError("Device not initialized. Call initialize() first.")

        # TODO: Implement actual tip drop logic
        # Example:
        # self._connection.drop_tips()

        logger.info("Dropping tips")
        self._has_tips = False
        self._current_volume_ul = 0.0

    def get_status(self) -> dict:
        """Get the current status of the liquid handler.

        Returns:
            dict: Device status information including connection state,
                initialization state, tip state, and volume information.

        Raises:
            RuntimeError: If not connected to the device.
        """
        if not self._connected:
            raise RuntimeError("Not connected to device. Call connect() first.")

        # TODO: Implement actual status retrieval
        # Example:
        # hw_status = self._connection.get_status()
        # return {
        #     "connected": self._connected,
        #     "initialized": self._initialized,
        #     "has_tips": hw_status.has_tips,
        #     "current_volume_ul": hw_status.current_volume,
        #     "status_code": hw_status.code,
        # }

        logger.info("Getting {{ module_name }} liquid handler status")
        return {
            "connected": self._connected,
            "initialized": self._initialized,
            "has_tips": self._has_tips,
            "current_volume_ul": self._current_volume_ul,
            "status_code": 0,
        }
