<?php
/**
 * Package Manifest Value Object
 *
 * Immutable value object representing a parsed sequensy-package.json manifest.
 *
 * @package Sequensy
 * @since 4.0.0
 */

declare(strict_types=1);

namespace Sequensy\Packages;

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

/**
 * Class PackageManifest
 *
 * Immutable value object parsed from a package's sequensy-package.json file.
 * Contains all metadata needed to discover, validate, and load a package.
 */
class PackageManifest {

	/**
	 * Package slug identifier.
	 *
	 * @var string
	 */
	public readonly string $slug;

	/**
	 * Package display name.
	 *
	 * @var string
	 */
	public readonly string $name;

	/**
	 * Package version.
	 *
	 * @var string
	 */
	public readonly string $version;

	/**
	 * Package description (optional).
	 *
	 * @var string
	 */
	public readonly string $description;

	/**
	 * Package author (optional).
	 *
	 * @var string
	 */
	public readonly string $author;

	/**
	 * Package type: integration, native, plugin, utility.
	 *
	 * @var string
	 */
	public readonly string $type;

	/**
	 * Package category.
	 *
	 * @var string
	 */
	public readonly string $category;

	/**
	 * Icon identifier for the UI.
	 *
	 * @var string
	 */
	public readonly string $icon;

	/**
	 * Website URL (optional).
	 *
	 * @var string|null
	 */
	public readonly ?string $website;

	/**
	 * Documentation URL (optional).
	 *
	 * @var string|null
	 */
	public readonly ?string $docs_url;

	/**
	 * Authentication configuration (optional).
	 *
	 * @var array|null
	 */
	public readonly ?array $auth;

	/**
	 * Plugin dependency requirements (optional).
	 *
	 * @var array|null
	 */
	public readonly ?array $requires;

	/**
	 * Minimum required Sequensy Pro version.
	 *
	 * @var string
	 */
	public readonly string $min_sequensy_version;

	/**
	 * Minimum required PHP version.
	 *
	 * @var string
	 */
	public readonly string $min_php_version;

	/**
	 * Minimum required WordPress version.
	 *
	 * @var string
	 */
	public readonly string $min_wp_version;

	/**
	 * Fully qualified class name for the integration class.
	 *
	 * Null for array-based packages that use package.php instead.
	 *
	 * @var string|null
	 */
	public readonly ?string $integration_class;

	/**
	 * Fully qualified class name for the REST routes class (optional).
	 *
	 * @var string|null
	 */
	public readonly ?string $routes_class;

	/**
	 * PSR-4 namespace prefix for the package.
	 *
	 * Null for array-based packages that use package.php instead.
	 *
	 * @var string|null
	 */
	public readonly ?string $namespace;

	/**
	 * Absolute path to the package directory.
	 *
	 * Computed at load time, not stored in the JSON file.
	 *
	 * @var string
	 */
	public readonly string $path;

	/**
	 * Private constructor to enforce factory method usage.
	 *
	 * @param string      $slug                 Package slug.
	 * @param string      $name                 Display name.
	 * @param string      $version              Version string.
	 * @param string      $description          Description.
	 * @param string      $author               Author name.
	 * @param string      $type                 Package type.
	 * @param string      $category             Category.
	 * @param string      $icon                 Icon identifier.
	 * @param string|null $website              Website URL.
	 * @param string|null $docs_url             Documentation URL.
	 * @param array|null  $auth                 Authentication configuration.
	 * @param array|null  $requires             Plugin dependency requirements.
	 * @param string      $min_sequensy_version Minimum Sequensy version.
	 * @param string      $min_php_version      Minimum PHP version.
	 * @param string      $min_wp_version       Minimum WordPress version.
	 * @param string|null $integration_class    Integration FQCN or null for array-based packages.
	 * @param string|null $routes_class         Routes FQCN or null.
	 * @param string|null $pkg_namespace        PSR-4 namespace prefix or null for array-based packages.
	 * @param string      $path                 Absolute path to package directory.
	 */
	private function __construct(
		string $slug,
		string $name,
		string $version,
		string $description,
		string $author,
		string $type,
		string $category,
		string $icon,
		?string $website,
		?string $docs_url,
		?array $auth,
		?array $requires,
		string $min_sequensy_version,
		string $min_php_version,
		string $min_wp_version,
		?string $integration_class,
		?string $routes_class,
		?string $pkg_namespace,
		string $path
	) {
		$this->slug                 = $slug;
		$this->name                 = $name;
		$this->version              = $version;
		$this->description          = $description;
		$this->author               = $author;
		$this->type                 = $type;
		$this->category             = $category;
		$this->icon                 = $icon;
		$this->website              = $website;
		$this->docs_url             = $docs_url;
		$this->auth                 = $auth;
		$this->requires             = $requires;
		$this->min_sequensy_version = $min_sequensy_version;
		$this->min_php_version      = $min_php_version;
		$this->min_wp_version       = $min_wp_version;
		$this->integration_class    = $integration_class;
		$this->routes_class         = $routes_class;
		$this->namespace            = $pkg_namespace;
		$this->path                 = $path;
	}

