<?php
/**
 * Code Snippet Revisions (custom revision storage for snippets).
 *
 * @package CodeSnippet
 * @since 1.0.0
 */

namespace CodeSnippet;

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

/**
 * Class Wpps_Revisions.
 */
class Wpps_Revisions {

	const TABLE = 'wpps_revisions';
	const DB_VERSION = '1.0';
	const DB_VERSION_KEY = 'wpps_revisions_db_version';

	/**
	 * Snippet data before update (for comparison).
	 *
	 * @var array|null
	 */
	protected $previous_snippet_data;

	/**
	 * Constructor.
	 */
	public function __construct() {
		add_action( 'admin_init', [ $this, 'maybe_create_table' ] );
	}

	/**
	 * Create revisions table if needed.
	 */
	public function maybe_create_table(): void {
		$version = get_option( self::DB_VERSION_KEY, '0' );
		if ( version_compare( self::DB_VERSION, $version, '<=' ) ) {
			return;
		}
		global $wpdb;
		$table      = $wpdb->prefix . self::TABLE;
		$charset    = $wpdb->get_charset_collate();
		$sql        = "CREATE TABLE $table (
			revision_id BIGINT(20) NOT NULL AUTO_INCREMENT,
			snippet_id BIGINT(20) NOT NULL,
			revision_data LONGTEXT NOT NULL,
			author_id BIGINT(20) NOT NULL,
			created DATETIME NOT NULL,
			PRIMARY KEY (revision_id),
			KEY snippet_id (snippet_id)
		) $charset;";
		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
		dbDelta( $sql );
		update_option( self::DB_VERSION_KEY, self::DB_VERSION, false );
	}

	/**
	 * Capture snippet data before update.
	 *
	 * @param int $snippet_id Snippet post ID.
	 */
	public function capture_snippet_data( int $snippet_id ): void {
		if ( get_post_type( $snippet_id ) !== 'wpps_code_snippet' ) {
			return;
		}
		$this->previous_snippet_data = self::get_snippet_revision_data( $snippet_id );
	}

	/**
	 * Maybe create revision after snippet save.
	 *
	 * @param int   $snippet_id Snippet post ID.
	 * @param array $new_data   New snippet data (title, code, location, etc.).
	 */
	public function maybe_create_revision( int $snippet_id, array $new_data ): void {
		if ( empty( $this->previous_snippet_data ) ) {
			return;
		}
		$updated = [];
		foreach ( $new_data as $key => $value ) {
			if ( ! isset( $this->previous_snippet_data[ $key ] ) || $value !== $this->previous_snippet_data[ $key ] ) {
				$updated[ $key ] = $value;
			}
		}
		if ( ! empty( $updated ) ) {
			// Store the previous state so restore can undo this save.
			$this->create_revision( $snippet_id, $this->previous_snippet_data );
		}
	}

	/**
	 * Get all snippet data for revision storage.
	 *
	 * @param int $snippet_id Snippet post ID.
	 * @return array<string, mixed>
	 */
	public static function get_snippet_revision_data( int $snippet_id ): array {
		$post = get_post( $snippet_id );
		if ( ! $post || $post->post_type !== 'wpps_code_snippet' ) {
			return [];
		}
		$loc = get_post_meta( $snippet_id, '_wpps_location', true ) ?: 'footer';
		$labels = Module::getLocations();
		$location_label = $loc;
		if ( $loc === 'shortcode' ) {
			$custom = get_post_meta( $snippet_id, '_wpps_custom_shortcode', true ) ?: '';
			$slug   = preg_replace( '/[^a-z0-9_]/', '', strtolower( $custom ) );
			$location_label = $slug !== '' ? $labels['shortcode'] . ': [' . $slug . ']' : $labels['shortcode'];
		} elseif ( isset( $labels[ $loc ] ) ) {
			$location_label = $labels[ $loc ];
		}
		$cond_rules = get_post_meta( $snippet_id, '_wpps_conditional_rules', true );
		return [
			'title'                  => $post->post_title ?: '',
			'code'                   => $post->post_content,
			'code_type'              => get_post_meta( $snippet_id, '_wpps_code_type', true ) ?: 'html',
			'location'               => $loc,
			'location_label'         => $location_label,
			'location_param'         => (int) ( get_post_meta( $snippet_id, '_wpps_location_param', true ) ?: 1 ),
			'location_selector'      => get_post_meta( $snippet_id, '_wpps_location_selector', true ) ?: '',
			'location_element_index' => get_post_meta( $snippet_id, '_wpps_location_element_index', true ) ?: '',
			'custom_shortcode'       => get_post_meta( $snippet_id, '_wpps_custom_shortcode', true ) ?: '',
			'auto_insert'            => ( get_post_meta( $snippet_id, '_wpps_auto_insert', true ) ?: '0' ) === '1',
			'priority'               => (int) ( get_post_meta( $snippet_id, '_wpps_priority', true ) ?: 10 ),
			'notes'                  => get_post_meta( $snippet_id, '_wpps_notes', true ) ?: '',
			'schedule_enabled'       => ( get_post_meta( $snippet_id, '_wpps_schedule_enabled', true ) ?: '0' ) === '1',
			'schedule_start'         => get_post_meta( $snippet_id, '_wpps_schedule_start', true ) ?: '',
			'schedule_end'           => get_post_meta( $snippet_id, '_wpps_schedule_end', true ) ?: '',
			'device_type'            => get_post_meta( $snippet_id, '_wpps_device_type', true ) ?: 'all',
			'conditional_enabled'    => ( get_post_meta( $snippet_id, '_wpps_conditional_enabled', true ) ?: '0' ) === '1',
			'conditional_show'       => get_post_meta( $snippet_id, '_wpps_conditional_show', true ) ?: '1',
			'conditional_rules'      => is_array( $cond_rules ) ? $cond_rules : [],
			'revisions_enabled'      => ( get_post_meta( $snippet_id, '_wpps_revisions_enabled', true ) ?: '0' ) === '1',
			'load_as_file'           => ( get_post_meta( $snippet_id, '_wpps_load_as_file', true ) ?: '0' ) === '1',
			'active'                 => $post->post_status === 'publish',
		];
	}

	/**
	 * Create a revision.
	 *
	 * @param int   $snippet_id Snippet post ID.
	 * @param array $data       Full revision data.
	 */
	public function create_revision( int $snippet_id, array $data ): void {
		if ( empty( $data ) ) {
			return;
		}
		global $wpdb;
		$table = $wpdb->prefix . self::TABLE;
		$wpdb->insert(
			$table,
			[
				'snippet_id'    => $snippet_id,
				'revision_data' => wp_json_encode( $data ),
				'author_id'     => get_current_user_id(),
				'created'       => current_time( 'mysql' ),
			],
			[ '%d', '%s', '%d', '%s' ]
		);
		wp_cache_delete( 'wpps_revisions_' . $snippet_id, 'wpps' );
		$this->prune_revisions( $snippet_id );
	}

	/**
	 * Delete oldest revisions when count exceeds the limit.
	 *
	 * @param int $snippet_id Snippet post ID.
	 */
	protected function prune_revisions( int $snippet_id ): void {
		$limit = (int) Module::getSetting( 'revisions_to_keep', 10 );
		if ( $limit < 1 ) {
			return;
		}
		global $wpdb;
		$table = $wpdb->prefix . self::TABLE;
		$count = (int) $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT(*) FROM $table WHERE snippet_id=%d",
				$snippet_id
			)
		);
		if ( $count <= $limit ) {
			return;
		}
		$to_delete = $count - $limit;
		$ids = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT revision_id FROM (SELECT revision_id FROM $table WHERE snippet_id=%d ORDER BY created ASC LIMIT %d) old",
				$snippet_id,
				$to_delete
			)
		);
		if ( ! empty( $ids ) ) {
			$placeholders = implode( ',', array_fill( 0, count( $ids ), '%d' ) );
			$wpdb->query( $wpdb->prepare( "DELETE FROM $table WHERE revision_id IN ($placeholders)", ...array_values( $ids ) ) );
		}
		wp_cache_delete( 'wpps_revisions_' . $snippet_id, 'wpps' );
	}

	/**
	 * Count revisions for a snippet.
	 */
	public function count_snippet_revisions( int $snippet_id ): int {
		global $wpdb;
		$table = $wpdb->prefix . self::TABLE;
		return (int) $wpdb->get_var(
			$wpdb->prepare(
				"SELECT COUNT(*) FROM $table WHERE snippet_id=%d",
				$snippet_id
			)
		);
	}

	/**
	 * Get revisions for a snippet (full list, cached).
	 *
	 * @param int $snippet_id Snippet post ID.
	 * @return array<int, object>
	 */
	public function get_snippet_revisions( int $snippet_id ): array {
		global $wpdb;
		$cache_key = 'wpps_revisions_' . $snippet_id;
		$results   = wp_cache_get( $cache_key, 'wpps' );
		if ( false === $results ) {
			$table  = $wpdb->prefix . self::TABLE;
			$results = $wpdb->get_results(
				$wpdb->prepare(
					"SELECT * FROM $table WHERE snippet_id=%d ORDER BY created DESC",
					$snippet_id
				)
			);
			wp_cache_set( $cache_key, $results, 'wpps' );
		}
		return is_array( $results ) ? $results : [];
	}

	/**
	 * Get a paginated slice of revisions (not cached — used by AJAX list).
	 *
	 * @return array<int, object>
	 */
	public function get_snippet_revisions_page( int $snippet_id, int $limit, int $offset ): array {
		global $wpdb;
		$table = $wpdb->prefix . self::TABLE;
		$results = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT * FROM $table WHERE snippet_id=%d ORDER BY created DESC LIMIT %d OFFSET %d",
				$snippet_id,
				max( 1, $limit ),
				max( 0, $offset )
			)
		);
		return is_array( $results ) ? $results : [];
	}

	/**
	 * Get revision parent (snippet) ID.
	 *
	 * @param int $revision_id Revision ID.
	 * @return int|false
	 */
	public function get_revision_parent_id( int $revision_id ) {
		global $wpdb;
		$table      = $wpdb->prefix . self::TABLE;
		$snippet_id = $wpdb->get_var(
			$wpdb->prepare(
				"SELECT snippet_id FROM $table WHERE revision_id=%d",
				$revision_id
			)
		);
		return $snippet_id !== null ? (int) $snippet_id : false;
	}

	/**
	 * Get revision row by ID.
	 *
	 * @param int $revision_id Revision ID.
	 * @return object|null
	 */
	public function get_revision( int $revision_id ) {
		global $wpdb;
		$table = $wpdb->prefix . self::TABLE;
		return $wpdb->get_row(
			$wpdb->prepare(
				"SELECT * FROM $table WHERE revision_id=%d",
				$revision_id
			)
		);
	}

	/**
	 * Get revision data (decoded array).
	 *
	 * @param int $revision_id Revision ID.
	 * @return array<string, mixed>
	 */
	public function get_revision_data( int $revision_id ): array {
		$row = $this->get_revision( $revision_id );
		if ( ! $row || empty( $row->revision_data ) ) {
			return [];
		}
		$data = json_decode( $row->revision_data, true );
		return is_array( $data ) ? $data : [];
	}

	/**
	 * Delete all revision rows for a snippet (e.g. on permanent snippet delete).
	 */
	public function delete_for_snippet( int $snippet_id ): void {
		global $wpdb;
		$table = $wpdb->prefix . self::TABLE;
		$wpdb->delete( $table, [ 'snippet_id' => $snippet_id ], [ '%d' ] );
		wp_cache_delete( 'wpps_revisions_' . $snippet_id, 'wpps' );
	}
}
