//! NATS endpoint contracts

use std::marker::PhantomData;

use super::super::rpc::id::CallId;
use super::NatsSubject;

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NatsUnary<A, B = ()> {
    subject: NatsSubject,
    marker: PhantomData<fn(A) -> B>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NatsServerStream<A, B = ()> {
    subject: NatsSubject,
    marker: PhantomData<fn(A) -> B>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NatsClientStream<A, B = ()> {
    subject: NatsSubject,
    marker: PhantomData<fn(A) -> B>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NatsBidiStream<A, B = ()> {
    subject: NatsSubject,
    marker: PhantomData<fn(A) -> B>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NatsEvent<T> {
    subject: NatsSubject,
    marker: PhantomData<fn(T)>,
}

impl<A, B> NatsUnary<A, B> {
    pub fn new(subject: NatsSubject) -> Self {
        Self {
            subject,
            marker: PhantomData,
        }
    }

    pub const fn subject(&self) -> &NatsSubject {
        &self.subject
    }
}

impl<A, B> NatsServerStream<A, B> {
    pub fn new(subject: NatsSubject) -> Self {
        Self {
            subject,
            marker: PhantomData,
        }
    }

    pub const fn subject(&self) -> &NatsSubject {
        &self.subject
    }

    pub fn stream_subjects(&self, call: CallId) -> NatsStreamSubjects {
        NatsStreamSubjects::for_call(&self.subject, call)
    }
}

impl<A, B> NatsClientStream<A, B> {
    pub fn new(subject: NatsSubject) -> Self {
        Self {
            subject,
            marker: PhantomData,
        }
    }

    pub const fn subject(&self) -> &NatsSubject {
        &self.subject
    }

    pub fn stream_subjects(&self, call: CallId) -> NatsStreamSubjects {
        NatsStreamSubjects::for_call(&self.subject, call)
    }
}

impl<A, B> NatsBidiStream<A, B> {
    pub fn new(subject: NatsSubject) -> Self {
        Self {
            subject,
            marker: PhantomData,
        }
    }

    pub const fn subject(&self) -> &NatsSubject {
        &self.subject
    }

    pub fn stream_subjects(&self, call: CallId) -> NatsStreamSubjects {
        NatsStreamSubjects::for_call(&self.subject, call)
    }
}

impl<T> NatsEvent<T> {
    pub fn new(subject: NatsSubject) -> Self {
        Self {
            subject,
            marker: PhantomData,
        }
    }

    pub const fn subject(&self) -> &NatsSubject {
        &self.subject
    }
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NatsStreamSubjects {
    out: NatsSubject,
    out_flow: NatsSubject,
    in_subject: NatsSubject,
    in_flow: NatsSubject,
}

impl NatsStreamSubjects {
    pub fn for_call(base: &NatsSubject, call: CallId) -> Self {
        let base = base.as_str();
        Self {
            out: generated(format!("{base}.call.{call}.out"), "stream out"),
            out_flow: generated(format!("{base}.call.{call}.out.flow"), "stream out flow"),
            in_subject: generated(format!("{base}.call.{call}.in"), "stream in"),
            in_flow: generated(format!("{base}.call.{call}.in.flow"), "stream in flow"),
        }
    }

    pub const fn out(&self) -> &NatsSubject {
        &self.out
    }

    pub const fn out_flow(&self) -> &NatsSubject {
        &self.out_flow
    }

    pub const fn in_subject(&self) -> &NatsSubject {
        &self.in_subject
    }

    pub const fn in_flow(&self) -> &NatsSubject {
        &self.in_flow
    }
}

fn generated(value: String, context: &'static str) -> NatsSubject {
    NatsSubject::from_generated(value, context)
}
