<?php

/**
 * Schema Map Endpoint Gate
 *
 * Single source of truth for whether `/schema-map` should serve.
 *
 * Layers (all must be true for the endpoint to be enabled):
 *
 *   1. The `schema` module is active in ModuleManager.
 *   2. The `prorank_schema_map_enabled` option is unset or truthy.
 *   3. The `prorank_schema_map_endpoint_enabled` filter (applied last)
 *      doesn't return false — lets third-party code force the gate
 *      either direction on top of the module + option state.
 *
 * This helper is intentionally evaluated INSIDE the controllers on every
 * request, not inside SchemaModule::init_hooks(). That way the endpoint
 * honours the gate even when the schema module itself is disabled — a
 * disabled module can't register hooks, but the gate must still block
 * the endpoint.
 *
 * @package ProRank\SEO\Core\Schema
 * @since   2.2.0
 */

declare(strict_types=1);

namespace ProRank\SEO\Core\Schema;

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


class SchemaMapGate {

    /**
     * Option name for the operator toggle. Default absent = enabled.
     */
    public const OPTION_NAME = 'prorank_schema_map_enabled';

    /**
     * Schema module slug in ModuleManager.
     */
    public const MODULE_SLUG = 'schema';

    /**
     * Filter name for third-party overrides of the final decision.
     */
    public const FILTER_NAME = 'prorank_schema_map_endpoint_enabled';

    /**
     * Whether the endpoint should serve.
     *
     * Precedence:
     *   1. Module must be active  (hard prerequisite)
     *   2. Option must be on      (hard prerequisite)
     *   3. Filter can force off   (off-only switch)
     *
     * The filter is deliberately NOT allowed to force an endpoint back on
     * whose module is inactive or whose option is off. If the base state
     * is already false, we short-circuit and don't even apply the filter —
     * that way an accidental `__return_true` hook can't re-expose an
     * endpoint the operator intended to turn off.
     *
     * Safe to call during the REST handler — never throws, defaults to
     * conservative fallbacks if the runtime environment is partially loaded.
     *
     * @return bool
     */
    public static function is_enabled(): bool {
        if ( ! self::is_module_active() ) {
            return false;
        }
        if ( ! self::is_option_enabled() ) {
            return false;
        }

        /**
         * Filter the final enabled decision for the schema-map endpoint.
         *
         * Off-only. Return false to force the endpoint off even when the
         * module and option agree it should serve. Returning true has no
         * effect — the base value passed in is already true at this point.
         *
         * @param bool $enabled Always true when this filter is reached
         *                     (module active AND option on).
         */
        return (bool) apply_filters( self::FILTER_NAME, true );
    }

    /**
     * Reason string for why the gate is closed, for UI/diagnostics.
     *
     * Null when the gate is open. Stable strings so the admin UI and
     * telemetry can differentiate causes.
     *
     * @return string|null
     */
    public static function reason_when_disabled(): ?string {
        if ( ! self::is_module_active() ) {
            return 'module_inactive';
        }
        if ( ! self::is_option_enabled() ) {
            return 'option_disabled';
        }
        // Re-evaluate the filter with TRUE as the base to see if an override
        // forced it off on its own.
        if ( ! (bool) apply_filters( self::FILTER_NAME, true ) ) {
            return 'filter_override';
        }
        return null;
    }

    /**
     * Whether the schema module is active in ModuleManager.
     *
     * Defaults to TRUE when the plugin singleton or ModuleManager isn't
     * available (e.g. very early requests) — the option and filter layers
     * still guard against accidental exposure.
     *
     * @return bool
     */
    private static function is_module_active(): bool {
        if ( ! class_exists( '\\ProRank\\SEO\\Plugin' ) ) {
            return true;
        }
        try {
            $plugin = \ProRank\SEO\Plugin::get_instance();
            if ( ! $plugin ) {
                return true;
            }
            if ( ! method_exists( $plugin, 'modules' ) ) {
                return true;
            }
            $modules = $plugin->modules();
            if ( ! $modules || ! method_exists( $modules, 'is_active' ) ) {
                return true;
            }
            return (bool) $modules->is_active( self::MODULE_SLUG );
        } catch ( \Throwable $e ) {
            return true;
        }
    }

    /**
     * Whether the operator toggle option is on (or absent — defaults to on).
     *
     * @return bool
     */
    private static function is_option_enabled(): bool {
        $value = get_option( self::OPTION_NAME, null );
        if ( $value === null ) {
            return true;
        }
        if ( $value === true || $value === 1 ) {
            return true;
        }
        return in_array( (string) $value, [ '1', 'true', 'yes', 'on' ], true );
    }
}
