<?php
/**
 * Abstract module base class.
 *
 * @package Kramar
 * @since   1.0.0
 */

declare( strict_types=1 );

namespace Kramar;

// Prevent direct access.
defined( 'ABSPATH' ) || exit;

/**
 * Module base class.
 *
 * @since 1.0.0
 */
abstract class Module {

	/**
	 * Plugin instance.
	 *
	 * @since 1.0.0
	 */
	protected Plugin $plugin;

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 *
	 * @param Plugin $plugin Main plugin instance.
	 */
	public function __construct( Plugin $plugin ) {
		$this->plugin = $plugin;
	}

	/**
	 * Get the module slug.
	 *
	 * @since 1.0.0
	 *
	 * @return string Module slug.
	 */
	abstract public function get_slug(): string;

	/**
	 * Get the module category.
	 *
	 * @since 1.0.0
	 *
	 * @return string Module category.
	 */
	abstract public function get_category(): string;

	/**
	 * Get the human-readable module name.
	 *
	 * @since 1.0.0
	 *
	 * @return string Module name.
	 */
	abstract public function get_name(): string;

	/**
	 * Get the module description.
	 *
	 * @since 1.0.0
	 *
	 * @return string Module description.
	 */
	abstract public function get_description(): string;

	/**
	 * Run the module's logic.
	 *
	 * @since 1.0.0
	 */
	abstract protected function run(): void;

	/**
	 * Get default settings for this module.
	 *
	 * @since 1.0.0
	 *
	 * @return array<string, mixed> Default settings.
	 */
	public function get_default_settings(): array {
		return [
			'enabled' => false,
		];
	}

	/**
	 * Sanitize module settings.
	 *
	 * @since 1.0.0
	 *
	 * @param array<string, mixed> $input Raw input.
	 * @return array<string, mixed> Sanitized settings.
	 */
	public function sanitize_settings( array $input ): array {
		return [
			'enabled' => ! empty( $input['enabled'] ),
		];
	}

	/**
	 * Render extra settings fields below the toggle.
	 *
	 * @since 1.0.0
	 *
	 * @param string $option_name Base option name for field names.
	 */
	public function render_extra_settings( string $option_name ): void {
		// Default: no extra settings.
	}

	/**
	 * Check if the module is enabled.
	 *
	 * @since 1.0.0
	 *
	 * @return bool Whether the module is enabled.
	 */
	public function is_enabled(): bool {
		return (bool) $this->plugin->get_setting(
			$this->get_category(),
			$this->get_slug(),
			'enabled'
		);
	}

	/**
	 * Whether this module's prerequisites are met.
	 *
	 * @since 1.0.0
	 *
	 * @return bool
	 */
	public function is_available(): bool {
		return true;
	}

	/**
	 * Human-readable reason the module is unavailable.
	 *
	 * @since 1.0.0
	 *
	 * @return string
	 */
	public function unavailable_reason(): string {
		return '';
	}

	/**
	 * Render standalone management UI outside the main settings form.
	 *
	 * Modules with their own forms (add/delete/toggle actions) override this
	 * method instead of embedding forms inside render_extra_settings(), which
	 * would create invalid nested HTML forms.
	 *
	 * @since 1.0.0
	 *
	 * @param string $active_tab Currently active tab slug.
	 */
	public function render_management_ui( string $active_tab ): void {
		// Default: no standalone UI.
	}

	/**
	 * Register the module.
	 *
	 * @since 1.0.0
	 */
	public function register(): void {
		if ( $this->is_enabled() && $this->is_available() ) {
			$this->run();
		}
	}
	/**
	 * Whether an auto-created page id still points at a usable page.
	 *
	 * get_post() alone returns a trashed post perfectly happily, so a configured
	 * page that an admin binned still counted as present — the module never
	 * noticed and never made a replacement, while customer-facing links kept
	 * pointing at it.
	 *
	 * @since 1.0.2
	 *
	 * @param int $page_id Page id.
	 */
	protected static function page_is_usable( int $page_id ): bool {
		if ( $page_id <= 0 ) {
			return false;
		}

		$post = get_post( $page_id );

		return $post instanceof \WP_Post
			&& in_array( $post->post_status, [ 'publish', 'draft', 'pending', 'private' ], true );
	}

}
