<?php
/**
 * Migration: create `affilun_product_meta` table (schema version 2).
 *
 * Installs the auxiliary key/value metadata table described in
 * `specifications/database-schema.md` §5. The table holds supplementary
 * per-product attributes that are not important enough (or not
 * frequently-searched enough) to warrant promotion to a dedicated
 * column on `affilun_products` — for example review counts, store names,
 * provider-specific disclaimer variants, and future minor fields.
 *
 * This migration targets {@see SchemaVersion::of(2)} — the second step
 * in affilun's schema pipeline, installed immediately
 * after `affilun_products` (schema version 1).
 *
 * Scope of this migration:
 *
 *   - Creates `{$prefix}affilun_product_meta` with the full column set
 *     defined in §5.2 (six columns: surrogate primary key, foreign
 *     reference to `affilun_products.id`, meta key, meta value, and
 *     audit timestamps).
 *
 *   - Adds the unique constraint on `(product_id, meta_key)` that
 *     enforces the "one value per meta key per product" invariant
 *     (§5.3; required from the initial version per §5.3 "推奨方針").
 *
 *   - Adds the two secondary indexes listed in §5.3
 *     (`idx_product_id`, `idx_meta_key`).
 *
 *   - Uses the charset/collation returned by
 *     `$wpdb->get_charset_collate()` (per §17.2) and relies on
 *     `InnoDB` as the storage engine (§17.1).
 *
 * Scope explicitly NOT covered here:
 *
 *   - Foreign keys to `affilun_products.id`. Per §11.2 and §13.4, AFFILUN does
 *     not declare foreign keys at the DB level; referential integrity
 *     is enforced by the application layer when rows are inserted
 *     and when a product is archived or deleted.
 *
 *   - `DEFAULT CURRENT_TIMESTAMP` on `created_at` / `updated_at`. Per
 *     §13.4, environment-sensitive default clauses are avoided;
 *     timestamps are supplied by the application layer on insert /
 *     update.
 *
 *   - Any DML. This migration creates the empty table only; seed data
 *     and backfills belong in later migrations or runtime code paths.
 *
 * The shared DDL-assembly and installer-dispatch skeleton lives in
 * {@see AbstractCreateTableMigration}; this subclass only declares the
 * bits that actually differ between tables (target version,
 * description, logical name, column manifest, index manifest). See the
 * base class's file-level docblock for the idempotency and
 * failure-propagation guarantees inherited here.
 *
 * @package Affilun\Infrastructure\Persistence\Migrations
 */

declare(strict_types=1);

namespace Affilun\Infrastructure\Persistence\Migrations;

use Affilun\Infrastructure\Persistence\SchemaVersion;

/**
 * Creates the `affilun_product_meta` table at schema version 2.
 *
 * Concrete {@see AbstractCreateTableMigration} subclass. See the
 * file-level docblock for the scope of this migration and the columns
 * and indexes it installs; see the base class for the formatting rules
 * and failure semantics that apply to every AFFILUN table-creation
 * migration.
 */
final class CreateProductMetaTable extends AbstractCreateTableMigration {

	/**
	 * Logical, unprefixed AFFILUN table name installed by this migration.
	 *
	 * Kept as a class-level constant so tests can reference the exact
	 * name without duplicating the string literal, and so a future
	 * rename has a single location to update.
	 */
	public const TABLE_LOGICAL_NAME = 'affilun_product_meta';

	/**
	 * Target schema version installed by this migration.
	 *
	 * Returns version 2 — the revision that adds auxiliary product
	 * metadata storage on top of the initial `affilun_products` table
	 * (schema version 1). See `database-schema.md` §13.2 for the
	 * canonical version numbering.
	 */
	public function targetVersion(): SchemaVersion {
		return SchemaVersion::of( 2 );
	}

	/**
	 * Short, human-readable description of this migration.
	 *
	 * Used by the diagnostics surface and migration logs. Kept terse
	 * and operator-friendly per the
	 * {@see \Affilun\Infrastructure\Persistence\Migration::description()}
	 * contract.
	 */
	public function description(): string {
		return 'create affilun_product_meta table';
	}

	/**
	 * Logical, unprefixed AFFILUN table name installed by this migration.
	 *
	 * Consumed by {@see AbstractCreateTableMigration::up()} and
	 * {@see AbstractCreateTableMigration::tableName()} to resolve the
	 * physical, prefix-applied name through the injected
	 * {@see \Affilun\Infrastructure\Persistence\TableNameResolver}.
	 */
	protected function logicalTableName(): string {
		return self::TABLE_LOGICAL_NAME;
	}

	/**
	 * Column definitions for `affilun_product_meta`, in the canonical order
	 * defined by `database-schema.md` §5.2.
	 *
	 * Each entry is a single column definition without its trailing
	 * comma; the base class adds the indent and inter-column commas.
	 *
	 * @return list<string>
	 */
	protected function columnDefinitions(): array {
		return array(
			'id bigint unsigned NOT NULL AUTO_INCREMENT',
			'product_id bigint unsigned NOT NULL',
			'meta_key varchar(191) NOT NULL',
			'meta_value longtext NULL',
			'created_at datetime NOT NULL',
			'updated_at datetime NOT NULL',
		);
	}

	/**
	 * Index definitions for `affilun_product_meta`, in the canonical order
	 * defined by `database-schema.md` §5.3.
	 *
	 * The `PRIMARY KEY` entry uses the two-space separator that
	 * `dbDelta()` relies on to detect the primary key line — this is
	 * a WordPress-specific formatting quirk, not a typo.
	 *
	 * The unique constraint on `(product_id, meta_key)` is required
	 * from the initial version (per §5.3 "推奨方針"); future needs
	 * for multi-value meta are handled by introducing a new table
	 * rather than by relaxing this constraint.
	 *
	 * @return list<string>
	 */
	protected function indexDefinitions(): array {
		return array(
			'PRIMARY KEY  (id)',
			'UNIQUE KEY uq_product_meta (product_id, meta_key)',
			'KEY idx_product_id (product_id)',
			'KEY idx_meta_key (meta_key)',
		);
	}
}
