<?php
/**
 * Plugin abilities toggler.
 *
 * Data-driven post-install MCP ability toggles per plugin slug.
 *
 * @package zip-ai
 */

namespace ZipAI\MCP\Classes\Core;

defined( 'ABSPATH' ) || exit;

class Plugin_Abilities_Toggler {

	/**
	 * Wire the `activated_plugin` core action so any activation path
	 * (server-side ability, browser-proxied `POST /wp/v2/plugins`, admin
	 * UI, WP-CLI) triggers MCP-ability toggles for mapped slugs.
	 *
	 * Idempotent — `enable_for_slug` is safe to call multiple times and
	 * returns `handled=false` for unmapped slugs, so this hook adds no
	 * cost for plugins outside MAP.
	 */
	public static function init(): void {
		add_action( 'activated_plugin', array( __CLASS__, 'on_activated_plugin' ), 20, 2 );
	}

	/**
	 * Map the WP-core plugin file ("folder/file.php") to a wp.org slug
	 * (folder) and dispatch to `enable_for_slug`. Non-wp.org plugins
	 * (single-file or non-conforming folder layout) fall through to a
	 * no-op since the slug won't appear in MAP.
	 *
	 * @param string $plugin_file  Relative plugin file path as stored in
	 *                             the `active_plugins` option.
	 * @param bool   $network_wide Whether activation was network-wide.
	 */
	public static function on_activated_plugin( $plugin_file, $network_wide = false ): void {
		if ( ! is_string( $plugin_file ) || $plugin_file === '' ) {
			return;
		}
		if ( false === strpos( $plugin_file, '/' ) ) {
			return;
		}
		$slug = strtolower( explode( '/', $plugin_file )[0] );
		if ( $slug === '' ) {
			return;
		}

		if ( is_multisite() && $network_wide ) {
			$site_ids = get_sites(
				array(
					'fields' => 'ids',
					'number' => 0,
				)
			);
			foreach ( $site_ids as $site_id ) {
				switch_to_blog( (int) $site_id );
				try {
					self::enable_for_slug( $slug );
				} finally {
					restore_current_blog();
				}
			}
			return;
		}

		self::enable_for_slug( $slug );
	}

