<?php
/**
 * FormForge License Manager
 *
 * Delegates plan and license state to the Freemius SDK while keeping
 * the same public API so that no call sites need to change.
 *
 * @package FormForge
 * @since 1.0.0
 */

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

class FORMFORGE_License {

    /** @var FORMFORGE_License|null */
    private static $instance = null;

    /**
 * Free-tier capability whitelist.
 *
 * `can` returns true for these no matter what — they're shipped in the
 * Free plugin and require no Pro add-on. Everything NOT in this list is a
 * Pro feature and is delegated to FORMFORGE_Pro_Bridge::is_enabled.
 *
 * See SPLIT_PLAN.md §5.3.
 */
    private const FREE_FEATURES = [
        'basic_form_builder',
        'fifteen_free_fields',
        'honeypot',
        'nonce',
        'email_notifications',
        'file_uploads_basic',
        'file_uploads',
        'basic_submissions',
        'csv_export',
        // 2026-05-29 Free/Pro rebalance. Owner decision: unlimited forms in
        // Free (no competitor caps form count). Conditional logic + webhooks
        // are free in Fluent Forms / Forminator and are pure-local (no AI, no
        // Worker, no payment). PRO field types (payment, calculation,
        // repeater, map_address, calendar_picker, page_break) and multi-step /
        // conversational / AI / Stripe stay Pro — apply_license_gates still
        // downgrades a Pro field to a "(Requires Pro)" text field on Free.
        'unlimited_forms',
        'conditional_logic',
        'webhooks',
    ];

    /**
 * Pro-feature registry kept here for backward compatibility with callers
 * that read the metadata (e.g. admin UIs listing Pro features for upsell
 * cards). Authoritative gating now happens via FORMFORGE_Pro_Bridge.
 */
    private $features = [
        'ai_generation'      => ['pro', 'trial'],
        'analytics'          => ['pro', 'trial'],
        'abandonment'        => ['pro', 'trial'],
        'submissions_ai'     => ['pro', 'trial'],
        'advanced_templates' => ['pro', 'trial'],
        'advanced_antispam'  => ['pro', 'trial'],
        'multi_step'         => ['pro', 'trial'],
        'payment'            => ['pro', 'trial'],
        'user_registration'  => ['pro', 'trial'],
        'calculation'        => ['pro', 'trial'],
        'repeater'           => ['pro', 'trial'],
        'conversational'     => ['pro', 'trial'],
        'pdf_generation'     => ['pro', 'trial'],
        'whatsapp'           => ['pro', 'trial'],
        'telegram'           => ['pro', 'trial'],
        'slack'              => ['pro', 'trial'],
        'discord'            => ['pro', 'trial'],
        'google_sheets'      => ['pro', 'trial'],
        'google_calendar'    => ['pro', 'trial'],
        'stripe'             => ['pro', 'trial'],
        'hubspot'            => ['pro', 'trial'],
        // 2026-05-29 rebalance follow-up : generic outbound `webhooks`
        // moved to Free, but the BRANDED integrations below must stay Pro.
        // They previously shared the `webhooks` gate, so freeing webhooks
        // accidentally unlocked Slack/Discord/Mailchimp in the Settings UI and
        // builder. Each branded integration now has its own slug.
        'mailchimp'          => ['pro', 'trial'],
        'post_submission'    => ['pro', 'trial'],
        'yandex_geocoder'    => ['pro', 'trial'],
    ];

    private $feature_labels = [
        'ai_generation'      => 'AI Form Generation',
        'analytics'          => 'Form Analytics',
        'abandonment'        => 'Abandonment Tracking',
        'submissions_ai'     => 'AI Submission Analysis',
        'advanced_templates' => 'All Templates (168)',
        'advanced_antispam'  => 'reCAPTCHA & Akismet',
        'multi_step'         => 'Multi-Step Forms',
        'payment'            => 'Payment Integration',
        'user_registration'  => 'User Registration',
        'calculation'        => 'Calculation Fields',
        'repeater'           => 'Repeater Fields',
        'conversational'     => 'Conversational Forms',
        'mailchimp'          => 'Mailchimp',
        'post_submission'    => 'Post Submission',
    ];

    // 2026-05-29: Free form count is now effectively unlimited (owner
    // decision). Kept as a high numeric cap rather than null so the existing
    // get_all slice / over-limit UI math stays well-defined.
    private $limits = [
        'forms' => ['free' => 999, 'pro' => 999, 'trial' => 999],
    ];

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

    private function __construct() {}

    
    // Freemius bridge
    

