"""Core-side view of a project's Cloud record-engine catalog.

Cloud remains the owner of entities and records. Core keeps a last-known copy so
codegen never treats a failed or empty schema fetch as a greenfield database.
"""
from __future__ import annotations

from collections.abc import Iterable
from typing import Any, Literal, TypedDict


class DatabaseCatalogView(TypedDict):
    schema_status: Literal["ready", "empty", "unavailable"]
    schema_source: Literal["live", "cached", "none"]
    entities: list[dict[str, Any]] | None
    persist: list[dict[str, Any]] | None


def canonical_entity_key(key: str) -> str:
    """Collapse trivial plural/singular spellings so university ~= universities."""
    k = (key or "").strip().lower().replace("-", "_")
    if k.endswith("ies") and len(k) > 3:
        return k[:-3] + "y"
    if k.endswith("ses") and len(k) > 3:
        return k[:-2]
    if k.endswith("s") and not k.endswith("ss") and len(k) > 1:
        return k[:-1]
    return k


def colliding_existing_key(declared_key: str, existing_keys: Iterable[str]) -> str | None:
    """Return the live key that declared_key would fork, or None if reuse/new."""
    declared = (declared_key or "").strip().lower()
    existing = [(k or "").strip().lower() for k in existing_keys if k]
    if declared in existing:
        return None
    target = canonical_entity_key(declared)
    for key in existing:
        if canonical_entity_key(key) == target:
            return key
    return None


def compact_entity(entity: dict[str, Any]) -> dict[str, Any]:
    key = entity.get("key") or entity.get("entity_key") or ""
    fields: list[dict[str, Any]] = []
    for field in entity.get("fields") or []:
        if not isinstance(field, dict) or not field.get("key"):
            continue
        fields.append({
            "key": field["key"],
            "label": field.get("label") or field["key"],
            "type": field.get("type") or "text",
            "required": bool(field.get("required", False)),
            "unique": bool(field.get("unique", False)),
            "options": field.get("options"),
            "relation_entity_key": field.get("relation_entity_key"),
        })
    return {
        "key": key,
        "label": entity.get("label") or entity.get("name") or key,
        "fields": fields,
        "rls_enabled": bool(entity.get("rls_enabled", False)),
        "status": entity.get("status") or "active",
        "record_count": entity.get("record_count"),
    }


def compact_entities(entities: Iterable[dict[str, Any]]) -> list[dict[str, Any]]:
    compacted = [compact_entity(e) for e in entities if isinstance(e, dict)]
    return [e for e in compacted if e["key"]]


def catalog_keys(*groups: Iterable[dict[str, Any]] | None) -> set[str]:
    keys: set[str] = set()
    for group in groups:
        for entity in group or []:
            key = entity.get("key") or entity.get("entity_key")
            if key:
                keys.add(str(key))
    return keys


def resolve_database_view(
    *,
    live_status: Literal["ok", "error"],
    live_entities: list[dict[str, Any]] | None,
    cached_entities: list[dict[str, Any]] | None,
) -> DatabaseCatalogView:
    """Decide what codegen should see, and whether Core should refresh its snapshot.

    A successful Cloud response of zero entities must not overwrite a non-empty
    last-known catalog. That empty list is usually a fetch/normalize miss, and
    treating it as greenfield is how follow-up codegen forks new entity keys
    and leaves existing D1 rows unreachable.
    """
    cached = compact_entities(cached_entities or [])
    if live_status == "ok":
        live = compact_entities(live_entities or [])
        if live:
            return {
                "schema_status": "ready",
                "schema_source": "live",
                "entities": live,
                "persist": live,
            }
        if cached:
            return {
                "schema_status": "ready",
                "schema_source": "cached",
                "entities": cached,
                "persist": None,
            }
        return {
            "schema_status": "empty",
            "schema_source": "live",
            "entities": [],
            "persist": [],
        }
    if cached:
        return {
            "schema_status": "unavailable",
            "schema_source": "cached",
            "entities": cached,
            "persist": None,
        }
    return {
        "schema_status": "unavailable",
        "schema_source": "none",
        "entities": None,
        "persist": None,
    }
