"""Canonical JSON + SHA-256 for grant command_hash (must match Cloud TypeScript)."""
from __future__ import annotations

import hashlib
import json
import math
from typing import Any


STUB_REQUEST_TYPES = frozenset()

PUBLIC_COMMANDS = frozenset(
    {
        "data.record.get",
        "data.record.create",
        "data.record.update",
        "data.record.upsert",
        "content.page.create",
        "content.page.update",
        "content.post.create",
        "content.post.update",
        "content.navigation.update",
        "content.publish",
        "codegen.request_change",
        "codegen.approve",
        "codegen.deploy",
        "codegen.cancel",
        "deployment.create",
    }
)

SYNC_COMMANDS = frozenset(
    {
        "data.record.get",
        "data.record.create",
        "data.record.update",
        "data.record.upsert",
        "content.page.create",
        "content.page.update",
        "content.post.create",
        "content.post.update",
        "content.navigation.update",
        "content.publish",
    }
)

SENSITIVE_COMMANDS = frozenset(
    {
        "data.bulk_update",
        "data.delete",
        "content.publish",
        "codegen.deploy",
        "deployment.create",
    }
)

REQUIRES_USER_PRINCIPAL = frozenset(
    {
        "codegen.deploy",
        "codegen.approve",
        "deployment.create",
        "data.delete",
        "data.bulk_update",
        "content.publish",
    }
)


def _canon_number(n: int | float) -> str:
    if isinstance(n, bool):
        raise TypeError("bool is not a number")
    if isinstance(n, int) and not isinstance(n, bool):
        return str(n)
    if not math.isfinite(n):
        raise ValueError("non-finite numbers are not allowed")
    if n == math.trunc(n) and abs(n) < 1e15:
        return str(int(n))
    s = format(n, ".15e")
    return s


def canonical_json(value: Any) -> str:
    """UTF-8 JSON, sorted object keys, no insignificant whitespace."""
    if value is None:
        return "null"
    if isinstance(value, bool):
        return "true" if value else "false"
    if isinstance(value, int) and not isinstance(value, bool):
        return str(value)
    if isinstance(value, float):
        return _canon_number(value)
    if isinstance(value, str):
        return json.dumps(value, ensure_ascii=False)
    if isinstance(value, list):
        return "[" + ",".join(canonical_json(item) for item in value) + "]"
    if isinstance(value, dict):
        rebuilt = []
        for k in sorted(value.keys(), key=lambda x: str(x)):
            rebuilt.append(json.dumps(str(k), ensure_ascii=False) + ":" + canonical_json(value[k]))
        return "{" + ",".join(rebuilt) + "}"
    raise TypeError(f"unsupported type: {type(value)!r}")


def command_hash(command: dict[str, Any]) -> str:
    body = canonical_json(command).encode("utf-8")
    return hashlib.sha256(body).hexdigest()
