// This file is part of Gear.

// Copyright (C) 2026 Gear Technologies Inc.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! Awesome Message Tracker service.
//!
//! This crate provides a library for tracking asynchronous messages and their statuses.

#![no_std]

use core::marker::PhantomData;
use sails_rs::prelude::*;

pub mod storage;

pub use storage::error::TrackerError;

/// Trait for message status storage.
pub trait MessageStorage<T> {
    /// The error type returned by storage operations.
    type Error;

    /// Inserts a message ID and its status.
    fn insert(&mut self, msg_id: MessageId, status: T) -> Result<(), Self::Error>;

    /// Returns a reference to the status associated with the message ID.
    fn get(&self, msg_id: &MessageId) -> Option<&T>;

    /// Returns a mutable reference to the status associated with the message ID.
    fn get_mut(&mut self, msg_id: &MessageId) -> Option<&mut T>;

    /// Removes the message ID and its status.
    fn remove(&mut self, msg_id: &MessageId) -> Option<T>;

    /// Returns a list of tracked message IDs and their statuses with optional pagination.
    fn get_statuses(&self, query: Option<Pagination>) -> Vec<(MessageId, T)>
    where
        T: Clone;

    /// Returns the number of tracked messages.
    fn len(&self) -> usize;

    /// Returns `true` if no messages are tracked.
    fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Clears all tracked messages.
    fn clear(&mut self);
}

/// Tracker for asynchronous messages and their statuses.
pub struct MsgTracker<T, S>
where
    S: StateMut<Error = Infallible>,
    S::Item: MessageStorage<T>,
{
    storage: S,
    _phantom: PhantomData<T>,
}

/// Pagination parameters for listing tracked messages.
#[derive(Clone, Copy, Debug, Decode, Encode, TypeInfo, ReflectHash)]
#[codec(crate = sails_rs::scale_codec)]
#[reflect_hash(crate = sails_rs)]
pub struct Pagination {
    /// The number of items to skip.
    pub offset: u32,
    /// The maximum number of items to return.
    pub limit: u32,
}

impl<T, S, E> MsgTracker<T, S>
where
    S: StateMut<Error = Infallible>,
    S::Item: MessageStorage<T, Error = E>,
{
    /// Creates a new `MsgTracker` instance.
    pub fn new(storage: S) -> Self {
        Self {
            storage,
            _phantom: PhantomData,
        }
    }

    /// Starts tracking a message with the given ID and status.
    pub fn insert(&mut self, msg_id: MessageId, status: T) -> Result<(), E> {
        self.storage.get_mut().insert(msg_id, status)
    }

    /// Retrieves the status of a message.
    pub fn get_status(&self, msg_id: &MessageId) -> Option<T>
    where
        T: Clone,
    {
        self.storage.get().get(msg_id).cloned()
    }

    /// Retrieves all tracked messages and their statuses with optional pagination.
    pub fn get_statuses(&self, query: Option<Pagination>) -> Vec<(MessageId, T)>
    where
        T: Clone,
    {
        self.storage.get().get_statuses(query)
    }

    /// Updates the status of a tracked message.
    ///
    /// Returns `Ok(true)` if the message was already tracked and its status was updated.
    pub fn update_status(&mut self, msg_id: MessageId, status: T) -> Result<bool, E> {
        let mut storage = self.storage.get_mut();
        if storage.get(&msg_id).is_some() {
            storage.insert(msg_id, status)?;
            Ok(true)
        } else {
            Ok(false)
        }
    }

    /// Stops tracking a message and returns its final status.
    pub fn remove(&mut self, msg_id: &MessageId) -> Option<T> {
        self.storage.get_mut().remove(msg_id)
    }

    /// Returns the number of messages currently being tracked.
    pub fn len(&self) -> usize {
        self.storage.get().len()
    }

    /// Checks if no messages are being tracked.
    pub fn is_empty(&self) -> bool {
        self.storage.get().is_empty()
    }

    /// Clears all message statuses from the tracker.
    pub fn clear(&mut self) {
        self.storage.get_mut().clear();
    }
}

impl Pagination {
    /// Helper to convert optional pagination into (offset, limit).
    pub fn range(query: Option<Self>) -> (usize, usize) {
        query
            .map(|q| (q.offset as usize, q.limit as usize))
            .unwrap_or((0, usize::MAX))
    }
}
