<?php

/**
 * Custom post type for generated WordPress plugins (Plugin Builder library).
 *
 * @package LolaCore\PluginBuilder\PostType
 * @license GPL-2.0-or-later
 */

declare(strict_types=1);

namespace LolaCore\PluginBuilder\PostType;

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

/**
 * Registers lolacore_plugin CPT, post meta, and revision tracking.
 */
class PluginPostType {

	public const POST_TYPE = 'lolacore_plugin';

	public const META_SLUG           = '_lc_pb_slug';
	public const META_PRD            = '_lc_pb_prd';
	public const META_DESCRIPTION    = '_lc_pb_description';
	public const META_PHP            = '_lc_pb_php';
	public const META_JS             = '_lc_pb_js';
	public const META_CSS            = '_lc_pb_css';
	public const META_DEPENDENCIES   = '_lc_pb_dependencies';
	public const META_CONFIDENCE     = '_lc_pb_confidence';
	public const META_CONFIDENCE_NOTE = '_lc_pb_confidence_note';
	public const META_SUGGESTIONS    = '_lc_pb_suggestions';
	public const META_VERSION        = '_lc_pb_version';
	public const META_LOAD_SAFE_STATUS = '_lc_pb_load_safe_status';
	public const META_SAFETY_FINDINGS  = '_lc_pb_safety_findings';

	/** Playground gate — see PlaygroundGate. */
	public const META_PG_GATE_STATUS       = '_lc_pb_pg_gate_status';
	public const META_PG_ATTESTED_VERSION  = '_lc_pb_pg_attested_version';
	public const META_PG_ATTESTED_AT       = '_lc_pb_pg_attested_at';
	public const META_PG_OPENED_AT         = '_lc_pb_pg_opened_at';

	/**
	 * Meta keys stored on each generated-plugin post.
	 *
	 * @return array<string>
	 */
	public static function metaKeys(): array {
		return array(
			self::META_SLUG,
			self::META_PRD,
			self::META_DESCRIPTION,
			self::META_PHP,
			self::META_JS,
			self::META_CSS,
			self::META_DEPENDENCIES,
			self::META_CONFIDENCE,
			self::META_CONFIDENCE_NOTE,
			self::META_SUGGESTIONS,
			self::META_VERSION,
			self::META_LOAD_SAFE_STATUS,
			self::META_SAFETY_FINDINGS,
			self::META_PG_GATE_STATUS,
			self::META_PG_ATTESTED_VERSION,
			self::META_PG_ATTESTED_AT,
			self::META_PG_OPENED_AT,
		);
	}

	/**
	 * Meta keys included in post revisions (code + context fields).
	 *
	 * @return array<string>
	 */
	public static function revisionMetaKeys(): array {
		return array(
			self::META_PHP,
			self::META_JS,
			self::META_CSS,
			self::META_DESCRIPTION,
			self::META_PRD,
			self::META_DEPENDENCIES,
		);
	}

	/**
	 * Hook CPT registration, meta registration, and revision filters.
	 */
	public function register(): void {
		add_action('init', array($this, 'registerPostType'));
		add_action('init', array($this, 'registerMeta'), 11);
		add_action('admin_menu', array($this, 'addAdminSubmenu'), 20);
		add_filter('parent_file', array($this, 'highlightParentMenu'));
		add_filter('submenu_file', array($this, 'highlightSubmenu'));
		add_filter('wp_post_revision_meta_keys', array($this, 'addRevisionMetaKeys'), 10, 2);
	}

	/**
	 * Add "My Built Plugins" under Lola after Settings and MCP Settings.
	 */
	public function addAdminSubmenu(): void {
		add_submenu_page(
			'lolacore',
			__('My Built Plugins', 'lolacore'),
			__('My Built Plugins', 'lolacore'),
			'manage_options',
			'edit.php?post_type=' . self::POST_TYPE
		);
	}

	/**
	 * Keep the Lola top-level menu active on Plugin Builder screens.
	 *
	 * @param string $parent_file Parent menu file slug.
	 * @return string
	 */
	public function highlightParentMenu( string $parent_file ): string {
		if ($this->isPluginBuilderAdminScreen()) {
			return 'lolacore';
		}

		return $parent_file;
	}

	/**
	 * Highlight the My Built Plugins submenu on list and edit screens.
	 *
	 * @param string|null $submenu_file Submenu file slug.
	 * @return string|null
	 */
	public function highlightSubmenu( $submenu_file ) {
		if ($this->isPluginBuilderAdminScreen()) {
			return 'edit.php?post_type=' . self::POST_TYPE;
		}

		return $submenu_file;
	}

	/**
	 * Whether the current admin screen belongs to the Plugin Builder CPT.
	 */
	private function isPluginBuilderAdminScreen(): bool {
		if (! function_exists('get_current_screen')) {
			return false;
		}

		$screen = get_current_screen();
		if (! $screen instanceof \WP_Screen) {
			return false;
		}

		return $screen->post_type === self::POST_TYPE;
	}

