"""Base register definitions and parser abstraction."""

from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass
from enum import Enum


class RegisterType(Enum):
    """Modbus register data types."""

    UINT16 = "uint16"
    INT16 = "int16"
    UINT32 = "uint32"


@dataclass(frozen=True)
class RegisterDefinition:
    """Definition of a single Modbus register mapping."""

    field_name: str
    address: int
    register_type: RegisterType = RegisterType.UINT16
    scale: int = 1
    count: int = 1


class DeviceParser(ABC):
    """Abstract base for device-specific register parsers."""

    @property
    @abstractmethod
    def device_type(self) -> str:
        """Return device type identifier."""

    @property
    @abstractmethod
    def product_name(self) -> str:
        """Return human-readable product name."""

    @property
    @abstractmethod
    def registers(self) -> tuple[RegisterDefinition, ...]:
        """Return register definitions for this device."""

    @property
    def register_ranges(self) -> list[tuple[int, int]]:
        """Compute contiguous read blocks to minimise Modbus transactions.

        Returns list of (start_address, count) tuples.
        """
        if not self.registers:
            return []

        sorted_regs = sorted(self.registers, key=lambda r: r.address)
        ranges: list[tuple[int, int]] = []
        block_start = sorted_regs[0].address
        block_end = block_start + sorted_regs[0].count

        for reg in sorted_regs[1:]:
            # Allow small gaps (up to 3) to reduce transactions, but not
            # large gaps which may contain non-existent registers
            if reg.address <= block_end + 3:
                block_end = max(block_end, reg.address + reg.count)
            else:
                ranges.append((block_start, block_end - block_start))
                block_start = reg.address
                block_end = reg.address + reg.count

        ranges.append((block_start, block_end - block_start))
        return ranges

    def parse(self, register_map: dict[int, int]) -> dict[str, float]:
        """Parse raw register values into scaled field values.

        Args:
            register_map: dict mapping register address to raw 16-bit value.

        Returns:
            dict mapping field_name to scaled float value.
        """
        result: dict[str, float] = {}

        for reg in self.registers:
            if reg.address not in register_map:
                continue

            raw = register_map[reg.address]

            if reg.register_type == RegisterType.UINT32:
                high = raw
                low = register_map.get(reg.address + 1, 0)
                raw = (high << 16) | low
            elif reg.register_type == RegisterType.INT16:
                if raw >= 0x8000:
                    raw = raw - 0x10000

            value = raw / reg.scale if reg.scale != 1 else float(raw)
            result[reg.field_name] = value

        return result
