<?php
/**
 * File Path: includes/data/class-mbs-book-territorial-rights-dao.php
 *
 * Book Territorial Rights DAO
 *
 * Single-row DAO for the mbs_book_territorial_rights table, which stores the
 * structured territorial rights for each book. The rights model is:
 *
 *   type 'world'   — rights apply globally; countries array is empty.
 *   type 'include' — rights apply only to the listed country codes.
 *   type 'exclude' — rights apply everywhere except the listed country codes.
 *
 * This matches the mbs_territorial_rights_v2 JSON structure that was
 * previously stored in wp_postmeta.
 *
 * Write rule: WRITE-PROTOCOL.md §A + §B.
 *
 * @package MyBookShowroom
 * @since   1.7.0
 */

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

/**
 * DAO for mbs_book_territorial_rights.
 *
 * @since 1.7.0
 */
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- DAO/migration: table names via $wpdb->prefix are trusted server-controlled values, not user input
// phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter -- same reason: $wpdb->prefix table names are safe; $where_sql is built from sanitized server-controlled inputs
class MBS_Book_Territorial_Rights_DAO { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- mbs_/MBS_ prefix is correct; scanner not configured with plugin prefix

	/**
	 * Allowed rights type values.
	 *
	 * @var string[]
	 */
	private const ALLOWED_TYPES = array( 'world', 'include', 'exclude' );

	// -------------------------------------------------------------------------
	// Write
	// -------------------------------------------------------------------------

	/**
	 * Save (upsert) territorial rights for a book.
	 *
	 * $rights_type must be one of 'world', 'include', 'exclude'. An invalid
	 * type is normalised to 'world'. $countries is ignored for 'world' type
	 * and stored as a JSON array for 'include'/'exclude'.
	 *
	 * @param int      $post_id     Book post ID.
	 * @param string   $rights_type Rights type: 'world' | 'include' | 'exclude'.
	 * @param string[] $countries   ISO 3166-1 alpha-2 country codes.
	 * @return void
	 */
	public static function save( int $post_id, string $rights_type, array $countries ): void {
		if ( ! in_array( $rights_type, self::ALLOWED_TYPES, true ) ) {
			$rights_type = 'world';
		}

		global $wpdb;
		$table = $wpdb->prefix . 'mbs_book_territorial_rights';

		// Normalise country codes; ignore for 'world' type.
		$normalised = ( 'world' === $rights_type )
			? array()
			: array_values( array_filter(
				array_map( 'strtoupper', $countries ),
				static fn( $c ) => '' !== $c
			) );

		$countries_json = empty( $normalised ) ? null : wp_json_encode( $normalised );

		if ( null === $countries_json ) {
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- DAO layer -- direct DB access is intentional per MBS architecture
				$wpdb->prepare(
					"INSERT INTO {$table} (post_id, rights_type, countries)
					 VALUES (%d, %s, NULL)
					 ON DUPLICATE KEY UPDATE rights_type = VALUES(rights_type), countries = NULL",
					$post_id,
					$rights_type
				)
			);
		} else {
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
			$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- DAO layer -- direct DB access is intentional per MBS architecture
				$wpdb->prepare(
					"INSERT INTO {$table} (post_id, rights_type, countries)
					 VALUES (%d, %s, %s)
					 ON DUPLICATE KEY UPDATE rights_type = VALUES(rights_type),
					                         countries   = VALUES(countries)",
					$post_id,
					$rights_type,
					$countries_json
				)
			);
		}
	}

	/**
	 * Delete territorial rights row for a book.
	 *
	 * Called on book post deletion.
	 *
	 * @param int $post_id Book post ID.
	 * @return void
	 */
	public static function delete( int $post_id ): void {
		global $wpdb;
		$table = $wpdb->prefix . 'mbs_book_territorial_rights';
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- DAO layer -- direct DB access is intentional per MBS architecture
			$wpdb->prepare(
				"DELETE FROM {$table} WHERE post_id = %d",
				$post_id
			)
		);
	}

	// -------------------------------------------------------------------------
	// Read
	// -------------------------------------------------------------------------

	/**
	 * Retrieve territorial rights for a book.
	 *
	 * Returns an object with string $rights_type and string[] $countries, or
	 * null if no rights row exists for this book.
	 *
	 * @param int $post_id Book post ID.
	 * @return object|null {rights_type: string, countries: string[]} or null.
	 */
	public static function get( int $post_id ): ?object {
		global $wpdb;
		$table = $wpdb->prefix . 'mbs_book_territorial_rights';

		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$row = $wpdb->get_row( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- DAO layer -- direct DB access is intentional per MBS architecture
			$wpdb->prepare(
				"SELECT rights_type, countries FROM {$table} WHERE post_id = %d LIMIT 1",
				$post_id
			)
		);

		if ( ! $row ) {
			return null;
		}

		// Decode countries JSON; return empty array if null or malformed.
		$countries_raw = json_decode( (string) $row->countries, true );
		$row->countries = ( is_array( $countries_raw ) ) ? $countries_raw : array();

		return $row;
	}
}
