"""User-defined custom VCP controls. Pure, no GTK.

ddcutil's `getvcp ALL` only enumerates *known* MCCS codes, so manufacturer-
specific features (e.g. 0xE1) are invisible to auto-discovery even when the
monitor accepts them. Custom controls let the user pin an arbitrary
`setvcp <code> <value>` as a one-click action, persisted under XDG config.
"""

from __future__ import annotations

import json
import os
from dataclasses import dataclass
from pathlib import Path

from .profile import atomic_write_json

_APP_DIR = "big-ddcgui"
_FILE = "custom.json"


@dataclass
class CustomControl:
    code: int
    label: str
    value: int

    def as_json(self) -> dict[str, object]:
        return {"code": f"{self.code:02X}", "label": self.label, "value": self.value}


def parse_code(text: str) -> int:
    """Parse a VCP feature code (always hex), e.g. 'E1', 'e1', '0xE1'."""
    code = int(text.strip(), 16)
    if not 0 <= code <= 0xFF:
        raise ValueError("code must be 0x00–0xFF")
    return code


def parse_value(text: str) -> int:
    """Parse a value: '0x..' is hex, bare digits are decimal."""
    stripped = text.strip().lower()
    base = 16 if stripped.startswith("0x") else 10
    value = int(stripped, base)
    if not 0 <= value <= 0xFFFF:
        raise ValueError("value must be 0–65535")
    return value


def config_path() -> Path:
    base = os.environ.get("XDG_CONFIG_HOME") or os.path.join(os.path.expanduser("~"), ".config")
    return Path(base) / _APP_DIR / _FILE


def parse_custom(data: object) -> list[CustomControl]:
    """Validate a decoded custom-controls document. Skips malformed entries."""
    if not isinstance(data, dict):
        return []
    items = data.get("controls")
    if not isinstance(items, list):
        return []
    controls: list[CustomControl] = []
    for item in items:
        if not isinstance(item, dict):
            continue
        try:
            code = int(str(item["code"]), 16)
            value = int(item["value"])
            label = str(item["label"])
        except (KeyError, ValueError, TypeError):
            continue
        if 0 <= code <= 0xFF and 0 <= value <= 0xFFFF and label:
            controls.append(CustomControl(code=code, label=label, value=value))
    return controls


def load_custom() -> list[CustomControl]:
    path = config_path()
    if not path.exists():
        return []
    try:
        return parse_custom(json.loads(path.read_text(encoding="utf-8")))
    except (OSError, json.JSONDecodeError):
        return []


def save_custom(controls: list[CustomControl]) -> None:
    path = config_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    atomic_write_json(path, {"controls": [c.as_json() for c in controls]})
