<?php
namespace WpRigel\Commandify;

defined( 'ABSPATH' ) || exit;

class Registry {

	private static $instance = null;
	private $commands        = array();

	public static function get_instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	private function __construct() {
		// Load commands on init at priority 10 (after all command files have registered their hooks on plugins_loaded).
		add_action( 'init', array( $this, 'load_commands' ), 9999 );
	}

	public function load_commands() {
		// Don't run if already loaded.
		if ( ! empty( $this->commands ) ) {
			return;
		}

		// Fire the action for all command sources to register.
		do_action( 'commandify_register_commands' );
	}

	public function register_command( $command ) {
		$validation = $this->validate_command( $command );

		if ( is_wp_error( $validation ) ) {
			return $validation;
		}

		// Set defaults.
		$defaults = array(
			'description' => '',
			'category'    => 'general',
			'icon'        => 'dashicons-admin-generic',
			'keywords'    => array(),
			'capability'  => 'read',
			'priority'    => 10,
			'source'      => $this->detect_source(),
			'disabled'    => false,
			'context'     => array(),
		);

		$command = wp_parse_args( $command, $defaults );

		// Store command.
		$this->commands[ $command['id'] ] = $command;

		return true;
	}

	public function get_commands() {
		// Ensure commands are loaded if not already (for REST API context).
		if ( empty( $this->commands ) ) {
			$this->load_commands();
		}

		$user_commands = array();

		foreach ( $this->commands as $command ) {
			// Skip pattern type commands - they're used internally for dynamic pattern detection.
			// Patterns are passed separately to JavaScript via commandifyPro.patterns
			if ( 'pattern' === $command['type'] ) {
				continue;
			}

			// Setting commands are included but hidden by Pro smart default view feature
			// They only appear when user types ">" namespace.

			// Check capability - skip in non-web contexts (CLI/REST without user)
			if ( is_user_logged_in() || is_admin() ) {
				$capability = $command['capability'];
				if ( is_array( $capability ) ) {
					$has_cap = false;
					foreach ( $capability as $cap ) {
						if ( current_user_can( $cap ) ) {
							$has_cap = true;
							break;
						}
					}
					if ( ! $has_cap ) {
						continue;
					}
				} elseif ( ! current_user_can( $capability ) ) {
						continue;
				}
			}

			// Don't send callback to frontend for action commands.
			if ( 'action' === $command['type'] ) {
				$command['has_callback'] = isset( $command['callback'] ) && is_callable( $command['callback'] );
				unset( $command['callback'] );
			}

			$user_commands[] = $command;
		}//end foreach

		return $user_commands;
	}

	public function get_command( $command_id ) {
		return $this->commands[ $command_id ] ?? null;
	}

	/**
	 * Get all registered commands including pattern types
	 * Used internally by features like DynamicPatterns
	 * Does not filter by capability or type
	 *
	 * @return array
	 */
	public function get_all_commands_internal() {
		// Ensure commands are loaded.
		if ( empty( $this->commands ) ) {
			$this->load_commands();
		}

		return $this->commands;
	}

	private function validate_command( $command ) {
		// Required fields.
		$required = array( 'id', 'title', 'type' );

		foreach ( $required as $field ) {
			if ( empty( $command[ $field ] ) ) {
				return new \WP_Error( 'missing_field', sprintf( 'Command missing required field: %s', $field ) );
			}
		}

		// Validate type.
		$valid_types = array( 'navigation', 'action', 'search', 'toggle', 'pattern', 'setting' );
		if ( ! in_array( $command['type'], $valid_types, true ) ) {
			return new \WP_Error( 'invalid_type', 'Command type must be navigation, action, search, toggle, pattern, or setting' );
		}

		// Validate ID format.
		if ( ! preg_match( '/^[a-z0-9_-]+$/', $command['id'] ) ) {
			return new \WP_Error( 'invalid_id', 'Command ID must be lowercase alphanumeric with hyphens and underscores only.' );
		}

		// Check for duplicate ID.
		if ( isset( $this->commands[ $command['id'] ] ) ) {
			return new \WP_Error( 'duplicate_id', sprintf( 'Command ID already exists: %s', $command['id'] ) );
		}

		return true;
	}

	/**
	 * Attempt to detect the calling source, avoiding debug_backtrace.
	 *
	 * @return string The plugin or theme slug, or 'commandify'.
	 */
	private function detect_source() {
		// Try to detect core plugin/theme via constants.
		if ( defined( 'WP_PLUGIN_DIR' ) ) {
			// Check if the file exists in the plugins directory.
			$current_file = isset( $_SERVER['SCRIPT_FILENAME'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SCRIPT_FILENAME'] ) ) : '';

			if ( $current_file && false !== strpos( $current_file, WP_PLUGIN_DIR ) ) {
				$plugin_file = str_replace( WP_PLUGIN_DIR . '/', '', $current_file );
				$plugin_slug = explode( '/', $plugin_file )[0];
				return $plugin_slug;
			}
		}

		// Try to detect theme context.
		if ( function_exists( 'get_theme_root' ) && function_exists( 'get_stylesheet' ) ) {
			$current_file = isset( $_SERVER['SCRIPT_FILENAME'] ) ? sanitize_text_field( wp_unslash( $_SERVER['SCRIPT_FILENAME'] ) ) : '';
			if ( $current_file && strpos( $current_file, get_theme_root() ) !== false ) {
				return 'theme-' . get_stylesheet();
			}
		}

		// Default fallback.
		return 'commandify';
	}
}