	/**
	 * In-memory plugin abilities map.
	 *
	 * Add new plugins here by slug with an `operations` list.
	 */
	private const MAP = array(
		'sureforms' => array(
			'operations' => array(
				array( 'type' => 'set_option', 'name' => 'srfm_abilities_api', 'value' => '1' ),
				array( 'type' => 'set_option', 'name' => 'srfm_abilities_api_edit', 'value' => '1' ),
				array( 'type' => 'set_option', 'name' => 'srfm_abilities_api_delete', 'value' => '1' ),
				array( 'type' => 'set_option', 'name' => 'srfm_mcp_server', 'value' => '1' ),
				array(
					'type'  => 'set_option',
					'name'  => 'srfm_mcp_settings_options',
					'value' => array(
						'srfm_abilities_api'        => true,
						'srfm_abilities_api_edit'   => true,
						'srfm_abilities_api_delete' => true,
						'srfm_mcp_server'           => true,
					),
				),
				array(
					'type' => 'call_function_if_exists',
					'name' => 'srfm_save_mcp_settings',
					'args' => array(
						array(
							'srfm_abilities_api'        => true,
							'srfm_abilities_api_edit'   => true,
							'srfm_abilities_api_delete' => true,
							'srfm_mcp_server'           => true,
						),
					),
				),
				// Suppress post-activation redirect to onboarding wizard.
				array( 'type' => 'delete_option', 'name' => '__srfm_do_redirect' ),
			),
		),
		'suremails' => array(
			'operations' => array(
				array( 'type' => 'set_option', 'name' => 'suremails_abilities_api_edit', 'value' => '1' ),
				// Suppress post-activation redirect to onboarding wizard.
				array( 'type' => 'delete_option', 'name' => 'suremails_do_redirect' ),
			),
		),
		'surecart' => array(
			'operations' => array(
				array( 'type' => 'set_option', 'name' => 'surecart_mcp_abilities_enabled', 'value' => '1' ),
				array( 'type' => 'set_option', 'name' => 'surecart_mcp_edit_abilities_enabled', 'value' => '1' ),
				array( 'type' => 'set_option', 'name' => 'surecart_mcp_delete_abilities_enabled', 'value' => '1' ),
				// Mark zipwp as the SureCart referrer source for marketing attribution.
				array( 'type' => 'set_option_if_absent', 'name' => 'surecart_source', 'value' => 'zipwp' ),
			),
		),
		'surerank' => array(
			'operations' => array(
				// Suppress post-activation redirect + mark onboarding complete.
				array( 'type' => 'delete_option', 'name' => 'surerank_redirect_on_activation' ),
				array( 'type' => 'set_option', 'name' => 'surerank_onboarding_completed', 'value' => true ),
			),
		),
		'astra-sites' => array(
			'operations' => array(
				array( 'type' => 'delete_option', 'name' => 'st_start_onboarding' ),
				array( 'type' => 'set_option', 'name' => 'astra_sites_import_complete', 'value' => '1' ),
			),
		),
		'latepoint' => array(
			'operations' => array(
				array( 'type' => 'delete_option', 'name' => 'latepoint_redirect_to_wizard' ),
			),
		),
		'suretriggers' => array(
			'operations' => array(
				array( 'type' => 'delete_transient', 'name' => 'st-redirect-after-activation' ),
				array( 'type' => 'set_option_if_absent', 'name' => 'suretriggers_source', 'value' => 'zipwp' ),
			),
		),
		'cartflows' => array(
			'operations' => array(
				array( 'type' => 'set_option', 'name' => 'wcf_start_onboarding', 'value' => 'false' ),
				array( 'type' => 'set_option', 'name' => 'wcf_setup_skipped', 'value' => 'true' ),
			),
		),
		'modern-cart' => array(
			'operations' => array(
				array( 'type' => 'set_option', 'name' => 'moderncart_is_onboarding_complete', 'value' => 'yes' ),
				array( 'type' => 'delete_transient', 'name' => 'moderncart_redirect_to_onboarding' ),
			),
		),
		'suredash' => array(
			'operations' => array(
				// Canonical API — `SureDashboard\Inc\Modules\MCP\Module::save_settings`
				// merges the four toggles into `portal_settings` and updates the
				// SUREDASHBOARD_SETTINGS option in one call. Plugin namespace is
				// `SureDashboard` (not `SureDash`); class lives at
				// inc/modules/mcp/module.php.
				array(
					'type'   => 'call_static_method_if_exists',
					'class'  => '\\SureDashboard\\Inc\\Modules\\MCP\\Module',
					'method' => 'save_settings',
					'args'   => array(
						array(
							'suredash_abilities_api'        => true,
							'suredash_abilities_api_edit'   => true,
							'suredash_abilities_api_delete' => true,
							'suredash_mcp_server'           => true,
						),
					),
				),
				// Fallback — write `portal_settings` keys directly when the
				// canonical class isn't loaded (e.g. autoloader race during
				// activation). Same key names as the canonical settings shape.
				array(
					'type' => 'set_portal_settings_true_if_exists',
					'keys' => array(
						'suredash_abilities_api',
						'suredash_abilities_api_edit',
						'suredash_abilities_api_delete',
						'suredash_mcp_server',
					),
				),
				// Suppress post-activation redirect to onboarding wizard.
				// `__suredash_do_redirect` is set true in activation_actions
				// and consumed in activation_redirect (loader.php).
				array( 'type' => 'delete_option', 'name' => '__suredash_do_redirect' ),
				array( 'type' => 'set_option', 'name' => 'suredash_onboarding_completed', 'value' => true ),
			),
		),
		'astra' => array(
			'operations' => array(
				array(
					'type'   => 'set_option_array_keys_if_exists',
					'name'   => 'astra_admin_settings',
					'values' => array(
						'enable_abilities'      => true,
						'enable_edit_abilities' => true,
						'enable_mcp_server'     => true,
					),
				),
			),
		),
	);

	/**
	 * Enable MCP abilities for a plugin slug when mapped.
	 *
	 * @param string $slug Plugin slug.
	 * @return array<string,mixed>
	 */
	public static function enable_for_slug( string $slug ): array {
		$slug = sanitize_key( $slug );
		$map  = self::MAP;

		if ( ! isset( $map[ $slug ] ) || ! is_array( $map[ $slug ] ) ) {
			return array(
				'handled' => false,
				'slug'    => $slug,
				'mode'    => 'none',
				'applied' => 0,
			);
		}

		$operations = $map[ $slug ]['operations'] ?? null;
		if ( ! is_array( $operations ) ) {
			return array(
				'handled' => false,
				'slug'    => $slug,
				'mode'    => 'invalid_config',
				'applied' => 0,
			);
		}

		$applied = 0;
		$mode = 'operations';

		foreach ( $operations as $operation ) {
			if ( ! is_array( $operation ) ) {
				continue;
			}
			if ( self::run_operation( $operation ) ) {
				$applied++;
			}
			if ( ( $operation['type'] ?? '' ) === 'set_portal_settings_true_if_exists' ) {
				$mode = 'portal_settings';
			}
		}

		return array(
			'handled' => true,
			'slug'    => $slug,
			'mode'    => $mode,
			'applied' => $applied,
		);
	}

