from __future__ import annotations

import json
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Tuple


@dataclass(frozen=True)
class DigitSlot:
    name: str
    power: int
    x: int
    y: int
    w: int
    h: int


@dataclass(frozen=True)
class SymbolSlot:
    name: str
    x: int
    y: int
    w: int
    h: int
    index: Optional[int] = None


@dataclass(frozen=True)
class OdometerLayout:
    screen_size: Tuple[int, int] = (0, 0)
    frame_anchor: Tuple[int, int] = (0, 0)
    digit_slots: Tuple[DigitSlot, ...] = ()
    symbol_slots: Tuple[SymbolSlot, ...] = ()
    display_scale: float = 1.0
    digit_baseline_y: Optional[int] = None
    digit_top_y: Optional[int] = None
    digit_cell_w: int = 0
    digit_cell_h: int = 0
    aligned_cells: Optional[Dict[str, Any]] = None
    source_path: str = ""
    raw: Dict[str, Any] = field(default_factory=dict, repr=False)

    @property
    def aligned_anchor_right(self) -> Optional[int]:
        if isinstance(self.aligned_cells, dict):
            value = self.aligned_cells.get("anchor_right")
            if value is not None:
                return int(value)
        return None

    @property
    def aligned_anchor_bottom(self) -> Optional[int]:
        if isinstance(self.aligned_cells, dict):
            value = self.aligned_cells.get("anchor_bottom")
            if value is not None:
                return int(value)
        if self.digit_baseline_y is not None:
            return int(self.digit_baseline_y)
        return None


def _parse_digit_slot(data: Dict[str, Any]) -> DigitSlot:
    return DigitSlot(
        name=str(data["name"]),
        power=int(data["power"]),
        x=int(data["x"]),
        y=int(data["y"]),
        w=int(data["w"]),
        h=int(data["h"]),
    )


def _parse_symbol_slot(data: Dict[str, Any]) -> SymbolSlot:
    return SymbolSlot(
        name=str(data["name"]),
        x=int(data["x"]),
        y=int(data["y"]),
        w=int(data["w"]),
        h=int(data["h"]),
        index=int(data["index"]) if data.get("index") is not None else None,
    )


def layout_from_dict(
    data: Dict[str, Any], *, source_path: str = ""
) -> OdometerLayout:
    digit_slots = tuple(
        _parse_digit_slot(row) for row in data.get("slots", {}).get("digits", [])
    )
    symbol_slots = tuple(
        _parse_symbol_slot(row) for row in data.get("slots", {}).get("symbols", [])
    )
    return OdometerLayout(
        screen_size=tuple(data.get("screen", [0, 0])),
        frame_anchor=tuple(data.get("frame_anchor", [0, 0])),
        digit_slots=digit_slots,
        symbol_slots=symbol_slots,
        display_scale=float(data.get("display_scale", 1.0)),
        digit_baseline_y=(
            int(data["digit_baseline_y"])
            if data.get("digit_baseline_y") is not None
            else None
        ),
        digit_top_y=(
            int(data["digit_top_y"]) if data.get("digit_top_y") is not None else None
        ),
        digit_cell_w=int(data.get("digit_cell_w", 0)),
        digit_cell_h=int(data.get("digit_cell_h", 0)),
        aligned_cells=data.get("aligned_cells"),
        source_path=source_path,
        raw=dict(data),
    )


def load_layout_file(path: str) -> OdometerLayout:
    with open(path, encoding="utf-8") as handle:
        data = json.load(handle)
    return layout_from_dict(data, source_path=path)


def load_layout_data(path: str) -> Dict[str, Any]:
    with open(path, encoding="utf-8") as handle:
        return json.load(handle)


def layout_to_dict(layout: OdometerLayout) -> Dict[str, Any]:
    if layout.raw:
        return dict(layout.raw)
    data: Dict[str, Any] = {
        "screen": list(layout.screen_size),
        "frame_anchor": list(layout.frame_anchor),
        "display_scale": layout.display_scale,
        "digit_cell_w": layout.digit_cell_w,
        "digit_cell_h": layout.digit_cell_h,
    }
    if layout.digit_baseline_y is not None:
        data["digit_baseline_y"] = layout.digit_baseline_y
    if layout.digit_top_y is not None:
        data["digit_top_y"] = layout.digit_top_y
    if layout.aligned_cells is not None:
        data["aligned_cells"] = dict(layout.aligned_cells)
    if layout.digit_slots or layout.symbol_slots:
        data["slots"] = {
            "digits": [
                {
                    "name": slot.name,
                    "power": slot.power,
                    "x": slot.x,
                    "y": slot.y,
                    "w": slot.w,
                    "h": slot.h,
                }
                for slot in layout.digit_slots
            ],
            "symbols": [
                {
                    "name": slot.name,
                    "x": slot.x,
                    "y": slot.y,
                    "w": slot.w,
                    "h": slot.h,
                    **({"index": slot.index} if slot.index is not None else {}),
                }
                for slot in layout.symbol_slots
            ],
        }
    return data
