<?php
/**
 * Catalogue wordpress.org — stable-check + version-check API.
 *
 * @package CVS
 * @license GPL-2.0-or-later
 */

defined( 'ABSPATH' ) || exit;

class CVS_Releases {

	const STABLE_TRANSIENT = 'cvs_stable_check';
	const CACHE_TTL        = 12 * HOUR_IN_SECONDS;

	/**
	 * @return array<string,string>|WP_Error
	 */
	public static function fetch_stable_map( $force = false ) {
		$cached = get_transient( self::STABLE_TRANSIENT );
		if ( ! $force && is_array( $cached ) ) {
			return $cached;
		}

		$response = wp_remote_get(
			'https://api.wordpress.org/core/stable-check/1.0/',
			array(
				'timeout'   => 20,
				'sslverify' => true,
			)
		);

		if ( is_wp_error( $response ) ) {
			return is_array( $cached ) ? $cached : $response;
		}

		$body = json_decode( wp_remote_retrieve_body( $response ), true );
		if ( ! is_array( $body ) ) {
			return is_array( $cached ) ? $cached : new WP_Error( 'cvs_stable', __( 'Could not load WordPress release data.', 'core-version-switcher-by-volade' ) );
		}

		set_transient( self::STABLE_TRANSIENT, $body, self::CACHE_TTL );
		return $body;
	}

	public static function get_latest_version() {
		$map = self::fetch_stable_map();
		if ( is_wp_error( $map ) ) {
			return get_bloginfo( 'version' );
		}
		foreach ( $map as $version => $status ) {
			if ( 'latest' === $status ) {
				return (string) $version;
			}
		}
		$versions = self::sort_versions( array_keys( $map ) );
		return $versions ? (string) end( $versions ) : get_bloginfo( 'version' );
	}

	/**
	 * @param string $version
	 * @return string latest|outdated|insecure|unknown
	 */
	public static function get_security_status( $version ) {
		$version = CVS_Settings::sanitize_version( $version );
		if ( ! $version ) {
			return 'unknown';
		}
		$map = self::fetch_stable_map();
		if ( is_wp_error( $map ) ) {
			return 'unknown';
		}
		return isset( $map[ $version ] ) ? (string) $map[ $version ] : 'unknown';
	}

	/**
	 * Recent releases for the version picker (latest + outdated only).
	 *
	 * @return array<int,array{version:string,status:string}>
	 */
	public static function get_recent_releases( $limit = 48, $include_insecure = false ) {
		$map = self::fetch_stable_map();
		if ( is_wp_error( $map ) ) {
			return array();
		}

		$rows = array();
		foreach ( $map as $version => $status ) {
			$allowed = array( 'latest', 'outdated' );
			if ( $include_insecure ) {
				$allowed[] = 'insecure';
			}
			if ( ! in_array( $status, $allowed, true ) ) {
				continue;
			}
			$rows[] = array(
				'version' => (string) $version,
				'status'  => (string) $status,
			);
		}

		usort(
			$rows,
			static function ( $a, $b ) {
				return version_compare( $b['version'], $a['version'] );
			}
		);

		return array_slice( $rows, 0, max( 1, (int) $limit ) );
	}

	/**
	 * Previous patch in the same major.minor line (e.g. 6.7.2 → 6.7.1).
	 *
	 * @param string|null $current
	 */
	public static function get_previous_patch( $current = null ) {
		$current = CVS_Settings::sanitize_version( $current ?: get_bloginfo( 'version' ) );
		if ( ! $current ) {
			return '';
		}

		$parts = explode( '.', $current );
		if ( count( $parts ) < 3 ) {
			return '';
		}

		$major_minor = $parts[0] . '.' . $parts[1];
		$patch       = (int) $parts[2];
		$map         = self::fetch_stable_map();
		if ( is_wp_error( $map ) ) {
			return '';
		}

		$candidates = array();
		foreach ( array_keys( $map ) as $version ) {
			if ( 0 !== strpos( $version, $major_minor . '.' ) ) {
				continue;
			}
			$candidates[] = $version;
		}

		$candidates = self::sort_versions( $candidates );
		$index      = array_search( $current, $candidates, true );
		if ( false === $index || 0 === (int) $index ) {
			if ( $patch > 0 ) {
				return $major_minor . '.' . ( $patch - 1 );
			}
			return '';
		}

		return (string) $candidates[ (int) $index - 1 ];
	}

	/**
	 * Latest still-supported release on the same major (highest outdated in branch).
	 *
	 * @param string|null $current
	 */
	public static function get_latest_secure_in_major( $current = null ) {
		$current = CVS_Settings::sanitize_version( $current ?: get_bloginfo( 'version' ) );
		if ( ! $current ) {
			return '';
		}
		$major = explode( '.', $current )[0] ?? '';
		if ( ! $major ) {
			return '';
		}

		$map = self::fetch_stable_map();
		if ( is_wp_error( $map ) ) {
			return '';
		}

		$best = '';
		foreach ( $map as $version => $status ) {
			if ( 'outdated' !== $status && 'latest' !== $status ) {
				continue;
			}
			if ( 0 !== strpos( $version, $major . '.' ) ) {
				continue;
			}
			if ( ! $best || version_compare( $version, $best, '>' ) ) {
				$best = $version;
			}
		}
		return $best;
	}

	/**
	 * Rough PHP minimum for common WP releases (diagnostic only).
	 *
	 * @param string $version
	 */
	public static function estimated_min_php( $version ) {
		$version = CVS_Settings::sanitize_version( $version );
		if ( ! $version ) {
			return '7.4';
		}
		if ( version_compare( $version, '6.7', '>=' ) ) {
			return '7.2';
		}
		if ( version_compare( $version, '6.5', '>=' ) ) {
			return '7.0';
		}
		if ( version_compare( $version, '6.3', '>=' ) ) {
			return '7.0';
		}
		if ( version_compare( $version, '6.0', '>=' ) ) {
			return '5.6';
		}
		return '5.6';
	}

	public static function php_compatible_with_target( $version ) {
		$min = self::estimated_min_php( $version );
		return version_compare( PHP_VERSION, $min, '>=' );
	}

	/**
	 * @param array<int,string> $versions
	 * @return array<int,string>
	 */
	private static function sort_versions( array $versions ) {
		usort(
			$versions,
			static function ( $a, $b ) {
				return version_compare( $a, $b );
			}
		);
		return $versions;
	}
}
