"""Small persistence helpers for safer SQL text usage.

ClientFlow still uses SQLAlchemy ``text()`` for PostgreSQL-specific JSONB and
reconciliation queries.  These helpers standardise JSONB patches so new code can
prefer ``metadata = metadata || CAST(:metadata_patch AS jsonb)`` over fragile
``jsonb_build_object('x', :param)`` patterns.
"""
from __future__ import annotations

import json
from datetime import date, datetime, timezone
from decimal import Decimal
from typing import Any, Dict, Mapping, Optional
from uuid import UUID


def json_default(value: Any) -> Any:
    if isinstance(value, (datetime, date)):
        return value.isoformat()
    if isinstance(value, UUID):
        return str(value)
    if isinstance(value, Decimal):
        # Use string to avoid float rounding in money fields.
        return str(value)
    if isinstance(value, set):
        return sorted(str(item) for item in value)
    return str(value)


def jsonb_param(value: Any) -> str:
    """Return a JSON string suitable for ``CAST(:param AS jsonb)``."""
    return json.dumps(value if value is not None else {}, ensure_ascii=False, default=json_default)


def metadata_patch(**items: Any) -> Dict[str, Any]:
    """Create a small metadata patch with an ISO timestamp when requested."""
    return {key: value for key, value in items.items() if value is not None}


def utc_now_text() -> str:
    return datetime.now(timezone.utc).isoformat()
