import type * as Effect from "effect/Effect";
import type * as Schema from "effect/Schema";
import type * as Stream from "effect/Stream";
import type * as Socket from "effect/unstable/socket/Socket";

import * as Function from "effect/Function";
import * as Sink from "effect/Sink";
import * as Tuple from "effect/Tuple";

import type * as MobyDemux from "../../MobyDemux.js";

import { demuxMultiplexedToSingleSink, isMultiplexedChannel, isMultiplexedSocket } from "./multiplexed.js";
import { demuxRawToSingleSink, isRawChannel, isRawSocket } from "./raw.js";

/**
 * Demux either a raw socket or a multiplexed socket to a single sink.
 *
 * @since 1.0.0
 * @category Demux
 */
export const demuxToSingleSink = Function.dual<
    // Data-last signature.
    <A1, L1, E1, E2, R1, R2>(
        source: Stream.Stream<string | Uint8Array, E1, R1>,
        sink: Sink.Sink<A1, string, L1, E2, R2>,
        options?: { encoding?: string | undefined } | undefined
    ) => <IE = never, OE = Socket.SocketError, R3 = never>(
        sockets: MobyDemux.EitherRawInput<E1 | IE, OE, R3> | MobyDemux.EitherMultiplexedInput<E1 | IE, OE, R3>
    ) => Effect.Effect<A1, E1 | E2 | IE | OE | Schema.SchemaError, R1 | R2 | R3>,
    // Data-first signature.
    <A1, L1, E1, E2, R1, R2, IE = never, OE = Socket.SocketError, R3 = never>(
        sockets: MobyDemux.EitherRawInput<E1 | IE, OE, R3> | MobyDemux.EitherMultiplexedInput<E1 | IE, OE, R3>,
        source: Stream.Stream<string | Uint8Array, E1, R1>,
        sink: Sink.Sink<A1, string, L1, E2, R2>,
        options?: { encoding?: string | undefined } | undefined
    ) => Effect.Effect<A1, E1 | E2 | IE | OE | Schema.SchemaError, R1 | R2 | R3>
>(
    /**
     * We are data-first if the first argument is a channel or if the first
     * argument is an object with a "stdout", "stderr", or "stdin" key.
     */
    (arguments_) =>
        isRawSocket(arguments_[0]) ||
        isRawChannel(arguments_[0]) ||
        isMultiplexedSocket(arguments_[0]) ||
        isMultiplexedChannel(arguments_[0]),

    // Implementation, need the type parameters for checking the underlying.
    <A1, L1, E1, E2, R1, R2, IE = never, OE = Socket.SocketError, R3 = never>(
        socketOptions: MobyDemux.EitherRawInput<E1 | IE, OE, R3> | MobyDemux.EitherMultiplexedInput<E1 | IE, OE, R3>,
        source: Stream.Stream<string | Uint8Array, E1, R1>,
        sink: Sink.Sink<A1, string, L1, E2, R2>,
        options?: { encoding?: string | undefined } | undefined
    ): Effect.Effect<A1, E1 | E2 | IE | OE | Schema.SchemaError, R1 | R2 | R3> => {
        if (isRawSocket(socketOptions) || isRawChannel<E1 | IE, OE, R3>(socketOptions)) {
            return demuxRawToSingleSink(socketOptions, source, sink, options);
        }

        if (isMultiplexedSocket(socketOptions) || isMultiplexedChannel<E1 | IE, OE, R3>(socketOptions)) {
            return demuxMultiplexedToSingleSink(socketOptions, source, Sink.mapInput(sink, Tuple.get(1)), options);
        }

        return Function.absurd(socketOptions);
    }
);
