<?php

namespace WPDRMS\ASP\Index\ORM;

use WPDRMS\PluginCore\ORM\Model;

/**
 * Tracks post IDs that were evaluated during extend-mode indexing but excluded
 * by taxonomy or custom-field exclusion rules (or via the asp/index/document/exclude
 * filter). Without this table those posts would never get a row in wp_asp_index,
 * causing the LEFT JOIN "r.doc IS NULL" query to return them on every batch and
 * making postsToIndex never reach zero.
 *
 * The table is truncated at the start of a fresh "Create New Index" run so that
 * previously-excluded posts are re-evaluated against current rules.
 */
class IndexSkipTable extends Model {

	protected static string $table_name = 'asp_index_skip';
	protected static bool   $global     = true;

	/** @var array<string, string> */
	protected static array $columns = array(
		'doc'                    => 'int UNSIGNED NOT NULL',
		'blogid'                 => 'mediumint(9) UNSIGNED NOT NULL DEFAULT 0',
		'PRIMARY KEY doc_blogid' => '(doc, blogid)',
	);

	public static function markAsExcluded( int $post_id, int $blog_id ): void {
		// The table can legitimately be absent (e.g. delete_post firing on an
		// upgrade before the table-creation migration has run). Skip silently.
		if ( !static::tableReady() ) {
			return;
		}
		global $wpdb;
		$table = static::getTableName();
		$wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO $table (doc, blogid) VALUES (%d, %d)", $post_id, $blog_id ) ); // phpcs:ignore
	}

	public static function removeDocument( int $post_id, int $blog_id ): void {
		// The table can legitimately be absent (e.g. delete_post firing on an
		// upgrade before the table-creation migration has run). A missing skip
		// table simply means there is nothing to remove.
		if ( !static::tableReady() ) {
			return;
		}
		global $wpdb;
		$table = static::getTableName();
		$wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE doc = %d AND blogid = %d", $post_id, $blog_id ) ); // phpcs:ignore
	}

	/**
	 * Whether the skip table is safe to query.
	 *
	 * Returns false when the table is missing — and also when tableExists()
	 * itself is unavailable: during a plugin update the previous release's
	 * ORM\Model can still be in memory (PHP can't redefine a loaded class)
	 * while this newer subclass is autoloaded fresh, so the inherited
	 * tableExists() may not exist for that one request (e.g. delete_post
	 * firing while WordPress deletes the uploaded plugin zip). In that
	 * degenerate one-off we simply skip. Same family as the Factory
	 * skip-missing-class guard that survives mid-update file swaps.
	 */
	private static function tableReady(): bool {
		return method_exists( static::class, 'tableExists' ) && static::tableExists();
	}
}
