<?php
/**
 * AP Family Archive - pretty permalinks for record pages.
 *
 * Maps /<person-page>/I123/ (and family/report equivalents) onto the mapped
 * archive pages, carrying the record reference as a registered query var
 * instead of a plain ?apfaar_pid= arg. Query-arg URLs keep working unchanged -
 * the rewrites are additive, and sites without pretty permalinks (or with
 * unpublished archive pages) simply stay on query args.
 *
 * Rules are registered on every request; the PERSISTED rule set is flushed
 * lazily whenever the rule signature changes (activation, "Make the pages
 * for me", a page slug edit, a permalink-structure change) - no manual
 * "flush permalinks" step for the site owner, and no flush on requests
 * where nothing changed. The deactivator drops the plugin's rules and
 * flushes so nothing stale survives deactivation.
 *
 * @package    APFAAR_Family_Archive
 * @subpackage APFAAR_Family_Archive/includes
 * @since      0.1.0
 */

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

/**
 * Rewrite rules + query vars for the record pages.
 *
 * @since 0.1.0
 */
class APFAAR_Rewrites {

	/**
	 * Page key => the query var its pretty URL carries.
	 *
	 * @var array
	 */
	const PARAMS = array(
		'person'      => 'apfaar_pid',
		'family'      => 'apfaar_fid',
		'ahnentafel'  => 'apfaar_pid',
		'descendants' => 'apfaar_pid',
		'source'      => 'apfaar_sid',
	);

	/**
	 * Option holding the signature of the currently-persisted rule set.
	 *
	 * @var string
	 */
	const SIG_OPTION = 'apfaar_rewrite_sig';

	/**
	 * Register the record query vars so WP carries them through rewrites.
	 *
	 * @param  string[] $vars Public query vars.
	 * @return string[]
	 */
	public function register_query_vars( $vars ) {
		$vars[] = 'apfaar_pid';
		$vars[] = 'apfaar_fid';
		$vars[] = 'apfaar_sid';
		$vars[] = 'apfaar_tree'; // Multi-tree context (P5).
		return $vars;
	}

	/**
	 * The rules this site currently needs: regex => query redirect.
	 *
	 * Only published, mapped pages under a pretty permalink structure get a
	 * rule - everything else keeps working on plain query args.
	 *
	 * @return array
	 */
	public static function rules() {
		if ( ! get_option( 'permalink_structure' ) ) {
			return array();
		}
		$rules = array();
		foreach ( self::PARAMS as $key => $param ) {
			$page_id = absint( get_option( 'apfaar_page_' . $key, 0 ) );
			if ( ! $page_id || 'publish' !== get_post_status( $page_id ) ) {
				continue;
			}
			$uri = get_page_uri( $page_id );
			if ( ! $uri ) {
				continue;
			}
			// Multi-tree form first (P5): /page/t/<tree>/<ref>/ - the literal
			// /t/ segment keeps tree slugs unambiguous from record refs. The
			// tree-less form stays the primary tree's URL, unchanged.
			$rules[ '^' . $uri . '/t/([^/]+)/([^/]+)/?$' ] = 'index.php?page_id=' . $page_id . '&apfaar_tree=$matches[1]&' . $param . '=$matches[2]';
			$rules[ '^' . $uri . '/([^/]+)/?$' ]           = 'index.php?page_id=' . $page_id . '&' . $param . '=$matches[1]';
		}
		return $rules;
	}

	/**
	 * Register the rules (every request - rules only persist via flush).
	 *
	 * @return void
	 */
	public function add_rules() {
		foreach ( self::rules() as $regex => $query ) {
			add_rewrite_rule( $regex, $query, 'top' );
		}
	}

	/**
	 * Flush the persisted rules when the needed set changed (lazy, admin
	 * requests only - every change that alters the set happens in admin:
	 * activation, page creation/slug edits, permalink settings).
	 *
	 * @return void
	 */
	public function maybe_flush() {
		if ( ! is_admin() ) {
			return;
		}
		$sig = md5( wp_json_encode( self::rules() ) );
		if ( get_option( self::SIG_OPTION ) === $sig ) {
			return;
		}
		flush_rewrite_rules( false );
		update_option( self::SIG_OPTION, $sig );
	}

	/**
	 * Deactivation: drop this plugin's rules from the in-memory set, flush
	 * the persisted rules without them, and clear the signature so a
	 * reactivation re-flushes.
	 *
	 * @return void
	 */
	public static function deactivate_flush() {
		global $wp_rewrite;
		if ( isset( $wp_rewrite->extra_rules_top ) && is_array( $wp_rewrite->extra_rules_top ) ) {
			foreach ( array_keys( self::rules() ) as $regex ) {
				unset( $wp_rewrite->extra_rules_top[ $regex ] );
			}
		}
		delete_option( self::SIG_OPTION );
		flush_rewrite_rules( false );
	}

	/**
	 * Pretty URL for a record on a mapped page, or '' when unavailable
	 * (no pretty permalinks, page unmapped/unpublished, wrong param).
	 *
	 * @param  string $which Page key (person|family|ahnentafel|descendants).
	 * @param  string $ref   Record reference (I123 / F45).
	 * @param  string $tree  Optional non-primary tree context (P5) -
	 *                       /page/t/<tree>/<ref>/.
	 * @return string
	 */
	public static function pretty_url( $which, $ref, $tree = '' ) {
		if ( ! isset( self::PARAMS[ $which ] ) || '' === (string) $ref || ! get_option( 'permalink_structure' ) ) {
			return '';
		}
		$page_id = absint( get_option( 'apfaar_page_' . $which, 0 ) );
		if ( ! $page_id || 'publish' !== get_post_status( $page_id ) ) {
			return '';
		}
		$base = get_permalink( $page_id );
		if ( ! $base || false !== strpos( $base, '?' ) ) {
			return '';
		}
		$path = '' !== (string) $tree
			? 't/' . rawurlencode( (string) $tree ) . '/' . rawurlencode( (string) $ref )
			: rawurlencode( (string) $ref );
		return user_trailingslashit( trailingslashit( $base ) . $path );
	}
}
