<?php
/**
 * Page Installer
 *
 * @package AmbassadorHub\Admin\Setup
 */

namespace AmbassadorHub\Admin\Setup;

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

use function __;
use function apply_filters;
use function do_action;
use function file_exists;
use function file_get_contents;
use function get_current_user_id;
use function get_option;
use function get_page_by_path;
use function get_post;
use function get_post_status;
use function has_block;
use function is_wp_error;
use function update_option;
use function wp_insert_post;

/**
 * Installs the required WordPress pages for AmbassadorHub.
 *
 * Called from the Tools tab "Create missing pages" button and — in the future
 * — from the setup wizard. Always idempotent: pages that already have a valid
 * option pointing to a published post are skipped without touching anything.
 *
 * ## Extension point
 *
 * Feature modules add their own pages via the `ambh_page_installer_pages` filter:
 *
 * ```php
 * add_filter( 'ambh_page_installer_pages', function( array $pages ): array {
 *     $pages['fundraisers'] = [
 *         'label'      => __( 'Fundraisers', 'ambassadorhub' ),
 *         'title'      => __( 'Fundraisers', 'ambassadorhub' ),
 *         'slug'       => 'fundraisers',
 *         'option_key' => 'ambh_fundraisers_page_id',
 *         'content'    => '<!-- wp:ambassadorhub/fundraiser-list /-->',
 *         'expected_blocks' => [ 'ambassadorhub/fundraiser-list' ],
 *     ];
 *     return $pages;
 * } );
 * ```
 */
final class PageInstaller {

	/**
	 * Get all page definitions.
	 *
	 * @return array<string, array{
	 *     label:            string,
	 *     title:            string,
	 *     slug:             string,
	 *     option_key:       string,
	 *     content:          string,
	 *     template?:        string,
	 *     expected_blocks?: string[],
	 * }>
	 */
	public static function get_definitions(): array {
		$base = AMBASSADOR_HUB_PATH . 'src/Admin/Setup/page-content/';

		$pages = array(
			'account'              => array(
				'label'           => __( 'Account', 'ambassadorhub' ),
				'title'           => __( 'Account', 'ambassadorhub' ),
				'slug'            => 'account',
				'option_key'      => 'ambh_account_page_id',
				'content'         => self::read_content( 'account' ),
				'template'        => $base . 'account.html',
				'expected_blocks' => array( 'ambassadorhub/account' ),
			),
			'signup'               => array(
				'label'           => __( 'Login / Sign up', 'ambassadorhub' ),
				'title'           => __( 'Sign up', 'ambassadorhub' ),
				'slug'            => 'signup',
				'option_key'      => 'ambh_login_page_id',
				'content'         => self::read_content( 'signup' ),
				'template'        => $base . 'signup.html',
				'expected_blocks' => array( 'ambassadorhub/signup' ),
			),
			'payment-confirmation' => array(
				'label'           => __( 'Post-payment confirmation', 'ambassadorhub' ),
				'title'           => __( 'Payment confirmation', 'ambassadorhub' ),
				'slug'            => 'payment-confirmation',
				'option_key'      => 'ambh_psp_redirect_page_id',
				'content'         => self::read_content( 'payment-confirmation' ),
				'template'        => $base . 'payment-confirmation.html',
				'expected_blocks' => array( 'ambassadorhub/donation-confirmation' ),
			),
			'create-fundraiser'    => array(
				'label'           => __( 'Create fundraiser form', 'ambassadorhub' ),
				'title'           => __( 'Create fundraiser', 'ambassadorhub' ),
				'slug'            => 'create-fundraiser',
				'option_key'      => 'ambh_create_fundraiser_page_id',
				'content'         => self::read_content( 'create-fundraiser' ),
				'template'        => $base . 'create-fundraiser.html',
				'expected_blocks' => array( 'ambassadorhub/create-fundraiser-form' ),
			),
		);

		/**
		 * Filter the page definitions used by the page installer.
		 *
		 * Feature modules can append their own page definitions here so they
		 * are created alongside the core pages when the installer runs.
		 *
		 * @since 0.1.0
		 *
		 * @param array $pages Page definitions keyed by an arbitrary unique slug.
		 */
		return (array) apply_filters( 'ambh_page_installer_pages', $pages );
	}

