//! [`Handle`]: a typed, ref-counted reference to a loaded asset.
//!
//! A handle is the unit of *ownership* in the asset system. It is cheap to clone
//! (an `Arc` bump), and the asset behind it lives exactly as long as at least
//! one handle does — drop the last handle and the asset is freed. The
//! [`AssetServer`](super::AssetServer) keeps only a [`Weak`] reference in its
//! dedup cache, so it never keeps an otherwise-unused asset alive.

use std::fmt;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Condvar, Mutex};

/// A process-unique identifier assigned to every asset slot.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AssetId(pub(crate) u64);

impl AssetId {
    /// The raw numeric id.
    pub fn value(self) -> u64 {
        self.0
    }
}

/// The lifecycle state of an asset behind a [`Handle`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoadState {
    /// A background load is in progress; the value is not ready yet.
    Loading,
    /// The asset loaded successfully and can be read with [`Handle::get`].
    Loaded,
    /// Loading failed; see [`Handle::error`] for why.
    Failed,
}

/// The interior of an asset slot: its current state and (once ready) the value.
///
/// The value is stored as an `Arc<T>` so it can be cloned out cheaply and so a
/// live reload can swap in fresh contents without disturbing readers that
/// already hold the previous `Arc`.
pub(crate) enum CellState<T> {
    Loading,
    Loaded(Arc<T>),
    Failed(Arc<str>),
}

/// The shared, reference-counted storage for one asset.
///
/// Handles hold an `Arc<AssetCell<T>>`; the server's cache holds a
/// `Weak<dyn Any>` to the same allocation for deduplication only.
pub(crate) struct AssetCell<T> {
    id: AssetId,
    source: Option<PathBuf>,
    state: Mutex<CellState<T>>,
    ready: Condvar,
}

impl<T> AssetCell<T> {
    pub(crate) fn new_loading(id: AssetId, source: Option<PathBuf>) -> Arc<Self> {
        Arc::new(Self {
            id,
            source,
            state: Mutex::new(CellState::Loading),
            ready: Condvar::new(),
        })
    }

    pub(crate) fn new_loaded(id: AssetId, source: Option<PathBuf>, value: T) -> Arc<Self> {
        Arc::new(Self {
            id,
            source,
            state: Mutex::new(CellState::Loaded(Arc::new(value))),
            ready: Condvar::new(),
        })
    }

    pub(crate) fn new_failed(id: AssetId, source: Option<PathBuf>, message: String) -> Arc<Self> {
        Arc::new(Self {
            id,
            source,
            state: Mutex::new(CellState::Failed(Arc::from(message))),
            ready: Condvar::new(),
        })
    }

    pub(crate) fn set_loaded(&self, value: T) {
        *self.state.lock().unwrap() = CellState::Loaded(Arc::new(value));
        self.ready.notify_all();
    }

    pub(crate) fn set_failed(&self, message: String) {
        *self.state.lock().unwrap() = CellState::Failed(Arc::from(message));
        self.ready.notify_all();
    }
}

/// A typed, reference-counted handle to an asset of type `T`.
///
/// Clone it freely to share ownership; the asset is freed when the last handle
/// is dropped. Read the value with [`get`](Self::get) (returns `None` until the
/// asset is loaded) or block for it with [`wait`](Self::wait).
pub struct Handle<T> {
    cell: Arc<AssetCell<T>>,
}

impl<T> Handle<T> {
    pub(crate) fn from_cell(cell: Arc<AssetCell<T>>) -> Self {
        Self { cell }
    }

    /// This asset's process-unique id.
    pub fn id(&self) -> AssetId {
        self.cell.id
    }

    /// The source path the asset was loaded from, if any (in-memory assets added
    /// with [`AssetServer::add`](super::AssetServer::add) have none).
    pub fn source(&self) -> Option<&Path> {
        self.cell.source.as_deref()
    }

    /// The current lifecycle state.
    pub fn state(&self) -> LoadState {
        match &*self.cell.state.lock().unwrap() {
            CellState::Loading => LoadState::Loading,
            CellState::Loaded(_) => LoadState::Loaded,
            CellState::Failed(_) => LoadState::Failed,
        }
    }

    /// Whether the asset has finished loading successfully.
    pub fn is_loaded(&self) -> bool {
        matches!(&*self.cell.state.lock().unwrap(), CellState::Loaded(_))
    }

    /// The loaded value as a cheap `Arc<T>` clone, or `None` if it is still
    /// loading or failed.
    pub fn get(&self) -> Option<Arc<T>> {
        match &*self.cell.state.lock().unwrap() {
            CellState::Loaded(value) => Some(value.clone()),
            _ => None,
        }
    }

    /// The error message if loading failed, else `None`.
    pub fn error(&self) -> Option<String> {
        match &*self.cell.state.lock().unwrap() {
            CellState::Failed(message) => Some(message.to_string()),
            _ => None,
        }
    }

    /// Blocks until the asset is no longer [`Loading`](LoadState::Loading),
    /// returning the value on success or `None` if it failed.
    pub fn wait(&self) -> Option<Arc<T>> {
        let mut guard = self.cell.state.lock().unwrap();
        loop {
            match &*guard {
                CellState::Loading => guard = self.cell.ready.wait(guard).unwrap(),
                CellState::Loaded(value) => return Some(value.clone()),
                CellState::Failed(_) => return None,
            }
        }
    }

    /// The number of live handles to this asset (including this one). The
    /// server holds only a weak reference, so this counts handles alone.
    pub fn ref_count(&self) -> usize {
        Arc::strong_count(&self.cell)
    }

    /// Replaces the asset's contents in place; every existing handle observes
    /// the new value on its next [`get`](Self::get). Used by live reload.
    pub(crate) fn set_loaded(&self, value: T) {
        self.cell.set_loaded(value);
    }

    /// Marks the asset as failed in place.
    pub(crate) fn set_failed(&self, message: String) {
        self.cell.set_failed(message);
    }
}

impl<T> Clone for Handle<T> {
    fn clone(&self) -> Self {
        Self {
            cell: self.cell.clone(),
        }
    }
}

impl<T> fmt::Debug for Handle<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Handle")
            .field("id", &self.cell.id.0)
            .field("state", &self.state())
            .field("source", &self.cell.source)
            .finish()
    }
}
