use rquickjs::{class::OwnedBorrowMut, Class, Result};

use crate::writable::{
    default_controller::{
        WritableStreamDefaultControllerClass, WritableStreamDefaultControllerOwned,
    },
    default_writer::WritableStreamDefaultWriterOwned,
    stream::{WritableStream, WritableStreamOwned},
    writer::{UndefinedWriter, WritableStreamWriter},
};

pub(crate) struct WritableStreamObjects<'js, W> {
    pub(crate) stream: WritableStreamOwned<'js>,
    pub(crate) controller: WritableStreamDefaultControllerOwned<'js>,
    pub(crate) writer: W,
}

pub(crate) struct WritableStreamClassObjects<'js, W: WritableStreamWriter<'js>> {
    pub(crate) stream: Class<'js, WritableStream<'js>>,
    pub(crate) controller: WritableStreamDefaultControllerClass<'js>,
    pub(crate) writer: W::Class,
}

impl<'js, W: WritableStreamWriter<'js>> Clone for WritableStreamClassObjects<'js, W> {
    fn clone(&self) -> Self {
        Self {
            stream: self.stream.clone(),
            controller: self.controller.clone(),
            writer: self.writer.clone(),
        }
    }
}

impl<'js, W: WritableStreamWriter<'js>> WritableStreamObjects<'js, W> {
    pub(super) fn into_inner(self) -> WritableStreamClassObjects<'js, W> {
        WritableStreamClassObjects {
            stream: self.stream.into_inner(),
            controller: self.controller.into_inner(),
            writer: self.writer.into_inner(),
        }
    }

    pub(crate) fn from_class(objects_class: WritableStreamClassObjects<'js, W>) -> Self {
        Self {
            stream: OwnedBorrowMut::from_class(objects_class.stream),
            controller: OwnedBorrowMut::from_class(objects_class.controller),
            writer: W::from_class(objects_class.writer),
        }
    }

    pub(super) fn from_class_no_writer(
        objects_class: WritableStreamClassObjects<'js, W>,
    ) -> WritableStreamObjects<'js, UndefinedWriter> {
        WritableStreamObjects {
            stream: OwnedBorrowMut::from_class(objects_class.stream),
            controller: OwnedBorrowMut::from_class(objects_class.controller),
            writer: UndefinedWriter,
        }
    }

    pub(super) fn with_writer(
        mut self,
        default: impl FnOnce(
            WritableStreamObjects<'js, WritableStreamDefaultWriterOwned<'js>>,
        ) -> Result<
            WritableStreamObjects<'js, WritableStreamDefaultWriterOwned<'js>>,
        >,
        none: impl FnOnce(
            WritableStreamObjects<'js, UndefinedWriter>,
        ) -> Result<WritableStreamObjects<'js, UndefinedWriter>>,
    ) -> Result<Self> {
        ((self.stream, self.controller), self.writer) = self.writer.with_writer(
            (self.stream, self.controller),
            |(stream, controller), writer| {
                let objects = default(WritableStreamObjects {
                    stream,
                    controller,
                    writer,
                })?;

                Ok(((objects.stream, objects.controller), objects.writer))
            },
            |(stream, controller)| {
                let objects = none(WritableStreamObjects {
                    stream,
                    controller,
                    writer: UndefinedWriter,
                })?;

                Ok((objects.stream, objects.controller))
            },
        )?;

        Ok(self)
    }
}

impl<'js, W: WritableStreamWriter<'js>> WritableStreamObjects<'js, W> {
    pub(super) fn refresh_writer(
        mut self,
    ) -> WritableStreamObjects<'js, Option<WritableStreamDefaultWriterOwned<'js>>> {
        drop(self.writer);
        let writer = self.stream.writer_mut();
        WritableStreamObjects {
            stream: self.stream,
            controller: self.controller,
            writer,
        }
    }
}

impl<'js> WritableStreamObjects<'js, UndefinedWriter> {
    pub(super) fn from_stream(stream: WritableStreamOwned<'js>) -> Self {
        let controller = OwnedBorrowMut::from_class(
            stream
                .controller
                .clone()
                .expect("WritableStream must have controller"),
        );

        WritableStreamObjects {
            stream,
            controller,
            writer: UndefinedWriter,
        }
    }

    pub(super) fn from_controller(controller: WritableStreamDefaultControllerOwned<'js>) -> Self {
        let stream = OwnedBorrowMut::from_class(controller.stream.clone());

        WritableStreamObjects {
            stream,
            controller,
            writer: UndefinedWriter,
        }
    }
}

impl<'js> WritableStreamObjects<'js, WritableStreamDefaultWriterOwned<'js>> {
    pub(super) fn from_writer(writer: WritableStreamDefaultWriterOwned<'js>) -> Self {
        let stream = OwnedBorrowMut::from_class(
            writer
                .stream
                .clone()
                .expect("WritableStreamDefaultWriter must have a stream"),
        );

        let controller = OwnedBorrowMut::from_class(
            stream
                .controller
                .clone()
                .expect("WritableStreamDefaultWriter stream must have a controller"),
        );

        WritableStreamObjects {
            stream,
            controller,
            writer,
        }
    }
}
