<?php
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class AWXCT_Clone_Translate_Clone {

	/**
	 * Cache group for this plugin.
	 *
	 * @var string
	 */
	private $cache_group = 'awxct';

	public function __construct() {
		add_filter( 'post_row_actions', array( $this, 'add_clone_link_to_row_actions' ), 10, 2 );
		add_filter( 'page_row_actions', array( $this, 'add_clone_link_to_row_actions' ), 10, 2 );
		add_action( 'admin_action_awxct_clone_post', array( $this, 'process_clone_post' ) );
	}

	/**
	 * Cache helper: blog-aware key.
	 *
	 * @param string $suffix
	 * @return string
	 */
	private function cache_key( $suffix ) {
		$blog_id = function_exists( 'get_current_blog_id' ) ? (int) get_current_blog_id() : 0;
		return 'awxct_' . $blog_id . '_' . (string) $suffix;
	}

	/**
	 * Cached get_col helper.
	 *
	 * @param string $cache_suffix
	 * @param string $sql
	 * @param int    $ttl
	 * @return array
	 */
	private function db_get_col_cached( $cache_suffix, $sql, $ttl = 3600 ) {
		$key = $this->cache_key( 'dbcol_' . (string) $cache_suffix );
		$val = wp_cache_get( $key, $this->cache_group );
		if ( false !== $val && is_array( $val ) ) {
			return $val;
		}

		global $wpdb;

		// $sql must be constant SQL or already prepared via $wpdb->prepare().
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared
		$val = (array) $wpdb->get_col( (string) $sql, 0 );

		wp_cache_set( $key, $val, $this->cache_group, (int) $ttl );
		return $val;
	}

	/**
	 * Cached get_var helper.
	 *
	 * @param string $cache_suffix
	 * @param string $sql
	 * @param int    $ttl
	 * @return mixed
	 */
	private function db_get_var_cached( $cache_suffix, $sql, $ttl = 30 ) {
		$key = $this->cache_key( 'dbvar_' . (string) $cache_suffix );
		$val = wp_cache_get( $key, $this->cache_group );
		if ( false !== $val ) {
			return $val;
		}

		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- $sql is built internally by this plugin; callers prepare values and identifiers are validated where applicable.
		$val = $wpdb->get_var( (string) $sql );

		wp_cache_set( $key, $val, $this->cache_group, (int) $ttl );
		return $val;
	}

	/**
	 * Adds clone link to row actions.
	 */
	public function add_clone_link_to_row_actions( $actions, $post ) {
		if ( current_user_can( 'edit_posts' ) && $post instanceof WP_Post ) {
			$url = wp_nonce_url(
				admin_url( 'admin.php?action=awxct_clone_post&post=' . $post->ID ),
				'awxct_clone_post_' . $post->ID
			);

			$actions['awxct_clone'] = sprintf(
				'<a href="%s">%s</a>',
				esc_url( $url ),
				esc_html__( 'Clone or Translate', 'anywarex-clone-translate' )
			);
		}

		return $actions;
	}

	/**
	 * Clone handler.
	 */
	public function process_clone_post() {

		if ( ! isset( $_GET['post'] ) || ! is_numeric( $_GET['post'] ) ) {
			wp_die( esc_html__( 'No post to duplicate has been supplied!', 'anywarex-clone-translate' ) );
		}

		$post_id = absint( wp_unslash( $_GET['post'] ) );
		$nonce   = isset( $_GET['_wpnonce'] )
			? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) )
			: '';

		if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $nonce ) ), 'awxct_clone_post_' . $post_id ) ) {
			wp_die( esc_html__( 'Security check failed.', 'anywarex-clone-translate' ) );
		}

		$post = get_post( $post_id );
		if ( ! ( $post instanceof WP_Post ) ) {
			wp_die( esc_html__( 'Post not found!', 'anywarex-clone-translate' ) );
		}

				$new_post_id = wp_insert_post(
			array(
				'post_title'      => wp_slash( ( ( $post->post_title ? $post->post_title : '#' . $post->ID ) . ' (Clone)' ) ),
				'post_content'    => wp_slash( (string) $post->post_content ),
				'post_excerpt'    => wp_slash( (string) $post->post_excerpt ),
				'post_status'     => 'draft',
				'post_type'       => $post->post_type,
				'post_author'     => get_current_user_id(),
				'post_parent'     => (int) $post->post_parent,
				'menu_order'      => (int) $post->menu_order,
				'comment_status'  => (string) $post->comment_status,
				'ping_status'     => (string) $post->ping_status,
				'post_password'   => (string) $post->post_password,
				'post_name'       => '',
			),
			true
		);
		if ( ! $new_post_id ) {
			wp_die( esc_html__( 'Cloning failed!', 'anywarex-clone-translate' ) );
		}

				// Copy all post meta (Duplicate Page behavior).
		$skip_keys = array(
			'_wp_old_slug',
			'_edit_lock',
			'_edit_last',
		);

		/**
		 * Filter meta keys to skip during cloning.
		 *
		 * @param array $skip_keys
		 * @param int   $source_post_id
		 * @param int   $new_post_id
		 */
		$skip_keys = (array) apply_filters( 'awxct_clone_skip_meta_keys', $skip_keys, $post_id, $new_post_id );

		$meta_keys = get_post_custom_keys( $post_id );
		if ( is_array( $meta_keys ) ) {
			foreach ( $meta_keys as $meta_key ) {
				$meta_key = (string) $meta_key;
				if ( in_array( $meta_key, $skip_keys, true ) ) {
					continue;
				}

				$values = get_post_custom_values( $meta_key, $post_id );
				if ( ! is_array( $values ) ) {
					continue;
				}

				// Ensure the cloned post does not keep any default value for this meta key.
				delete_post_meta( $new_post_id, $meta_key );

				foreach ( $values as $value ) {
					$un = maybe_unserialize( $value );
					add_post_meta( $new_post_id, $meta_key, wp_slash( $un ) );
				}
			}
		}
