<?php
/**
 * Static route → capability registry for scoped API keys (Program F).
 *
 * Maps `seovaultai/v1` route patterns to required capability strings.
 * Unknown routes require `connector:write` (safest default).
 *
 * @package SEOvaultSEO\Api
 */

namespace SEOvaultSEO\Api;

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

class RouteCapabilities {

	/** Fallback capability for unmapped routes. */
	const CAP_CONNECTOR_WRITE = 'connector:write';

	/**
	 * Capability strings used by the registry.
	 */
	const CAP_SITE_VERIFY         = 'site:verify';
	const CAP_POSTS_READ          = 'posts:read';
	const CAP_POSTS_WRITE         = 'posts:write';
	const CAP_PAGES_READ          = 'pages:read';
	const CAP_PAGES_WRITE         = 'pages:write';
	const CAP_MEDIA_READ          = 'media:read';
	const CAP_MEDIA_WRITE         = 'media:write';
	const CAP_CATEGORIES_READ     = 'categories:read';
	const CAP_AI_VISIBILITY_READ  = 'ai_visibility:read';
	const CAP_AI_VISIBILITY_WRITE = 'ai_visibility:write';
	const CAP_SEO_READ            = 'seo:read';
	const CAP_SEO_IMPORT_WRITE    = 'seo_import:write';
	const CAP_INDEXNOW_READ       = 'indexnow:read';
	const CAP_INDEXNOW_WRITE      = 'indexnow:write';
	const CAP_CRAWLER_LOGS_READ   = 'crawler_logs:read';
	const CAP_CRAWLER_LOGS_WRITE  = 'crawler_logs:write';
	const CAP_INTERNAL_LINKS_READ = 'internal_links:read';
	const CAP_SITEMAP_READ        = 'sitemap:read';

	/**
	 * Ordered route patterns (first match wins). Keys are regexes matched
	 * against the path after `/seovaultai/v1` (leading slash, no query).
	 *
	 * Each value is `array( 'GET' => cap, 'POST' => cap, ... )` or a string
	 * capability applied to all methods.
	 *
	 * @return array<string, string|array<string, string>>
	 */
	public static function get_route_map() {
		return array(
			'#^/verify$#'                          => self::CAP_SITE_VERIFY,
			'#^/posts$#'                           => array(
				'GET'  => self::CAP_POSTS_READ,
				'POST' => self::CAP_POSTS_WRITE,
			),
			'#^/posts/\d+/restore-revision$#'      => self::CAP_POSTS_WRITE,
			'#^/posts/\d+/inject-image-alt$#'      => self::CAP_POSTS_WRITE,
			'#^/posts/\d+/inject-aeo$#'            => self::CAP_POSTS_WRITE,
			'#^/posts/\d+/featured-image$#'        => array(
				'GET'    => self::CAP_POSTS_READ,
				'POST'   => self::CAP_POSTS_WRITE,
				'DELETE' => self::CAP_POSTS_WRITE,
			),
			'#^/posts/\d+/schema-instances/preview$#' => self::CAP_POSTS_WRITE,
			'#^/posts/\d+/schema-instances/[^/]+/delete$#' => self::CAP_POSTS_WRITE,
			'#^/posts/\d+/schema-instances/[^/]+$#' => self::CAP_POSTS_WRITE,
			'#^/posts/\d+/schema-instances$#'      => array(
				'GET'  => self::CAP_POSTS_READ,
				'POST' => self::CAP_POSTS_WRITE,
			),
			'#^/posts/\d+/schema-instance-contract(/[^/]+)?$#' => self::CAP_POSTS_READ,
			'#^/posts/\d+/legacy-faq/preview$#'             => self::CAP_POSTS_READ,
			'#^/posts/\d+/legacy-faq/(convert|rollback)$#'  => self::CAP_POSTS_WRITE,
			'#^/posts/\d+/third-party-blocks/preview$#'            => self::CAP_POSTS_READ,
			'#^/posts/\d+/third-party-blocks/(convert|rollback)$#' => self::CAP_POSTS_WRITE,
			'#^/posts/\d+$#'                       => array(
				'GET'  => self::CAP_POSTS_READ,
				'POST' => self::CAP_POSTS_WRITE,
			),
			'#^/pages$#'                           => self::CAP_PAGES_READ,
			'#^/pages/\d+/restore-revision$#'      => self::CAP_PAGES_WRITE,
			'#^/pages/\d+$#'                       => array(
				'GET'  => self::CAP_PAGES_READ,
				'POST' => self::CAP_PAGES_WRITE,
			),
			'#^/categories$#'                      => self::CAP_CATEGORIES_READ,
			'#^/media/upload$#'                    => self::CAP_MEDIA_WRITE,
			'#^/media/\d+/alt$#'                   => self::CAP_MEDIA_WRITE,
			'#^/media/\d+$#'                       => self::CAP_MEDIA_WRITE,
			'#^/media$#'                           => self::CAP_MEDIA_READ,
			'#^/indexnow/setup$#'                  => self::CAP_INDEXNOW_WRITE,
			'#^/indexnow/status$#'                 => self::CAP_INDEXNOW_READ,
			'#^/indexnow/submit$#'                 => self::CAP_INDEXNOW_WRITE,
			'#^/sitemap-proxy$#'                   => self::CAP_SITEMAP_READ,
			'#^/seo/capabilities$#'                => self::CAP_SEO_READ,
			'#^/seo/import/summary$#'              => self::CAP_SEO_READ,
			'#^/seo/import$#'                      => self::CAP_SEO_IMPORT_WRITE,
			'#^/seo/third-party-blocks/scan$#'     => self::CAP_SEO_READ,
			'#^/seo/third-party-blocks/convert-batch$#' => self::CAP_SEO_IMPORT_WRITE,
			'#^/internal-links/manifest$#'         => self::CAP_INTERNAL_LINKS_READ,
			'#^/internal-links/sync$#'             => self::CAP_INTERNAL_LINKS_READ,
			'#^/ai-visibility/status$#'            => self::CAP_AI_VISIBILITY_READ,
			'#^/llms/status$#'                     => self::CAP_AI_VISIBILITY_READ,
			'#^/llms/preview$#'                    => self::CAP_AI_VISIBILITY_WRITE,
			'#^/llms/publish$#'                    => self::CAP_AI_VISIBILITY_WRITE,
			'#^/robots/status$#'                   => self::CAP_AI_VISIBILITY_READ,
			'#^/robots/preview$#'                  => self::CAP_AI_VISIBILITY_WRITE,
			'#^/robots/apply$#'                    => self::CAP_AI_VISIBILITY_WRITE,
			'#^/robots/restore$#'                  => self::CAP_AI_VISIBILITY_WRITE,
			'#^/schema/status$#'                   => self::CAP_AI_VISIBILITY_READ,
			'#^/schema/preview$#'                  => self::CAP_AI_VISIBILITY_WRITE,
			'#^/schema/inject$#'                   => self::CAP_AI_VISIBILITY_WRITE,
			'#^/crawler-logs/status$#'             => self::CAP_CRAWLER_LOGS_READ,
			'#^/crawler-logs/enable$#'             => self::CAP_CRAWLER_LOGS_WRITE,
			'#^/crawler-logs/disable$#'            => self::CAP_CRAWLER_LOGS_WRITE,
			'#^/crawler-logs$#'                    => self::CAP_CRAWLER_LOGS_READ,
		);
	}

