from __future__ import annotations

import json
from dataclasses import dataclass
from pathlib import Path
from typing import Any


@dataclass(frozen=True)
class VisibilityPolicy:
    default_visible: bool | None
    services: dict[str, bool]
    operations: dict[str, bool]

    @property
    def is_empty(self) -> bool:
        return self.default_visible is None and not self.services and not self.operations


def _coerce_visible(value: Any) -> bool | None:
    if isinstance(value, bool):
        return value
    if isinstance(value, str):
        lowered = value.strip().lower()
        if lowered in {"true", "1", "yes"}:
            return True
        if lowered in {"false", "0", "no"}:
            return False
    return None


def _extract_visibility_entry(entry: Any) -> bool | None:
    if isinstance(entry, dict):
        return _coerce_visible(entry.get("visible"))
    return _coerce_visible(entry)


def load_visibility_policy(path: Path) -> VisibilityPolicy:
    if not path.exists():
        return VisibilityPolicy(default_visible=None, services={}, operations={})

    parsed = json.loads(path.read_text(encoding="utf-8"))
    if not isinstance(parsed, dict):
        raise ValueError(f"Visibility policy must be a JSON object: {path}")

    default_visible = _coerce_visible(parsed.get("defaultVisible"))
    raw_services = parsed.get("services") or {}
    raw_operations = parsed.get("operations") or {}

    if not isinstance(raw_services, dict) or not isinstance(raw_operations, dict):
        raise ValueError("Visibility policy 'services' and 'operations' must be JSON objects.")

    services: dict[str, bool] = {}
    for key, value in raw_services.items():
        visible = _extract_visibility_entry(value)
        if visible is not None:
            services[str(key)] = visible

    operations: dict[str, bool] = {}
    for key, value in raw_operations.items():
        visible = _extract_visibility_entry(value)
        if visible is not None:
            operations[str(key)] = visible

    return VisibilityPolicy(
        default_visible=default_visible,
        services=services,
        operations=operations,
    )
