"""Change tracking for designs and data: optimistic concurrency (a save based
on stale data is refused instead of silently overwriting someone else's
newer change) and compact before/after summaries for the audit log
(app/crud/audit.py)."""

from datetime import datetime
from typing import Any

from fastapi import HTTPException

_MAX_TEXT = 300


def check_fresh(obj: Any, expected: datetime | None) -> None:
    """409 when `obj` changed since the caller read it (expected = the
    updated_at they read). No expectation sent = no check (older clients)."""
    if expected is None or getattr(obj, "updated_at", None) is None:
        return
    if obj.updated_at != expected:
        raise HTTPException(
            status_code=409,
            detail="Someone else saved changes to this after you opened it. Reload to see their changes, then make yours again.",
        )


def _short(value: Any) -> Any:
    if isinstance(value, str) and len(value) > _MAX_TEXT:
        return value[:_MAX_TEXT] + "…"
    if isinstance(value, (list, dict)):
        text = str(value)
        return text[:_MAX_TEXT] + "…" if len(text) > _MAX_TEXT else value
    return value


def values_diff(before: dict | None, after: dict | None) -> dict:
    """{field: [old, new]} for the values that differ (long values trimmed)."""
    before, after = before or {}, after or {}
    return {k: [_short(before.get(k)), _short(after.get(k))] for k in sorted(set(before) | set(after)) if before.get(k) != after.get(k)}


def fields_diff(before: list[dict] | None, after: list[dict] | None) -> dict:
    """What changed in a field list (Form field_definitions / Master field_schema)."""
    b = {f.get("name"): f for f in before or []}
    a = {f.get("name"): f for f in after or []}
    out = {
        "fields_added": [n for n in a if n not in b],
        "fields_removed": [n for n in b if n not in a],
        "fields_changed": [n for n in a if n in b and a[n] != b[n]],
    }
    if [f.get("name") for f in before or [] if f.get("name") in a] != [f.get("name") for f in after or [] if f.get("name") in b]:
        out["fields_reordered"] = True
    return {k: v for k, v in out.items() if v}


def _walk_blocks(blocks: list[dict] | None) -> dict[str, dict]:
    out: dict[str, dict] = {}
    for block in blocks or []:
        out[str(block.get("id"))] = block
        out.update(_walk_blocks(block.get("children")))
        for group in block.get("groups") or []:
            out.update(_walk_blocks(group.get("children")))
    return out


def blocks_diff(before: list[dict] | None, after: list[dict] | None) -> dict:
    """What changed in a Page's block tree, by block id."""

    def shallow(block: dict) -> dict:
        return {k: v for k, v in block.items() if k not in ("children", "groups")}

    b, a = _walk_blocks(before), _walk_blocks(after)
    out = {
        "blocks_added": [f"{a[i].get('type')}:{i}" for i in a if i not in b],
        "blocks_removed": [f"{b[i].get('type')}:{i}" for i in b if i not in a],
        "blocks_changed": [f"{a[i].get('type')}:{i}" for i in a if i in b and shallow(a[i]) != shallow(b[i])],
    }
    return {k: v for k, v in out.items() if v}


def changed_keys(before: dict, after: dict, keys: list[str]) -> list[str]:
    return [k for k in keys if before.get(k) != after.get(k)]
