// SPDX-License-Identifier: Apache-2.0
#pragma once

/// @file
/// Cross-platform factory functions for the async socket layer. Consumers use
/// these instead of including the per-platform implementation headers directly;
/// each resolves to the right backend (PosixSocket/Listener or
/// WindowsSocket/Listener) at compile time.

#include <cstddef>
#include <cstdint>
#include <expected>
#include <memory>
#include <string_view>
#include <vector>

#include <coro/Task.hpp>
#include <net/EventLoop.hpp>
#include <net/IListener.hpp>
#include <net/ISocket.hpp>
#include <net/IoResult.hpp>

namespace net
{

/// Binds a TCP listener on @p host : @p port driven by @p loop's reactor.
/// @param loop The loop whose reactor drives accept readiness (not owned).
/// @param host The bind address ("127.0.0.1", "0.0.0.0", "::").
/// @param port The bind port; 0 requests an OS-assigned ephemeral port.
/// @param backlog The listen backlog.
/// @return The bound listener, or a @c NetError on failure.
[[nodiscard]] std::expected<std::unique_ptr<IListener>, NetError> listen(EventLoop& loop,
                                                                         std::string_view host,
                                                                         std::uint16_t port,
                                                                         int backlog = 128);

/// Connects a TCP client socket to @p host : @p port, parking the caller until the
/// connection completes.
/// @param loop The loop whose reactor drives connect readiness (not owned; a
///        pointer, since coroutine reference parameters can dangle).
/// @param host The remote host ("127.0.0.1", a hostname).
/// @param port The remote port.
/// @return A task resolving to the connected socket, or a @c NetError on failure.
[[nodiscard]] coro::Task<std::expected<std::unique_ptr<ISocket>, NetError>> connect(EventLoop* loop,
                                                                                    std::string_view host,
                                                                                    std::uint16_t port);

/// Binds an AF_UNIX listener on the socket file @p path, hardening its parent
/// directory first (see UnixListener::bind for the exact policy).
/// @param loop The loop whose reactor drives accept readiness (not owned).
/// @param path The socket file path.
/// @param backlog The listen backlog.
/// @return The bound listener; @c NetErrorCode::Unsupported on Windows (for now).
[[nodiscard]] std::expected<std::unique_ptr<IListener>, NetError> listenUnix(EventLoop& loop,
                                                                             std::string_view path,
                                                                             int backlog = 128);

/// Connects to the AF_UNIX socket file @p path.
/// @param loop The loop whose reactor drives connect readiness (not owned; a
///        pointer, since coroutine reference parameters can dangle).
/// @param path The socket file path.
/// @return A task resolving to the connected socket; a @c NetError on failure,
///         @c NetErrorCode::Unsupported on Windows (for now).
[[nodiscard]] coro::Task<std::expected<std::unique_ptr<ISocket>, NetError>> connectUnix(
    EventLoop* loop, std::string_view path);

/// Adopts an already-open stream file descriptor (a socketpair end, a PTY
/// master) as an @c ISocket driven by @p loop's reactor. Ownership of the fd
/// transfers to the returned socket.
/// @param loop The loop whose reactor drives readiness (not owned).
/// @param fd The open, stream-capable descriptor.
/// @return The adopted socket; @c NetErrorCode::Unsupported on Windows.
[[nodiscard]] std::expected<std::unique_ptr<ISocket>, NetError> adoptFd(EventLoop& loop, int fd);

/// Appends one read chunk from @p socket to @p buffer — the accumulate step of
/// every binary-framed decode loop.
///
/// Reports EOF and failure DISTINCTLY: a decode loop that cannot tell "the peer hung up" from
/// "the transport broke" cannot say why it dropped a connection, which is the whole content of
/// the resulting diagnostic.
///
/// @param socket The transport to read from (not owned; a pointer, since
///        coroutine reference parameters can dangle).
/// @param buffer Receives the read bytes.
/// @return The number of bytes appended, 0 on a clean EOF, or the transport error.
[[nodiscard]] coro::Task<IoResult> appendReadChunk(ISocket* socket, std::vector<std::byte>* buffer);

} // namespace net
