"""Pluggable EEPROM access backends.

A backend abstracts *where the bytes come from and go to*. The engine and CLI
only ever deal with whole-image reads and per-byte/whole-image writes, so a new
transport (libusb) can be added later without touching field logic.

Contract:
  - read_image() -> bytes            (FREE; never gated)
  - write_bytes(offset, values)      (GATED; physical writes require escalation
                                      handled one layer up, in the CLI)
  - reload()                         (ask the device to reload its EEPROM image)

Backends must NOT perform escalation themselves; the CLI owns the human-in-the-
loop gate so the policy lives in exactly one place.
"""
from __future__ import annotations

import abc


class BackendError(Exception):
    pass


class EepromBackend(abc.ABC):
    #: True if this backend talks to real hardware (writes must be escalated).
    physical: bool = False
    name: str = "base"

    @abc.abstractmethod
    def read_image(self) -> bytes:
        """Return the full EEPROM image. Free / non-destructive."""

    @abc.abstractmethod
    def write_bytes(self, offset: int, values: bytes) -> None:
        """Write `values` starting at `offset`. May be per-byte under the hood."""

    def reload(self) -> None:
        """Optional: ask the device to reload the EEPROM (replug equivalent)."""
        raise BackendError(f"{self.name} backend does not support reload")

    def close(self) -> None:
        pass
