// Copyright (C) 2026 ren-yamanashi
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, version 2.0,
// as published by the Free Software Foundation.
//
// This program is designed to work with certain software (including
// but not limited to OpenSSL) that is licensed under separate terms,
// as designated in a particular file or component or in included license
// documentation. The authors of this program hereby grant you an additional
// permission to link the program and your derivative works with the
// separately licensed software that they have either included with
// the program or referenced in the documentation.
//
// 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/>.

//! Error type and result alias for the storage-engine interface.

use crate::sys;

/// Errors a storage engine can return; each maps to a MySQL `HA_ERR_*` code
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum EngineError {
    /// End of a table or index scan, returned from [`StorageEngine::rnd_next`]
    /// when the scan is exhausted.
    ///
    /// [`StorageEngine::rnd_next`]: crate::engine::StorageEngine::rnd_next
    EndOfFile,
    /// The engine does not support the requested operation
    WrongCommand,
    /// The requested operation is unsupported on this engine, mapped to
    /// `HA_ERR_UNSUPPORTED`. Distinct from [`WrongCommand`](Self::WrongCommand):
    /// MySQL uses this code for capability gaps such as bulk-load execution
    /// that the engine has not opted into.
    Unsupported,
    /// The supplied table or schema name is not valid UTF-8 or otherwise
    /// unusable. Mapped to `HA_ERR_WRONG_TABLE_NAME` so operators see a
    /// name-level diagnostic instead of a generic internal error.
    InvalidName,
    /// Generic internal error; prefer a more specific variant when possible
    Internal,
}

impl EngineError {
    /// Convert to the matching MySQL `HA_ERR_*` integer expected at the
    /// `extern "C"` boundary.
    #[must_use]
    pub fn to_mysql_errno(self) -> i32 {
        match self {
            Self::EndOfFile => sys::HA_ERR_END_OF_FILE,
            Self::WrongCommand => sys::HA_ERR_WRONG_COMMAND,
            Self::Unsupported => sys::HA_ERR_UNSUPPORTED,
            Self::InvalidName => sys::HA_ERR_WRONG_TABLE_NAME,
            Self::Internal => sys::HA_ERR_INTERNAL_ERROR,
        }
    }
}

impl core::fmt::Display for EngineError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        let message = match self {
            Self::EndOfFile => "end of table or index scan",
            Self::WrongCommand => "operation not supported by the engine",
            Self::Unsupported => "operation unsupported on this engine",
            Self::InvalidName => "invalid table or schema name",
            Self::Internal => "internal engine error",
        };
        f.write_str(message)
    }
}

impl std::error::Error for EngineError {}

/// Result alias used throughout the [`StorageEngine`](crate::engine::StorageEngine) trait
pub type EngineResult<T = ()> = Result<T, EngineError>;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn errno_mapping_matches_ha_err_codes() {
        assert_eq!(
            EngineError::EndOfFile.to_mysql_errno(),
            sys::HA_ERR_END_OF_FILE
        );
        assert_eq!(
            EngineError::WrongCommand.to_mysql_errno(),
            sys::HA_ERR_WRONG_COMMAND
        );
        assert_eq!(
            EngineError::Unsupported.to_mysql_errno(),
            sys::HA_ERR_UNSUPPORTED
        );
        assert_eq!(
            EngineError::InvalidName.to_mysql_errno(),
            sys::HA_ERR_WRONG_TABLE_NAME
        );
        assert_eq!(
            EngineError::Internal.to_mysql_errno(),
            sys::HA_ERR_INTERNAL_ERROR
        );
    }

    #[test]
    fn display_renders_a_distinct_message_per_variant() {
        let variants = [
            EngineError::EndOfFile,
            EngineError::WrongCommand,
            EngineError::Unsupported,
            EngineError::InvalidName,
            EngineError::Internal,
        ];
        let messages: Vec<String> = variants.iter().map(ToString::to_string).collect();
        assert!(messages.iter().all(|m| !m.is_empty()));
        for (i, a) in messages.iter().enumerate() {
            for b in &messages[i + 1..] {
                assert_ne!(a, b);
            }
        }
    }

    #[test]
    fn usable_as_std_error() {
        fn take(_: &dyn std::error::Error) {}
        take(&EngineError::Internal);
    }
}
