<?php
/**
 * PK Core - the Site Profile screen and its save handler.
 *
 * Renders the question set (PKCORE_Questions) through the shared renderer
 * (PKCORE_Fields), in sections, with the "Used by" line generated from the
 * consumer matrix. The wizard renders the same questions at the `wizard` tier,
 * so there is one implementation and two views.
 *
 * ITS OWN TOP-LEVEL MENU ENTRY. The profile belongs to no single plugin, so it
 * is not nested under one. It appears once however many PublisherKits plugins
 * are installed - which is the whole point, given the estate reached eleven
 * separate capture surfaces asking overlapping questions.
 *
 * @package PublisherKits\Core
 * @since   0.10.0
 */

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

if ( ! class_exists( 'PKCORE_Profile_Page' ) ) {

	/**
	 * Class PKCORE_Profile_Page
	 *
	 * @since 0.10.0
	 */
	class PKCORE_Profile_Page {

		const NONCE = 'pkcore_profile_save';

		/**
		 * Collect every answer a submitted form actually carried.
		 *
		 * PURE, so it can be tested without WordPress. Returns core profile keys
		 * only; contributed questions are handed to their own storage by
		 * handle_save(), because core never owns a contributed answer.
		 *
		 * NULLS ARE EXCLUDED, NEVER WRITTEN. PKCORE_Fields::collect() returns null
		 * for "this field was not on the submitted form", which is a different
		 * thing from "the operator cleared it". Writing nulls through would mean
		 * the nine-question wizard blanked every answer the full page collected -
		 * the same failure that had SocialPromoter's settings screen wiping
		 * Insight Engine's primary goal.
		 *
		 * @param array  $post      Raw $_POST.
		 * @param string $tier      'wizard' or 'full'.
		 * @param array  $questions Question set; defaults to the core set.
		 * @return array Profile key => value.
		 */
		public static function collect_all( $post, $tier = 'full', $questions = null ) {
			if ( null === $questions ) {
				$questions = class_exists( 'PKCORE_Questions' ) ? PKCORE_Questions::core() : array();
			}

			$out = array();
			foreach ( $questions as $key => $spec ) {
				if ( 'wizard' === $tier && 'wizard' !== ( isset( $spec['tier'] ) ? $spec['tier'] : 'full' ) ) {
					continue;
				}

				$value = PKCORE_Fields::collect( $key, $spec, $post, $tier );
				if ( null !== $value ) {
					$out[ $key ] = $value;
				}

				// A wizard question may capture a second key in the same box -
				// editorial_rules carries context at first run, because nobody
				// arriving at a setup screen distinguishes "house rules" from
				// "brand voice" and asking twice invites the same answer twice.
				if ( 'wizard' === $tier && ! empty( $spec['wizard_captures'] ) ) {
					foreach ( (array) $spec['wizard_captures'] as $extra ) {
						$extra_spec  = isset( $questions[ $extra ] ) ? $questions[ $extra ] : array( 'type' => 'textarea' );
						$extra_value = PKCORE_Fields::collect( $extra, $extra_spec, $post, $tier );
						if ( null !== $extra_value ) {
							$out[ $extra ] = $extra_value;
						}
					}
				}
			}

			return $out;
		}

		/**
		 * Persist a submitted Site Profile form.
		 *
		 * @return void
		 */
		public static function handle_save() {
			if ( ! current_user_can( 'manage_options' ) ) {
				wp_die( esc_html__( 'You do not have permission to do that.', 'publisherkits-socialpromoter' ) );
			}
			check_admin_referer( self::NONCE );

			$tier = isset( $_POST['pkcore_tier'] ) && 'wizard' === $_POST['pkcore_tier'] ? 'wizard' : 'full';

			// phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- Each value is sanitised per key in PKCORE_Profile::set_many().
			$raw = wp_unslash( $_POST );

			$values = self::collect_all( $raw, $tier );
			if ( ! empty( $values ) ) {
				PKCORE_Profile::set_many( $values );
			}

			self::save_contributed( $raw, $tier );

			$redirect = add_query_arg(
				array(
					'page'          => 'publisherkits-profile',
					'pkcore-notice' => 'saved',
				),
				admin_url( 'admin.php' )
			);
			wp_safe_redirect( $redirect );
			exit;
		}

		/**
		 * Hand contributed answers to their own plugin's storage.
		 *
		 * Core does not read or write a contributed question's data - that
		 * boundary is what stops the shared profile becoming the place plugins
		 * park their configuration, which is the disease this module treats.
		 *
		 * @param array  $post Raw $_POST.
		 * @param string $tier Tier submitted.
		 * @return void
		 */
		private static function save_contributed( $post, $tier ) {
			if ( ! class_exists( 'PKCORE_Questions' ) ) {
				return;
			}
			foreach ( PKCORE_Questions::contributed() as $key => $spec ) {
				if ( 'wizard' === $tier && 'wizard' !== $spec['tier'] ) {
					continue;
				}
				$value = PKCORE_Fields::collect( $key, $spec, $post, $tier );
				if ( null === $value ) {
					continue;
				}
				call_user_func( $spec['storage']['set'], $value );
			}
		}

		/**
		 * Render the full Site Profile page.
		 *
		 * @return void
		 */
		public static function render() {
			if ( ! current_user_can( 'manage_options' ) ) {
				return;
			}

			// Prefill still-empty fields from the pre-core options, exactly as the
			// wizard does. Without this the migration path depends on the operator
			// happening to run the wizard: anyone who skipped it, or who arrived
			// here first, would see blanks where the estate already holds an
			// answer, and would retype what it already knows.
			//
			// PREFILL ONLY. Nothing is written until the operator saves - the same
			// rule the shim has always followed.
			$profile = PKCORE_Profile::all();
			if ( class_exists( 'PKCORE_Legacy_Profile' ) ) {
				$profile = PKCORE_Legacy_Profile::with_defaults( $profile );
			}

			$questions = PKCORE_Questions::all();
			$sections  = PKCORE_Questions::sections();

			// Contributed questions group under their own plugin heading rather
			// than being scattered through the core sections.
			foreach ( $questions as $spec ) {
				if ( ! empty( $spec['contributed'] ) && ! isset( $sections[ $spec['section'] ] ) ) {
					$sections[ $spec['section'] ] = sprintf(
						/* translators: %s: plugin name. */
						__( 'For %s', 'publisherkits-socialpromoter' ),
						ucfirst( str_replace( 'plugin:', '', $spec['section'] ) )
					);
				}
			}

			echo '<div class="wrap pkcore-wrap">';
			echo '<h1>' . esc_html__( 'Site Profile', 'publisherkits-socialpromoter' ) . '</h1>';
			echo '<p class="pkcore-intro">' . esc_html__( 'One shared description of this publication. Fill it in once and every PublisherKits plugin reads it. Each question shows which of your installed plugins actually uses the answer.', 'publisherkits-socialpromoter' ) . '</p>';

			if ( isset( $_GET['pkcore-notice'] ) && 'saved' === $_GET['pkcore-notice'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
				echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Site Profile saved.', 'publisherkits-socialpromoter' ) . '</p></div>';
			}

			echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '">';
			echo '<input type="hidden" name="action" value="pkcore_save_profile">';
			echo '<input type="hidden" name="pkcore_tier" value="full">';
			wp_nonce_field( self::NONCE );

			foreach ( $sections as $id => $title ) {
				$in_section = array();
				foreach ( $questions as $key => $spec ) {
					if ( $id === $spec['section'] ) {
						$in_section[ $key ] = $spec;
					}
				}
				if ( empty( $in_section ) ) {
					continue;
				}

				echo '<div class="pkcore-section">';
				echo '<h2>' . esc_html( $title ) . '</h2>';
				foreach ( $in_section as $key => $spec ) {
					$value = self::value_for( $key, $spec, $profile );
					PKCORE_Fields::render( $key, $spec, $value, 'full' );
				}
				echo '</div>';
			}

			submit_button( __( 'Save Site Profile', 'publisherkits-socialpromoter' ) );
			echo '</form>';
			echo '</div>';
		}

		/**
		 * Current value for a question - core profile, or a contributed
		 * question's own storage.
		 *
		 * @param string $key     Question key.
		 * @param array  $spec    Question spec.
		 * @param array  $profile Core profile.
		 * @return mixed
		 */
		private static function value_for( $key, $spec, $profile ) {
			if ( ! empty( $spec['contributed'] ) ) {
				return call_user_func( $spec['storage']['get'] );
			}
			return isset( $profile[ $key ] ) ? $profile[ $key ] : '';
		}
	}
}
