<?php
/**
 * Package REST API Controller
 *
 * Provides REST API endpoints for local package management (list, activate, deactivate).
 * This is the free plugin's package controller — no marketplace, install, update, or uninstall.
 * Pro overrides this by registering its own PackageController on sequensy-pro/v1.
 *
 * @package Sequensy
 * @since 4.1.0
 */

declare(strict_types=1);

namespace Sequensy\Rest;

use Sequensy\Container;
use Sequensy\Packages\PackageLoader;
use WP_REST_Controller;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;

// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
	die;
}

/**
 * Class PackageController
 *
 * REST API controller for local package listing and activation management.
 */
class PackageController extends WP_REST_Controller {

	use RestControllerTrait;

	/**
	 * Package loader instance.
	 *
	 * @var PackageLoader
	 */
	private PackageLoader $package_loader;

	/**
	 * Factory method for creating controller instance with container.
	 *
	 * @param Container $container Service container instance.
	 * @return self Controller instance.
	 */
	public static function create( Container $container ): self {
		return new self(
			$container->get( 'package_loader' )
		);
	}

	/**
	 * Constructor.
	 *
	 * @param PackageLoader $package_loader Package loader instance.
	 */
	public function __construct( PackageLoader $package_loader ) {
		$this->namespace      = 'sequensy/v1';
		$this->rest_base      = 'packages';
		$this->package_loader = $package_loader;
	}

	/**
	 * Register REST API routes.
	 *
	 * @return void
	 */
	public function register_routes(): void {
		// List installed/bundled packages.
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base,
			array(
				'methods'             => WP_REST_Server::READABLE,
				'callback'            => array( $this, 'get_packages' ),
				'permission_callback' => array( $this, 'permissions_check' ),
			)
		);

		// Activate a package.
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<slug>[a-z0-9-]+)/activate',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'activate_package' ),
				'permission_callback' => array( $this, 'permissions_check' ),
				'args'                => array(
					'slug' => array(
						'description'       => 'Package slug',
						'type'              => 'string',
						'required'          => true,
						'sanitize_callback' => 'sanitize_text_field',
						'validate_callback' => 'rest_validate_request_arg',
					),
				),
			)
		);

		// Deactivate a package.
		register_rest_route(
			$this->namespace,
			'/' . $this->rest_base . '/(?P<slug>[a-z0-9-]+)/deactivate',
			array(
				'methods'             => WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'deactivate_package' ),
				'permission_callback' => array( $this, 'permissions_check' ),
				'args'                => array(
					'slug'                         => array(
						'description'       => 'Package slug',
						'type'              => 'string',
						'required'          => true,
						'sanitize_callback' => 'sanitize_text_field',
						'validate_callback' => 'rest_validate_request_arg',
					),
					'confirm_deactivate_workflows' => array(
						'description' => 'Confirm deactivating affected workflows',
						'type'        => 'boolean',
						'default'     => false,
					),
				),
			)
		);
	}

	/**
	 * List locally installed/bundled packages.
	 *
	 * Returns all discovered packages with their manifest data and active status.
	 * Response format matches Pro's PackageController so the frontend works identically.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response Response object.
	 */
	public function get_packages( WP_REST_Request $request ): WP_REST_Response {
		$manifests = $this->package_loader->discover_all();
		$installed = $this->package_loader->get_installed();
		$packages  = array();

		foreach ( $manifests as $manifest ) {
			$is_active = isset( $installed[ $manifest->slug ] )
				? ( $installed[ $manifest->slug ]['active'] ?? true )
				: false;

			$packages[] = array(
				'slug'              => $manifest->slug,
				'name'              => $manifest->name,
				'version'           => $manifest->version,
				'description'       => $manifest->description,
				'author'            => $manifest->author,
				'category'          => $manifest->category,
				'status'            => 'installed',
				'installed_version' => $manifest->version,
				'is_active'         => $is_active,
			);
		}

		return rest_ensure_response( $packages );
	}

	/**
	 * Activate an installed package.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error Response object.
	 */
	public function activate_package( WP_REST_Request $request ): WP_REST_Response|WP_Error {
		$slug = $request->get_param( 'slug' );

		$result = $this->package_loader->activate( $slug );

		if ( is_wp_error( $result ) ) {
			$result->add_data( array( 'status' => 404 ) );
			return $result;
		}

		$this->clear_metadata_cache();

		return rest_ensure_response(
			array(
				'success' => true,
				'slug'    => $slug,
				'message' => sprintf(
					/* translators: %s: package slug */
					__( 'Package "%s" activated successfully.', 'sequensy' ),
					$slug
				),
			)
		);
	}

	/**
	 * Deactivate an installed package.
	 *
	 * Checks for active workflows using this package's actions or triggers.
	 * If affected workflows exist, returns a 409 error with the workflow list
	 * unless the user has confirmed deactivating them.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error Response object.
	 */
	public function deactivate_package( WP_REST_Request $request ): WP_REST_Response|WP_Error {
		$slug               = $request->get_param( 'slug' );
		$confirm_deactivate = $request->get_param( 'confirm_deactivate_workflows' );

		// Check for affected workflows before deactivating.
		$workflow_storage   = new \Sequensy\Storage\WorkflowStorage();
		$affected_workflows = $workflow_storage->get_workflows_using_integration( $slug );

		// If there are affected workflows and user hasn't confirmed, return error with workflow list.
		if ( ! empty( $affected_workflows ) && ! $confirm_deactivate ) {
			$workflow_data = array_map(
				function ( $workflow ) {
					return array(
						'id'   => $workflow->id,
						'name' => $workflow->name,
					);
				},
				$affected_workflows
			);

			return $this->rest_error(
				'workflows_affected',
				sprintf(
					/* translators: %d: number of affected workflows */
					_n(
						'%d active workflow uses this package and will be deactivated.',
						'%d active workflows use this package and will be deactivated.',
						count( $affected_workflows ),
						'sequensy'
					),
					count( $affected_workflows )
				),
				409,
				array( 'affected_workflows' => $workflow_data )
			);
		}

		// User confirmed — deactivate affected workflows before deactivating package.
		if ( ! empty( $affected_workflows ) && $confirm_deactivate ) {
			$manifest     = $this->package_loader->get_manifest( $slug );
			$display_name = $manifest ? $manifest->name : $slug;

			$reason = sprintf(
				/* translators: %s: package display name */
				__( 'Package deactivated: %s', 'sequensy' ),
				$display_name
			);

			foreach ( $affected_workflows as $workflow ) {
				$workflow_storage->deactivate_workflow( (int) $workflow->id, $reason );
			}
		}

		$result = $this->package_loader->deactivate( $slug );

		if ( is_wp_error( $result ) ) {
			$result->add_data( array( 'status' => 404 ) );
			return $result;
		}

		$this->clear_metadata_cache();

		return rest_ensure_response(
			array(
				'success' => true,
				'slug'    => $slug,
				'message' => sprintf(
					/* translators: %s: package slug */
					__( 'Package "%s" deactivated successfully.', 'sequensy' ),
					$slug
				),
			)
		);
	}

	/**
	 * Clear cached trigger and action metadata.
	 *
	 * @return void
	 */
	private function clear_metadata_cache(): void {
		delete_transient( 'sequensy_trigger_metadata' );
		delete_transient( 'sequensy_action_metadata' );
	}
}
