<?php
/**
 * WordPress-native update integration for PressConduit.
 *
 * Hooks into the standard plugin update flow so Dashboard → Updates
 * shows our plugin alongside everything else. The manifest comes from
 * the PC Designs backend (api/plugin/latest); WordPress handles the
 * actual download + install via Plugin_Upgrader as if we were on
 * WordPress.org.
 */

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

class PCD_PC_Updater {

	const TRANSIENT_MANIFEST = 'pcd_pc_update_manifest';
	const MANIFEST_TTL       = 15 * MINUTE_IN_SECONDS;
	const OPTION_AUTO_UPDATE = 'pcd_pc_auto_update';

	public static function init(): void {
		add_filter( 'pre_set_site_transient_update_plugins', [ __CLASS__, 'inject_update' ] );
		add_filter( 'plugins_api', [ __CLASS__, 'plugin_info' ], 10, 3 );
		add_action( 'upgrader_process_complete', [ __CLASS__, 'after_upgrade' ], 10, 2 );
		add_filter( 'auto_update_plugin', [ __CLASS__, 'maybe_auto_update' ], 10, 2 );
	}

	/**
	 * Whether unattended background updates are enabled for PressConduit.
	 *
	 * Defaults to ON: the relay contract can require a current build (several
	 * AI tools refuse to run against an outdated plugin), so staying current is
	 * the safer default. Site owners can opt out on the Settings tab.
	 */
	public static function auto_update_enabled(): bool {
		return (bool) get_option( self::OPTION_AUTO_UPDATE, 1 );
	}

	/**
	 * Drive WordPress's unattended-update decision for our plugin only, leaving
	 * every other plugin's setting untouched. Never auto-installs a build the
	 * site can't run (WordPress / PHP floors from the manifest).
	 *
	 * @param bool|null $update Whether to auto-update. Passed through for other plugins.
	 * @param object    $item   The update offer (has ->plugin on the plugins path).
	 * @return bool|null
	 */
	public static function maybe_auto_update( $update, $item ) {
		$plugin = is_object( $item ) && isset( $item->plugin ) ? $item->plugin : '';
		if ( self::plugin_file() !== $plugin ) {
			return $update;
		}
		if ( ! self::auto_update_enabled() ) {
			return false;
		}
		return self::site_meets_requirements();
	}

	/**
	 * Compatibility guard: does this site meet the manifest's minimum WordPress
	 * and PHP versions? Returns true when we have no manifest data (WordPress
	 * core still gates on the update object's `requires` / `requires_php`).
	 */
	private static function site_meets_requirements(): bool {
		$manifest = self::get_manifest();
		if ( ! $manifest ) {
			return true;
		}
		$min_wp  = $manifest['minimum_wp'] ?? '';
		$min_php = $manifest['minimum_php'] ?? '';
		if ( $min_wp && version_compare( get_bloginfo( 'version' ), $min_wp, '<' ) ) {
			return false;
		}
		if ( $min_php && version_compare( PHP_VERSION, $min_php, '<' ) ) {
			return false;
		}
		return true;
	}

	/**
	 * Force a fresh fetch — clears our manifest cache, clears WordPress's
	 * update_plugins transient, then re-fetches both. Used by the admin
	 * "Check for Updates" button and on each visit to the plugin's admin page.
	 *
	 * @return array|null Manifest array on success, null on failure.
	 */
	public static function force_check(): ?array {
		delete_transient( self::TRANSIENT_MANIFEST );
		delete_site_transient( 'update_plugins' );

		$manifest = self::get_manifest( true );
		if ( ! $manifest ) {
			return null;
		}

		// Repopulate WordPress's transient by triggering a re-run.
		wp_update_plugins();
		return $manifest;
	}

	private static function plugin_file(): string {
		return 'pressconduit/pressconduit.php';
	}

	/**
	 * Pull the latest version metadata from the backend, with a short
	 * transient cache so admin page loads don't all hit Vercel.
	 */
	private static function get_manifest( bool $force_refresh = false ): ?array {
		if ( ! $force_refresh ) {
			$cached = get_transient( self::TRANSIENT_MANIFEST );
			if ( is_array( $cached ) && ! empty( $cached['version'] ) ) {
				return $cached;
			}
		}

		$backend = PCD_PC_Pair::backend_url();
		if ( ! $backend ) {
			return null;
		}

		$response = wp_remote_get( $backend . '/plugin/latest?product=pressconduit', [
			'headers' => [ 'X-Conduit-Product' => 'pressconduit' ],
			'timeout' => 8,
		] );
		if ( is_wp_error( $response ) || (int) wp_remote_retrieve_response_code( $response ) >= 300 ) {
			return null;
		}

		$body = json_decode( wp_remote_retrieve_body( $response ), true );
		if ( ! is_array( $body ) || empty( $body['version'] ) || empty( $body['zip_url'] ) ) {
			return null;
		}

		set_transient( self::TRANSIENT_MANIFEST, $body, self::MANIFEST_TTL );
		return $body;
	}

