<?php
/**
 * Admin-side UI: settings page, asset enqueueing, Settings API registration.
 *
 * @package Ppok\AIToolkit
 * @license GPL-2.0-or-later
 */

namespace Ppok\AIToolkit\Admin;

use Ppok\AIToolkit\Abilities\Ability_Registrar;
use Ppok\AIToolkit\Activator;

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

class Admin {

	public const PAGE_SLUG    = 'ppok-ai-toolkit';
	public const OPTION_GROUP = 'ppok_ai_toolkit';
	public const OPTION_KEY   = 'ppok_ai_toolkit_settings';

	protected string $plugin_name;
	protected string $version;

	public function __construct( string $plugin_name, string $version ) {
		$this->plugin_name = $plugin_name;
		$this->version     = $version;
	}

	public function enqueue_styles( string $hook ): void {
		if ( false === strpos( $hook, 'ppok-ai-toolkit' ) && false === strpos( $hook, 'ppok-activity' ) ) {
			return;
		}
		wp_enqueue_style(
			$this->plugin_name,
			PPOK_AI_TOOLKIT_URL . 'admin/css/ppok-ai-toolkit-admin.css',
			array(),
			$this->version
		);
	}

	public function enqueue_scripts( string $hook ): void {
		if ( false === strpos( $hook, 'ppok-ai-toolkit' ) && false === strpos( $hook, 'ppok-activity' ) ) {
			return;
		}
		wp_enqueue_script(
			$this->plugin_name,
			PPOK_AI_TOOLKIT_URL . 'admin/js/ppok-ai-toolkit-admin.js',
			array( 'jquery' ),
			$this->version,
			true
		);
	}

	public function register_settings_page(): void {
		add_options_page(
			__( 'Ppok AI Toolkit', 'ppok-ai-toolkit' ),
			__( 'AI Toolkit', 'ppok-ai-toolkit' ),
			'manage_options',
			self::PAGE_SLUG,
			array( $this, 'render_settings_page' )
		);
	}

	public function register_settings(): void {
		register_setting(
			self::OPTION_GROUP,
			self::OPTION_KEY,
			array(
				'type'              => 'array',
				'sanitize_callback' => array( $this, 'sanitize_settings' ),
				'default'           => Activator::default_settings(),
			)
		);

		add_settings_section(
			'ppok_ai_toolkit_abilities',
			__( 'Write abilities', 'ppok-ai-toolkit' ),
			array( $this, 'render_abilities_section_intro' ),
			self::PAGE_SLUG
		);

		add_settings_field(
			'abilities_enabled',
			__( 'Enabled write abilities', 'ppok-ai-toolkit' ),
			array( $this, 'render_abilities_enabled_field' ),
			self::PAGE_SLUG,
			'ppok_ai_toolkit_abilities'
		);

		add_settings_field(
			'log_retention_days',
			__( 'Audit log retention (days)', 'ppok-ai-toolkit' ),
			array( $this, 'render_log_retention_days_field' ),
			self::PAGE_SLUG,
			'ppok_ai_toolkit_abilities'
		);

		add_settings_section(
			'ppok_ai_toolkit_scope',
			__( 'Write scope', 'ppok-ai-toolkit' ),
			array( $this, 'render_scope_section_intro' ),
			self::PAGE_SLUG
		);

		add_settings_field(
			'write_post_types',
			__( 'Allowed post types', 'ppok-ai-toolkit' ),
			array( $this, 'render_write_post_types_field' ),
			self::PAGE_SLUG,
			'ppok_ai_toolkit_scope'
		);

		add_settings_field(
			'write_post_statuses',
			__( 'Allowed post statuses', 'ppok-ai-toolkit' ),
			array( $this, 'render_write_post_statuses_field' ),
			self::PAGE_SLUG,
			'ppok_ai_toolkit_scope'
		);

		add_settings_field(
			'block_allowlist_mode',
			__( 'Block allowlist mode', 'ppok-ai-toolkit' ),
			array( $this, 'render_block_allowlist_mode_field' ),
			self::PAGE_SLUG,
			'ppok_ai_toolkit_scope'
		);

		add_settings_field(
			'block_curated_allowlist',
			__( 'Curated block allowlist', 'ppok-ai-toolkit' ),
			array( $this, 'render_block_curated_allowlist_field' ),
			self::PAGE_SLUG,
			'ppok_ai_toolkit_scope'
		);
	}