	/**
	 * Register the lolacore_plugin custom post type.
	 */
	public function registerPostType(): void {
		register_post_type(
			self::POST_TYPE,
			array(
				'labels'              => array(
					'name'               => __('My Built Plugins', 'lolacore'),
					'singular_name'      => __('Built Plugin', 'lolacore'),
					'menu_name'          => __('My Built Plugins', 'lolacore'),
					'add_new'            => __('Add New', 'lolacore'),
					'add_new_item'       => __('Add New Plugin', 'lolacore'),
					'edit_item'          => __('Edit Plugin', 'lolacore'),
					'new_item'           => __('New Plugin', 'lolacore'),
					'view_item'          => __('View Plugin', 'lolacore'),
					'search_items'       => __('Search Plugins', 'lolacore'),
					'not_found'          => __('No built plugins found.', 'lolacore'),
					'not_found_in_trash' => __('No built plugins found in Trash.', 'lolacore'),
				),
				'public'              => false,
				'show_ui'             => true,
				'show_in_menu'        => false,
				'show_in_rest'        => false,
				'exclude_from_search' => true,
				'publicly_queryable'  => false,
				'has_archive'         => false,
				'hierarchical'        => false,
				'supports'            => array('title', 'revisions'),
				'capability_type'     => 'post',
				'map_meta_cap'        => true,
				// Primitive caps only — meta caps (edit_post, read_post, delete_post) are mapped by core.
				'capabilities'        => array(
					'edit_posts'             => 'manage_options',
					'edit_others_posts'      => 'manage_options',
					'delete_posts'           => 'manage_options',
					'delete_others_posts'    => 'manage_options',
					'delete_private_posts'   => 'manage_options',
					'delete_published_posts' => 'manage_options',
					'publish_posts'          => 'manage_options',
					'read_private_posts'     => 'manage_options',
					'create_posts'           => 'manage_options',
				),
			)
		);
	}

	/**
	 * Register post meta for generated plugin payloads.
	 */
	public function registerMeta(): void {
		$auth_callback = static function (): bool {
			return current_user_can('manage_options');
		};

		register_post_meta(
			self::POST_TYPE,
			self::META_SLUG,
			array(
				'type'              => 'string',
				'single'            => true,
				'show_in_rest'      => false,
				'auth_callback'     => $auth_callback,
				'sanitize_callback' => 'sanitize_title',
			)
		);

		register_post_meta(
			self::POST_TYPE,
			self::META_PRD,
			array(
				'type'              => 'string',
				'single'            => true,
				'show_in_rest'      => false,
				'auth_callback'     => $auth_callback,
				'sanitize_callback' => 'sanitize_textarea_field',
			)
		);

		register_post_meta(
			self::POST_TYPE,
			self::META_DESCRIPTION,
			array(
				'type'              => 'string',
				'single'            => true,
				'show_in_rest'      => false,
				'auth_callback'     => $auth_callback,
				'sanitize_callback' => 'sanitize_textarea_field',
			)
		);

		foreach (array(self::META_PHP, self::META_JS, self::META_CSS) as $code_key) {
			register_post_meta(
				self::POST_TYPE,
				$code_key,
				array(
					'type'              => 'string',
					'single'            => true,
					'show_in_rest'      => false,
					'auth_callback'     => $auth_callback,
					'sanitize_callback' => array($this, 'sanitizeCodeMeta'),
				)
			);
		}

		register_post_meta(
			self::POST_TYPE,
			self::META_DEPENDENCIES,
			array(
				'type'              => 'array',
				'single'            => true,
				'show_in_rest'      => false,
				'auth_callback'     => $auth_callback,
				'sanitize_callback' => array($this, 'sanitizeDependencies'),
			)
		);

		register_post_meta(
			self::POST_TYPE,
			self::META_CONFIDENCE,
			array(
				'type'              => 'integer',
				'single'            => true,
				'show_in_rest'      => false,
				'auth_callback'     => $auth_callback,
				'sanitize_callback' => array($this, 'sanitizeConfidence'),
			)
		);

		register_post_meta(
			self::POST_TYPE,
			self::META_CONFIDENCE_NOTE,
			array(
				'type'              => 'string',
				'single'            => true,
				'show_in_rest'      => false,
				'auth_callback'     => $auth_callback,
				'sanitize_callback' => 'sanitize_textarea_field',
			)
		);

		register_post_meta(
			self::POST_TYPE,
			self::META_SUGGESTIONS,
			array(
				'type'              => 'array',
				'single'            => true,
				'show_in_rest'      => false,
				'auth_callback'     => $auth_callback,
				'sanitize_callback' => array($this, 'sanitizeSuggestions'),
			)
		);

		register_post_meta(
			self::POST_TYPE,
			self::META_VERSION,
			array(
				'type'              => 'integer',
				'single'            => true,
				'show_in_rest'      => false,
				'auth_callback'     => $auth_callback,
				'sanitize_callback' => 'absint',
				'default'           => 1,
			)
		);

		register_post_meta(
			self::POST_TYPE,
			self::META_LOAD_SAFE_STATUS,
			array(
				'type'              => 'string',
				'single'            => true,
				'show_in_rest'      => false,
				'auth_callback'     => $auth_callback,
				'sanitize_callback' => 'sanitize_key',
			)
		);

		register_post_meta(
			self::POST_TYPE,
			self::META_SAFETY_FINDINGS,
			array(
				'type'              => 'string',
				'single'            => true,
				'show_in_rest'      => false,
				'auth_callback'     => $auth_callback,
				'sanitize_callback' => array($this, 'sanitizeSafetyFindingsJson'),
			)
		);

		register_post_meta(
			self::POST_TYPE,
			self::META_PG_GATE_STATUS,
			array(
				'type'              => 'string',
				'single'            => true,
				'show_in_rest'      => false,
				'auth_callback'     => $auth_callback,
				'sanitize_callback' => 'sanitize_key',
			)
		);

		register_post_meta(
			self::POST_TYPE,
			self::META_PG_ATTESTED_VERSION,
			array(
				'type'              => 'integer',
				'single'            => true,
				'show_in_rest'      => false,
				'auth_callback'     => $auth_callback,
				'sanitize_callback' => 'absint',
			)
		);

		foreach (array(self::META_PG_ATTESTED_AT, self::META_PG_OPENED_AT) as $datetime_key) {
			register_post_meta(
				self::POST_TYPE,
				$datetime_key,
				array(
					'type'              => 'string',
					'single'            => true,
					'show_in_rest'      => false,
					'auth_callback'     => $auth_callback,
					'sanitize_callback' => 'sanitize_text_field',
				)
			);
		}
	}

