<?php
defined( 'ABSPATH' ) || exit;

class BW_WebP_Manifest {

	const STATUS_PENDING    = 'pending';
	const STATUS_CONVERTING = 'converting';
	const STATUS_DONE       = 'done';
	const STATUS_FAILED     = 'failed';
	const STATUS_SKIPPED    = 'skipped';

	public static function table(): string {
		global $wpdb;
		return $wpdb->prefix . 'bw_webp_jobs';
	}

	public static function install(): void {
		global $wpdb;
		require_once ABSPATH . 'wp-admin/includes/upgrade.php';

		$table   = self::table();
		$charset = $wpdb->get_charset_collate();

		$sql = "CREATE TABLE {$table} (
			id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
			src_path VARCHAR(512) NOT NULL,
			src_mtime BIGINT UNSIGNED NOT NULL,
			src_size BIGINT UNSIGNED NOT NULL,
			dest_path VARCHAR(512) NOT NULL,
			status VARCHAR(16) NOT NULL DEFAULT 'pending',
			error TEXT NULL,
			converter VARCHAR(16) NULL,
			converted_at BIGINT UNSIGNED NULL,
			PRIMARY KEY (id),
			UNIQUE KEY src_path (src_path(255)),
			KEY status (status),
			KEY src_mtime (src_mtime)
		) {$charset};";

		dbDelta( $sql );
	}

	public static function drop(): void {
		global $wpdb;
		$table = self::table();
		$wpdb->query( "DROP TABLE IF EXISTS {$table}" );
	}

	public function upsert( string $src_path, int $src_mtime, int $src_size, string $dest_path ): void {
		global $wpdb;
		$table = self::table();

		$existing = $wpdb->get_row(
			$wpdb->prepare( "SELECT id, src_mtime, status FROM {$table} WHERE src_path = %s", $src_path ),
			ARRAY_A
		);

		if ( ! $existing ) {
			$wpdb->insert(
				$table,
				array(
					'src_path'  => $src_path,
					'src_mtime' => $src_mtime,
					'src_size'  => $src_size,
					'dest_path' => $dest_path,
					'status'    => self::STATUS_PENDING,
				),
				array( '%s', '%d', '%d', '%s', '%s' )
			);
			return;
		}

		// Re-queue if source changed since last record.
		if ( (int) $existing['src_mtime'] !== $src_mtime ) {
			$wpdb->update(
				$table,
				array(
					'src_mtime'    => $src_mtime,
					'src_size'     => $src_size,
					'dest_path'    => $dest_path,
					'status'       => self::STATUS_PENDING,
					'error'        => null,
					'converted_at' => null,
				),
				array( 'id' => (int) $existing['id'] ),
				array( '%d', '%d', '%s', '%s', '%s', '%s' ),
				array( '%d' )
			);
		}
	}

	public function mark_converting( int $id ): void {
		global $wpdb;
		$wpdb->update(
			self::table(),
			array( 'status' => self::STATUS_CONVERTING ),
			array( 'id' => $id ),
			array( '%s' ),
			array( '%d' )
		);
	}

	public function mark_done( int $id, string $converter ): void {
		global $wpdb;
		$wpdb->update(
			self::table(),
			array(
				'status'       => self::STATUS_DONE,
				'error'        => null,
				'converter'    => $converter,
				'converted_at' => time(),
			),
			array( 'id' => $id ),
			array( '%s', '%s', '%s', '%d' ),
			array( '%d' )
		);
	}

	public function mark_failed( int $id, string $error ): void {
		global $wpdb;
		$wpdb->update(
			self::table(),
			array(
				'status' => self::STATUS_FAILED,
				'error'  => mb_substr( $error, 0, 1000 ),
			),
			array( 'id' => $id ),
			array( '%s', '%s' ),
			array( '%d' )
		);
	}

	public function counts(): array {
		global $wpdb;
		$table = self::table();
		$rows  = $wpdb->get_results( "SELECT status, COUNT(*) AS n FROM {$table} GROUP BY status", ARRAY_A );
		$out   = array(
			'total'      => 0,
			'pending'    => 0,
			'converting' => 0,
			'done'       => 0,
			'failed'     => 0,
			'skipped'    => 0,
		);
		foreach ( (array) $rows as $r ) {
			$status = (string) $r['status'];
			$n      = (int) $r['n'];
			if ( isset( $out[ $status ] ) ) {
				$out[ $status ] = $n;
			}
			$out['total'] += $n;
		}
		return $out;
	}

	/**
	 * Atomically claim a batch of pending rows.
	 *
	 * Why: when N parallel workers run, two workers must never claim the same row.
	 * We do the claim with a UPDATE ... LIMIT keyed by a per-worker token written
	 * into the `error` column, then SELECT back the claimed IDs. MySQL guarantees
	 * the UPDATE is atomic per row.
	 */
	public function claim_batch( int $limit, string $token ): array {
		global $wpdb;
		$table = self::table();

		$wpdb->query(
			$wpdb->prepare(
				"UPDATE {$table} SET status = %s, error = %s WHERE status = %s LIMIT %d",
				self::STATUS_CONVERTING,
				$token,
				self::STATUS_PENDING,
				$limit
			)
		);

		return (array) $wpdb->get_results(
			$wpdb->prepare(
				"SELECT id, src_path, dest_path, src_mtime FROM {$table} WHERE error = %s AND status = %s",
				$token,
				self::STATUS_CONVERTING
			),
			ARRAY_A
		);
	}

	public function clear(): void {
		global $wpdb;
		$wpdb->query( 'TRUNCATE TABLE ' . self::table() );
	}

	public function recent_failures( int $limit = 50 ): array {
		global $wpdb;
		$table = self::table();
		return (array) $wpdb->get_results(
			$wpdb->prepare(
				"SELECT id, src_path, error, converted_at FROM {$table} WHERE status = %s ORDER BY id DESC LIMIT %d",
				self::STATUS_FAILED,
				$limit
			),
			ARRAY_A
		);
	}
}