    private function effective_plan() {
        // Add-on architecture: without the Pro plugin physically loaded,
        // no plan upgrade is possible regardless of stored license_data
        // or the 24h grace window. Stale forge_license_last_pro_at from
        // a previous Pro activation must NOT surface when Pro is gone —
        // otherwise the Free admin UI shows "PRO Active", Pro integrations
        // accept toggles, AI generation tries to run, etc.
        
        // FORMFORGE_Pro_Loader is the main class of formforge-pro and is
        // only declared when that plugin is active.
        if (!class_exists('FORMFORGE_Pro_Loader')) {
            return 'free';
        }

        // E2E test bypass: allow forcing plan via WP option. Gated on
        // `FORGE_SKIP_LICENSE_REVALIDATION` constant — only our dev/test
        // wp-config.php defines it. On a customer site the constant is
        // absent, so the option cannot be used to escalate to PRO even
        // if set through SQLi or a compromised admin account.
        if (defined('FORGE_SKIP_LICENSE_REVALIDATION') && FORGE_SKIP_LICENSE_REVALIDATION) {
            $e2e_plan = get_option('forge_e2e_plan', '');
            if (in_array($e2e_plan, ['pro', 'trial'], true)) {
                return $e2e_plan;
            }
        }

        // / FC-63 v2: an explicit Disconnect (Forge_Connect::disconnect_product
        // → cache_license_inactive) rewrites formforge_license_data to an INACTIVE
        // cache (key kept, 'inactive'=>true). Treat that as Free BEFORE the grace-marker
        // / Pro-plan paths so a disconnected plugin can't keep Pro off a stale
        // Pro signal or the shared forge_license_last_pro_at. Mirrors the resolver's
        // $is_inactive / 'saved_but_inactive'.
        $ff_data = get_option('formforge_license_data', []);
        if (is_array($ff_data) && !empty($ff_data['inactive'])) {
            return 'free';
        }

        if ($this->allows_local_license_expiration_override()) {
            $last_pro = (int) get_option('forge_license_last_pro_at', 0);
            if ($last_pro > 0) {
                return (time() - $last_pro) < DAY_IN_SECONDS ? 'pro' : 'free';
            }
        }

        if (defined('FORGE_SKIP_LICENSE_REVALIDATION') && FORGE_SKIP_LICENSE_REVALIDATION) {
            $local_plan = $this->local_license_data_plan();
            if ($local_plan) {
                return $local_plan;
            }
        }

        // Pro-plan confirmation is supplied by the Pro add-on, which hooks
        // 'forge_pro_plan' with its own licensing backend. The wp.org Free base
        // ships no licensing SDK — this filter is the single injection point.
        // Returns: '' (add-on absent / could not determine), 'pro', 'trial', or
        // 'free' (add-on explicitly reports no active access — clear stale grace).
        $pro_plan = apply_filters('forge_pro_plan', '', 'formforge');
        if ($pro_plan === 'pro') {
            update_option('forge_license_last_pro_at', time(), false);
            return 'pro';
        }
        if ($pro_plan === 'trial') {
            return 'trial';
        }

        if ($this->has_worker_trial()) {
            return 'trial';
        }

        if ($pro_plan === 'free') {
            if ((int) get_option('forge_license_last_pro_at', 0) > 0) {
                update_option('forge_license_last_pro_at', 0, false);
            }
            return 'free';
        }

        // Grace-period fallback — see langforge class-license.php for rationale.
        $last_pro = (int) get_option('forge_license_last_pro_at', 0);
        if ($last_pro > 0 && (time() - $last_pro) < DAY_IN_SECONDS) {
            return 'pro';
        }

        return 'free';
    }

    private function allows_local_license_expiration_override() {
        return (defined('FORGE_SKIP_LICENSE_REVALIDATION') && FORGE_SKIP_LICENSE_REVALIDATION)
            || (defined('WP_FS__DEV_MODE') && WP_FS__DEV_MODE);
    }

    private function has_worker_trial() {
        $data = get_option('formforge_license_data', []);
        if (!is_array($data) || empty($data['trial_token'])) {
            return false;
        }

        $expires = isset($data['trial_expires_at'])
            ? (string) $data['trial_expires_at']
            : (isset($data['trial_expires']) ? (string) $data['trial_expires'] : '');
        if ($expires !== '') {
            $ts = strtotime($expires);
            if ($ts && $ts < time()) {
                return false;
            }
        }

        return true;
    }

    private function local_license_data_plan() {
        $data = get_option('formforge_license_data', []);
        if (!is_array($data)) {
            return '';
        }

        $status = get_transient('formforge_license_status');
        if ($status === false) {
            $status = get_option('formforge_license_status', '');
        }

        if ($status && $status !== 'valid') {
            return '';
        }

        $plan = isset($data['plan']) ? sanitize_key((string) $data['plan']) : '';
        return in_array($plan, ['pro', 'trial'], true) ? $plan : '';
    }

    
    // Public API — Plan queries
    
    // Declared static to match the cross-suite contract: LangForge and
    // SEOForge expose these statically and the shared dashboard calls
    // `XX_License::is_pro` (see class-forge-dashboard-license-activation.php).
    // FormForge was missed in the Free/Pro split and kept them as instance
    // methods, so `FORMFORGE_License::is_pro` — used by QA, E2E and the
    // retest steps — fataled on PHP 8 with "Non-static method cannot
    // be called statically" → WordPress critical-error page. Existing
    // instance call sites (`FORMFORGE_License::instance->is_pro`,
    // `$this->is_free`) keep working: PHP permits invoking a static method
    // through an object instance.
    

