use futures_signals::signal::{Mutable, Signal, SignalExt};
use futures_signals_ext::{MutableExt, MutableOption};
use log::debug;
use serde::de::DeserializeOwned;
use smol_str::SmolStr;

use crate::{Messages, NoMac, StatusCode};

use super::{fetch, request::Request, transferstate::TransferState};

#[derive(Default)]
pub struct UploadStore {
    transfer_state: Mutable<TransferState>,
}

impl UploadStore {
    pub fn new() -> Self {
        Self {
            transfer_state: Mutable::new(TransferState::Empty),
        }
    }

    pub fn invalidate(&self) {
        self.transfer_state.set(TransferState::Empty);
    }

    pub fn transfer_state(&self) -> &Mutable<TransferState> {
        &self.transfer_state
    }

    pub fn set_transfer_state(&self, transfer_state: TransferState) {
        self.transfer_state.set_neq(transfer_state);
    }

    pub fn stored(&self) -> bool {
        self.transfer_state.map(TransferState::stored)
    }

    pub fn stored_signal(&self) -> impl Signal<Item = bool> + use<> {
        self.transfer_state.signal_ref(|state| state.stored())
    }

    pub fn stored_status(&self) -> Option<StatusCode> {
        self.transfer_state.map(TransferState::stored_status)
    }

    pub fn stored_status_signal(&self) -> impl Signal<Item = Option<StatusCode>> + use<> {
        self.transfer_state
            .signal_ref(TransferState::stored_status)
            .dedupe()
    }

    pub fn pending(&self) -> bool {
        self.transfer_state.map(TransferState::pending)
    }

    pub fn pending_signal(&self) -> impl Signal<Item = bool> + use<> {
        self.transfer_state.signal_ref(|state| state.pending())
    }

    pub fn store<C>(&self, request: Request<'_>, response_messages: Messages, result_callback: C)
    where
        C: FnOnce(StatusCode) + 'static,
    {
        self.do_store::<SmolStr, _>(request, None, response_messages, result_callback)
    }

    pub fn store_with_response<R, C>(
        &self,
        request: Request<'_>,
        response_entity: MutableOption<R>,
        response_messages: Messages,
        result_callback: C,
    ) where
        R: DeserializeOwned + 'static,
        C: FnOnce(StatusCode) + 'static,
    {
        self.do_store::<_, _>(
            request,
            Some(response_entity),
            response_messages,
            result_callback,
        );
    }

    fn do_store<R, C>(
        &self,
        request: Request<'_>,
        response_entity: Option<MutableOption<R>>,
        response_messages: Messages,
        result_callback: C,
    ) where
        C: FnOnce(StatusCode) + 'static,
        R: DeserializeOwned + 'static,
    {
        if request.logging() {
            debug!("Request to store {}", request.url());
        }
        fetch::<_, _, NoMac>(
            request,
            self.transfer_state.clone(),
            response_messages,
            response_entity,
            result_callback,
        );
    }
}