	/**
	 * Sanitize the full settings array.
	 *
	 * @param mixed $input Raw input from $_POST.
	 * @return array<string, mixed>
	 */
	public function sanitize_settings( $input ): array {
		$input    = is_array( $input ) ? $input : array();
		$defaults = Activator::default_settings();

		$write_abilities = array_keys( Ability_Registrar::write_ability_choices() );
		$abilities_in    = (array) ( $input['abilities_enabled'] ?? array() );
		$abilities_clean = array_values(
			array_intersect(
				array_map( 'sanitize_text_field', $abilities_in ),
				$write_abilities
			)
		);

		$post_types       = self::content_post_type_names();
		$post_types_in    = (array) ( $input['write_post_types'] ?? $defaults['write_post_types'] );
		$post_types_clean = array_values( array_intersect( array_map( 'sanitize_key', $post_types_in ), $post_types ) );
		if ( empty( $post_types_clean ) ) {
			$post_types_clean = $defaults['write_post_types'];
		}

		$statuses       = array_keys( get_post_statuses() );
		$statuses_in    = (array) ( $input['write_post_statuses'] ?? $defaults['write_post_statuses'] );
		$statuses_clean = array_values( array_intersect( array_map( 'sanitize_key', $statuses_in ), $statuses ) );
		if ( empty( $statuses_clean ) ) {
			$statuses_clean = $defaults['write_post_statuses'];
		}

		$mode = isset( $input['block_allowlist_mode'] ) ? (string) $input['block_allowlist_mode'] : $defaults['block_allowlist_mode'];
		if ( ! in_array( $mode, array( 'registered', 'curated' ), true ) ) {
			$mode = $defaults['block_allowlist_mode'];
		}

		$curated_raw = $input['block_curated_allowlist'] ?? array();
		if ( is_string( $curated_raw ) ) {
			$curated_raw = preg_split( '/[\r\n,]+/', $curated_raw ) ?: array();
		}
		$curated = array_values(
			array_unique(
				array_filter(
					array_map(
						static function ( $name ) {
							$name = strtolower( trim( sanitize_text_field( (string) $name ) ) );
							return preg_match( '/^[a-z0-9_-]+\/[a-z0-9_-]+$/', $name ) ? $name : '';
						},
						(array) $curated_raw
					)
				)
			)
		);

		$retention = isset( $input['log_retention_days'] ) ? (int) $input['log_retention_days'] : $defaults['log_retention_days'];
		if ( $retention < 1 ) {
			$retention = $defaults['log_retention_days'];
		}
		if ( $retention > 3650 ) {
			$retention = 3650;
		}

		return array(
			'abilities_enabled'       => $abilities_clean,
			'write_post_types'        => $post_types_clean,
			'write_post_statuses'     => $statuses_clean,
			'block_allowlist_mode'    => $mode,
			'block_curated_allowlist' => $curated,
			'log_retention_days'      => $retention,
		);
	}

	public function render_abilities_section_intro(): void {
		echo '<p>' . esc_html__(
			'Read-side abilities are always available to authenticated users — only the per-call WordPress capability check gates them. Write-side abilities are off until you tick them below, and each call passes input-schema, block-allowlist, scope, and audit-log gates per architecture.',
			'ppok-ai-toolkit'
		) . '</p>';
	}

	public function render_scope_section_intro(): void {
		echo '<p>' . esc_html__(
			'These constraints apply to write-side abilities only. Reads are unaffected.',
			'ppok-ai-toolkit'
		) . '</p>';
	}

	public function render_abilities_enabled_field(): void {
		$settings = $this->settings();
		$enabled  = (array) ( $settings['abilities_enabled'] ?? array() );
		$choices  = Ability_Registrar::write_ability_choices();

		if ( empty( $choices ) ) {
			echo '<p class="description">' . esc_html__( 'No write-side abilities are registered.', 'ppok-ai-toolkit' ) . '</p>';
			return;
		}

		echo '<fieldset>';
		foreach ( $choices as $slug => $label ) {
			printf(
				'<label style="display:block;margin-bottom:.5em"><input type="checkbox" name="%1$s[abilities_enabled][]" value="%2$s" %3$s /> <strong>%2$s</strong> &mdash; %4$s</label>',
				esc_attr( self::OPTION_KEY ),
				esc_attr( $slug ),
				checked( in_array( $slug, $enabled, true ), true, false ),
				esc_html( $label )
			);
		}
		echo '</fieldset>';
		echo '<p class="description">' . esc_html__( 'Enabling a write ability flips meta.mcp.public=true at the next request. Disabling re-hides it without uninstalling.', 'ppok-ai-toolkit' ) . '</p>';
	}