	/**
	 * Tell WordPress about the new version when it refreshes its update list.
	 */
	public static function inject_update( $transient ) {
		if ( ! is_object( $transient ) ) {
			return $transient;
		}

		$manifest = self::get_manifest();
		if ( ! $manifest ) {
			return $transient;
		}

		$plugin_file = self::plugin_file();
		$current     = defined( 'PCD_PC_VERSION' ) ? PCD_PC_VERSION : '0.0.0';
		$icon_url    = defined( 'PCD_PC_URL' ) ? PCD_PC_URL . 'assets/icon.svg' : '';
		$icons       = $icon_url
			? [ 'default' => $icon_url, 'svg' => $icon_url, '1x' => $icon_url, '2x' => $icon_url ]
			: [];

		// Always populate the "no_update" branch so wp-admin shows our current version cleanly.
		$no_update_entry = (object) [
			'slug'         => 'pressconduit',
			'plugin'       => $plugin_file,
			'new_version'  => $current,
			'url'          => 'https://pcdesignstx.com',
			'package'      => '',
			'tested'       => $manifest['minimum_wp'] ?? '6.0',
			'requires_php' => $manifest['minimum_php'] ?? '7.4',
			'icons'        => $icons,
		];

		if ( version_compare( $current, $manifest['version'] ) < 0 ) {
			// Real update available — add to ->response so WP shows the update.
			if ( ! isset( $transient->response ) || ! is_array( $transient->response ) ) {
				$transient->response = [];
			}
			$transient->response[ $plugin_file ] = (object) [
				'slug'         => 'pressconduit',
				'plugin'       => $plugin_file,
				'new_version'  => $manifest['version'],
				'package'      => $manifest['zip_url'],
				'url'          => 'https://pcdesignstx.com',
				'requires'     => $manifest['minimum_wp'] ?? '6.0',
				'tested'       => $manifest['tested_wp'] ?? ( $manifest['minimum_wp'] ?? '6.0' ),
				'requires_php' => $manifest['minimum_php'] ?? '7.4',
				'icons'        => $icons,
			];
		} else {
			// No update — surface in ->no_update so the plugins list shows we're on the latest.
			if ( ! isset( $transient->no_update ) || ! is_array( $transient->no_update ) ) {
				$transient->no_update = [];
			}
			$transient->no_update[ $plugin_file ] = $no_update_entry;
		}

		return $transient;
	}

	/**
	 * Power the "View details" link on the Plugins screen / update dialog.
	 */
	public static function plugin_info( $result, string $action, $args ) {
		if ( 'plugin_information' !== $action || empty( $args->slug ) || 'pressconduit' !== $args->slug ) {
			return $result;
		}

		$manifest = self::get_manifest();
		if ( ! $manifest ) {
			return $result;
		}

		$icon_url = defined( 'PCD_PC_URL' ) ? PCD_PC_URL . 'assets/icon.svg' : '';
		return (object) [
			'name'         => 'PressConduit',
			'slug'         => 'pressconduit',
			'version'      => $manifest['version'],
			'author'       => '<a href="https://pcdesignstx.com">PC Designs</a>',
			'homepage'     => 'https://pcdesignstx.com',
			'requires'     => $manifest['minimum_wp'] ?? '6.0',
			'tested'       => $manifest['minimum_wp'] ?? '6.0',
			'requires_php' => $manifest['minimum_php'] ?? '7.4',
			'download_link'=> $manifest['zip_url'],
			'icons'        => $icon_url ? [
				'default' => $icon_url,
				'svg'     => $icon_url,
			] : [],
			'sections'     => [
				'description' => '<p>The conduit between AI assistants and your WordPress site. Pairs with the PressConduit relay so Claude, ChatGPT, and Cursor get a full Divi or Bricks + WordPress tool surface.</p>',
				'changelog'   => '<p>See <a href="https://pcdesignstx.com/pressconduit/">pcdesignstx.com/pressconduit</a>.</p>',
			],
		];
	}

	/**
	 * Bust our cached manifest after an upgrade completes so the next page
	 * load reflects the new installed version.
	 */
	public static function after_upgrade( $upgrader, $hook_extra ): void {
		if ( empty( $hook_extra['plugins'] ) || ! is_array( $hook_extra['plugins'] ) ) {
			return;
		}
		if ( in_array( self::plugin_file(), $hook_extra['plugins'], true ) ) {
			delete_transient( self::TRANSIENT_MANIFEST );
		}
	}
}