	/**
	 * Include Plugin Builder meta in post revisions.
	 *
	 * @param array<string> $keys      Registered revision meta keys.
	 * @param string        $post_type Post type slug.
	 * @return array<string>
	 */
	public function addRevisionMetaKeys( array $keys, string $post_type ): array {
		if ($post_type !== self::POST_TYPE) {
			return $keys;
		}

		return array_values(array_unique(array_merge($keys, self::revisionMetaKeys())));
	}

	/**
	 * Preserve generated source code on save (admin-only meta).
	 *
	 * @param mixed $value Raw meta value.
	 * @return string
	 */
	public function sanitizeCodeMeta( $value ): string {
		if (! is_scalar($value)) {
			return '';
		}

		return (string) $value;
	}

	/**
	 * Normalize dependency slugs to kebab-case strings.
	 *
	 * @param mixed $value Raw meta value.
	 * @return array<int, string>
	 */
	public function sanitizeDependencies( $value ): array {
		if (! is_array($value)) {
			return array();
		}

		$slugs = array();
		foreach ($value as $slug) {
			if (! is_scalar($slug)) {
				continue;
			}
			$clean = sanitize_title((string) $slug);
			if ($clean !== '') {
				$slugs[] = $clean;
			}
		}

		return array_values(array_unique($slugs));
	}

	/**
	 * Clamp confidence score to 0-100.
	 *
	 * @param mixed $value Raw meta value.
	 * @return int
	 */
	public function sanitizeConfidence( $value ): int {
		$score = (int) $value;
		if ($score < 0) {
			$score = 0;
		}

		return min(100, $score);
	}

	/**
	 * Keep at most five suggestion strings.
	 *
	 * @param mixed $value Raw meta value.
	 * @return array<int, string>
	 */
	public function sanitizeSuggestions( $value ): array {
		if (! is_array($value)) {
			return array();
		}

		$suggestions = array();
		foreach ($value as $item) {
			if (! is_scalar($item)) {
				continue;
			}
			$text = sanitize_text_field((string) $item);
			if ($text !== '') {
				$suggestions[] = $text;
			}
			if (count($suggestions) >= 5) {
				break;
			}
		}

		return $suggestions;
	}

	/**
	 * Store Capa A/B safety findings as JSON for library diagnostics.
	 *
	 * @param mixed $value Raw meta value.
	 */
	public function sanitizeSafetyFindingsJson( $value ): string {
		if (is_string($value)) {
			$decoded = json_decode($value, true);
			if (is_array($decoded)) {
				$value = $decoded;
			}
		}

		if (! is_array($value)) {
			return '[]';
		}

		$findings = array();
		foreach ($value as $finding) {
			if (! is_array($finding)) {
				continue;
			}

			$code = sanitize_key((string) ( $finding['code'] ?? '' ));
			if ($code === '') {
				continue;
			}

			$severity = sanitize_key((string) ( $finding['severity'] ?? 'warning' ));
			if (! in_array($severity, array('fatal', 'error', 'warning'), true)) {
				$severity = 'warning';
			}

			$entry = array(
				'severity' => $severity,
				'code'     => $code,
				'message'  => sanitize_text_field((string) ( $finding['message'] ?? '' )),
			);

			if (isset($finding['line'])) {
				$entry['line'] = max(0, (int) $finding['line']);
			}

			$findings[] = $entry;
			if (count($findings) >= 20) {
				break;
			}
		}

		return wp_json_encode($findings) ?: '[]';
	}
}