	public function render_log_retention_days_field(): void {
		$settings = $this->settings();
		$value    = (int) ( $settings['log_retention_days'] ?? 90 );

		printf(
			'<input type="number" min="1" max="3650" step="1" name="%1$s[log_retention_days]" value="%2$d" class="small-text" />',
			esc_attr( self::OPTION_KEY ),
			$value
		);
		echo '<p class="description">' . esc_html__( 'Daily WP-Cron prune of the ppok_ai_events table beyond this age.', 'ppok-ai-toolkit' ) . '</p>';
	}

	public function render_write_post_types_field(): void {
		$settings = $this->settings();
		$selected = (array) ( $settings['write_post_types'] ?? array( 'post' ) );
		$types    = self::content_post_types();

		echo '<fieldset style="display:grid;grid-template-columns:repeat(auto-fill, minmax(220px, 1fr));gap:.5em 1em">';
		foreach ( $types as $type ) {
			$label = (string) ( $type->labels->singular_name ?? $type->name );
			printf(
				'<label style="display:flex;align-items:center;gap:.5em"><input type="checkbox" name="%1$s[write_post_types][]" value="%2$s" %3$s /><span>%4$s <code style="color:#646970;font-size:11px">%5$s</code></span></label>',
				esc_attr( self::OPTION_KEY ),
				esc_attr( $type->name ),
				checked( in_array( $type->name, $selected, true ), true, false ),
				esc_html( $label ),
				esc_html( $type->name )
			);
		}
		echo '</fieldset>';
		echo '<p class="description">' . esc_html__( 'Filtered to post types reachable via the REST API — the surface ppok/rest-call can actually write to. Custom post types registered with show_in_rest=false won\'t appear here; re-register them in your theme or plugin if AI should write to them. Infrastructure types (reusable blocks, templates, ACF field groups) are excluded by name.', 'ppok-ai-toolkit' ) . '</p>';
	}

	public function render_write_post_statuses_field(): void {
		$settings = $this->settings();
		$selected = (array) ( $settings['write_post_statuses'] ?? array( 'draft' ) );
		$statuses = get_post_statuses();

		echo '<fieldset style="display:grid;grid-template-columns:repeat(auto-fill, minmax(180px, 1fr));gap:.5em 1em">';
		foreach ( $statuses as $slug => $label ) {
			printf(
				'<label style="display:flex;align-items:center;gap:.5em"><input type="checkbox" name="%1$s[write_post_statuses][]" value="%2$s" %3$s /><span>%4$s <code style="color:#646970;font-size:11px">%5$s</code></span></label>',
				esc_attr( self::OPTION_KEY ),
				esc_attr( $slug ),
				checked( in_array( $slug, $selected, true ), true, false ),
				esc_html( $label ),
				esc_html( $slug )
			);
		}
		echo '</fieldset>';
		echo '<p class="description">' . esc_html__( 'Add "publish" only after a careful review — published-post writes also require the edit_published_posts capability.', 'ppok-ai-toolkit' ) . '</p>';
	}

	public function render_block_allowlist_mode_field(): void {
		$settings = $this->settings();
		$mode     = (string) ( $settings['block_allowlist_mode'] ?? 'registered' );

		$options = array(
			'registered' => __( 'All registered blocks (recommended)', 'ppok-ai-toolkit' ),
			'curated'    => __( 'Curated allowlist below', 'ppok-ai-toolkit' ),
		);

		printf( '<select name="%s[block_allowlist_mode]">', esc_attr( self::OPTION_KEY ) );
		foreach ( $options as $value => $label ) {
			printf(
				'<option value="%s" %s>%s</option>',
				esc_attr( $value ),
				selected( $mode, $value, false ),
				esc_html( $label )
			);
		}
		echo '</select>';
	}

