<?php
/**
 * CPT Builder storage.
 *
 * Persists all post type definitions in a single, non-autoloaded option to
 * avoid bloating WordPress' alloptions cache. Definitions are keyed by post
 * type key for O(1) lookups.
 *
 * @package CptBuilder
 * @since 1.0.0
 */

namespace CptBuilder;

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

/**
 * Data access layer for stored CPT definitions.
 */
final class Storage {

	/**
	 * Option holding the keyed map of post type definitions.
	 */
	public const OPTION = 'wp_powersuite_cptb_post_types';

	/**
	 * Option flag indicating rewrite rules need a deferred flush.
	 */
	public const FLUSH_FLAG = 'wp_powersuite_cptb_flush_needed';

	/**
	 * Request-level cache of the decoded option.
	 *
	 * @var array<string, array<string, mixed>>|null
	 */
	private ?array $cache = null;

	/**
	 * Retrieve all stored definitions keyed by post type key.
	 *
	 * @return array<string, array<string, mixed>>
	 */
	public function all(): array {
		if ( $this->cache !== null ) {
			return $this->cache;
		}

		$stored = get_option( self::OPTION, [] );
		if ( ! is_array( $stored ) ) {
			$stored = [];
		}

		$clean = [];
		foreach ( $stored as $key => $definition ) {
			if ( ! is_string( $key ) || ! is_array( $definition ) ) {
				continue;
			}
			$clean[ $key ] = $definition;
		}

		$this->cache = $clean;

		return $this->cache;
	}

	/**
	 * Retrieve a single definition by post type key.
	 *
	 * @param string $key Post type key.
	 * @return array<string, mixed>|null
	 */
	public function get( string $key ): ?array {
		$all = $this->all();

		return $all[ $key ] ?? null;
	}

	/**
	 * Whether a definition exists for the given key.
	 *
	 * @param string $key Post type key.
	 */
	public function exists( string $key ): bool {
		return $this->get( $key ) !== null;
	}

	/**
	 * Create or update a single definition.
	 *
	 * @param string               $key        Post type key.
	 * @param array<string, mixed> $definition Sanitized definition.
	 */
	public function save( string $key, array $definition ): bool {
		if ( $key === '' ) {
			return false;
		}

		$all         = $this->all();
		$all[ $key ] = $definition;

		return $this->persist( $all );
	}

	/**
	 * Delete a single definition.
	 *
	 * @param string $key Post type key.
	 */
	public function delete( string $key ): bool {
		$all = $this->all();
		if ( ! isset( $all[ $key ] ) ) {
			return false;
		}

		unset( $all[ $key ] );

		return $this->persist( $all );
	}

	/**
	 * Persist the full map back to storage (non-autoloaded).
	 *
	 * @param array<string, array<string, mixed>> $all Full definition map.
	 */
	private function persist( array $all ): bool {
		$this->cache = $all;

		// Stored with autoload disabled: this option is only read on the
		// `init` hook (registration) and on admin screens, never globally.
		update_option( self::OPTION, $all, false );

		return true;
	}

	/**
	 * Flag that rewrite rules must be flushed on the next admin load.
	 */
	public function flagFlush(): void {
		update_option( self::FLUSH_FLAG, '1', false );
	}

	/**
	 * Whether a rewrite flush is pending.
	 */
	public function needsFlush(): bool {
		return get_option( self::FLUSH_FLAG, '' ) === '1';
	}

	/**
	 * Clear the pending rewrite flush flag.
	 */
	public function clearFlushFlag(): void {
		delete_option( self::FLUSH_FLAG );
	}
}
