<?php
/**
 * PK Core - admin surface: the shared PublisherKits menu, the Site Profile
 * page (profile module) and the Topics curation page (classify module).
 *
 * Rendered ONCE by the winning bundle, and only for modules at least one
 * active plugin requested. Follows the PublisherKits admin design standard:
 * navy band page header (Style A), section cards, structured fields with
 * per-field help text stating what each does.
 *
 * @package PublisherKits\Core
 * @since   0.1.0
 */

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

if ( ! class_exists( 'PKCORE_Admin' ) ) {

	/**
	 * Class PKCORE_Admin
	 */
	class PKCORE_Admin {

		/**
		 * Menu slugs.
		 */
		const MENU_SLUG   = 'publisherkits';
		const TOPICS_SLUG = 'publisherkits-topics';

		/**
		 * Dedicated Site Profile page slug. Like TOPICS_SLUG, never the shared
		 * 'publisherkits' menu slug: on sites where another plugin (Insight
		 * Engine) owns that menu and hooks its own page to it, both callbacks
		 * fire and two pages render stacked on one screen (found live).
		 */
		const PROFILE_SLUG = 'publisherkits-profile';

		/**
		 * Hook everything. Called once by PKCORE_Core when is_admin().
		 *
		 * @return void
		 */
		public static function init() {
			add_action( 'admin_menu', array( __CLASS__, 'register_menu' ), 9 );
			add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue' ) );
			// The Site Profile screen and the first-run wizard are both rendered
			// and saved by the v2 module (PKCORE_Profile_Page / PKCORE_Setup).
			// This class keeps ownership of the MENU and of Topics only.
			add_action( 'admin_post_pkcore_save_profile', array( 'PKCORE_Profile_Page', 'handle_save' ) );
			add_action( 'admin_post_pkcore_save_setup', array( 'PKCORE_Setup', 'handle' ) );
			add_action( 'admin_post_pkcore_topic_action', array( __CLASS__, 'handle_topic_action' ) );
		}

		/**
		 * Register the shared PublisherKits menu (guarded - another plugin
		 * may have claimed it) and this core's submenus.
		 *
		 * @return void
		 */
		public static function register_menu() {
			global $admin_page_hooks;

			$profile_on  = PKCORE_Core::module_active( 'profile' );
			$classify_on = PKCORE_Core::module_active( 'classify' );
			if ( ! $profile_on && ! $classify_on ) {
				return;
			}

			$top_cb = $profile_on ? array( 'PKCORE_Profile_Page', 'render' ) : array( __CLASS__, 'render_topics_page' );

			if ( empty( $admin_page_hooks[ self::MENU_SLUG ] ) ) {
				add_menu_page(
					__( 'PublisherKits', 'publisherkits-socialpromoter' ),
					__( 'PublisherKits', 'publisherkits-socialpromoter' ),
					'manage_options',
					self::MENU_SLUG,
					$top_cb,
					'dashicons-analytics',
					58
				);
			}

			if ( $profile_on ) {
				add_submenu_page(
					self::MENU_SLUG,
					__( 'Site Profile', 'publisherkits-socialpromoter' ),
					__( 'Site Profile', 'publisherkits-socialpromoter' ),
					'manage_options',
					self::PROFILE_SLUG,
					array( 'PKCORE_Profile_Page', 'render' )
				);
			}
			if ( $profile_on ) {
				// The wizard is a HIDDEN page (null parent): reachable by URL and
				// by the first-run redirect, but never a permanent menu item. A
				// setup screen that lives in the menu forever is clutter the day
				// after setup.
				add_submenu_page(
					null,
					__( 'Set up your Site Profile', 'publisherkits-socialpromoter' ),
					__( 'Set up your Site Profile', 'publisherkits-socialpromoter' ),
					'manage_options',
					PKCORE_Setup::SLUG,
					array( 'PKCORE_Setup', 'render' )
				);
			}
			if ( $classify_on ) {
				// Always the dedicated slug: the shared 'publisherkits' slug
				// is ambiguous on sites where another plugin (Insight Engine)
				// owns the menu and renders its own page there.
				add_submenu_page(
					self::MENU_SLUG,
					__( 'Topics', 'publisherkits-socialpromoter' ),
					__( 'Topics', 'publisherkits-socialpromoter' ),
					'manage_options',
					self::TOPICS_SLUG,
					array( __CLASS__, 'render_topics_page' )
				);
			}
		}

		/**
		 * Enqueue the core admin stylesheet on core pages only, cache-busted
		 * by filemtime (estate rule: never the version constant).
		 *
		 * @param string $hook Current admin page hook.
		 * @return void
		 */
		public static function enqueue( $hook ) {
			if ( false === strpos( (string) $hook, self::MENU_SLUG ) ) {
				return;
			}
			$file = trailingslashit( PKCORE_Core::dir() ) . 'includes/admin/css/pkcore-admin.css';
			if ( ! file_exists( $file ) ) {
				return;
			}
			// A vendored library has no plugin URL of its own; derive it from
			// the bundle path, which always lives inside a plugin.
			$url = plugins_url( 'includes/admin/css/pkcore-admin.css', trailingslashit( PKCORE_Core::dir() ) . 'pkcore.php' );
			wp_enqueue_style( 'pkcore-admin', $url, array(), (string) filemtime( $file ) );
		}

		/* ------------------------------------------------------------------
		 * Shared render helpers
		 * ---------------------------------------------------------------- */

		/**
		 * The navy band page header (design standard Style A).
		 *
		 * @param string $title    Page title (Title Case).
		 * @param string $subtitle One-line subtitle.
		 * @return void
		 */
		public static function render_band( $title, $subtitle = '' ) {
			?>
			<div class="pkcore-band">
				<div class="pkcore-mark" aria-hidden="true">PK</div>
				<div>
					<div class="pkcore-band-eyebrow">
						<span class="pkcore-band-kicker"><?php esc_html_e( 'PUBLISHERKITS', 'publisherkits-socialpromoter' ); ?></span>
						<span class="pkcore-band-sep">|</span>
						<span class="pkcore-band-name"><?php esc_html_e( 'Core', 'publisherkits-socialpromoter' ); ?></span>
					</div>
					<h1 class="pkcore-band-title"><?php echo esc_html( $title ); ?></h1>
					<?php if ( '' !== $subtitle ) : ?>
						<p class="pkcore-band-subtitle"><?php echo esc_html( $subtitle ); ?></p>
					<?php endif; ?>
				</div>
			</div>
			<?php
			self::render_setup_strip();
		}

		/**
		 * The shared setup destinations: the core's own pages plus anything a
		 * sibling PublisherKits plugin registers (its goals, connections or
		 * configuration screens). One list, shown identically everywhere, so
		 * the setup surface reads as one place rather than scattered screens.
		 *
		 * @since 0.6.0
		 * @return array[] Each: { slug: admin page slug, label: string }.
		 */
		public static function setup_links() {
			$links      = array();
			$profile_on = PKCORE_Core::module_active( 'profile' );

			if ( $profile_on ) {
				$links[] = array(
					'slug'  => self::PROFILE_SLUG,
					'label' => __( 'Site Profile', 'publisherkits-socialpromoter' ),
				);
			}
			if ( PKCORE_Core::module_active( 'classify' ) ) {
				$links[] = array(
					'slug'  => self::TOPICS_SLUG,
					'label' => __( 'Topics', 'publisherkits-socialpromoter' ),
				);
			}
			$links[] = array(
				'slug'  => 'publisherkits-setup',
				'label' => __( 'Setup', 'publisherkits-socialpromoter' ),
			);

			/**
			 * Register additional PublisherKits setup destinations.
			 *
			 * Sibling plugins append entries for their own setup-family admin
			 * pages so the shared strip can take the user anywhere in the
			 * PublisherKits setup surface from any of its pages.
			 *
			 * @since 0.6.0
			 * @param array[] $links Each: { slug: admin page slug, label: string }.
			 */
			$links = apply_filters( 'pkcore_setup_links', $links );

			$out  = array();
			$seen = array();
			foreach ( (array) $links as $link ) {
				if ( ! is_array( $link ) || empty( $link['slug'] ) || empty( $link['label'] ) ) {
					continue;
				}
				$slug = sanitize_key( $link['slug'] );
				if ( '' === $slug || isset( $seen[ $slug ] ) ) {
					continue;
				}
				$seen[ $slug ] = true;
				$out[]         = array(
					'slug'  => $slug,
					'label' => sanitize_text_field( (string) $link['label'] ),
				);
			}
			return $out;
		}

		/**
		 * Render the shared setup strip (current page highlighted). Sibling
		 * plugins call this on their own setup pages, guarded by
		 * class_exists + method_exists; it carries its own minimal styling so
		 * it renders correctly outside the core's stylesheet.
		 *
		 * @since 0.6.0
		 * @return void
		 */
		public static function render_setup_strip() {
			$links = self::setup_links();
			if ( count( $links ) < 2 ) {
				return;
			}
			$current = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only highlight.

			static $styled = false;
			if ( ! $styled ) {
				$styled = true;
				// Enqueued (not echoed) so plugin-checkers see the standard API; a
				// late enqueue prints in the admin footer, which is fine for this
				// non-critical strip styling.
				wp_register_style( 'pkcore-setup-strip', false, array(), '0.8.4' );
				wp_enqueue_style( 'pkcore-setup-strip' );
				wp_add_inline_style( 'pkcore-setup-strip', '.pkcore-setup-strip{display:flex;flex-wrap:wrap;align-items:center;gap:4px;margin:0 0 16px;padding:6px 10px;background:#fff;border:1px solid #dfe5ec;border-radius:6px}.pkcore-setup-strip-label{font-size:11px;font-weight:700;letter-spacing:.4px;text-transform:uppercase;color:#6b7a90;margin-right:6px}.pkcore-setup-strip a{display:inline-block;padding:4px 10px;border-radius:4px;font-size:13px;text-decoration:none;color:#1d2b3a}.pkcore-setup-strip a:hover{background:#eef3f8}.pkcore-setup-strip a.pkcore-setup-current{background:#1d2b3a;color:#fff;font-weight:600}' );
			}

			echo '<nav class="pkcore-setup-strip" aria-label="' . esc_attr__( 'PublisherKits setup pages', 'publisherkits-socialpromoter' ) . '">';
			echo '<span class="pkcore-setup-strip-label">' . esc_html__( 'PublisherKits setup', 'publisherkits-socialpromoter' ) . '</span>';
			foreach ( $links as $link ) {
				$class = $link['slug'] === $current ? ' class="pkcore-setup-current" aria-current="page"' : '';
				echo '<a href="' . esc_url( admin_url( 'admin.php?page=' . $link['slug'] ) ) . '"' . $class . '>' . esc_html( $link['label'] ) . '</a>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $class is a fixed literal.
			}
			echo '</nav>';
		}

		/**
		 * A saved/error notice from the redirect flag.
		 *
		 * @return void
		 */
		private static function render_result_notice() {
			if ( ! isset( $_GET['pkcore_result'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Display-only flag.
				return;
			}
			$result = sanitize_key( wp_unslash( $_GET['pkcore_result'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			if ( 'saved' === $result ) {
				echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Saved.', 'publisherkits-socialpromoter' ) . '</p></div>';
			} elseif ( 'error' === $result ) {
				echo '<div class="notice notice-error is-dismissible"><p>' . esc_html__( 'That change could not be applied. Common causes: a duplicate topic name, or the core ring is at its cap (accept/add/promote into Core needs room - merge related topics or use the Related ring).', 'publisherkits-socialpromoter' ) . '</p></div>';
			} elseif ( 'seeded' === $result ) {
				echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Topic suggestions generated from your categories and tags. Review and accept them below.', 'publisherkits-socialpromoter' ) . '</p></div>';
			} elseif ( 'imported' === $result ) {
				echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Starter pack imported. Its topics are active immediately; rename, merge or archive any that do not fit your site.', 'publisherkits-socialpromoter' ) . '</p></div>';
			}
		}

		/**
		 * Redirect back to the referring core page with a result flag.
		 *
		 * @param string $slug   Page slug to return to.
		 * @param string $result Result flag.
		 * @return void
		 */
		private static function redirect_back( $slug, $result ) {
			wp_safe_redirect( add_query_arg( array( 'page' => $slug, 'pkcore_result' => $result ), admin_url( 'admin.php' ) ) );
			exit;
		}

		/* ------------------------------------------------------------------
		 * Site Profile page
		 * ---------------------------------------------------------------- */

		/**
		 * Render the Site Profile page.
		 *
		 * @return void
		 */
		public static function render_profile_page() {
			if ( ! current_user_can( 'manage_options' ) ) {
				return;
			}
			// Still-empty fields prefill from pre-core PublisherKits settings
			// (temporary migration shim); nothing is written until Save.
			$prefilled = PKCORE_Legacy_Profile::pending();
			$profile   = PKCORE_Legacy_Profile::with_defaults( PKCORE_Profile::all() );
			$style     = $profile['house_style'];
			?>
			<div class="wrap pkcore-wrap">
				<?php
				self::render_band(
					__( 'Site Profile', 'publisherkits-socialpromoter' ),
					__( 'One shared profile of this site, read by every PublisherKits plugin. Fill it once; everything downstream uses it.', 'publisherkits-socialpromoter' )
				);
				self::render_result_notice();
				if ( ! empty( $prefilled ) ) {
					echo '<div class="notice notice-info"><p>' . esc_html__( 'Some empty fields below are prefilled from settings your PublisherKits plugins already hold (site context, shared profile, keywords, house style). Review, adjust and Save to make them the shared profile.', 'publisherkits-socialpromoter' ) . '</p></div>';
				}
				?>
				<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
					<?php wp_nonce_field( 'pkcore_save_profile' ); ?>
					<input type="hidden" name="action" value="pkcore_save_profile" />

					<div class="pkcore-layout">
						<div class="pkcore-main">
							<div class="pkcore-card">
								<h2 class="pkcore-card-title"><?php esc_html_e( 'About the site', 'publisherkits-socialpromoter' ); ?></h2>
								<p class="pkcore-card-description"><?php esc_html_e( 'The basics every plugin needs: what this site covers and who reads it.', 'publisherkits-socialpromoter' ); ?></p>

								<div class="pkcore-field">
									<label for="pkcore-sector"><?php esc_html_e( 'Sector', 'publisherkits-socialpromoter' ); ?></label>
									<input type="text" class="pkcore-input" id="pkcore-sector" name="sector" value="<?php echo esc_attr( $profile['sector'] ); ?>" placeholder="<?php esc_attr_e( 'e.g. HR and L&D trade publishing', 'publisherkits-socialpromoter' ); ?>" />
									<p class="pkcore-field-description"><?php esc_html_e( 'The niche this site publishes in. Used to steer classification language and AI copy across the suite.', 'publisherkits-socialpromoter' ); ?></p>
								</div>

								<div class="pkcore-field">
									<label for="pkcore-audience"><?php esc_html_e( 'Audience', 'publisherkits-socialpromoter' ); ?></label>
									<select class="pkcore-select" id="pkcore-audience" name="audience">
										<option value="" <?php selected( $profile['audience'], '' ); ?>><?php esc_html_e( 'Not set', 'publisherkits-socialpromoter' ); ?></option>
										<option value="b2b" <?php selected( $profile['audience'], 'b2b' ); ?>><?php esc_html_e( 'B2B (professionals, trade)', 'publisherkits-socialpromoter' ); ?></option>
										<option value="b2c" <?php selected( $profile['audience'], 'b2c' ); ?>><?php esc_html_e( 'B2C (consumers)', 'publisherkits-socialpromoter' ); ?></option>
										<option value="mixed" <?php selected( $profile['audience'], 'mixed' ); ?>><?php esc_html_e( 'Mixed', 'publisherkits-socialpromoter' ); ?></option>
									</select>
									<p class="pkcore-field-description"><?php esc_html_e( 'Who the content is written for. Shapes tone and which suite features are relevant.', 'publisherkits-socialpromoter' ); ?></p>
								</div>

								<div class="pkcore-field">
									<label for="pkcore-description"><?php esc_html_e( 'Site description', 'publisherkits-socialpromoter' ); ?></label>
									<textarea class="pkcore-input" id="pkcore-description" name="description" rows="3" placeholder="<?php esc_attr_e( 'One paragraph on what this site is and does.', 'publisherkits-socialpromoter' ); ?>"><?php echo esc_textarea( $profile['description'] ); ?></textarea>
									<p class="pkcore-field-description"><?php esc_html_e( 'A short plain-English description. AI features quote this back to stay on-brief.', 'publisherkits-socialpromoter' ); ?></p>
								</div>
							</div>

							<div class="pkcore-card">
								<h2 class="pkcore-card-title"><?php esc_html_e( 'Vocabulary & style', 'publisherkits-socialpromoter' ); ?></h2>
								<p class="pkcore-card-description"><?php esc_html_e( 'The words and voice of the site. Optional, but the more here, the sharper everything downstream.', 'publisherkits-socialpromoter' ); ?></p>

								<div class="pkcore-field">
									<label for="pkcore-keywords"><?php esc_html_e( 'Target keywords', 'publisherkits-socialpromoter' ); ?></label>
									<textarea class="pkcore-input" id="pkcore-keywords" name="keywords" rows="3" placeholder="<?php esc_attr_e( 'one per line, or comma-separated', 'publisherkits-socialpromoter' ); ?>"><?php echo esc_textarea( implode( "\n", $profile['keywords'] ) ); ?></textarea>
									<p class="pkcore-field-description"><?php esc_html_e( 'SEO / topic vocabulary the site targets. Stored lower-case, deduplicated.', 'publisherkits-socialpromoter' ); ?></p>
								</div>

								<div class="pkcore-field">
									<label for="pkcore-language"><?php esc_html_e( 'Writing language', 'publisherkits-socialpromoter' ); ?></label>
									<input type="text" class="pkcore-input pkcore-input-short" id="pkcore-language" name="house_language" value="<?php echo esc_attr( $style['language'] ); ?>" placeholder="en-GB" />
									<p class="pkcore-field-description"><?php esc_html_e( 'The language/locale content is written in, e.g. en-GB. AI copy follows it.', 'publisherkits-socialpromoter' ); ?></p>
								</div>

								<div class="pkcore-field">
									<label for="pkcore-tone"><?php esc_html_e( 'House style notes', 'publisherkits-socialpromoter' ); ?></label>
									<textarea class="pkcore-input" id="pkcore-tone" name="house_tone_notes" rows="3" placeholder="<?php esc_attr_e( 'e.g. plain English, no exclamation marks, sentence-case headlines', 'publisherkits-socialpromoter' ); ?>"><?php echo esc_textarea( $style['tone_notes'] ); ?></textarea>
									<p class="pkcore-field-description"><?php esc_html_e( 'Tone and style rules any AI-written copy must follow.', 'publisherkits-socialpromoter' ); ?></p>
								</div>
							</div>

							<div class="pkcore-card">
								<h2 class="pkcore-card-title"><?php esc_html_e( 'Channels & goals', 'publisherkits-socialpromoter' ); ?></h2>
								<p class="pkcore-card-description"><?php esc_html_e( 'Where you publish and what you are trying to achieve. Tells analytics which channels matter.', 'publisherkits-socialpromoter' ); ?></p>

								<div class="pkcore-field">
									<span class="pkcore-field-label"><?php esc_html_e( 'Channels you publish to', 'publisherkits-socialpromoter' ); ?></span>
									<?php
									$channel_options = array(
										'newsletter'    => __( 'Email newsletter', 'publisherkits-socialpromoter' ),
										'product-email' => __( 'Product / promo email', 'publisherkits-socialpromoter' ),
										'social'        => __( 'Social media', 'publisherkits-socialpromoter' ),
										'onsite'        => __( 'On-site only', 'publisherkits-socialpromoter' ),
									);
									foreach ( $channel_options as $value => $label ) :
										?>
										<label class="pkcore-check">
											<input type="checkbox" name="channels[]" value="<?php echo esc_attr( $value ); ?>" <?php checked( in_array( $value, $profile['channels'], true ) ); ?> />
											<?php echo esc_html( $label ); ?>
										</label>
									<?php endforeach; ?>
									<p class="pkcore-field-description"><?php esc_html_e( 'Tick everything you actively publish to.', 'publisherkits-socialpromoter' ); ?></p>
								</div>

								<div class="pkcore-field">
									<span class="pkcore-field-label"><?php esc_html_e( 'What do you most want from PublisherKits?', 'publisherkits-socialpromoter' ); ?></span>
									<?php
									$goal_options = array(
										'understand-competitors' => __( 'Understand competitors', 'publisherkits-socialpromoter' ),
										'grow'                   => __( 'Grow audience', 'publisherkits-socialpromoter' ),
										'monetise'               => __( 'Monetise', 'publisherkits-socialpromoter' ),
										'improve-content'        => __( 'Improve content', 'publisherkits-socialpromoter' ),
									);
									foreach ( $goal_options as $value => $label ) :
										?>
										<label class="pkcore-check">
											<input type="checkbox" name="goals[]" value="<?php echo esc_attr( $value ); ?>" <?php checked( in_array( $value, $profile['goals'], true ) ); ?> />
											<?php echo esc_html( $label ); ?>
										</label>
									<?php endforeach; ?>
									<p class="pkcore-field-description"><?php esc_html_e( 'Used to surface the most relevant screens and tools first.', 'publisherkits-socialpromoter' ); ?></p>
								</div>

								<div class="pkcore-field">
									<label for="pkcore-context"><?php esc_html_e( 'Extra context', 'publisherkits-socialpromoter' ); ?></label>
									<textarea class="pkcore-input" id="pkcore-context" name="context" rows="3" placeholder="<?php esc_attr_e( 'Anything else a colleague (or an AI) should know about this site.', 'publisherkits-socialpromoter' ); ?>"><?php echo esc_textarea( $profile['context'] ); ?></textarea>
									<p class="pkcore-field-description"><?php esc_html_e( 'Free-text notes appended to AI prompts across the suite.', 'publisherkits-socialpromoter' ); ?></p>
								</div>
							</div>

							<p><button type="submit" class="button button-primary"><?php esc_html_e( 'Save profile', 'publisherkits-socialpromoter' ); ?></button></p>
						</div>

						<div class="pkcore-sidebar">
							<div class="pkcore-sidebar-card">
								<h3><?php esc_html_e( 'How this is used', 'publisherkits-socialpromoter' ); ?></h3>
								<p><?php esc_html_e( 'This profile is stored once, centrally, and read by every PublisherKits plugin on this site - classification, analytics and AI copy all draw on it.', 'publisherkits-socialpromoter' ); ?></p>
								<p><?php esc_html_e( 'Nothing here is sent anywhere. It is site configuration, not telemetry.', 'publisherkits-socialpromoter' ); ?></p>
							</div>
						</div>
					</div>
				</form>
			</div>
			<?php
		}

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

			// phpcs:disable WordPress.Security.ValidatedSanitizedInput -- Each value is sanitised in PKCORE_Profile::set_many() per key.
			PKCORE_Profile::set_many(
				array(
					'sector'      => isset( $_POST['sector'] ) ? wp_unslash( $_POST['sector'] ) : '',
					'audience'    => isset( $_POST['audience'] ) ? wp_unslash( $_POST['audience'] ) : '',
					'description' => isset( $_POST['description'] ) ? wp_unslash( $_POST['description'] ) : '',
					'keywords'    => isset( $_POST['keywords'] ) ? wp_unslash( $_POST['keywords'] ) : '',
					'channels'    => isset( $_POST['channels'] ) ? wp_unslash( (array) $_POST['channels'] ) : array(),
					'goals'       => isset( $_POST['goals'] ) ? wp_unslash( (array) $_POST['goals'] ) : array(),
					'context'     => isset( $_POST['context'] ) ? wp_unslash( $_POST['context'] ) : '',
					'house_style' => array(
						'language'   => isset( $_POST['house_language'] ) ? wp_unslash( $_POST['house_language'] ) : '',
						'tone_notes' => isset( $_POST['house_tone_notes'] ) ? wp_unslash( $_POST['house_tone_notes'] ) : '',
					),
				)
			);
			// phpcs:enable

			self::redirect_back( self::PROFILE_SLUG, 'saved' );
		}

		/* ------------------------------------------------------------------
		 * Topics page
		 * ---------------------------------------------------------------- */

		/**
		 * Render the Topics curation page.
		 *
		 * @return void
		 */
		public static function render_topics_page() {
			if ( ! current_user_can( 'manage_options' ) ) {
				return;
			}
			$pack      = PKCORE_Pack::get();
			$active    = PKCORE_Pack::topics( 'active' );
			$suggested = PKCORE_Pack::topics( 'suggested' );
			$archived  = PKCORE_Pack::topics( 'archived' );
			$provider  = PKCORE_Classify::provider_info();
			$slug      = self::TOPICS_SLUG;

			// Merge targets: every active topic.
			$merge_targets = array();
			foreach ( $active as $topic ) {
				$merge_targets[ $topic['slug'] ] = $topic['label'];
			}
			?>
			<div class="wrap pkcore-wrap">
				<?php
				self::render_band(
					__( 'Topics', 'publisherkits-socialpromoter' ),
					__( 'The one topic framework for this site. Every PublisherKits plugin classifies against this same list, so results can be compared across channels.', 'publisherkits-socialpromoter' )
				);
				self::render_result_notice();
				?>

				<div class="pkcore-layout">
					<div class="pkcore-main">

						<div class="pkcore-card">
							<h2 class="pkcore-card-title"><?php esc_html_e( 'Status', 'publisherkits-socialpromoter' ); ?></h2>
							<table class="pkcore-status-table">
								<tr>
									<th><?php esc_html_e( 'Classification engine', 'publisherkits-socialpromoter' ); ?></th>
									<td>
										<?php echo esc_html( $provider['label'] ); ?>
										<?php if ( empty( $provider['semantic'] ) ) : ?>
											<span class="pkcore-muted"><?php esc_html_e( '(keyword matching - upgrade to the PublisherKits Classifier for AI semantic classification)', 'publisherkits-socialpromoter' ); ?></span>
										<?php endif; ?>
									</td>
								</tr>
								<tr>
									<th><?php esc_html_e( 'Topics', 'publisherkits-socialpromoter' ); ?></th>
									<td>
										<?php
										printf(
											/* translators: 1: active count, 2: suggested count, 3: archived count. */
											esc_html__( '%1$d active, %2$d suggested, %3$d archived', 'publisherkits-socialpromoter' ),
											count( $active ),
											count( $suggested ),
											count( $archived )
										);
										?>
									</td>
								</tr>
								<tr>
									<th><?php esc_html_e( 'Core ring', 'publisherkits-socialpromoter' ); ?></th>
									<td>
										<?php
										printf(
											/* translators: 1: active core-ring topics, 2: the cap. */
											esc_html__( '%1$d of %2$d', 'publisherkits-socialpromoter' ),
											(int) PKCORE_Pack::core_ring_count(),
											(int) PKCORE_Pack::core_ring_cap()
										);
										?>
										<span class="pkcore-muted"><?php esc_html_e( '(capped: analytics beyond ~15 topics become statistically weak and unreadable - merge related topics, or keep extras in the Related ring)', 'publisherkits-socialpromoter' ); ?></span>
									</td>
								</tr>
								<tr>
									<th><?php esc_html_e( 'Last generated', 'publisherkits-socialpromoter' ); ?></th>
									<td><?php echo '' !== $pack['generated_at'] ? esc_html( $pack['generated_at'] . ' UTC' ) : esc_html__( 'Never - use the button below to suggest topics from your categories and tags.', 'publisherkits-socialpromoter' ); ?></td>
								</tr>
							</table>
							<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" class="pkcore-inline-form">
								<?php wp_nonce_field( 'pkcore_topic_action' ); ?>
								<input type="hidden" name="action" value="pkcore_topic_action" />
								<input type="hidden" name="op" value="seed" />
								<input type="hidden" name="return" value="<?php echo esc_attr( $slug ); ?>" />
								<button type="submit" class="button"><?php esc_html_e( 'Suggest topics from my categories & tags', 'publisherkits-socialpromoter' ); ?></button>
							</form>
							<p class="pkcore-field-description"><?php esc_html_e( 'Reads your existing categories and tags and proposes a topic list - nothing is applied until you accept each suggestion. Safe to run again later; existing topics are never changed.', 'publisherkits-socialpromoter' ); ?></p>
						</div>

						<?php $starters = PKCORE_Pack::starters(); ?>
						<?php if ( ! empty( $starters ) ) : ?>
						<div class="pkcore-card">
							<h2 class="pkcore-card-title"><?php esc_html_e( 'Starter packs', 'publisherkits-socialpromoter' ); ?></h2>
							<p class="pkcore-card-description"><?php esc_html_e( 'Curated, sector-specific topic sets provided by installed PublisherKits plugins. Importing adds a starter\'s topics to your framework immediately (as active topics you can rename, merge or archive); topics you already have are never changed. Deriving topics from your own categories and tags above is the usual route - a starter is a head start for a known sector.', 'publisherkits-socialpromoter' ); ?></p>
							<table class="pkcore-topics-table">
								<?php foreach ( $starters as $starter ) : ?>
								<tr>
									<td>
										<strong><?php echo esc_html( $starter['label'] ); ?></strong>
										<?php if ( '' !== $starter['description'] ) : ?>
											<span class="pkcore-muted"><?php echo esc_html( $starter['description'] ); ?></span>
										<?php endif; ?>
										<span class="pkcore-muted">
											<?php
											printf(
												/* translators: %d: number of topics in the starter. */
												esc_html( _n( '%d topic', '%d topics', count( $starter['topics'] ), 'publisherkits-socialpromoter' ) ),
												count( $starter['topics'] )
											);
											?>
										</span>
									</td>
									<td class="pkcore-actions">
										<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" class="pkcore-inline-form">
											<?php wp_nonce_field( 'pkcore_topic_action' ); ?>
											<input type="hidden" name="action" value="pkcore_topic_action" />
											<input type="hidden" name="op" value="import_starter" />
											<input type="hidden" name="starter" value="<?php echo esc_attr( $starter['slug'] ); ?>" />
											<input type="hidden" name="return" value="<?php echo esc_attr( $slug ); ?>" />
											<button type="submit" class="button"><?php esc_html_e( 'Import', 'publisherkits-socialpromoter' ); ?></button>
										</form>
									</td>
								</tr>
								<?php endforeach; ?>
							</table>
						</div>
						<?php endif; ?>

						<?php if ( ! empty( $suggested ) ) : ?>
						<div class="pkcore-card">
							<h2 class="pkcore-card-title"><?php esc_html_e( 'Suggested topics', 'publisherkits-socialpromoter' ); ?></h2>
							<p class="pkcore-card-description"><?php esc_html_e( 'Proposed from your site (or by AI, when the Classifier is installed). Accept the ones that describe your editorial territory. Suggestions are not used for classification until accepted, so discarding is safe - nothing that was never accepted is referenced anywhere, and running the suggestion tool again can re-propose.', 'publisherkits-socialpromoter' ); ?></p>
							<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" class="pkcore-inline-form" style="margin-bottom:10px;">
								<?php wp_nonce_field( 'pkcore_topic_action' ); ?>
								<input type="hidden" name="action" value="pkcore_topic_action" />
								<input type="hidden" name="op" value="discard_suggested" />
								<input type="hidden" name="return" value="<?php echo esc_attr( $slug ); ?>" />
								<button type="submit" class="button"><?php echo esc_html( sprintf( /* translators: %d: number of suggestions. */ __( 'Discard all %d suggestions', 'publisherkits-socialpromoter' ), count( $suggested ) ) ); ?></button>
							</form>
							<table class="pkcore-topics-table">
								<?php foreach ( $suggested as $topic ) : ?>
								<tr>
									<td><strong><?php echo esc_html( $topic['label'] ); ?></strong> <span class="pkcore-muted"><?php echo esc_html( $topic['slug'] ); ?></span></td>
									<td class="pkcore-actions">
										<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" class="pkcore-inline-form">
											<?php wp_nonce_field( 'pkcore_topic_action' ); ?>
											<input type="hidden" name="action" value="pkcore_topic_action" />
											<input type="hidden" name="return" value="<?php echo esc_attr( $slug ); ?>" />
											<input type="hidden" name="topic" value="<?php echo esc_attr( $topic['slug'] ); ?>" />
											<button type="submit" name="op" value="accept" class="button button-primary button-small"><?php esc_html_e( 'Accept', 'publisherkits-socialpromoter' ); ?></button>
											<button type="submit" name="op" value="archive" class="button button-small"><?php esc_html_e( 'Archive', 'publisherkits-socialpromoter' ); ?></button>
										</form>
									</td>
								</tr>
								<?php endforeach; ?>
							</table>
						</div>
						<?php endif; ?>

						<?php foreach ( array( 'core' => __( 'Core topics', 'publisherkits-socialpromoter' ), 'related' => __( 'Related topics', 'publisherkits-socialpromoter' ), 'peripheral' => __( 'Peripheral (fixed)', 'publisherkits-socialpromoter' ) ) as $ring => $ring_label ) : ?>
							<?php
							$ring_topics = array();
							foreach ( $active as $topic ) {
								if ( $topic['ring'] === $ring ) {
									$ring_topics[] = $topic;
								}
							}
							if ( empty( $ring_topics ) && 'core' !== $ring ) {
								continue;
							}
							?>
						<div class="pkcore-card">
							<h2 class="pkcore-card-title"><?php echo esc_html( $ring_label ); ?></h2>
							<p class="pkcore-card-description">
								<?php
								if ( 'core' === $ring ) {
									esc_html_e( 'Your editorial territory - the only ring analytics roll up by. Keywords drive the built-in keyword classifier: an item mentioning any of them files under the topic.', 'publisherkits-socialpromoter' );
								} elseif ( 'related' === $ring ) {
									esc_html_e( 'Adjacent subjects you touch but do not own. Classified, but held out of core analytics.', 'publisherkits-socialpromoter' );
								} else {
									esc_html_e( 'A fixed, generic catch-all ring shared by every site so off-topic content lands somewhere. Not editable.', 'publisherkits-socialpromoter' );
								}
								?>
							</p>
							<?php if ( empty( $ring_topics ) ) : ?>
								<p class="pkcore-muted"><?php esc_html_e( 'No topics yet. Accept suggestions above or add one below.', 'publisherkits-socialpromoter' ); ?></p>
							<?php endif; ?>
							<?php foreach ( $ring_topics as $topic ) : ?>
								<?php if ( 'peripheral' === $ring ) : ?>
									<div class="pkcore-topic-row pkcore-topic-row-fixed">
										<strong><?php echo esc_html( $topic['label'] ); ?></strong>
										<span class="pkcore-muted"><?php echo esc_html( $topic['slug'] ); ?></span>
									</div>
								<?php else : ?>
									<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" class="pkcore-topic-row">
										<?php wp_nonce_field( 'pkcore_topic_action' ); ?>
										<input type="hidden" name="action" value="pkcore_topic_action" />
										<input type="hidden" name="return" value="<?php echo esc_attr( $slug ); ?>" />
										<input type="hidden" name="topic" value="<?php echo esc_attr( $topic['slug'] ); ?>" />
										<div class="pkcore-topic-main">
											<input type="text" name="label" value="<?php echo esc_attr( $topic['label'] ); ?>" class="pkcore-input pkcore-topic-label" aria-label="<?php esc_attr_e( 'Topic label', 'publisherkits-socialpromoter' ); ?>" />
											<input type="text" name="keywords" value="<?php echo esc_attr( implode( ', ', (array) $topic['keywords'] ) ); ?>" class="pkcore-input pkcore-topic-keywords" placeholder="<?php esc_attr_e( 'keywords, comma-separated', 'publisherkits-socialpromoter' ); ?>" aria-label="<?php esc_attr_e( 'Topic keywords', 'publisherkits-socialpromoter' ); ?>" />
										</div>
										<div class="pkcore-topic-controls">
											<select name="ring" aria-label="<?php esc_attr_e( 'Ring', 'publisherkits-socialpromoter' ); ?>">
												<option value="core" <?php selected( $ring, 'core' ); ?>><?php esc_html_e( 'Core', 'publisherkits-socialpromoter' ); ?></option>
												<option value="related" <?php selected( $ring, 'related' ); ?>><?php esc_html_e( 'Related', 'publisherkits-socialpromoter' ); ?></option>
											</select>
											<select name="merge_into" aria-label="<?php esc_attr_e( 'Merge into', 'publisherkits-socialpromoter' ); ?>">
												<option value=""><?php esc_html_e( 'Merge into...', 'publisherkits-socialpromoter' ); ?></option>
												<?php foreach ( $merge_targets as $t_slug => $t_label ) : ?>
													<?php if ( $t_slug !== $topic['slug'] ) : ?>
														<option value="<?php echo esc_attr( $t_slug ); ?>"><?php echo esc_html( $t_label ); ?></option>
													<?php endif; ?>
												<?php endforeach; ?>
											</select>
											<button type="submit" name="op" value="update" class="button button-small"><?php esc_html_e( 'Save', 'publisherkits-socialpromoter' ); ?></button>
											<button type="submit" name="op" value="archive" class="button button-small button-link-delete"><?php esc_html_e( 'Archive', 'publisherkits-socialpromoter' ); ?></button>
										</div>
									</form>
								<?php endif; ?>
							<?php endforeach; ?>
						</div>
						<?php endforeach; ?>

						<div class="pkcore-card">
							<h2 class="pkcore-card-title"><?php esc_html_e( 'Add a topic', 'publisherkits-socialpromoter' ); ?></h2>
							<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
								<?php wp_nonce_field( 'pkcore_topic_action' ); ?>
								<input type="hidden" name="action" value="pkcore_topic_action" />
								<input type="hidden" name="op" value="add" />
								<input type="hidden" name="return" value="<?php echo esc_attr( $slug ); ?>" />
								<div class="pkcore-field">
									<label for="pkcore-new-label"><?php esc_html_e( 'Label', 'publisherkits-socialpromoter' ); ?></label>
									<input type="text" class="pkcore-input" id="pkcore-new-label" name="label" placeholder="<?php esc_attr_e( 'e.g. Employment Law', 'publisherkits-socialpromoter' ); ?>" />
									<p class="pkcore-field-description"><?php esc_html_e( 'The display name. A permanent slug is derived from it once, then never changes.', 'publisherkits-socialpromoter' ); ?></p>
								</div>
								<div class="pkcore-field">
									<label for="pkcore-new-keywords"><?php esc_html_e( 'Keywords', 'publisherkits-socialpromoter' ); ?></label>
									<input type="text" class="pkcore-input" id="pkcore-new-keywords" name="keywords" placeholder="<?php esc_attr_e( 'comma-separated; the label itself always counts', 'publisherkits-socialpromoter' ); ?>" />
									<p class="pkcore-field-description"><?php esc_html_e( 'Words and phrases that identify this topic in titles and tags.', 'publisherkits-socialpromoter' ); ?></p>
								</div>
								<div class="pkcore-field">
									<label for="pkcore-new-ring"><?php esc_html_e( 'Ring', 'publisherkits-socialpromoter' ); ?></label>
									<select id="pkcore-new-ring" name="ring">
										<option value="core"><?php esc_html_e( 'Core - my editorial territory', 'publisherkits-socialpromoter' ); ?></option>
										<option value="related"><?php esc_html_e( 'Related - adjacent, not core', 'publisherkits-socialpromoter' ); ?></option>
									</select>
								</div>
								<p><button type="submit" class="button button-primary"><?php esc_html_e( 'Add topic', 'publisherkits-socialpromoter' ); ?></button></p>
							</form>
						</div>

						<div class="pkcore-card">
							<h2 class="pkcore-card-title"><?php esc_html_e( 'Content types', 'publisherkits-socialpromoter' ); ?></h2>
							<p class="pkcore-card-description"><?php esc_html_e( 'The format facet (news, analysis, opinion...). The built-in list is fixed; add site-specific types if you need them. Content-type detection itself is an AI (Classifier) feature.', 'publisherkits-socialpromoter' ); ?></p>
							<p><?php echo esc_html( implode( ', ', $pack['content_types'] ) ); ?></p>
							<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" class="pkcore-inline-form">
								<?php wp_nonce_field( 'pkcore_topic_action' ); ?>
								<input type="hidden" name="action" value="pkcore_topic_action" />
								<input type="hidden" name="op" value="content_type" />
								<input type="hidden" name="return" value="<?php echo esc_attr( $slug ); ?>" />
								<input type="text" name="content_type" class="pkcore-input pkcore-input-short" placeholder="<?php esc_attr_e( 'e.g. case-study', 'publisherkits-socialpromoter' ); ?>" aria-label="<?php esc_attr_e( 'New content type', 'publisherkits-socialpromoter' ); ?>" />
								<button type="submit" class="button"><?php esc_html_e( 'Add content type', 'publisherkits-socialpromoter' ); ?></button>
							</form>
						</div>

						<?php if ( ! empty( $archived ) ) : ?>
						<div class="pkcore-card">
							<h2 class="pkcore-card-title"><?php esc_html_e( 'Archived', 'publisherkits-socialpromoter' ); ?></h2>
							<p class="pkcore-card-description"><?php esc_html_e( 'Retired topics. Kept (never deleted) so historical data still resolves; merged topics point at their survivor.', 'publisherkits-socialpromoter' ); ?></p>
							<p class="pkcore-muted">
								<?php
								$names = array();
								foreach ( $archived as $topic ) {
									$alias   = PKCORE_Pack::resolve_alias( $topic['slug'] );
									$names[] = $alias !== $topic['slug']
										? sprintf( '%s → %s', $topic['label'], $alias )
										: $topic['label'];
								}
								echo esc_html( implode( ', ', $names ) );
								?>
							</p>
						</div>
						<?php endif; ?>
					</div>

					<div class="pkcore-sidebar">
						<div class="pkcore-sidebar-card">
							<h3><?php esc_html_e( 'How topics work', 'publisherkits-socialpromoter' ); ?></h3>
							<p><?php esc_html_e( 'One list, three rings. Core is what you are about; Related is what you brush against; Peripheral catches everything else.', 'publisherkits-socialpromoter' ); ?></p>
							<p><?php esc_html_e( 'Renaming a label is free. Accepting, merging, archiving or editing keywords changes classification, so content is re-checked on the next read.', 'publisherkits-socialpromoter' ); ?></p>
							<p><?php esc_html_e( 'Merging never loses data - the old topic becomes a signpost to its replacement.', 'publisherkits-socialpromoter' ); ?></p>
						</div>
					</div>
				</div>
			</div>
			<?php
		}

		/**
		 * Handle all Topics page operations.
		 *
		 * @return void
		 */
		public static function handle_topic_action() {
			if ( ! current_user_can( 'manage_options' ) ) {
				wp_die( esc_html__( 'You do not have permission to do that.', 'publisherkits-socialpromoter' ) );
			}
			check_admin_referer( 'pkcore_topic_action' );

			$op     = isset( $_POST['op'] ) ? sanitize_key( wp_unslash( $_POST['op'] ) ) : '';
			$topic  = isset( $_POST['topic'] ) ? sanitize_key( wp_unslash( $_POST['topic'] ) ) : '';
			$return = isset( $_POST['return'] ) ? sanitize_key( wp_unslash( $_POST['return'] ) ) : self::TOPICS_SLUG;
			$return = in_array( $return, array( self::MENU_SLUG, self::TOPICS_SLUG, self::PROFILE_SLUG ), true ) ? $return : self::TOPICS_SLUG;
			$ok     = false;

			switch ( $op ) {
				case 'seed':
					PKCORE_Pack::seed_from_taxonomy( false );
					self::redirect_back( $return, 'seeded' );
					return;
				case 'accept':
					$ok = PKCORE_Pack::accept_topic( $topic );
					break;
				case 'archive':
					$ok = PKCORE_Pack::archive_topic( $topic );
					break;
				case 'add':
					$label    = isset( $_POST['label'] ) ? sanitize_text_field( wp_unslash( $_POST['label'] ) ) : '';
					$keywords = isset( $_POST['keywords'] ) ? sanitize_text_field( wp_unslash( $_POST['keywords'] ) ) : '';
					$ring     = isset( $_POST['ring'] ) ? sanitize_key( wp_unslash( $_POST['ring'] ) ) : 'core';
					$ok       = false !== PKCORE_Pack::add_topic( $label, $ring, $keywords, 'user', 'active' );
					break;
				case 'update':
					$merge_into = isset( $_POST['merge_into'] ) ? sanitize_key( wp_unslash( $_POST['merge_into'] ) ) : '';
					if ( '' !== $merge_into ) {
						$ok = PKCORE_Pack::merge_topics( $topic, $merge_into );
						break;
					}
					$label    = isset( $_POST['label'] ) ? sanitize_text_field( wp_unslash( $_POST['label'] ) ) : '';
					$keywords = isset( $_POST['keywords'] ) ? sanitize_text_field( wp_unslash( $_POST['keywords'] ) ) : '';
					$ring     = isset( $_POST['ring'] ) ? sanitize_key( wp_unslash( $_POST['ring'] ) ) : '';
					$ok       = PKCORE_Pack::rename_topic( $topic, $label );
					$ok       = PKCORE_Pack::set_topic_keywords( $topic, $keywords ) && $ok;
					if ( in_array( $ring, array( 'core', 'related' ), true ) ) {
						$current = PKCORE_Pack::topic( $topic );
						if ( null !== $current && $current['ring'] !== $ring ) {
							$ok = PKCORE_Pack::move_ring( $topic, $ring ) && $ok;
						}
					}
					break;
				case 'content_type':
					$type = isset( $_POST['content_type'] ) ? sanitize_text_field( wp_unslash( $_POST['content_type'] ) ) : '';
					$ok   = PKCORE_Pack::add_content_type( $type );
					break;
				case 'discard_suggested':
					$ok = PKCORE_Pack::discard_suggested( '' !== $topic ? $topic : null ) > 0;
					break;
				case 'import_starter':
					$starter = isset( $_POST['starter'] ) ? sanitize_key( wp_unslash( $_POST['starter'] ) ) : '';
					$stats   = PKCORE_Pack::import_starter( $starter );
					if ( false !== $stats ) {
						self::redirect_back( $return, 'imported' );
						return;
					}
					break;
			}

			self::redirect_back( $return, $ok ? 'saved' : 'error' );
		}
	}
}