	/**
	 * Get the current status of every defined page.
	 *
	 * @return array<string, array{
	 *     definition:  array,
	 *     page_id:     int,
	 *     page_exists: bool,
	 *     option_set:  bool,
	 * }>
	 */
	public static function get_statuses(): array {
		$statuses = array();

		foreach ( self::get_definitions() as $key => $definition ) {
			$page_id     = (int) get_option( $definition['option_key'], 0 );
			$page_exists = $page_id > 0 && 'publish' === get_post_status( $page_id );

			// Check whether all expected blocks are present in the page content.
			$block_ok = null;
			if ( $page_exists && ! empty( $definition['expected_blocks'] ) ) {
				$post = get_post( $page_id );
				if ( $post ) {
					$block_ok = true;
					foreach ( $definition['expected_blocks'] as $block_name ) {
						if ( ! has_block( $block_name, $post ) ) {
							$block_ok = false;
							break;
						}
					}
				}
			}

			$statuses[ $key ] = array(
				'definition'  => $definition,
				'page_id'     => $page_id,
				'page_exists' => $page_exists,
				'option_set'  => $page_id > 0,
				'block_ok'    => $block_ok,
			);
		}

		return $statuses;
	}

	/**
	 * Install all missing pages.
	 *
	 * Idempotency rules (checked in order):
	 *   1. If the option already points to a published page → skip.
	 *   2. If a published page with the same slug already exists → claim it
	 *      (save option, no new post created).
	 *   3. Otherwise → create the page and save the option.
	 *
	 * @param string[] $keys Optional subset of definition keys to install.
	 *                        Defaults to all defined pages.
	 *
	 * @return array<string, array{status: 'created'|'skipped'|'error', page_id?: int, message?: string}>
	 */
	public static function install( array $keys = array() ): array {
		$definitions = self::get_definitions();

		if ( ! empty( $keys ) ) {
			$definitions = array_intersect_key( $definitions, array_flip( $keys ) );
		}

		$results = array();

		foreach ( $definitions as $key => $definition ) {

			// Rule 1: option already points to a published page — sync content if
			// it no longer matches the template (e.g. after a plugin update that
			// adds a constraining wrapper). These are plugin-owned infrastructure
			// pages, so re-syncing the content on a deliberate "Create pages" run
			// is safe and expected.
			$existing_id = (int) get_option( $definition['option_key'], 0 );
			if ( $existing_id > 0 ) {
				$existing_post = get_post( $existing_id );
				if ( $existing_post && 'publish' === $existing_post->post_status ) {
					$template_content = $definition['content'];
					if ( $template_content && trim( $existing_post->post_content ) !== trim( $template_content ) ) {
						wp_update_post(
							array(
								'ID'           => $existing_id,
								'post_content' => $template_content,
							)
						);
						$results[ $key ] = array(
							'status'  => 'synced',
							'page_id' => $existing_id,
						);
					} else {
						$results[ $key ] = array(
							'status'  => 'skipped',
							'page_id' => $existing_id,
						);
					}
					continue;
				}
			}

			// Rule 2: a page with this slug already exists — claim it.
			$page_by_slug = get_page_by_path( $definition['slug'] );
			if ( $page_by_slug ) {
				update_option( $definition['option_key'], $page_by_slug->ID );
				$results[ $key ] = array(
					'status'  => 'skipped',
					'page_id' => $page_by_slug->ID,
				);
				continue;
			}

			// Rule 3: create the page.
			$page_id = wp_insert_post(
				array(
					'post_title'   => $definition['title'],
					'post_name'    => $definition['slug'],
					'post_content' => $definition['content'],
					'post_status'  => 'publish',
					'post_type'    => 'page',
					'post_author'  => get_current_user_id(),
				),
				true
			);

			if ( is_wp_error( $page_id ) ) {
				$results[ $key ] = array(
					'status'  => 'error',
					'message' => $page_id->get_error_message(),
				);
				continue;
			}

			update_option( $definition['option_key'], $page_id );
			$results[ $key ] = array(
				'status'  => 'created',
				'page_id' => $page_id,
			);
		}

		/**
		 * Fires after the page installer completes a run.
		 *
		 * @since 0.1.0
		 *
		 * @param array $results Installation results keyed by page key.
		 *                        Each value has 'status' ('created'|'skipped'|'error'),
		 *                        optionally 'page_id' and 'message'.
		 */
		do_action( 'ambh_pages_installed', $results );

		return $results;
	}

	/**
	 * Read the block-content template for a page from disk.
	 *
	 * @param string $slug Page key (maps to src/Admin/Setup/page-content/{slug}.html).
	 *
	 * @return string Block markup, or empty string if the file is missing.
	 */
	private static function read_content( string $slug ): string {
		$path = AMBASSADOR_HUB_PATH . 'src/Admin/Setup/page-content/' . $slug . '.html';

		if ( ! file_exists( $path ) ) {
			return '';
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- reading local plugin file.
		$content = file_get_contents( $path );

		return false !== $content ? $content : '';
	}
}
