<?php
/**
 * Migration contract.
 *
 * affilun applies database schema changes through an
 * ordered sequence of {@see Migration} implementations. Each migration
 * declares the schema version it installs (its "target version"), a
 * short human-readable description for logs and diagnostics, and an
 * `up()` method that performs the actual schema change.
 *
 * Ordering and idempotency:
 *
 *   - The {@see MigrationRunner} sorts registered migrations by target
 *     version ascending and applies only those whose target version is
 *     strictly greater than the currently-installed
 *     {@see SchemaVersion}. Target versions MUST be unique across the
 *     set of registered migrations; the runner enforces this so that
 *     ordering ambiguity surfaces loudly rather than silently.
 *
 *   - Implementations SHOULD be idempotent at the DDL level. Phase 2
 *     uses WordPress's `dbDelta()` for table creation, which is already
 *     idempotent (it inspects the current schema before issuing ALTERs).
 *     Data-transforming migrations that cannot be made idempotent at
 *     the storage layer MUST guard their work with an explicit version
 *     check or feature flag so that re-runs after a partial failure do
 *     not double-apply.
 *
 *   - Implementations MUST NOT persist the schema version themselves;
 *     that responsibility belongs to the runner, which writes to the
 *     {@see SchemaVersionRepository} after `up()` returns without
 *     throwing.
 *
 * Error signaling:
 *
 *   - A migration signals failure by throwing any {@see \Throwable}.
 *     The runner catches the exception, leaves the schema version at
 *     the last successfully-applied step, and re-throws so the caller
 *     sees the original cause.
 *
 *   - Implementations SHOULD prefer narrow, descriptive exception
 *     types that name the failing migration in their message so the
 *     diagnostics surface can surface a useful operator-facing error.
 *
 * Implementations SHOULD be final and stateless; any collaborators
 * (e.g. `$wpdb`, table-name resolver) must be injected through the
 * constructor rather than fetched from globals inside `up()`.
 *
 * @package Affilun\Infrastructure\Persistence
 */

declare(strict_types=1);

namespace Affilun\Infrastructure\Persistence;

/**
 * Contract for a single schema migration step.
 *
 * See the file-level docblock for ordering, idempotency, and error
 * signaling requirements.
 */
interface Migration {

	/**
	 * Target schema version installed by this migration.
	 *
	 * The value MUST be strictly positive: version 0 is reserved as the
	 * "no schema installed yet" sentinel (see {@see SchemaVersion}).
	 * Target versions MUST be unique across every migration registered
	 * with a given {@see MigrationRunner}; the runner enforces this.
	 */
	public function targetVersion(): SchemaVersion;

	/**
	 * Short, human-readable description of what this migration does.
	 *
	 * Used by the diagnostics surface and migration logs. The string
	 * SHOULD be terse (a single line, under ~80 characters) and name
	 * the concrete schema change in operator-friendly terms, for
	 * example `"create affilun_products table"` or
	 * `"add lifecycle_state column to affilun_products"`.
	 */
	public function description(): string;

	/**
	 * Apply the schema change.
	 *
	 * Called exactly once per migration per environment when the
	 * currently-installed schema version is strictly less than this
	 * migration's {@see self::targetVersion()}. Implementations MUST:
	 *
	 *   - Perform only the DDL / data transformations described by
	 *     {@see self::description()}.
	 *
	 *   - NOT write to the `affilun_db_schema_version` option; the runner
	 *     records the new version after `up()` returns without throwing.
	 *
	 *   - Throw any {@see \Throwable} to signal failure. The runner
	 *     catches the exception, leaves the schema version at the last
	 *     successfully-applied step, and re-throws the original cause.
	 */
	public function up(): void;
}
