<?php
declare( strict_types=1 );

namespace FiboFilters\SeoRules;

use FiboFilters\Settings\Storage;
use FiboFilters\Util\Cache;
use FiboFilters\Util\DbUtil;
use FiboFilters\Util\Helpers;

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

/**
 * Repository responsible for CRUD operations on SeoRule entities.
 *
 * Provides a thin, WordPress-friendly wrapper around low-level DB access
 * (via $wpdb) plus simple object hydration, caching and bulk helpers.
 */
class SeoRuleRepository {

	/**
	 * Storage.
	 *
	 * @var Storage
	 */
	protected Storage $storage;

	/**
	 * Cache utility instance.
	 *
	 * @var Cache
	 */
	protected Cache $cache;

	/**
	 * Database utilities helper.
	 *
	 * @var \FiboFilters\Util\DbUtil
	 */
	protected DbUtil $db_util;

	/**
	 * SeoRuleRepository constructor.
	 *
	 * @param Storage $storage Seo Rules Database.
	 * @param Cache   $cache Cache utility instance.
	 * @param DbUtil  $db_util Database utilities helper.
	 */
	public function __construct(
		Storage $storage,
		Cache $cache,
		DbUtil $db_util
	) {
		$this->storage = $storage;
		$this->cache   = $cache;
		$this->db_util = $db_util;
	}

	/**
	 * Returns cache group name.
	 *
	 * @return string Cache group.
	 */
	public function get_cache_group(): string {
		return 'seo_rules';
	}

	/**
	 * Key used for SEO Rules settings in the Storage.
	 *
	 * @return string
	 */
	public function get_storage_key(): string {
		return 'seo_rules';
	}

	/**
	 * Retrieves all SeoRule objects, optionally filtered by language.
	 *
	 * @param string $lang Optional. Language code (e.g. 'pl', 'en'). Default empty
	 *                     string retrieves rules in all languages.
	 *
	 * @return SeoRule[] Array of SeoRule objects (may be empty).
	 */
	public function get_all_rules( string $lang = '' ): array {
		return $this->get_rules( [ 'lang' => $lang ] );
	}

	/**
	 * Retrieve SeoRule objects according to given parameters.
	 *
	 * @param array $args {
	 *     Optional. Arguments that configure the query.
	 *
	 * @type bool   $cache Whether to use cache. Default true.
	 * @type string $cache_key Cache key (auto-generated if empty).
	 * @type int[]  $ids Array of rule IDs to limit the query.
	 * @type string $lang Language code, e.g. 'pl', 'en'.
	 * }
	 * @return SeoRule[] Array of SeoRule objects (may be empty).
	 */
	public function get_rules( array $args ): array {

		$defaults = [
			'cache'     => true,
			'cache_key' => '',
			'ids'       => [],
			'lang'      => '',
		];
		$args     = wp_parse_args( $args, $defaults );

		if ( '' === $args['cache_key'] ) {
			$args['cache_key'] = 'seo_rules_' . Helpers::generate_cache_hash( $args );
		}

		// Break early if language is incorrect.
		if ( ! empty( $args['lang'] ) && ! Helpers::is_language_code( $args['lang'] ) ) {
			return [];
		}

		$cached = $this->cache->get( $args['cache_key'], $this->get_cache_group() );

		if ( $args['cache'] && is_array( $cached ) ) {
			return $cached;
		}

		$json      = $this->storage->get( $this->get_storage_key(), $args['lang'] );
		$raw_rules = $this->unpack_from_block_settings( $json );

		$rules = [];
		foreach ( $raw_rules as $data ) {
			if ( ! empty( $args['ids'] )
				&& is_string( $data['id'] )
				&& preg_match( '/^seo-rule-(\d+)$/', $data['id'], $m ) ) {
				if ( ! in_array( (int) $m[1], $args['ids'], true ) ) {
					continue;
				}
			}

			if ( empty( $data['lang'] ) && ! empty( $args['lang'] ) ) {
				$data['lang'] = $args['lang'];
			}

			$rule = new SeoRule();
			$rule->populate( $data );
			$rules[] = $rule;
		}

		if ( $args['cache'] ) {
			$this->cache->set( $args['cache_key'], $rules, $this->get_cache_group() );
		}

		return $rules;
	}

	/**
	 * Extracts each WordPress block’s attribute array from a JSON-encoded list of blocks.
	 *
	 * The input should be a JSON string representing an array of WordPress blocks, where
	 * each item may contain an `attributes` key, for example:

	 * @param string $block_settings JSON-encoded array of blocks.
	 *                               An empty string is treated as an empty array.
	 * @return array<int, array<string, mixed>> Array of block-attribute arrays.
	 *                                          Returns an empty array when the
	 *                                          JSON is invalid or no attributes
	 *                                          are found.
	 */
	public function unpack_from_block_settings( string $block_settings ): array {

		// Treat an empty string as an empty JSON array (back-compat).
		if ( '' === $block_settings ) {
			$block_settings = '[]';
		}

		$blocks = json_decode( $block_settings, true );

		// Early return on JSON error or when the result is not an array.
		if ( JSON_ERROR_NONE !== json_last_error() || ! is_array( $blocks ) ) {
			return [];
		}

		$rules = [];

		foreach ( $blocks as $block ) {
			if ( ! empty( $block['attributes'] ) && is_array( $block['attributes'] ) ) {
				$rules[] = $block['attributes'];
			}
		}

		return $rules;
	}

	/**
	 * Get block data for settings CRUD in FiboFilters\Settings\Settings.
	 *
	 * @param string $lang Language.
	 *
	 * @return array
	 */
	public function get_blocks( string $lang = '' ): array {
		$blocks = [];

		foreach ( $this->get_all_rules( $lang ) as $rule ) {
			foreach ( $rule->get_block_data() as $block ) {
				$blocks[] = $block;
			}
		}

		return $blocks;
	}
}
