<?php
/**
 * Custom exception class for Restaurant Costing.
 *
 * @package RestaurantCosting
 */

declare(strict_types=1);

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Base exception for all RC plugin errors.
 *
 * Error codes for categorization:
 * - DB_ERROR        Database operation failure
 * - VALIDATION      Input validation failure
 * - PERMISSION      Capability check failure
 * - NOT_FOUND       Resource not found
 * - EXISTS          Duplicate resource conflict
 * - UPLOAD          File upload failure
 * - IMPORT          Import/export failure
 * - EMAIL           Email sending failure
 * - UNKNOWN         Catch-all
 */
class RC_Exception extends RuntimeException {

	private string $error_code;

	/** @var array<string, mixed> */
	private array $context;

	public function __construct(
		string $message = '',
		string $error_code = 'UNKNOWN',
		?Throwable $previous = null,
		array $context = array()
	) {
		$this->error_code = $error_code;
		$this->context    = $context;
		parent::__construct( $message, 0, $previous );
	}

	/**
	 * Get the string error code.
	 */
	public function get_error_code(): string {
		return $this->error_code;
	}

	/**
	 * Get all context data.
	 *
	 * @return array<string, mixed>
	 */
	public function get_context(): array {
		return $this->context;
	}

	/**
	 * Get a single context value.
	 */
	public function get_context_value( string $key, mixed $default = null ): mixed {
		return $this->context[ $key ] ?? $default;
	}

	/**
	 * Create from a WP_Error.
	 */
	public static function from_wp_error( WP_Error $wp_error, string $error_code = 'UNKNOWN', array $context = array() ): self {
		return new self(
			$wp_error->get_error_message(),
			$error_code,
			null,
			array_merge( array( 'wp_error_code' => $wp_error->get_error_code() ), $context )
		);
	}

	/**
	 * Create a DB error exception using $wpdb->last_error.
	 */
	public static function db_error( string $operation, string $table, ?Throwable $previous = null ): self {
		global $wpdb;
		return new self(
			sprintf( 'Database %s failed on %s: %s', $operation, $table, $wpdb->last_error ),
			'DB_ERROR',
			$previous,
			array(
				'operation'  => $operation,
				'table'      => $table,
				'last_query' => $wpdb->last_query,
				'last_error' => $wpdb->last_error,
			)
		);
	}

	/**
	 * Check $wpdb->last_error and throw RC_Exception if a DB error occurred.
	 *
	 * Call after every $wpdb->insert, update, delete, query, replace.
	 *
	 * @param string $operation Operation name (e.g. 'insert', 'update').
	 * @param string $table     Table name or constant.
	 * @throws RC_Exception If $wpdb->last_error is non-empty.
	 */
	public static function throw_on_db_error( string $operation, string $table ): void {
		global $wpdb;
		if ( '' !== $wpdb->last_error ) {
			throw self::db_error( $operation, $table );
		}
	}
}
