#!/usr/bin/env python3
"""Generate Dart and Go relay protocol constants from relay_protocol.json."""

from __future__ import annotations

import json
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
SPEC_PATH = ROOT / "relay_protocol.json"
DART_PATH = ROOT / "lib/watch_together/services/relay_protocol.g.dart"
GO_PATH = ROOT / "server/relay_protocol_gen.go"

SUPPORTED_ID_PATTERN = r"^[A-Za-z0-9_-]+$"


def camel_to_pascal(value: str) -> str:
    return value[:1].upper() + value[1:]


def validated_id_pattern(spec: dict) -> str:
    try:
        pattern = spec["idPattern"]
    except KeyError:
        raise ValueError("idPattern is required") from None
    if not isinstance(pattern, str):
        raise ValueError("idPattern must be a string")
    if pattern != SUPPORTED_ID_PATTERN:
        raise ValueError(
            f"unsupported idPattern {pattern!r}; expected {SUPPORTED_ID_PATTERN!r}"
        )
    return pattern


def dart_source(spec: dict) -> str:
    id_pattern = validated_id_pattern(spec)
    lines = [
        "// Generated by scripts/generate_relay_protocol.py. Do not edit.",
        "",
        "abstract final class RelayProtocol {",
    ]
    lines.extend(
        [
            f"  static const int protocolVersion = {spec['protocolVersion']};",
            f"  static const int legacyProtocolVersion = {spec['legacyProtocolVersion']};",
            "",
        ]
    )
    for group in ("clientMessageTypes", "serverMessageTypes"):
        for name, value in spec[group].items():
            lines.append(f"  static const String {name} = {value!r};")
    for name, value in spec["errorCodes"].items():
        lines.append(f"  static const String {name}Code = {value!r};")
    lines.append("")
    for name, value in spec["limits"].items():
        lines.append(f"  static const int {name} = {value};")
    lines.extend(
        [
            "",
            f"  static final RegExp _idPattern = RegExp(r{id_pattern!r});",
            "",
            "  static bool isValidSessionId(String value) =>",
            "      value.isNotEmpty && value.length <= maxSessionIdLength && _idPattern.hasMatch(value);",
            "",
            "  static bool isValidPeerId(String value) =>",
            "      value.isNotEmpty && value.length <= maxPeerIdLength && _idPattern.hasMatch(value);",
            "}",
            "",
        ]
    )
    return "\n".join(lines)


def go_source(spec: dict) -> str:
    validated_id_pattern(spec)
    lines = [
        "// Code generated by scripts/generate_relay_protocol.py. DO NOT EDIT.",
        "",
        "package main",
        "",
        "const (",
    ]
    lines.extend(
        [
            f"\trelayProtocolVersion       = {spec['protocolVersion']}",
            f"\tlegacyRelayProtocolVersion = {spec['legacyProtocolVersion']}",
            "",
        ]
    )
    protocol_constants = []
    for group in ("clientMessageTypes", "serverMessageTypes"):
        protocol_constants.extend(
            (f"relayType{camel_to_pascal(name)}", f'"{value}"')
            for name, value in spec[group].items()
        )
    protocol_constants.extend(
        (f"relayError{camel_to_pascal(name)}", f'"{value}"')
        for name, value in spec["errorCodes"].items()
    )
    protocol_name_width = max(len(name) for name, _ in protocol_constants)
    lines.extend(
        f"\t{name:<{protocol_name_width}} = {value}"
        for name, value in protocol_constants
    )
    lines.append("")
    go_limit_names = {
        "maxRoomSize": "maxRoomSize",
        "maxMessageSize": "maxMessageSize",
        "maxSessionIdLength": "maxSessionIDLength",
        "maxPeerIdLength": "maxPeerIDLength",
    }
    limit_constants = [
        (go_limit_names[name], str(value)) for name, value in spec["limits"].items()
    ]
    limit_name_width = max(len(name) for name, _ in limit_constants)
    lines.extend(
        f"\t{name:<{limit_name_width}} = {value}"
        for name, value in limit_constants
    )
    lines.extend(
        [
            ")",
            "",
            "func validRelayID(value string, maxLength int) bool {",
            "\tif len(value) == 0 || len(value) > maxLength {",
            "\t\treturn false",
            "\t}",
            "\tfor _, ch := range value {",
            "\t\tif (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z') &&",
            "\t\t\t(ch < '0' || ch > '9') && ch != '_' && ch != '-' {",
            "\t\t\treturn false",
            "\t\t}",
            "\t}",
            "\treturn true",
            "}",
            "",
        ]
    )
    return "\n".join(lines)


def main() -> None:
    spec = json.loads(SPEC_PATH.read_text(encoding="utf-8"))
    dart_output = dart_source(spec)
    go_output = go_source(spec)
    DART_PATH.write_text(dart_output, encoding="utf-8", newline="\n")
    GO_PATH.write_text(go_output, encoding="utf-8")


if __name__ == "__main__":
    main()