// Taxonomies.
		foreach ( get_object_taxonomies( $post->post_type ) as $taxonomy ) {
			$terms = wp_get_object_terms( $post_id, $taxonomy, array( 'fields' => 'ids' ) );
			if ( ! is_wp_error( $terms ) ) {
				wp_set_object_terms( $new_post_id, $terms, $taxonomy );
			}
		}

		// Featured image.
		$thumb_id = (int) get_post_thumbnail_id( $post_id );
		if ( $thumb_id > 0 ) {
			set_post_thumbnail( $new_post_id, $thumb_id );
		}

		update_post_meta( $new_post_id, '_awxct_translate_is_child', 1 );
		update_post_meta( $new_post_id, '_awxct_translate_lang', '' );

		$root_post_id = (int) get_post_meta( $post_id, '_awxct_root_post_id', true );
		if ( ! $root_post_id ) {
			$root_post_id = $post_id;
			update_post_meta( $post_id, '_awxct_root_post_id', $root_post_id );
		}
		update_post_meta( $new_post_id, '_awxct_root_post_id', $root_post_id );

		global $wpdb;

		// === translations table ===
		$tbl_raw = $wpdb->prefix . 'awxct_translations';
		if ( ! preg_match( '/^[A-Za-z0-9_]+$/', (string) $tbl_raw ) ) {
			wp_die( esc_html__( 'Invalid translations table name.', 'anywarex-clone-translate' ) );
		}
		$tbl_sql = esc_sql( $tbl_raw );

		$cols = $this->db_get_col_cached(
			'cols_' . md5( $tbl_raw ),
			"SHOW COLUMNS FROM `{$tbl_sql}`",
			HOUR_IN_SECONDS
		);

		$post_id_col = in_array( 'translated_post_id', $cols, true )
			? 'translated_post_id'
			: 'translation_post_id';

		if ( 'translation_post_id' === $post_id_col ) {
			// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
			$exists_sql = $wpdb->prepare(
				"SELECT id FROM `{$tbl_sql}` WHERE translation_post_id = %d",
				$new_post_id
			);
			// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
		} else {
			// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
			$exists_sql = $wpdb->prepare(
				"SELECT id FROM `{$tbl_sql}` WHERE translated_post_id = %d",
				$new_post_id
			);
			// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
		}

		$exists = $this->db_get_var_cached( 'trans_exists_' . $new_post_id, $exists_sql, 30 );

		if ( ! $exists ) {
			$data   = array( $post_id_col => $new_post_id );
			$format = array( '%d' );

			if ( in_array( 'root_post_id', $cols, true ) ) {
				$data['root_post_id'] = $root_post_id;
				$format[]             = '%d';
			}
			if ( in_array( 'original_post_id', $cols, true ) ) {
				$data['original_post_id'] = $post_id;
				$format[]                 = '%d';
			}
			if ( in_array( 'language_code', $cols, true ) ) {
				$data['language_code'] = '';
				$format[]              = '%s';
			}
			if ( in_array( 'is_active', $cols, true ) ) {
				$data['is_active'] = 1;
				$format[]          = '%d';
			}

			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->insert( $tbl_raw, $data, $format );
		}

		// Post-clone cleanup / normalization (keeps cloning universal, even if a builder is currently inactive).
		$this->after_clone_cleanup( $post_id, $new_post_id );
		$this->normalize_page_settings_meta( $new_post_id );

		// Elementor: clear cache + regenerate CSS for the cloned post (Duplicate Page compatibility).
		if ( class_exists( '\\Elementor\\Plugin' ) ) {
			try {
				$elementor = \Elementor\Plugin::instance();
				if ( isset( $elementor->files_manager ) && method_exists( $elementor->files_manager, 'clear_cache' ) ) {
					$elementor->files_manager->clear_cache();
				}

				if ( class_exists( '\\Elementor\\Core\\Files\\CSS\\Post' ) ) {
					$css = \Elementor\Core\Files\CSS\Post::create( $new_post_id );
					if ( is_object( $css ) && method_exists( $css, 'update' ) ) {
						$css->update();
					}
				}
			} catch ( \Throwable $e ) {
				// Do not break wp-admin if Elementor changes internals.
			}
		}

		/**
		 * Fires after a post has been cloned.
		 *
		 * Useful for integrations (page builders, SEO plugins, cache plugins) to refresh derived data.
		 *
		 * @param int $new_post_id
		 * @param int $source_post_id
		 */
		do_action( 'awxct_after_clone_post', $new_post_id, $post_id );

		wp_safe_redirect( admin_url( 'post.php?action=edit&post=' . $new_post_id ) );
		exit;
	}

	/**
	 * Post-clone cleanup for derived/cache metadata.
	 *
	 * This stays brand-neutral on purpose. Extensions can hook into
	 * 'awxct_after_clone_post' to refresh their own derived data.
	 */
	private function after_clone_cleanup( int $source_post_id, int $new_post_id ): void {
		$delete_keys = array(
			// Common derived/cache keys used by various builders/caching layers.
			'_elementor_css',
			'_elementor_controls_usage',
			'_elementor_page_assets',
		);

		/**
		 * Filter meta keys to delete on the cloned post after cloning.
		 *
		 * @param array $delete_keys
		 * @param int   $source_post_id
		 * @param int   $new_post_id
		 */
		$delete_keys = (array) apply_filters( 'awxct_clone_delete_meta_keys', $delete_keys, $source_post_id, $new_post_id );

		foreach ( $delete_keys as $k ) {
			$k = (string) $k;
			if ( '' === $k ) {
				continue;
			}
			delete_post_meta( $new_post_id, $k );
		}
	}

	/**
	 * Ensure certain "page settings" meta values are arrays.
	 *
	 * Some editors store settings in a post meta key that is expected to be an array.
	 * If that meta ends up as a plain string, the editor can throw a fatal when
	 * accessing offsets.
	 *
	 * This method is intentionally brand-neutral and only normalizes the value type.
	 */
	private function normalize_page_settings_meta( int $post_id ): void {
		$keys = array(
			'_elementor_page_settings',
			'elementor_page_settings',
		);

		$keys = (array) apply_filters( 'awxct_clone_normalize_settings_meta_keys', $keys, $post_id );

		foreach ( $keys as $key ) {
			$key = (string) $key;
			if ( '' === $key ) {
				continue;
			}

			$val = get_post_meta( $post_id, $key, true );

			// If the value is already an array, it's fine.
			if ( is_array( $val ) ) {
				continue;
			}

			// Empty / missing settings: store as empty array.
			if ( '' === $val || null === $val || false === $val ) {
				update_post_meta( $post_id, $key, array() );
				continue;
			}

			// If it's a string, try to recover.
			if ( is_string( $val ) ) {
				$maybe = maybe_unserialize( $val );
				if ( is_array( $maybe ) ) {
					update_post_meta( $post_id, $key, $maybe );
					continue;
				}

				$decoded = json_decode( $val, true );
				if ( is_array( $decoded ) ) {
					update_post_meta( $post_id, $key, $decoded );
					continue;
				}
			}

			// Fallback: force an empty settings array.
			update_post_meta( $post_id, $key, array() );
		}
	}
}
