//! # pmocache – Système de cache générique pour PMOMusic
//!
//! Cette crate fournit les briques communes utilisées par les caches de PMOMusic.
//! Elle gère l'association entre fichiers stockés sur disque et métadonnées
//! conservées dans une base SQLite, ainsi que les opérations de téléchargement,
//! d'éviction et de mise à jour.
//!
//! Les fonctionnalités clés incluent :
//! - **lazy caching** : génération de clés « lazy » (préfixées `L:`) permettant de publier des
//!   URLs stables avant le téléchargement. Le cache résout automatiquement un lazy PK lors du
//!   premier accès, commute l'entrée vers le PK réel et notifie les abonnés ;
//! - **local caching** : possibilité d’enregistrer un fichier déjà présent sur le disque via
//!   [`Cache::register_local_file_reference`] sans duplication physique. Cette méthode crée un
//!   lien (symlink ou hard link suivant la plateforme), marque l’élément comme complet et laisse
//!   la gestion métier (audio, images…) décider des métadonnées à persister.
//!
//! ## Vue d'ensemble
//!
//! `pmocache` met à disposition :
//! - un modèle `Cache` asynchrone pour stocker des fichiers et leurs métadonnées ;
//! - un module `db` encapsulant l'accès SQLite (table `asset` + table `metadata`) ;
//! - des utilitaires de téléchargement (`download`) réutilisables par les caches spécialisés ;
//! - un trait `CacheConfig` permettant de paramétrer l'extension, le nom et le type du cache.
//!
//! Les crates `pmocovers` (images) et `pmoaudiocache` (pistes audio) s'appuient sur ces
//! composants et ajoutent leurs propres contraintes métier (conversion WebP, métadonnées audio…).
//!
//! ## Exemple basique
//!
//! ```rust,no_run
//! use pmocache::{Cache, CacheConfig};
//!
//! struct MyConfig;
//! impl CacheConfig for MyConfig {
//!     fn file_extension() -> &'static str { "dat" }
//! }
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//!     let cache = Cache::<MyConfig>::new("./cache", 1000)?;
//!
//!    // Ajout d'un fichier depuis une URL
//!     let pk = cache.add_from_url("https://example.com/file.dat", None).await?;
//!     println!("Fichier ajouté avec la clé {pk}");
//!
//!     // Récupération du fichier local
//!     let path = cache.get(&pk).await?;
//!     println!("Fichier disponible à {path:?}");
//!
//!     Ok(())
//! }
//! ```
//!
//! ## Collections
//!
//! ```rust,no_run
//! use pmocache::{Cache, CacheConfig};
//!
//! struct AudioConfig;
//! impl CacheConfig for AudioConfig {
//!     fn file_extension() -> &'static str { "flac" }
//!     fn cache_type() -> &'static str { "audio" }
//!     fn cache_name() -> &'static str { "tracks" }
//! }
//!
//! #[tokio::main]
//! async fn main() -> anyhow::Result<()> {
//!     let cache = Cache::<AudioConfig>::new("./audio-cache", 200)?;
//!
//!     let album = "album:the_wall";
//!     cache.add_from_url("https://example.com/track1.flac", Some(album)).await?;
//!     cache.add_from_url("https://example.com/track2.flac", Some(album)).await?;
//!
//!     let files = cache.get_collection(album).await?;
//!     println!("Album {album} : {} fichiers en cache", files.len());
//!
//!     Ok(())
//! }
//! ```
//!
//! ## Structure sur disque
//!
//! ```text
//! cache/
//! ├── cache.db            # Base SQLite
//! ├── 1a2b3c4d.orig.dat   # Fichier original
//! └── 1a2b3c4d.thumb.dat  # Variante (qualifier différent)
//! ```
//!
//! Les métadonnées sont conservées dans deux tables :
//!
//! ```sql
//! CREATE TABLE asset (
//!     pk TEXT PRIMARY KEY,
//!     collection TEXT,
//!     id TEXT,
//!     hits INTEGER DEFAULT 0,
//!     last_used TEXT
//! );
//!
//! CREATE TABLE metadata (
//!     pk TEXT,
//!     key TEXT,
//!     value_type TEXT CHECK(value_type IN ('string','number','boolean','null')),
//!     value TEXT,
//!     PRIMARY KEY (pk, key),
//!     FOREIGN KEY (pk) REFERENCES asset(pk) ON DELETE CASCADE
//! );
//! ```
//!
//! ## Modules principaux
//!
//! - [`cache`] : gestion du cache sur disque + opérations asynchrones ;
//! - [`db`] : accès SQLite, contraintes et helpers métadonnées ;
//! - [`download`] : primitives de téléchargement et de transformation ;
//! - [`cache_trait`] : trait partagé entre implémentations spécialisées.
//!
//! ## Crates associées
//!
//! - [`pmocovers`] : cache d'images reposant sur `pmocache` ;
//! - [`pmoaudiocache`] : spécialisation audio avec extraction de métadonnées.

pub mod cache;
pub mod cache_trait;
pub mod db;
pub mod download;
pub mod lazy;
pub mod metadata_macros;

#[cfg(feature = "pmoserver")]
pub mod pmoserver_ext;

#[cfg(feature = "pmoserver")]
pub mod api;

#[cfg(feature = "openapi")]
pub mod openapi;

#[cfg(feature = "pmoconfig")]
pub mod config_ext;

pub use cache::{
    generate_lazy_pk, is_lazy_pk, Cache, CacheBroadcastEvent, CacheConfig, CacheEvent,
    CacheSubscription,
};
pub use cache_trait::{pk_from_content_header, FileCache};

/// Retourne la route relative pour une cover: `/covers/image/{pk}[/{param}]`
pub fn covers_route_for(pk: &str, param: Option<&str>) -> String {
    if let Some(p) = param {
        format!("/covers/image/{}/{}", pk, p)
    } else {
        format!("/covers/image/{}", pk)
    }
}

/// Retourne l'URL absolue pour une cover via `PMO_SERVER_URL`
/// Utilisée uniquement pour les contextes UPnP (LAN)
pub fn covers_absolute_url_for_upnp(pk: &str, param: Option<&str>) -> String {
    let base = std::env::var("PMO_SERVER_URL")
        .unwrap_or_else(|_| {
            panic!(
                "covers_absolute_url_for_upnp: PMO_SERVER_URL non configuré.\n\
                Le serveur doit être initialisé avant toute utilisation UPnP."
            );
        });
    format!("{}{}", base.trim_end_matches('/'), covers_route_for(pk, param))
}
pub use db::{CacheEntry, DB};
pub use download::{
    download, download_with_transformer, ingest_with_transformer, peek_header, peek_reader_header,
    Download, StreamTransformer, TransformContextHandle, TransformMetadata,
};
pub use lazy::{lazy_prefix_from_pk, LazyEntryRemoteData, LazyProvider};

#[cfg(feature = "pmoserver")]
pub use pmoserver_ext::{create_api_router, create_file_router, GenericCacheExt};

#[cfg(all(feature = "pmoserver", feature = "openapi"))]
pub use api::{
    AddItemRequest, AddItemResponse, DeleteItemResponse, DownloadStatus, ErrorResponse,
    PinResponse, PinStatus, SetTtlRequest,
};

#[cfg(feature = "pmoconfig")]
pub use config_ext::CacheConfigExt;