	/**
	 * Execute one operation.
	 *
	 * @param array<string,mixed> $operation Operation config.
	 * @return bool Whether it applied a change/action.
	 */
	private static function run_operation( array $operation ): bool {
		$type = isset( $operation['type'] ) ? (string) $operation['type'] : '';

		switch ( $type ) {
			case 'set_option':
				$name = isset( $operation['name'] ) ? (string) $operation['name'] : '';
				if ( $name === '' ) {
					return false;
				}
				$value = $operation['value'] ?? null;
				update_option( $name, $value );
				return true;

			case 'set_option_if_absent':
				$name = isset( $operation['name'] ) ? (string) $operation['name'] : '';
				if ( $name === '' ) {
					return false;
				}
				$value = $operation['value'] ?? null;
				return add_option( $name, $value, '', false );

			case 'delete_option':
				$name = isset( $operation['name'] ) ? (string) $operation['name'] : '';
				if ( $name === '' ) {
					return false;
				}
				delete_option( $name );
				return true;

			case 'delete_transient':
				$name = isset( $operation['name'] ) ? (string) $operation['name'] : '';
				if ( $name === '' ) {
					return false;
				}
				delete_transient( $name );
				return true;

			case 'call_function_if_exists':
				$name = isset( $operation['name'] ) ? (string) $operation['name'] : '';
				$args = isset( $operation['args'] ) && is_array( $operation['args'] ) ? $operation['args'] : array();
				if ( $name === '' || ! function_exists( $name ) ) {
					return false;
				}
				call_user_func_array( $name, $args );
				return true;

			case 'call_static_method_if_exists':
				$class  = isset( $operation['class'] ) ? (string) $operation['class'] : '';
				$method = isset( $operation['method'] ) ? (string) $operation['method'] : '';
				$args   = isset( $operation['args'] ) && is_array( $operation['args'] ) ? $operation['args'] : array();
				if ( $class === '' || $method === '' || ! class_exists( $class ) || ! method_exists( $class, $method ) ) {
					return false;
				}
				call_user_func_array( array( $class, $method ), $args );
				return true;

			case 'call_first_supported':
				$calls = isset( $operation['calls'] ) && is_array( $operation['calls'] ) ? $operation['calls'] : array();
				foreach ( $calls as $call_op ) {
					if ( ! is_array( $call_op ) ) {
						continue;
					}
					if ( self::run_operation( $call_op ) ) {
						return true;
					}
				}
				return false;

			case 'set_portal_settings_true_if_exists':
				$keys = isset( $operation['keys'] ) && is_array( $operation['keys'] ) ? $operation['keys'] : array();
				return self::set_portal_settings_true_if_exists( $keys );

			case 'set_option_array_keys':
				$name = isset( $operation['name'] ) ? (string) $operation['name'] : '';
				$values = isset( $operation['values'] ) && is_array( $operation['values'] ) ? $operation['values'] : array();
				if ( $name === '' || empty( $values ) ) {
					return false;
				}
				return self::set_option_array_keys( $name, $values );

			case 'set_option_array_keys_if_exists':
				$name = isset( $operation['name'] ) ? (string) $operation['name'] : '';
				$values = isset( $operation['values'] ) && is_array( $operation['values'] ) ? $operation['values'] : array();
				if ( $name === '' || empty( $values ) ) {
					return false;
				}
				return self::set_option_array_keys_if_exists( $name, $values );

		default:
			return false;
		}
	}

	/**
	 * Set known keys to true only when `portal_settings` exists and keys exist.
	 *
	 * @param array<int,mixed> $keys Candidate keys.
	 * @return bool Whether any value was updated.
	 */
	private static function set_portal_settings_true_if_exists( array $keys ): bool {
		$settings = get_option( 'portal_settings', null );
		if ( ! is_array( $settings ) ) {
			return false;
		}

		$changed = false;
		$truthy  = array( 1, true, '1', 'true' );

		foreach ( $keys as $raw_key ) {
			$key = is_string( $raw_key ) ? $raw_key : '';
			if ( $key === '' || ! array_key_exists( $key, $settings ) ) {
				continue;
			}
			if ( in_array( $settings[ $key ], $truthy, true ) ) {
				continue;
			}
			$settings[ $key ] = true;
			$changed = true;
		}

		if ( $changed ) {
			update_option( 'portal_settings', $settings );
		}

		return $changed;
	}

	/**
	 * Merge key/value pairs into an option array.
	 *
	 * @param string              $option_name Option key.
	 * @param array<string,mixed> $values      Keys to upsert.
	 * @return bool Whether changes were applied.
	 */
	private static function set_option_array_keys( string $option_name, array $values ): bool {
		$current = get_option( $option_name, array() );
		if ( ! is_array( $current ) ) {
			$current = array();
		}

		$changed = false;
		foreach ( $values as $key => $value ) {
			if ( ! is_string( $key ) || $key === '' ) {
				continue;
			}
			if ( array_key_exists( $key, $current ) && $current[ $key ] === $value ) {
				continue;
			}
			$current[ $key ] = $value;
			$changed = true;
		}

		if ( $changed ) {
			update_option( $option_name, $current );
		}

		return $changed;
	}

	/**
	 * Merge key/value pairs into an option array when the option already exists.
	 *
	 * Prevents pre-emptively creating option stubs before the plugin's own
	 * bootstrap writes defaults.
	 *
	 * @param string              $option_name Option key.
	 * @param array<string,mixed> $values      Keys to upsert.
	 * @return bool Whether changes were applied.
	 */
	private static function set_option_array_keys_if_exists( string $option_name, array $values ): bool {
		$current = get_option( $option_name, null );
		if ( null === $current || ! is_array( $current ) ) {
			return false;
		}
		return self::set_option_array_keys( $option_name, $values );
	}

}
