# DOCS_REF:
# - docs/user/03-programming-model.md
# - docs/user/reference/api-surface.md
# - docs/user/cookbook/flow-pipeline-patterns.md
# DOCS_SYNC:
# - If exported API names or flow decorator behavior changes, update the
#   referenced docs and examples/docs/ snippets that import from flownet.api.

from dataclasses import replace

from flownet.api.flow_exception_handlers import (
    flow_exception_handler,
    exception_handler,
)
from flownet.core.lockfree import lockfree
from flownet.api.topic_stream import topic_stream, TopicStream, from_topic, from_service
from flownet.api.stateless_op import stateless, stateless_op, StatelessOpRef, StatelessOpConfig, StatelessScope
from flownet.api.services import (
    get_services,
    try_get_services,
    start_service,
    publish_service,
    stop_service,
    list_services,
    start_source,
    publish_topic,
    stop_source,
    list_sources,
)
from flownet.api import services as services
from flownet.api import tasks as tasks
from flownet.api import node as node
from flownet.api import node_control as node_control
from flownet.api import scheduler as scheduler

from flownet.api.runtime import (
    create_actor,
    create_local_actor,
    list_runtime_topics,
    list_runtime_actors,
    list_runtime_nodes,
    flow_program_ref,
    register_flow_program,
    list_flow_programs,
    get_flow_program,
    delete_flow_program,
    submit_flow_program,
)
from flownet.api import runtime as runtime
from flownet.runtime.runtime import configure_runtime
from flownet.config import FlownetConfig, RuntimeConfigInput, coerce_flownet_config


# TODO: 里边加一个decorator，然后把func参数放到decorator里边去
# flow decorator
def flow(func):
    from flownet.compiler.flow_def import FlowDef
    return FlowDef(func, static = True)


from flownet.api.dataset import (
    Dataset,
    DatasetExecutionMetrics,
    DatasetWriteResult,
    SINK_IDEMPOTENCY_META_FIELD,
    read_rows,
    read_parquet,
    read_images,
    read_tfrecords,
)

# api里边要能给concurrency hints


def _init_with_boot_policy(
    config: RuntimeConfigInput | None = None,
    *,
    node_mode: str | None = None,
    node_address: str | None = None,
    node_id: str | None = None,
    node_threads: int | None = None,
    connect_timeout: float | None = None,
):
    if config is not None:
        configure_runtime(config)

    from flownet.platform.user.startup import boot_user_runtime

    return boot_user_runtime(
        node_mode=node_mode,
        node_address=node_address,
        node_id=node_id,
        node_threads=node_threads,
        connect_timeout=connect_timeout,
    )


_DEFAULT_NODE_PORT = 8787


def _normalize_node_address(node: str | None) -> str | None:
    if node is None:
        return None
    normalized = node.strip()
    if not normalized:
        raise ValueError("node address must not be empty.")
    if ":" in normalized:
        return normalized
    return f"{normalized}:{_DEFAULT_NODE_PORT}"


def _coerce_user_runtime_config(config: RuntimeConfigInput | None) -> FlownetConfig:
    if config is None:
        from flownet.runtime import runtime as runtime_module
        resolved = coerce_flownet_config(runtime_module._runtime_config)
    else:
        resolved = coerce_flownet_config(config)
    if resolved.schedulable is False:
        return resolved
    return replace(resolved, schedulable=False)


def init(
    config: RuntimeConfigInput | None = None,
    *,
    node_mode: str | None = None,
    node_address: str | None = None,
    node_id: str | None = None,
    node_threads: int | None = None,
    connect_timeout: float | None = None,
):
    # Compatibility entrypoint:
    # - preserves legacy behavior where startup mode defaults to env/default.
    # - for cluster/user decoupled attach flow, prefer init_user(...).
    return _init_with_boot_policy(
        config,
        node_mode=node_mode,
        node_address=node_address,
        node_id=node_id,
        node_threads=node_threads,
        connect_timeout=connect_timeout,
    )


def init_user(
    config: RuntimeConfigInput | None = None,
    *,
    node: str | None = None,
    node_address: str | None = None,
    node_id: str | None = None,
    node_threads: int | None = None,
    connect_timeout: float | None = None,
    node_mode: str = "connect_only",
):
    if node is not None and node_address is not None:
        raise ValueError("Pass either `node` or `node_address`, not both.")
    normalized_node_address = _normalize_node_address(
        node if node is not None else node_address
    )
    user_runtime_config = _coerce_user_runtime_config(config)
    # Decoupled user-runtime entrypoint:
    # - defaults to connect_only so cluster/node lifecycle is managed externally.
    # - user runtime is never a schedulable compute member in placement plans.
    # - supports explicit mode override for migration/debug.
    return _init_with_boot_policy(
        user_runtime_config,
        node_mode=node_mode,
        node_address=normalized_node_address,
        node_id=node_id,
        node_threads=node_threads,
        connect_timeout=connect_timeout,
    )

__all__ = [
    "flow",
    "init",
    "init_user",
    "create_actor",
    "create_local_actor",
    "list_runtime_topics",
    "list_runtime_actors",
    "list_runtime_nodes",
    "flow_program_ref",
    "register_flow_program",
    "list_flow_programs",
    "get_flow_program",
    "delete_flow_program",
    "submit_flow_program",
    "exception_handler",
    "flow_exception_handler",
    "configure_runtime",
    "lockfree",
    "topic_stream",
    "from_topic",
    "from_service",
    "TopicStream",
    "services",
    "tasks",
    "node",
    "node_control",
    "scheduler",
    "runtime",
    "get_services",
    "try_get_services",
    "start_service",
    "publish_service",
    "stop_service",
    "list_services",
    "start_source",
    "publish_topic",
    "stop_source",
    "list_sources",
    "stateless",
    "stateless_op",
    "StatelessOpRef",
    "StatelessOpConfig",
    "StatelessScope",
    "Dataset",
    "DatasetExecutionMetrics",
    "DatasetWriteResult",
    "SINK_IDEMPOTENCY_META_FIELD",
    "read_rows",
    "read_parquet",
    "read_images",
    "read_tfrecords",
]