	/**
	 * Check if this package requires a connection to function.
	 *
	 * @return bool True if the package has auth configuration.
	 */
	public function requires_connection(): bool {
		return null !== $this->auth;
	}

	/**
	 * Create a PackageManifest from a JSON manifest file.
	 *
	 * Reads and parses the sequensy-package.json file, validates required fields,
	 * and computes the absolute path from the file location.
	 *
	 * @param string $json_path Absolute path to the sequensy-package.json file.
	 * @return self
	 * @throws \InvalidArgumentException If the file cannot be read, parsed, or is missing required fields.
	 */
	public static function from_file( string $json_path ): self {
		if ( ! file_exists( $json_path ) || ! is_readable( $json_path ) ) {
			throw new \InvalidArgumentException(
				// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
				sprintf( 'Package manifest file not found or not readable: %s', $json_path )
			);
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local file read.
		$contents = file_get_contents( $json_path );
		if ( false === $contents ) {
			throw new \InvalidArgumentException(
				// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
				sprintf( 'Failed to read package manifest file: %s', $json_path )
			);
		}

		$data = json_decode( $contents, true );
		if ( ! is_array( $data ) ) {
			throw new \InvalidArgumentException(
				// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
				sprintf( 'Invalid JSON in package manifest file: %s', $json_path )
			);
		}

		$required_fields = array(
			'slug',
			'name',
			'version',
			'type',
			'category',
			'icon',
			'min_sequensy_version',
			'min_php_version',
			'min_wp_version',
		);

		$missing = array();
		foreach ( $required_fields as $field ) {
			if ( ! isset( $data[ $field ] ) || '' === $data[ $field ] ) {
				$missing[] = $field;
			}
		}

		if ( ! empty( $missing ) ) {
			throw new \InvalidArgumentException(
				sprintf(
					'Package manifest %s is missing required fields: %s',
					esc_html( $json_path ),
					esc_html( implode( ', ', $missing ) )
				)
			);
		}

		// Compute absolute path from the manifest file location.
		$path = dirname( $json_path ) . '/';

		return new self(
			slug:                 (string) $data['slug'],
			name:                 (string) $data['name'],
			version:              (string) $data['version'],
			description:          isset( $data['description'] ) ? (string) $data['description'] : '',
			author:               isset( $data['author'] ) ? (string) $data['author'] : '',
			type:                 (string) $data['type'],
			category:             (string) $data['category'],
			icon:                 (string) $data['icon'],
			website:              isset( $data['website'] ) && '' !== $data['website']
				? (string) $data['website'] : null,
			docs_url:             isset( $data['docs_url'] ) && '' !== $data['docs_url']
				? (string) $data['docs_url'] : null,
			auth:                 isset( $data['auth'] ) && is_array( $data['auth'] )
				? $data['auth'] : null,
			requires:             isset( $data['requires'] ) && is_array( $data['requires'] )
				? $data['requires'] : null,
			min_sequensy_version: (string) $data['min_sequensy_version'],
			min_php_version:      (string) $data['min_php_version'],
			min_wp_version:       (string) $data['min_wp_version'],
			integration_class:    isset( $data['integration_class'] )
				&& '' !== $data['integration_class']
				? (string) $data['integration_class'] : null,
			routes_class:         isset( $data['routes_class'] )
				&& '' !== $data['routes_class']
				? (string) $data['routes_class'] : null,
			pkg_namespace:        isset( $data['namespace'] )
				&& '' !== $data['namespace']
				? (string) $data['namespace'] : null,
			path:                 $path
		);
	}
}