	public function render_block_curated_allowlist_field(): void {
		$settings = $this->settings();
		$value    = (array) ( $settings['block_curated_allowlist'] ?? array() );
		$text     = implode( "\n", $value );

		printf(
			'<textarea name="%s[block_curated_allowlist]" rows="6" cols="50" class="large-text code">%s</textarea>',
			esc_attr( self::OPTION_KEY ),
			esc_textarea( $text )
		);
		echo '<p class="description">' . esc_html__( 'One block name per line (e.g. core/paragraph, acf/hero). Only consulted when mode is "curated".', 'ppok-ai-toolkit' ) . '</p>';
	}

	public function render_settings_page(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		require PPOK_AI_TOOLKIT_DIR . 'admin/partials/settings-page.php';
	}

	/**
	 * Tabs rendered at the top of the settings page. Tab => label.
	 *
	 * @return array<string, string>
	 */
	public function tabs(): array {
		return array(
			'settings' => __( 'Settings', 'ppok-ai-toolkit' ),
			'activity' => __( 'Activity', 'ppok-ai-toolkit' ),
		);
	}

	public function current_tab(): string {
		$req = isset( $_GET['tab'] ) ? sanitize_key( (string) wp_unslash( $_GET['tab'] ) ) : 'settings';
		return array_key_exists( $req, $this->tabs() ) ? $req : 'settings';
	}

	public function tab_url( string $tab ): string {
		return add_query_arg(
			array(
				'page' => self::PAGE_SLUG,
				'tab'  => $tab,
			),
			admin_url( 'options-general.php' )
		);
	}

	/**
	 * Add a Settings quick link to the row in plugins.php.
	 *
	 * @param array<string, string> $links Existing action links.
	 * @return array<string, string>
	 */
	public function plugin_action_links( array $links ): array {
		$settings_link = sprintf(
			'<a href="%s">%s</a>',
			esc_url( $this->tab_url( 'settings' ) ),
			esc_html__( 'Settings', 'ppok-ai-toolkit' )
		);
		array_unshift( $links, $settings_link );
		return $links;
	}

	private function settings(): array {
		$settings = get_option( self::OPTION_KEY, array() );
		return is_array( $settings ) ? $settings : array();
	}

	/**
	 * Post types AI should actually be able to write to: REST-reachable
	 * content (post, page, and user-defined CPTs), minus infrastructure
	 * (wp_block, wp_template, wp_navigation, acf-* and similar) and media
	 * (governed separately by media-upload abilities).
	 *
	 * Rationale: show_in_rest=true is the closest WP signal to "rest-call
	 * could meaningfully target this," but it still leaks Gutenberg /
	 * site-editor infrastructure types, so we layer an explicit blocklist
	 * on top. Built-ins (post, page) sort first; user CPTs follow
	 * alphabetically so they're easy to scan.
	 *
	 * @return array<string, \WP_Post_Type>
	 */
	public static function content_post_types(): array {
		$infrastructure = array(
			'attachment',
			'nav_menu_item',
			'revision',
			'oembed_cache',
			'wp_block',
			'wp_template',
			'wp_template_part',
			'wp_navigation',
			'wp_global_styles',
			'wp_font_family',
			'wp_font_face',
		);
		$infrastructure_prefixes = array( 'acf-' );

		$types = get_post_types( array( 'show_in_rest' => true ), 'objects' );

		foreach ( array_keys( $types ) as $slug ) {
			if ( in_array( $slug, $infrastructure, true ) ) {
				unset( $types[ $slug ] );
				continue;
			}
			foreach ( $infrastructure_prefixes as $prefix ) {
				if ( str_starts_with( $slug, $prefix ) ) {
					unset( $types[ $slug ] );
					continue 2;
				}
			}
		}

		uasort(
			$types,
			static function ( $a, $b ) {
				if ( ( $a->_builtin ?? false ) !== ( $b->_builtin ?? false ) ) {
					return ! empty( $a->_builtin ) ? -1 : 1;
				}
				return strcasecmp(
					(string) ( $a->labels->singular_name ?? $a->name ),
					(string) ( $b->labels->singular_name ?? $b->name )
				);
			}
		);

		return $types;
	}

	/**
	 * Slugs only, in the same sort order as content_post_types().
	 *
	 * @return array<int, string>
	 */
	public static function content_post_type_names(): array {
		return array_keys( self::content_post_types() );
	}
}
