# Copyright (c) 2026 동일비전(Dongil Vision Korea). All Rights Reserved.
from typing import Any, Callable, Dict, List, Optional, Type, Union

class DeviceSchema:
    """Helper class to build DEVICE_PROPERTY dictionary with a fluent API."""

    def __init__(self):
        self._props = {}
        self._resync = []

    def add(
        self,
        name: str,
        dtype: Optional[Type] = None,
        default: Any = None,
        validator: Optional[Union[Callable, str]] = None,
        setter: Optional[Union[Callable, str]] = None,
        getter: Optional[Union[Callable, str]] = None,
        readonly: bool = False,
        debounce: float = 0,
        per_sync: bool = False,
        notify_mode: str = "immediate"
    ) -> "DeviceSchema":
        """Add a property definition."""
        # Handle "name:int=100" style key if provided as name
        if ":" in name or "=" in name:
            # We'll just use name as key if it has formatting, 
            # but usually fluent API uses separate params.
            key = name
        else:
            dtype_name = dtype.__name__ if dtype else "Any"
            key = f"{name}:{dtype_name}"
            if default is not None:
                key += f"={default}"

        self._props[key] = {
            "validator": validator,
            "setter": setter,
            "getter": getter,
            "readonly": readonly,
            "debounce": debounce,
            "per_sync": per_sync,
            "notify_mode": notify_mode
        }
        return self

    def set_resync(
        self,
        condition: Union[Dict, Callable],
        order: List[Union[str, List[str]]],
        open: Optional[str] = None,
        close: Optional[str] = None
    ) -> "DeviceSchema":
        """Add a resync group definition."""
        group = {
            "condition": condition,
            "order": order
        }
        if open: group["open"] = open
        if close: group["close"] = close
        
        self._resync.append(group)
        return self

    def build(self) -> Dict[str, Any]:
        """Return the constructed DEVICE_PROPERTY dictionary."""
        result = self._props.copy()
        if len(self._resync) == 1:
            result["@resync"] = self._resync[0]
        elif len(self._resync) > 1:
            result["@resync"] = self._resync
        return result


def parse_key(key: str):
    """Parse 'name:dtype=default' string."""
    name_part = key
    default = None
    if "=" in key:
        name_part, default_str = key.split("=", 1)
        # Type inference for default? Better to just keep as string and let dtype handle
        default = default_str

    dtype = None
    name = name_part
    if ":" in name_part:
        name, dtype_str = name_part.split(":", 1)
        # Map dtype_str to type
        type_map = {
            "int": int,
            "float": float,
            "bool": bool,
            "str": str,
            "Any": None
        }
        dtype = type_map.get(dtype_str)
        
        # Handle bool default
        if dtype is bool and default is not None:
            default = default.lower() in ("true", "1", "yes")
        elif dtype is int and default is not None:
            default = int(default)
        elif dtype is float and default is not None:
            default = float(default)

    return name, dtype, default


def parse_condition(cond):
    """Convert JSON condition dict to a callable(instance) -> bool."""
    if callable(cond):
        return cond
    
    if not isinstance(cond, dict):
        raise ValueError(f"Invalid condition type: {type(cond)}. Must be dict or callable.")

    _OPS = {
        "Eq": lambda s, k, v: getattr(s, k) == v,
        "Ne": lambda s, k, v: getattr(s, k) != v,
        "Gt": lambda s, k, v: getattr(s, k) > v,
        "Lt": lambda s, k, v: getattr(s, k) < v,
        "All": lambda s, sub: all(parse_condition(c)(s) for c in sub),
        "Any": lambda s, sub: any(parse_condition(c)(s) for c in sub),
        "Not": lambda s, sub: not parse_condition(sub)(s),
    }

    op, args = list(cond.items())[0]
    if op not in _OPS:
        raise ValueError(f"Unsupported condition operator: {op}")

    if op in ("All", "Any", "Not"):
        return lambda s: _OPS[op](s, args)
    else:
        # Eq, Ne, Gt, Lt expect [key, value]
        if not isinstance(args, list) or len(args) != 2:
            raise ValueError(f"Operator {op} expects [key, value] as arguments")
        return lambda s: _OPS[op](s, *args)