    public static function is_pro() {
        return in_array(self::instance()->effective_plan(), ['pro', 'trial'], true);
    }

    public static function is_free() {
        return self::instance()->effective_plan() === 'free';
    }

    public static function get_plan() {
        return self::instance()->effective_plan();
    }

    public static function get_plan_name() {
        $names = [
            'free'  => 'Free',
            'pro'   => 'Pro',
            'trial' => 'Trial',
        ];
        $plan = self::instance()->effective_plan();
        return isset($names[$plan]) ? $names[$plan] : 'Free';
    }

    
    // Public API — Feature & limit checks
    

    public function can($feature) {
        // Free-tier capabilities are always allowed — they ship in the Free plugin.
        if (in_array($feature, self::FREE_FEATURES, true)) {
            return true;
        }

        // Pro-tier features delegate to the bridge. On a Free-only site the
        // bridge has no features registered → is_enabled returns false. On a
        // Free+Pro site the Pro loader registers + enables features based on
        // license state, so this returns true when the user has a valid
        // license/trial.
        if (class_exists('FORMFORGE_Pro_Bridge')) {
            return FORMFORGE_Pro_Bridge::instance()->is_enabled($feature);
        }

        // Fallback for the brief window before the bridge file loads (e.g.
        // direct file-include from a test that bypasses the bootstrap): use
        // the legacy plan-based check so callers don't break.
        if (!isset($this->features[$feature])) return false;
        return in_array($this->effective_plan(), $this->features[$feature], true);
    }

    public function check_limit($resource, $count) {
        $limit = $this->get_limit($resource);
        if ($limit === null) {
            return true;
        }
        return $count < $limit;
    }

    public function get_limit($resource) {
        if (!isset($this->limits[$resource])) {
            return null;
        }
        $plan = $this->effective_plan();
        return isset($this->limits[$resource][$plan]) ? $this->limits[$resource][$plan] : null;
    }

    public function get_features() {
        $result = [];
        foreach ($this->features as $slug => $plans) {
            $label = isset($this->feature_labels[$slug]) ? $this->feature_labels[$slug] : $slug;
            $result[$slug] = [
                'plans'   => $plans,
                'label'   => $label,
                'name'    => $label,
                'free'    => in_array($slug, self::FREE_FEATURES, true),
                'enabled' => $this->can($slug),
            ];
        }
        return $result;
    }

    
    // Public API — Activation / Deactivation (delegated to Freemius)
    

    public function activate($key) {
        return true;
    }

    public function deactivate() {
        return true;
    }

    public function upgrade_url() {
        // Phase 3 of AVAKODE_AUTH_PURCHASE_PLAN — route "Upgrade" CTAs to
        // avakode.com/dashboard/buy instead of Freemius inline checkout.
        $base = defined('FORGE_AVAKODE_URL') ? rtrim(FORGE_AVAKODE_URL, '/') : 'https://avakode.com';
        return $base . '/dashboard/buy/?product=formforge&plan=pro';
    }

    
    // Admin notice
    

    public function render_upgrade_banner() {
        if (!$this->is_free() || !current_user_can('manage_options')) {
            return;
        }

        $upgrade_url = esc_url($this->upgrade_url());

        printf(
            '<div class="forge-upgrade-notice" style="border-left:4px solid #3B82F6;background:#EFF6FF;border-radius:0 12px 12px 0;padding:14px 18px;margin:5px 0 15px;display:flex;align-items:center;justify-content:space-between;gap:12px;"><p style="margin:0;font-size:14px;color:#1e293b;flex:1;">%s <a href="%s" target="_blank" style="color:#3B82F6;font-weight:600;">%s</a></p><button type="button" onclick="this.parentElement.style.display=\x27none\x27" style="display:inline-flex;align-items:center;justify-content:center;background:#f8fafc;border:1px solid #e2e8f0;color:#94a3b8;cursor:pointer;padding:0;width:28px;height:28px;border-radius:8px;flex-shrink:0;transition:all 0.15s;" title="Dismiss" onmouseover="this.style.background=\x27#fef2f2\x27;this.style.borderColor=\x27#fca5a5\x27;this.style.color=\x27#ef4444\x27" onmouseout="this.style.background=\x27#f8fafc\x27;this.style.borderColor=\x27#e2e8f0\x27;this.style.color=\x27#94a3b8\x27"><span class="dashicons dashicons-no-alt" style="font-size:18px;width:18px;height:18px;"></span></button></div>',
            esc_html__("You're using Form Forge Free. Upgrade to Pro for AI form generation, multi-step forms, payment fields, advanced analytics, and more.", 'formforge'),
            $upgrade_url, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $upgrade_url already esc_url'd at assignment
            esc_html__('Upgrade Now', 'formforge')
        );
    }
}
