<?php
/**
 * Shared helpers for the WP PowerSuite WP-CLI integration.
 *
 * @package WP_PowerSuite\CLI\Support
 * @since 1.0.7
 */

namespace WP_PowerSuite\CLI\Support;

use WP_PowerSuite\Core\ModuleRegistry;
use WP_PowerSuite\Core\OptionStore;
use WP_PowerSuite\Core\SecurityCriticalModules;

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

/**
 * Small utility layer wrapping the registry/option store for CLI commands.
 *
 * Command classes stay thin adapters; all state access flows through the same
 * core services used by the admin UI.
 *
 * @since 1.0.7
 */
final class Cli {

	/**
	 * Return an initialized ModuleRegistry.
	 *
	 * @since 1.0.7
	 */
	public static function registry(): ModuleRegistry {
		$registry = ModuleRegistry::getInstance();
		if ( ! $registry->isInitialized() ) {
			$registry->init();
		}
		return $registry;
	}

	/**
	 * Resolve a user-supplied slug to registry metadata, or abort with a proper
	 * WP-CLI error. Only slugs registered by WP PowerSuite are ever accepted —
	 * arbitrary class names or paths cannot pass through.
	 *
	 * @since 1.0.7
	 * @param string $slug Raw slug from the command line.
	 * @return array<string, mixed> Normalized module metadata.
	 */
	public static function moduleOrError( string $slug ): array {
		$normalized = sanitize_key( $slug );
		$metadata   = $normalized !== '' ? self::registry()->getModuleMetadata( $normalized ) : null;

		if ( empty( $metadata ) ) {
			\WP_CLI::error(
				sprintf(
					/* translators: %s: module slug */
					__( 'Unknown WP PowerSuite module: %s', 'wp-powersuite' ),
					$slug
				)
			);
		}

		return $metadata;
	}

	/**
	 * Whether a module is currently enabled (via the shared OptionStore).
	 *
	 * @since 1.0.7
	 */
	public static function isEnabled( string $slug ): bool {
		return OptionStore::getInstance()->isModuleEnabled( $slug );
	}

	/**
	 * Human-readable yes/no for CLI tables.
	 *
	 * @since 1.0.7
	 */
	public static function yesNo( bool $value ): string {
		return $value ? 'yes' : 'no';
	}

	/**
	 * Coarse license state string for diagnostics. Never exposes the key itself.
	 *
	 * @since 1.0.7
	 * @return string One of: not-applicable, active, expired, inactive.
	 */
	public static function licenseState(): string {
		if ( function_exists( 'wp_powersuite_is_licensing_expected' ) && ! wp_powersuite_is_licensing_expected() ) {
			return 'not-applicable';
		}

		$valid   = function_exists( 'wp_powersuite_is_license_valid' ) && wp_powersuite_is_license_valid();
		$current = function_exists( 'wp_powersuite_is_license_current' ) && wp_powersuite_is_license_current();

		if ( $valid && $current ) {
			return 'active';
		}

		if ( $valid && ! $current ) {
			return 'expired';
		}

		return 'inactive';
	}

	/**
	 * Whether a module can load given the current license (mirrors the gating in
	 * {@see \WP_PowerSuite\Core\ModuleManager}). Free modules and license-exempt
	 * security modules are always available; premium modules require a valid
	 * license.
	 *
	 * @since 1.0.7
	 * @param array<string, mixed> $metadata Module metadata.
	 * @return bool
	 */
	public static function isModuleAvailable( array $metadata ): bool {
		$is_pro    = ! empty( $metadata['is_pro'] );
		$module_id = isset( $metadata['id'] ) ? (string) $metadata['id'] : '';

		if ( ! $is_pro ) {
			return true;
		}

		if ( function_exists( 'wp_powersuite_is_license_valid' ) && wp_powersuite_is_license_valid() ) {
			return true;
		}

		return SecurityCriticalModules::isLicenseExempt( $module_id );
	}

	/**
	 * Normalize and validate a --format flag.
	 *
	 * @since 1.0.7
	 * @param array<string, mixed> $assoc_args Command flags.
	 * @param list<string>         $allowed    Allowed formats.
	 * @param string               $default    Default format.
	 * @return string
	 */
	public static function format( array $assoc_args, array $allowed = [ 'table', 'json', 'csv', 'yaml', 'count', 'ids' ], string $default = 'table' ): string {
		$format = (string) \WP_CLI\Utils\get_flag_value( $assoc_args, 'format', $default );
		if ( ! in_array( $format, $allowed, true ) ) {
			\WP_CLI::error(
				sprintf(
					/* translators: 1: supplied format, 2: allowed formats */
					__( 'Invalid --format "%1$s". Allowed: %2$s.', 'wp-powersuite' ),
					$format,
					implode( ', ', $allowed )
				)
			);
		}
		return $format;
	}

	/**
	 * Render an associative array as a key/value table (or via --format for
	 * machine formats). Used by summary-style commands like `status`.
	 *
	 * @since 1.0.7
	 * @param array<string, scalar|null> $data       Field => value map.
	 * @param array<string, mixed>       $assoc_args Command flags (reads --format).
	 * @return void
	 */
	public static function renderKeyValue( array $data, array $assoc_args ): void {
		$format = self::format( $assoc_args, [ 'table', 'json', 'yaml' ], 'table' );

		if ( $format === 'json' || $format === 'yaml' ) {
			\WP_CLI::print_value( $data, [ 'format' => $format ] );
			return;
		}

		$rows = [];
		foreach ( $data as $field => $value ) {
			$rows[] = [
				'field' => (string) $field,
				'value' => is_bool( $value ) ? self::yesNo( $value ) : (string) $value,
			];
		}

		\WP_CLI\Utils\format_items( 'table', $rows, [ 'field', 'value' ] );
	}
}
