<?php
/**
 * Safe JSON decode and guarded callbacks (prevents plugin fatals from bad data).
 *
 * @package JOCBM
 *
 * Copyright (C) 2026 JayOnCode
 * License: GPL-2.0-or-later
 */

namespace JOCBM\Services;

defined( 'ABSPATH' ) || exit;

/**
 * Class JsonHelper
 */
final class JsonHelper {

	/**
	 * Decode JSON (or pass through arrays) into an array.
	 *
	 * @param mixed              $raw     JSON string, array, or other value.
	 * @param array<int, mixed>  $default Fallback when decode fails.
	 * @return array<int|string, mixed>
	 */
	public static function decode_array( $raw, array $default = array() ): array {
		try {
			if ( is_array( $raw ) ) {
				return $raw;
			}
			if ( ! is_string( $raw ) || '' === $raw ) {
				return $default;
			}
			$decoded = json_decode( $raw, true );
			return is_array( $decoded ) ? $decoded : $default;
		} catch ( \Throwable $e ) {
			self::log( 'decode_array', $e );
			return $default;
		}
	}

	/**
	 * Run a callback; on any throwable return a safe default instead of a 500.
	 *
	 * @param callable $callback Operation to run.
	 * @param mixed    $default  Value returned when the callback throws.
	 * @param string   $context  Short label for logs (e.g. register_library_blocks).
	 * @return mixed
	 */
	public static function guard( callable $callback, $default = null, string $context = '' ) {
		try {
			return $callback();
		} catch ( \Throwable $e ) {
			self::log( $context ?: 'guard', $e );
			return $default;
		}
	}

	/**
	 * @param string     $context Short label for logs.
	 * @param \Throwable $e       Caught exception or error.
	 */
	private static function log( string $context, \Throwable $e ): void {
		$message = sprintf(
			'[JOC Blockora Morph] %s: %s in %s:%d',
			$context,
			$e->getMessage(),
			$e->getFile(),
			$e->getLine()
		);

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			error_log( $message );
		}

		/**
		 * Fires when Blockora Morph catches a throwable in a guarded path.
		 *
		 * @param string     $context Context label.
		 * @param \Throwable $e       Caught throwable.
		 */
		do_action( 'jocbm_error', $context, $e );
	}
}
