"""Views & Visualization registry.

Core platform service that owns:

* **View types** — registered view renderers (list, detail, board, kanban,
  calendar, timeline, tree, org_chart, chart, dashboard).
* **View configurations** — persisted tenant-scoped view definitions that
  describe *how* an entity's data should be rendered in a particular view.
* **Dashboard widgets** — registered widget types that modules can expose.
* **Chart types** — registered chart renderers (bar, line, pie, donut,
  area, scatter, radar, funnel, heatmap).

Every module / feature / service connects through this registry — never
through ad-hoc per-module view implementations.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from threading import Lock
from typing import Any, ClassVar


class VisualizationError(RuntimeError):
    """View / visualization registration or resolution error."""


# ---------------------------------------------------------------------------
# View type registry
# ---------------------------------------------------------------------------

@dataclass(frozen=True)
class ViewTypeSpec:
    """Describes a view renderer available to the platform.

    ``kind`` must match one of the supported view kinds.
    ``supports_custom_config`` means users can create custom saved views
    of this type via the API.
    """

    kind: str
    label_key: str
    description: str = ""
    icon: str = ""
    supports_custom_config: bool = True
    supports_saved_views: bool = True
    supports_filters: bool = True
    supports_export: bool = False
    supports_grouping: bool = False
    config_schema: dict[str, Any] = field(default_factory=dict)

    def __post_init__(self) -> None:
        if not self.kind or not self.label_key:
            raise VisualizationError("ViewTypeSpec requires kind and label_key")


# Built-in view types registered at startup.
_BUILTIN_VIEW_TYPES: tuple[ViewTypeSpec, ...] = (
    ViewTypeSpec(
        kind="list",
        label_key="visualization.view_types.list",
        description="Tabular list view with sort, filter, pagination",
        icon="list",
        supports_export=True,
    ),
    ViewTypeSpec(
        kind="detail",
        label_key="visualization.view_types.detail",
        description="Detail view with tabs/sections",
        icon="file-text",
        supports_custom_config=True,
        supports_saved_views=False,
    ),
    ViewTypeSpec(
        kind="board",
        label_key="visualization.view_types.board",
        description="Board (card) view with columns",
        icon="layout",
        supports_grouping=True,
    ),
    ViewTypeSpec(
        kind="kanban",
        label_key="visualization.view_types.kanban",
        description="Kanban board with swimlanes and drag-drop",
        icon="columns",
        supports_grouping=True,
    ),
    ViewTypeSpec(
        kind="calendar",
        label_key="visualization.view_types.calendar",
        description="Calendar view by date fields",
        icon="calendar",
        supports_export=True,
    ),
    ViewTypeSpec(
        kind="timeline",
        label_key="visualization.view_types.timeline",
        description="Timeline / Gantt view with date ranges",
        icon="clock",
        supports_export=True,
    ),
    ViewTypeSpec(
        kind="tree",
        label_key="visualization.view_types.tree",
        description="Hierarchical tree view for parent-child data",
        icon="git-branch",
        supports_grouping=False,
    ),
    ViewTypeSpec(
        kind="org_chart",
        label_key="visualization.view_types.org_chart",
        description="Organization chart view",
        icon="users",
        supports_grouping=False,
    ),
    ViewTypeSpec(
        kind="chart",
        label_key="visualization.view_types.chart",
        description="Chart view (bar, line, pie, area…)",
        icon="bar-chart",
        supports_export=True,
        supports_grouping=True,
    ),
    ViewTypeSpec(
        kind="dashboard",
        label_key="visualization.view_types.dashboard",
        description="Dashboard with widget grid layout",
        icon="grid",
        supports_export=False,
        supports_saved_views=True,
    ),
)


# ---------------------------------------------------------------------------
# Chart type registry
# ---------------------------------------------------------------------------

@dataclass(frozen=True)
class ChartTypeSpec:
    """Describes a chart renderer type."""

    kind: str
    label_key: str
    description: str = ""
    icon: str = ""


_BUILTIN_CHART_TYPES: tuple[ChartTypeSpec, ...] = (
    ChartTypeSpec(kind="bar", label_key="visualization.chart_types.bar", description="Bar chart"),
    ChartTypeSpec(kind="line", label_key="visualization.chart_types.line", description="Line chart"),
    ChartTypeSpec(kind="pie", label_key="visualization.chart_types.pie", description="Pie chart"),
    ChartTypeSpec(kind="donut", label_key="visualization.chart_types.donut", description="Donut chart"),
    ChartTypeSpec(kind="area", label_key="visualization.chart_types.area", description="Area chart"),
    ChartTypeSpec(kind="scatter", label_key="visualization.chart_types.scatter", description="Scatter plot"),
    ChartTypeSpec(kind="radar", label_key="visualization.chart_types.radar", description="Radar chart"),
    ChartTypeSpec(kind="funnel", label_key="visualization.chart_types.funnel", description="Funnel chart"),
    ChartTypeSpec(kind="heatmap", label_key="visualization.chart_types.heatmap", description="Heatmap"),
)


# ---------------------------------------------------------------------------
# Dashboard widget type registry
# ---------------------------------------------------------------------------

@dataclass(frozen=True)
class WidgetTypeSpec:
    """Describes a dashboard widget that modules can register."""

    name: str
    kind: str  # "kpi" | "chart" | "data-table" | "calendar-mini" | "activity-stream" | "custom"
    label_key: str
    description: str = ""
    icon: str = ""
    default_cols: int = 3
    config_schema: dict[str, Any] = field(default_factory=dict)
    module: str = ""  # owning module name

    def __post_init__(self) -> None:
        if self.kind not in {"kpi", "chart", "data-table", "calendar-mini", "activity-stream", "custom"}:
            raise VisualizationError(f"Unknown widget kind '{self.kind}'")


_BUILTIN_WIDGET_TYPES: tuple[WidgetTypeSpec, ...] = (
    WidgetTypeSpec(
        name="kpi-card",
        kind="kpi",
        label_key="visualization.widgets.kpi_card",
        description="Single-metric KPI card with trend indicator",
        icon="trending-up",
        default_cols=1,
    ),
    WidgetTypeSpec(
        name="chart",
        kind="chart",
        label_key="visualization.widgets.chart",
        description="Chart widget (bar, line, pie, area…)",
        icon="bar-chart",
        default_cols=2,
    ),
    WidgetTypeSpec(
        name="data-table",
        kind="data-table",
        label_key="visualization.widgets.data_table",
        description="Embedded data table with sort and pagination",
        icon="table",
        default_cols=3,
    ),
    WidgetTypeSpec(
        name="calendar-mini",
        kind="calendar-mini",
        label_key="visualization.widgets.calendar_mini",
        description="Mini calendar with event markers",
        icon="calendar",
        default_cols=1,
    ),
    WidgetTypeSpec(
        name="activity-stream",
        kind="activity-stream",
        label_key="visualization.widgets.activity_stream",
        description="Chronological activity feed",
        icon="activity",
        default_cols=2,
    ),
)


# ---------------------------------------------------------------------------
# Registries (process-wide, thread-safe, idempotent)
# ---------------------------------------------------------------------------

@dataclass
class _ViewTypeRegistry:
    entries: dict[str, ViewTypeSpec] = field(default_factory=dict)
    lock: ClassVar[Lock] = Lock()


@dataclass
class _ChartTypeRegistry:
    entries: dict[str, ChartTypeSpec] = field(default_factory=dict)
    lock: ClassVar[Lock] = Lock()


@dataclass
class _WidgetTypeRegistry:
    entries: dict[str, WidgetTypeSpec] = field(default_factory=dict)
    lock: ClassVar[Lock] = Lock()


_view_types = _ViewTypeRegistry()
_chart_types = _ChartTypeRegistry()
_widget_types = _WidgetTypeRegistry()


def _bootstrap() -> None:
    """Register built-in types once at startup (idempotent)."""
    for vt in _BUILTIN_VIEW_TYPES:
        register_view_type(vt)
    for ct in _BUILTIN_CHART_TYPES:
        register_chart_type(ct)
    for wt in _BUILTIN_WIDGET_TYPES:
        register_widget_type(wt)


# ── View types ──────────────────────────────────────────────────────────────

def register_view_type(spec: ViewTypeSpec) -> ViewTypeSpec:
    with _view_types.lock:
        existing = _view_types.entries.get(spec.kind)
        if existing is not None:
            if existing != spec:
                raise VisualizationError(
                    f"View type '{spec.kind}' already registered with different spec"
                )
            return existing
        _view_types.entries[spec.kind] = spec
    return spec


def get_view_type(kind: str) -> ViewTypeSpec:
    try:
        return _view_types.entries[kind]
    except KeyError as exc:
        raise VisualizationError(f"Unknown view type '{kind}'") from exc


def list_view_types() -> list[ViewTypeSpec]:
    return sorted(_view_types.entries.values(), key=lambda v: v.kind)


# ── Chart types ─────────────────────────────────────────────────────────────

def register_chart_type(spec: ChartTypeSpec) -> ChartTypeSpec:
    with _chart_types.lock:
        existing = _chart_types.entries.get(spec.kind)
        if existing is not None:
            if existing != spec:
                raise VisualizationError(
                    f"Chart type '{spec.kind}' already registered with different spec"
                )
            return existing
        _chart_types.entries[spec.kind] = spec
    return spec


def get_chart_type(kind: str) -> ChartTypeSpec:
    try:
        return _chart_types.entries[kind]
    except KeyError as exc:
        raise VisualizationError(f"Unknown chart type '{kind}'") from exc


def list_chart_types() -> list[ChartTypeSpec]:
    return sorted(_chart_types.entries.values(), key=lambda c: c.kind)


# ── Widget types ────────────────────────────────────────────────────────────

def register_widget_type(spec: WidgetTypeSpec) -> WidgetTypeSpec:
    with _widget_types.lock:
        existing = _widget_types.entries.get(spec.name)
        if existing is not None:
            if existing != spec:
                raise VisualizationError(
                    f"Widget type '{spec.name}' already registered with different spec"
                )
            return existing
        _widget_types.entries[spec.name] = spec
    return spec


def get_widget_type(name: str) -> WidgetTypeSpec:
    try:
        return _widget_types.entries[name]
    except KeyError as exc:
        raise VisualizationError(f"Unknown widget type '{name}'") from exc


def list_widget_types() -> list[WidgetTypeSpec]:
    return sorted(_widget_types.entries.values(), key=lambda w: w.name)


def list_widget_types_by_kind(kind: str) -> list[WidgetTypeSpec]:
    return [w for w in _widget_types.entries.values() if w.kind == kind]


# ── Test helpers ────────────────────────────────────────────────────────────

def reset_registry_for_tests() -> None:
    with _view_types.lock:
        _view_types.entries.clear()
    with _chart_types.lock:
        _chart_types.entries.clear()
    with _widget_types.lock:
        _widget_types.entries.clear()


# Bootstrap built-in types on import
_bootstrap()
