<?php
/**
 * Scope_Gate — L4 enforcement of the admin-configured write scope.
 *
 * Consults the ppok_ai_toolkit_settings option, specifically:
 *   - write_post_types (default ['post'])
 *   - write_post_statuses (default ['draft'])
 *
 * Every write-side ability runs both checks against its target post BEFORE
 * touching the database. When the target's type or status falls outside the
 * configured scope, the gate returns a WP_Error with code 'scope_blocked'
 * which the Ability_Registrar maps to result='scope_blocked' in the audit
 * log. Publishing-grade writes additionally require edit_published_posts.
 *
 * See .context/project.json → architecture.scoped_writes_layers.L4.
 *
 * @package Ppok\AIToolkit
 * @license GPL-2.0-or-later
 */

namespace Ppok\AIToolkit\Abilities;

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

final class Scope_Gate {

	public const OPTION_KEY = 'ppok_ai_toolkit_settings';

	private const DEFAULT_POST_TYPES    = array( 'post' );
	private const DEFAULT_POST_STATUSES = array( 'draft' );

	/**
	 * Snapshot of the plugin settings array, defensive against missing /
	 * malformed option storage. Returns an array even when the option is
	 * absent so callers can use [] access without isset checks.
	 *
	 * @return array<string, mixed>
	 */
	public static function settings(): array {
		$value = get_option( self::OPTION_KEY, array() );
		return is_array( $value ) ? $value : array();
	}

	/**
	 * @return array<int, string>
	 */
	public static function allowed_post_types(): array {
		$settings = self::settings();
		$value    = isset( $settings['write_post_types'] ) ? (array) $settings['write_post_types'] : self::DEFAULT_POST_TYPES;
		return array_values( array_filter( array_map( 'strval', $value ), 'strlen' ) );
	}

	/**
	 * @return array<int, string>
	 */
	public static function allowed_post_statuses(): array {
		$settings = self::settings();
		$value    = isset( $settings['write_post_statuses'] ) ? (array) $settings['write_post_statuses'] : self::DEFAULT_POST_STATUSES;
		return array_values( array_filter( array_map( 'strval', $value ), 'strlen' ) );
	}

	/**
	 * @return true|\WP_Error
	 */
	public static function check_post_type( string $post_type ) {
		$allowed = self::allowed_post_types();
		if ( in_array( $post_type, $allowed, true ) ) {
			return true;
		}
		return new \WP_Error(
			'scope_blocked',
			sprintf(
				/* translators: 1: post type, 2: comma-separated allowed types */
				__( 'Post type "%1$s" is outside the configured write scope. Allowed types: %2$s.', 'ppok-ai-toolkit' ),
				$post_type,
				implode( ', ', $allowed )
			),
			array(
				'status'        => 403,
				'rejected_by'   => 'write_post_types',
				'value'         => $post_type,
				'allowed_value' => $allowed,
			)
		);
	}

	/**
	 * @return true|\WP_Error
	 */
	public static function check_post_status( string $post_status ) {
		$allowed = self::allowed_post_statuses();
		if ( ! in_array( $post_status, $allowed, true ) ) {
			return new \WP_Error(
				'scope_blocked',
				sprintf(
					/* translators: 1: post status, 2: comma-separated allowed statuses */
					__( 'Post status "%1$s" is outside the configured write scope. Allowed statuses: %2$s.', 'ppok-ai-toolkit' ),
					$post_status,
					implode( ', ', $allowed )
				),
				array(
					'status'        => 403,
					'rejected_by'   => 'write_post_statuses',
					'value'         => $post_status,
					'allowed_value' => $allowed,
				)
			);
		}

		// Writing to a published post requires the dedicated cap even when
		// the admin has added 'publish' to write_post_statuses.
		if ( 'publish' === $post_status && ! current_user_can( 'edit_published_posts' ) ) {
			return new \WP_Error(
				'scope_blocked',
				__( 'Published-post writes require the edit_published_posts capability.', 'ppok-ai-toolkit' ),
				array(
					'status'      => 403,
					'rejected_by' => 'edit_published_posts',
				)
			);
		}

		return true;
	}
}
