<?php
/**
 * ANCP Core - table map.
 *
 * The single authority for core table names. Every reference to a core table -
 * schema creation, data access, migrations - resolves its physical name here.
 *
 * The physical prefix is in transition (spec-00 Section 9): live installs use
 * apft_; the core adoption release renames the core tables to ancp_. Which
 * prefix is active is stored in the option ancp_core_prefix and flipped by
 * Migrator once (and only once) the physical RENAME has succeeded. Until
 * then this map returns the legacy apft_ names, so code never points at tables
 * that do not yet exist. Fresh installs are born on ancp_.
 *
 * charts is intentionally NOT here: it is a Trees-owned table (apft_charts) and
 * stays with the plugin, addressed via Tables::charts().
 *
 * @package AncestorPress\Core
 * @since   0.1.0
 */

namespace AncestorPress\Core;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Core table name authority.
 *
 * @since 0.1.0
 */
class Table_Map {

	/**
	 * Legacy core prefix (as shipped in AP Family Trees v1.0.2).
	 *
	 * @since 0.2.0
	 * @var string
	 */
	const LEGACY_PREFIX = 'apft_';

	/**
	 * Target core prefix (spec-00 Section 9 decision).
	 *
	 * @since 0.2.0
	 * @var string
	 */
	const TARGET_PREFIX = 'ancp_';

	/**
	 * Option holding the currently-active core prefix ('apft_' or 'ancp_').
	 *
	 * Absent on a not-yet-migrated legacy install (treated as apft_). Set by
	 * Migrator after a successful rename, and on fresh install.
	 *
	 * @since 0.2.0
	 * @var string
	 */
	const PREFIX_OPTION = 'ancestor_press_core_prefix';

	/**
	 * Earlier generations of the option name (newest first). The value is
	 * copied forward once by active_prefix(); the old rows are deliberately
	 * left in place so an older bundled core still finds its value if a site
	 * is ever downgraded.
	 *
	 * @since 0.21.0
	 * @var string[]
	 */
	const LEGACY_PREFIX_OPTIONS = array( 'ancestorpress_core_prefix', 'ancp_core_prefix' );

	/**
	 * Per-request cache of the active prefix.
	 *
	 * @since 0.2.0
	 * @var string|null
	 */
	private static $active = null;

	/**
	 * Logical keys of the tables owned by the core library.
	 *
	 * The 12 core-adopted tables from AP Family Trees v1.0.2 (spec-00 Section 4).
	 * `charts` is excluded - it is Trees-owned.
	 *
	 * @since 0.1.0
	 * @var string[]
	 */
	private static $core_keys = array(
		'trees',
		'people',
		'names',
		'families',
		'children',
		'events',
		'event_types',
		'places',
		'notes',
		'note_links',
		'profile_images',
		'media',
		'medialinks',
		'occurrences',
		'record_sets',
		'candidates',
		'judgements',
		'interests',
		'import_maps',
		'field_defs',
		'field_values',
		'repositories',
		'sources',
		'citations',
		'branches',
		'branchlinks',
	);

	/**
	 * Core tables introduced already on the ancp_ prefix (never shipped as
	 * apft_). The prefix migration must NOT try to rename these.
	 *
	 * @since 0.3.0
	 * @var string[]
	 */
	private static $born_ancp_keys = array(
		'names',
		'media',
		'medialinks',
		'occurrences',
		'record_sets',
		'candidates',
		'judgements',
		'interests',
		'import_maps',
		'repositories',
		'sources',
		'citations',
		'branches',
		'branchlinks',
	);

	/**
	 * Core tables that shipped under the legacy apft_ prefix and are therefore
	 * subject to the apft_ -> ancp_ rename migration.
	 *
	 * @since  0.3.0
	 * @return string[]
	 */
	public static function renamable_keys() {
		return array_values( array_diff( self::$core_keys, self::$born_ancp_keys ) );
	}

	/**
	 * The active core prefix for this site, honouring the migration state.
	 *
	 * Defaults to the legacy prefix when the option is unset, so a legacy
	 * install keeps resolving to its existing apft_ tables until the migration
	 * flips the flag.
	 *
	 * @since  0.2.0
	 * @return string 'apft_' or 'ancp_'.
	 */
	public static function active_prefix() {
		if ( null !== self::$active ) {
			return self::$active;
		}
		$stored = get_option( self::PREFIX_OPTION );
		if ( false === $stored ) {
			// One-time carry-forward from an earlier generation of the name.
			foreach ( self::LEGACY_PREFIX_OPTIONS as $legacy_name ) {
				$legacy = get_option( $legacy_name );
				if ( false !== $legacy ) {
					update_option( self::PREFIX_OPTION, $legacy );
					$stored = $legacy;
					break;
				}
			}
		}
		self::$active = ( self::TARGET_PREFIX === $stored || self::LEGACY_PREFIX === $stored )
			? $stored
			: self::LEGACY_PREFIX;
		return self::$active;
	}

	/**
	 * Set the active core prefix (persisted) and refresh the request cache.
	 *
	 * Only the migrator and fresh-install path call this.
	 *
	 * @since  0.2.0
	 * @param  string $prefix 'apft_' or 'ancp_'.
	 * @return bool           True if set.
	 */
	public static function set_active_prefix( $prefix ) {
		if ( self::LEGACY_PREFIX !== $prefix && self::TARGET_PREFIX !== $prefix ) {
			return false;
		}
		update_option( self::PREFIX_OPTION, $prefix );
		self::$active = $prefix;
		return true;
	}

	/**
	 * Fully-qualified physical name for a core table (active prefix).
	 *
	 * @since  0.1.0
	 * @param  string $key Logical table key (e.g. 'people').
	 * @return string      e.g. wp_apft_people or wp_ancp_people.
	 */
	public static function name( $key ) {
		global $wpdb;
		return $wpdb->prefix . self::active_prefix() . $key;
	}

	/**
	 * Physical name for a core table under a specific prefix, ignoring state.
	 * Used by the migrator to reference both sides of a rename.
	 *
	 * @since  0.2.0
	 * @param  string $key    Logical table key.
	 * @param  string $prefix 'apft_' or 'ancp_'.
	 * @return string
	 */
	public static function name_with_prefix( $key, $prefix ) {
		global $wpdb;
		return $wpdb->prefix . $prefix . $key;
	}

	/**
	 * Whether a logical key names a core table.
	 *
	 * @since  0.1.0
	 * @param  string $key Logical table key.
	 * @return bool
	 */
	public static function is_core( $key ) {
		return in_array( $key, self::$core_keys, true );
	}

	/**
	 * All core logical keys.
	 *
	 * @since  0.1.0
	 * @return string[]
	 */
	public static function core_keys() {
		return self::$core_keys;
	}

	/**
	 * Active physical prefix for core tables (WordPress prefix + core prefix).
	 *
	 * @since  0.1.0
	 * @return string e.g. wp_apft_ or wp_ancp_
	 */
	public static function full_prefix() {
		global $wpdb;
		return $wpdb->prefix . self::active_prefix();
	}
}
