from __future__ import annotations

from collections.abc import Mapping
from dataclasses import dataclass
from typing import Any

from flownet.v1.compiler.errors import FlowDefinitionError


@dataclass(frozen=True)
class SystemTargetRef:
    address: str = "__local__"
    actor_id: str = "__system__"
    method: str = "noop"


def system_target() -> SystemTargetRef:
    return SystemTargetRef()


def ensure_flow_target(
    target: Any,
    op: str,
    *,
    allow_flow_program_ref: bool = False,
) -> Any:
    if is_actor_target(target) or is_stateless_target(target):
        return target
    if allow_flow_program_ref:
        normalized_program_ref = coerce_flow_program_ref_target(target)
        if normalized_program_ref is not None:
            return normalized_program_ref

    raise FlowDefinitionError(
        f"{op}() expects actor/stateless target"
        + (" or flow-program reference target." if allow_flow_program_ref else ".")
        + " Actor-like target must provide address/actor_id/method."
    )


def coerce_flow_program_ref_target(target: Any) -> dict[str, str] | None:
    program_uri = _non_empty_str(_field(target, "program_uri"))
    program_rev = _non_empty_str(_field(target, "program_rev"))
    if program_uri is not None and program_rev is not None:
        return {
            "program_uri": program_uri,
            "program_rev": program_rev,
        }

    # Temporary compatibility bridge: normalize legacy names when provided.
    flow_uri = _non_empty_str(_field(target, "flow_uri"))
    version = _non_empty_str(_field(target, "version"))
    if flow_uri is not None and version is not None:
        return {
            "program_uri": flow_uri,
            "program_rev": version,
        }
    return None


def is_actor_target(target: Any) -> bool:
    return (
        _non_empty_str(_field(target, "address")) is not None
        and _non_empty_str(_field(target, "actor_id")) is not None
        and _non_empty_str(_field(target, "method")) is not None
    )


def is_stateless_target(target: Any) -> bool:
    return (
        _non_empty_str(_field(target, "op_id")) is not None
        and _non_empty_str(_field(target, "scope")) is not None
    )


def _field(target: Any, name: str) -> Any:
    if isinstance(target, Mapping):
        return target.get(name)
    return getattr(target, name, None)


def _non_empty_str(value: Any) -> str | None:
    if not isinstance(value, str):
        return None
    normalized = value.strip()
    if not normalized:
        return None
    return normalized


__all__ = [
    "SystemTargetRef",
    "system_target",
    "ensure_flow_target",
    "coerce_flow_program_ref_target",
    "is_actor_target",
    "is_stateless_target",
]
