use std::path::PathBuf;

use serde::Serialize;

use crate::types::ModelId;

/// Structured error type for Lumotia.
///
/// Implements `Serialize` so errors can be sent to the frontend as
/// structured JSON rather than opaque strings.
#[derive(Debug, thiserror::Error, Serialize)]
pub enum Error {
    #[error("model not found: {0}")]
    ModelNotFound(ModelId),

    #[error("model not downloaded: {0}")]
    ModelNotDownloaded(ModelId),

    #[error("engine not loaded: call load_model first")]
    EngineNotLoaded,

    #[error("transcription failed: {0}")]
    TranscriptionFailed(String),

    /// Inference exceeded the bounded wait imposed by the caller (live
    /// session `drain_inference`). The spawned worker has had its abort
    /// flag set so whisper-rs will return early on its next
    /// abort-callback poll; the lock-up itself is *not* recovered by
    /// this error — but the live-session lifecycle can now progress.
    #[error("inference timed out after {timeout_ms}ms")]
    InferenceTimeout { timeout_ms: u64 },

    #[error("audio decode failed: {0}")]
    AudioDecodeFailed(String),

    #[error("audio capture failed: {0}")]
    AudioCaptureFailed(String),

    #[error("model download failed: {0}")]
    DownloadFailed(String),

    #[error("file not found: '{}'", .0.display())]
    FileNotFound(PathBuf),

    /// Structured storage failure flowed up from `lumotia_storage::Error` via
    /// its `From` impl. Display reads through to `detail` so the operation +
    /// source context produced by the storage crate isn't double-prefixed.
    #[error("{detail}")]
    Storage {
        kind: StorageKind,
        operation: String,
        detail: String,
    },

    #[error("provider not registered: {0}")]
    ProviderNotRegistered(String),

    #[error("io error ({kind}): {message}")]
    Io {
        kind: String,
        message: String,
        raw_os_error: Option<i32>,
    },
}

/// Coarse discriminator for `Error::Storage`. The storage crate maps
/// its full typed error onto one of these kinds at the boundary. Backend code
/// that wants finer-grained branching pattern-matches on
/// `lumotia_storage::Error` directly.
#[derive(Debug, Clone, Copy, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum StorageKind {
    DatabaseOpen,
    Migration,
    Query,
    NotFound,
    InvalidReference,
    Filesystem,
}

impl From<std::io::Error> for Error {
    fn from(err: std::io::Error) -> Self {
        Self::Io {
            kind: format!("{:?}", err.kind()),
            message: err.to_string(),
            raw_os_error: err.raw_os_error(),
        }
    }
}

pub type Result<T> = std::result::Result<T, Error>;
