<?php

if (!defined('ABSPATH')) {
    die('You are not allowed to call this page directly.');
}

/**
 * Build-level feature availability.
 *
 * Brand builds strip whole features from the shipped plugin (via core-cli
 * ignoreSourcePaths) either by deleting a feature's anchor class file or by
 * replacing it with a MeprBaseStub no-op. This class is the single production
 * predicate for "does this feature ship in this build" and mirrors the test
 * framework's isStubbed() semantics, so production gating and test gating can
 * never disagree.
 *
 * Usage: gate any UI, routing, or request-input surface that belongs to a
 * strippable feature with MeprFeatures::active('coupons'). Reads of data at
 * rest (e.g. a subscription's stored coupon) should instead use
 * MeprFeatures::class_usable() on the class they are about to touch, so
 * historical records keep rendering even while the feature's UI is stripped.
 */
final class MeprFeatures
{
    /**
     * Map of feature keys to the anchor class whose presence defines the
     * feature. The anchor must be a file the brand strip set removes.
     *
     * @var array<string, string>
     */
    private const FEATURES = [
        'coupons' => 'MeprCouponsCtrl',
    ];

    /**
     * Whether a strippable feature is active in this build.
     *
     * @param  string $feature The feature key, e.g. 'coupons'.
     * @return boolean
     */
    public static function active($feature)
    {
        $class  = self::FEATURES[$feature] ?? '';
        $active = $class !== '' && self::class_usable($class);

        /**
         * Filters whether a strippable feature is active.
         *
         * Lets brand add-ons disable a feature without deleting files
         * (e.g. add_filter('mepr_feature_coupons_active', '__return_false')).
         * Add the filter no later than plugins_loaded priority 0 (any plugin
         * main file qualifies) so the feature module also skips registering
         * its hooks and post type; filters added later only affect surfaces
         * that consult this predicate at call time.
         *
         * @param boolean $active Whether the feature's anchor class is usable.
         */
        return (bool) MeprHooks::apply_filters("mepr_feature_{$feature}_active", $active);
    }

    /**
     * Whether a class is present and is not a brand stub.
     *
     * Mirrors tests/Framework/TestCase.php::isStubbed(): a class replaced by
     * a MeprBaseStub subclass counts as stripped, because its methods no-op
     * and must not be trusted for UI, routing, or price math.
     *
     * @param  string $class The fully qualified class name.
     * @return boolean
     */
    public static function class_usable($class)
    {
        return class_exists($class) && !is_subclass_of($class, 'MeprBaseStub', true);
    }
}
