"""PID + DTC data model and registry, backed by a vehicle Profile.

The actual PID numbers, scaling formulas, and DTC meanings live in JSON
vehicle profiles under profiles/ (data, not code) so the app is vehicle-
agnostic and others can contribute profiles.  This module is the in-memory
model + lookups; profile.py loads/saves the JSON.
"""
from dataclasses import dataclass, field
from typing import Callable, Tuple


@dataclass
class Pid:
    key: str
    name: str
    mode: str = "22"                # "01" | "22" | "atrv" | "derived"
    pid: str = ""                   # hex: "1446" (m22) or "0C" (m01)
    nbytes: int = 2
    formula: str = ""               # scaling expr in A/B/... (raw) or dep keys (derived)
    decode: Callable = None         # built from formula by profile loader
    unit: str = ""
    group: str = "misc"             # fuel | ficm | air | engine | driveline | power | misc
    vmin: float = 0.0
    vmax: float = 100.0
    confidence: str = "verified"    # verified | doc | tentative
    round: int = None               # display rounding (None=raw float, 0=int)
    deps: Tuple[str, ...] = ()
    notes: str = ""
    # optional gauge warning zones (all None = neutral, no redline drawn).
    # high-side: value >= threshold -> warn/redline.  low-side: value <= -> warn/redline.
    warn_hi: float = None
    redline_hi: float = None
    warn_lo: float = None
    redline_lo: float = None

    def zone(self, v):
        """Classify a value as 'crit' (red), 'warn' (amber), or 'ok' (green)."""
        if v is None:
            return "ok"
        if self.redline_hi is not None and v >= self.redline_hi:
            return "crit"
        if self.redline_lo is not None and v <= self.redline_lo:
            return "crit"
        if self.warn_hi is not None and v >= self.warn_hi:
            return "warn"
        if self.warn_lo is not None and v <= self.warn_lo:
            return "warn"
        return "ok"


@dataclass
class Dtc:
    code: str
    desc: str
    system: str = "powertrain"
    no_start: bool = False
    causes: str = ""


class PidRegistry:
    """In-memory PID set + presets for the active vehicle profile."""

    def __init__(self, profile):
        self.profile = profile
        self._by_key = {p.key: p for p in profile.pids}
        self.presets = dict(profile.presets)

    def get(self, key):
        return self._by_key.get(key)

    def all(self):
        return list(self._by_key.values())

    def group(self, g):
        return [p for p in self._by_key.values() if p.group == g]

    def preset(self, name):
        return [self._by_key[k] for k in self.presets.get(name, []) if k in self._by_key]

    def preset_names(self):
        return list(self.presets.keys())


_GENERIC = None


def _generic_dtcs():
    """Lazy-load the bundled generic SAE DTC database (code -> Dtc)."""
    global _GENERIC
    if _GENERIC is None:
        _GENERIC = {}
        try:
            import json
            import os
            from .profile import profiles_dir
            path = os.path.join(profiles_dir(), "_data", "generic-dtcs.json")
            for d in json.load(open(path)).get("dtcs", []):
                _GENERIC[d["code"]] = Dtc(code=d["code"], desc=d.get("desc", ""),
                                          system=d.get("system", "powertrain"),
                                          no_start=d.get("no_start", False))
        except Exception:
            pass
    return _GENERIC


class DtcDatabase:
    def __init__(self, profile):
        self._db = {d.code: d for d in profile.dtcs}   # profile codes take priority

    def get(self, code):
        return (self._db.get(code) or _generic_dtcs().get(code)
                or Dtc(code, "(unknown - look up this code)"))

    def all(self):
        return list(self._db.values())