	/**
	 * Normalize a REST route to `/path` under the connector namespace.
	 *
	 * @param string $route Full route e.g. `/seovaultai/v1/posts/1`.
	 * @return string
	 */
	public static function normalize_route_path( $route ) {
		$path = (string) $route;
		$path = strtok( $path, '?' );
		$path = untrailingslashit( strtolower( $path ) );
		if ( 0 === strpos( $path, '/seovaultai/v1' ) ) {
			$path = substr( $path, strlen( '/seovaultai/v1' ) );
		}
		if ( '' === $path ) {
			$path = '/';
		}
		if ( '/' !== $path[0] ) {
			$path = '/' . $path;
		}
		return $path;
	}

	/**
	 * Resolve required capability for a route + method.
	 *
	 * @param string $route  REST route.
	 * @param string $method HTTP method.
	 * @return string
	 */
	public static function get_required_capability( $route, $method = 'GET' ) {
		$path   = self::normalize_route_path( $route );
		$method = strtoupper( (string) $method );

		foreach ( self::get_route_map() as $pattern => $caps ) {
			if ( ! preg_match( $pattern, $path ) ) {
				continue;
			}
			if ( is_string( $caps ) ) {
				return $caps;
			}
			if ( isset( $caps[ $method ] ) ) {
				return $caps[ $method ];
			}
			// Method not listed on a multi-method route → safest default.
			return self::CAP_CONNECTOR_WRITE;
		}

		return self::CAP_CONNECTOR_WRITE;
	}

	/**
	 * Whether a capability is a read-scoped capability.
	 *
	 * @param string $cap Capability string.
	 * @return bool
	 */
	public static function is_read_capability( $cap ) {
		$cap = (string) $cap;
		if ( self::CAP_SITE_VERIFY === $cap ) {
			return true;
		}
		return (bool) preg_match( '/:read$/', $cap );
	}

	/**
	 * Whether a capability is a write-scoped capability.
	 *
	 * @param string $cap Capability string.
	 * @return bool
	 */
	public static function is_write_capability( $cap ) {
		$cap = (string) $cap;
		if ( self::CAP_CONNECTOR_WRITE === $cap ) {
			return true;
		}
		return (bool) preg_match( '/:write$/', $cap );
	}

	/**
	 * Whether the given key type may use the capability.
	 *
	 * @param string $key_type `legacy`|`read`|`write`.
	 * @param string $cap      Capability.
	 * @return bool
	 */
	public static function key_type_allows( $key_type, $cap ) {
		switch ( $key_type ) {
			case 'legacy':
				return true;
			case 'read':
				return self::is_read_capability( $cap );
			case 'write':
				return self::is_write_capability( $cap );
			default:
				return false;
		}
	}
}
