<?php
/**
 * Distributable Extension manifest.
 *
 * @package ScheduledChanges
 */

namespace ScheduledChanges\Extension;

use InvalidArgumentException;

/**
 * Declares one installable package and the modules it bundles.
 */
final class ExtensionDefinition {
	private string $id;
	private string $name;
	private string $version;
	private string $plugin_file;
	/** @var string[] */
	private array $modules;
	private string $description;
	private string $visibility;
	private string $documentation_url;

	public const VISIBILITY_PUBLIC      = 'public';
	public const VISIBILITY_DEVELOPMENT = 'development';
	public const VISIBILITY_HIDDEN      = 'hidden';

	/**
	 * @param string[] $modules Bundled module IDs.
	 */
	public function __construct(
		string $id,
		string $name,
		string $version,
		string $plugin_file,
		array $modules,
		string $description,
		string $visibility = self::VISIBILITY_PUBLIC,
		string $documentation_url = ''
	) {
		$id      = self::normalizeId( $id );
		$modules = array_values( array_unique( array_filter( array_map( array( self::class, 'normalizeId' ), $modules ) ) ) );

		if ( '' === $id || '' === trim( $name ) || '' === trim( $version ) || '' === trim( $description ) || array() === $modules ) {
			throw new InvalidArgumentException( 'Extension manifests require an ID, name, version, description, and bundled modules.' );
		}
		if ( ! in_array( $visibility, array( self::VISIBILITY_PUBLIC, self::VISIBILITY_DEVELOPMENT, self::VISIBILITY_HIDDEN ), true ) ) {
			throw new InvalidArgumentException( 'Extension visibility is invalid.' );
		}

		$this->id                = $id;
		$this->name              = trim( $name );
		$this->version           = trim( $version );
		$this->plugin_file       = trim( $plugin_file );
		$this->modules           = $modules;
		$this->description       = trim( $description );
		$this->visibility        = $visibility;
		$this->documentation_url = trim( $documentation_url );
	}

	/**
	 * Creates a definition from raw manifest data.
	 *
	 * @param array<string,mixed> $data Raw manifest data.
	 */
	public static function fromArray( array $data ): self {
		return new self(
			(string) ( $data['id'] ?? '' ),
			(string) ( $data['name'] ?? '' ),
			(string) ( $data['version'] ?? '' ),
			(string) ( $data['plugin_file'] ?? '' ),
			is_array( $data['modules'] ?? null ) ? $data['modules'] : array(),
			(string) ( $data['description'] ?? '' ),
			(string) ( $data['visibility'] ?? self::VISIBILITY_PUBLIC ),
			(string) ( $data['documentation_url'] ?? '' )
		);
	}

	public function id(): string {
		return $this->id; }
	public function name(): string {
		return $this->name; }
	public function version(): string {
		return $this->version; }
	public function pluginFile(): string {
		return $this->plugin_file; }
	/** @return string[] */
	public function modules(): array {
		return $this->modules; }
	public function description(): string {
		return $this->description; }
	public function visibility(): string {
		return $this->visibility; }
	public function documentationUrl(): string {
		return $this->documentation_url; }

	private static function normalizeId( string $id ): string {
		if ( function_exists( 'sanitize_key' ) ) {
			return sanitize_key( $id );
		}

		return (string) preg_replace( '/[^a-z0-9_\-]/', '', strtolower( $id ) );
	}
}
