use super::{
    events::{
        BookEvent, BookEventEnvelope, CancelCause, CancelledOrderEntry, EventVec,
        OrderCancellationCursor,
    },
    types::BookOrderInfo,
};
use crate::types::OrderId;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CancelledOrdersChunk {
    pub cancelled_orders: Vec<CancelledOrderEntry>,
    pub cursor_after: Option<OrderCancellationCursor>,
    pub done: bool,
}

impl CancelledOrderEntry {
    pub fn from_order_info(order_id: OrderId, info: &BookOrderInfo) -> Self {
        Self {
            order_id,
            account_id: info.account_id.clone(),
            correlation_id: info.correlation_id.clone(),
        }
    }
}

impl OrderCancellationCursor {
    pub fn from_order_info(order_id: OrderId, info: &BookOrderInfo) -> Self {
        Self {
            order_id,
            account_id: info.account_id.clone(),
        }
    }
}

impl CancelledOrdersChunk {
    pub fn into_event(self, cancel_cause: CancelCause, cause_detail: Option<String>) -> BookEvent {
        BookEvent::OrderCancelledBatched {
            cancelled_orders: self.cancelled_orders,
            cursor_after: if self.done { None } else { self.cursor_after },
            cancel_cause,
            cause_detail,
        }
    }
}

pub fn collect_cancelled_orders_chunk<'a, T: 'a, I, P, F>(
    iter: I,
    max_orders: usize,
    mut should_cancel: P,
    info_of: F,
) -> CancelledOrdersChunk
where
    I: Iterator<Item = (OrderId, &'a T)>,
    P: FnMut(&T) -> bool,
    F: Fn(&T) -> &BookOrderInfo,
{
    debug_assert!(max_orders > 0);

    let mut cancelled_orders = Vec::new();
    let mut cursor_after = None;
    let mut iter = iter.peekable();

    while let Some((order_id, order)) = iter.next() {
        let info = info_of(order);
        cursor_after = Some(OrderCancellationCursor::from_order_info(order_id, info));
        if !should_cancel(order) {
            continue;
        }

        cancelled_orders.push(CancelledOrderEntry::from_order_info(order_id, info));
        if cancelled_orders.len() >= max_orders {
            let done = iter.all(|(_, order)| !should_cancel(order));
            return CancelledOrdersChunk {
                cancelled_orders,
                cursor_after,
                done,
            };
        }
    }

    CancelledOrdersChunk {
        cancelled_orders,
        cursor_after,
        done: true,
    }
}

pub fn push_cancel_chunk(
    events: &mut EventVec,
    mut emit: impl FnMut(BookEvent) -> BookEventEnvelope,
    chunk: CancelledOrdersChunk,
    cancel_cause: CancelCause,
    cause_detail: Option<String>,
    emit_empty_event: bool,
    emit_completion: bool,
) {
    let done = chunk.done;
    if emit_empty_event || !chunk.cancelled_orders.is_empty() {
        events.push(emit(chunk.into_event(cancel_cause, cause_detail)));
    }
    if emit_completion && done {
        events.push(emit(BookEvent::BatchProcessCompleted { cancel_cause }));
    }
}
