<?php
if (!defined('ABSPATH')) exit;

require_once __DIR__ . '/class-forge-crypto.php';

class FORMFORGE_Forge_API {
    private static $instance = null;
    private $api_url = 'https://api.avakode.com';
    private $license_key = '';
    private $site_token = '';
    private $trial_token = '';

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

    private function __construct() {
        if (defined('FORGE_API_URL') && FORGE_API_URL) {
            $this->api_url = rtrim(FORGE_API_URL, '/');
        }
        $data = get_option('formforge_license_data', []);
        $this->license_key = $data['key'] ?? '';
        $this->site_token = Forge_Crypto::decrypt(isset($data['site_token']) ? (string) $data['site_token'] : '');
        $this->trial_token = Forge_Crypto::decrypt(isset($data['trial_token']) ? (string) $data['trial_token'] : '');

        // Fallback: use Freemius license key for real paying users
        if (empty($this->license_key) && function_exists('ff_freemius')) {
            $fs = ff_freemius();
            if ($fs && $fs->is_registered()) {
                $license = $fs->_get_license();
                if (is_object($license) && !empty($license->secret_key)) {
                    $this->license_key = $license->secret_key;
                }
            }
        }
    }

    public function has_license_key() {
        return $this->license_key !== '';
    }

    private function missing_license_error($service_label = '') {
        if ($service_label !== '') {
            return new \WP_Error(
                'missing_license_key',
                sprintf(
                    /* translators: %s = integration name */
                    __('Connect Form Forge to your Avakode license before using %s.', 'formforge'),
                    $service_label
                ),
                ['status' => 400]
            );
        }

        return new \WP_Error(
            'missing_license_key',
            __('Connect Form Forge to your Avakode license before using this integration.', 'formforge'),
            ['status' => 400]
        );
    }

    private function require_license_key($service_label = '') {
        if ($this->has_license_key()) {
            return true;
        }

        return $this->missing_license_error($service_label);
    }

    public function validate_license($key = null) {
        $key = $key ?: $this->license_key;
        if (empty($key)) return ['valid' => false, 'plan' => 'free', 'features' => []];

        $response = $this->post('/licenses/validate', [
            'license_key' => $key,
            'product' => 'formforge',
            'domain' => home_url('/'),
        ]);

        if (!is_wp_error($response) && is_array($response) && !empty($response['token'])) {
            $this->persist_site_token((string) $response['token'], $key);
        }

        return $response ?: ['valid' => false, 'plan' => 'free', 'features' => []];
    }

    private function persist_site_token($site_token, $license_key = '') {
        if ($site_token === '') return;
        $data = get_option('formforge_license_data', []);
        if (!is_array($data)) $data = [];
        if ($license_key !== '') {
            $data['key'] = $license_key;
        }
        $data['site_token'] = Forge_Crypto::encrypt($site_token);
        $data['site_url'] = home_url('/');
        $data['site_token_refreshed_at'] = time();
        update_option('formforge_license_data', $data, false);
        $this->site_token = $site_token;
    }

    /**
 * Ensure a per-site trial token exists (#39). Called on the first AI request
 * from a site with no license so the user gets free trial credits with no
 * setup. Best-effort.
 * @return bool True if a trial token is available afterwards.
 */
    public function maybe_start_trial() {
        if ($this->trial_token !== '') return true;
        if (!empty($this->license_key)) return false;
        $this->start_trial();
        return $this->trial_token !== '';
    }

    /**
 * Activate a trial against the Worker and persist the returned per-site
 * token (encrypted at rest). Defaults to the site admin email.
 * @param string $email Optional override; defaults to admin_email.
 * @return array|\WP_Error
 */
    public function start_trial($email = '') {
        $email = $email !== '' ? $email : (string) get_option('admin_email');
        $response = $this->post('/public/trial/activate', [
            'product' => 'formforge',
            'domain'  => home_url('/'),
            'email'   => $email,
        ]);
        if (!is_wp_error($response) && is_array($response)
            && !empty($response['success']) && !empty($response['trial']['token'])) {
            $this->persist_trial_token((string) $response['trial']['token'], $response['trial']);
        }
        return $response;
    }

    private function persist_trial_token($token, $trial = []) {
        if ($token === '') return;
        $data = get_option('formforge_license_data', []);
        if (!is_array($data)) $data = [];
        $data['trial_token'] = Forge_Crypto::encrypt($token);
        if (!empty($trial['expires_at'])) {
            $data['trial_expires_at'] = (string) $trial['expires_at'];
            $data['trial_expires'] = (string) $trial['expires_at'];
        }
        if (isset($trial['credits_remaining'])) $data['trial_credits_remaining'] = (int) $trial['credits_remaining'];
        if (isset($trial['credits_limit'])) $data['trial_credits_limit'] = (int) $trial['credits_limit'];
        update_option('formforge_license_data', $data, false);
        $this->trial_token = $token;
    }

    private function clear_site_token() {
        $data = get_option('formforge_license_data', []);
        if (!is_array($data)) $data = [];
        unset($data['site_token'], $data['site_token_refreshed_at']);
        update_option('formforge_license_data', $data, false);
        $this->site_token = '';
    }

    private function refresh_site_token() {
        if ($this->license_key === '') return false;
        $response = $this->validate_license($this->license_key);
        if (is_wp_error($response)) {
            return $this->site_token !== '';
        }
        if (is_array($response) && !empty($response['token'])) {
            return true;
        }
        $this->clear_site_token();
        return false;
    }

    private function integration_auth_body($extra = []) {
        if ($this->site_token === '') {
            $this->refresh_site_token();
        }
        $body = [
            'license_key' => $this->license_key,
            'product' => 'formforge',
            'site_url' => home_url('/'),
            'forge_site_url' => home_url('/'),
        ];
        if ($this->site_token !== '') {
            $body['site_token'] = $this->site_token;
        }
        return array_merge($body, $extra);
    }

    /**
 * / FF-GOOGLE-001: prime $this->site_token via the SHARED
 * Forge_Connect::get_or_refresh_site_token helper before an
 * integration call that needs a site capability token.
 *
 * The legacy refresh_site_token path only re-reads whatever was
 * stored at activation time; the shared helper transparently
 * refreshes a missing/stale token from the Worker /licenses/validate
 * endpoint. Without this, HubSpot Connect failed with
 * "Missing site capability token" whenever the cached token had
 * expired. Returns true when a usable token is now in instance state.
 *
 * @return bool
 */
    public function prime_site_token() {
        if ($this->license_key !== '') {
            $response = $this->validate_license($this->license_key);
            if (is_array($response) && !empty($response['token'])) {
                return true;
            }
        }
        if (class_exists('Forge_Connect')) {
            $token = \Forge_Connect::get_or_refresh_site_token('formforge', true);
            if ($token !== '') {
                $this->site_token = $token;
                return true;
            }
        }
        // Fallback to the legacy path (re-validate the license directly)
        // so installs where Forge_Connect somehow isn't loaded still work.
        if ($this->site_token === '') {
            $this->refresh_site_token();
        }
        return $this->site_token !== '';
    }

    private function require_integration_site_token($service_label) {
        $license = $this->require_license_key($service_label);
        if (is_wp_error($license)) return $license;

        if ($this->prime_site_token()) {
            return true;
        }

        return new \WP_Error(
            'missing_site_token',
            sprintf(
                /* translators: %s = integration name */
                __('Could not verify this site before using %s. Reconnect your Avakode license and try again.', 'formforge'),
                $service_label
            ),
            ['status' => 400]
        );
    }

    public function ai_generate_form($description, $industry = '', $language = '') {
        // Worker /ai/process Zod schema requires `language` to be either omitted
        // or 2-10 chars (BCP-47). Empty string fails min(2) validation. Caller
        // historically passed '' as default → deterministic 400 "Invalid payload".
        // Default to the WP locale's first 2 chars so the generated form's UI
        // copy (labels, placeholders, options) matches the site language.
        if ($language === '' || $language === null) {
            $locale = function_exists('get_user_locale') ? get_user_locale() : get_locale();
            $language = strtolower(substr((string) $locale, 0, 2));
            if (strlen($language) < 2) $language = 'en'; // ultimate fallback
        }
        $payload = [
            'description' => $description,
            'domain' => $this->get_domain(),
            'language' => $language,
        ];
        // industry is also `.optional` — only send if non-empty to avoid
        // tripping any future min-length constraint on the Worker side.
        if ($industry !== '' && $industry !== null) {
            $payload['industry'] = $industry;
        }
        return $this->post('/ai/process', [
            'action' => 'generate_form',
            'product' => 'formforge',
            'license_key' => $this->license_key,
            'payload' => $payload,
        ]);
    }

    public function ai_analyze_submissions($submissions, $question = '') {
        $rows = $this->normalize_submissions_payload($submissions);
        $payload = [
            'submissions' => $rows,
            'domain' => $this->get_domain(),
        ];
        $question = trim((string) $question);
        if ($question !== '') {
            $payload['question'] = $question;
        }

        return $this->post('/ai/process', [
            'action' => 'analyze_submissions',
            'product' => 'formforge',
            'license_key' => $this->license_key,
            'payload' => $payload,
        ]);
    }

    private function normalize_submissions_payload($submissions) {
        if (!is_array($submissions)) {
            return [['value' => (string) $submissions]];
        }

        $is_list = function_exists('array_is_list')
            ? array_is_list($submissions)
            : array_keys($submissions) === range(0, count($submissions) - 1);

        if ($is_list) {
            $rows = [];
            foreach ($submissions as $row) {
                if (is_array($row)) {
                    $rows[] = $row;
                } elseif (is_scalar($row)) {
                    $rows[] = ['value' => (string) $row];
                }
            }
            return $rows ?: [['value' => '']];
        }

        return [$submissions];
    }

    private function post($endpoint, $body) {
        $is_ai = strpos($endpoint, '/ai/') === 0;
        // Trial activation (#39) is started explicitly via the "Start free
        // trial" button in the shared credits widget now, not auto-started on
        // the first AI call. We still attach an existing trial token below.
        $headers = [
            'Content-Type' => 'application/json',
            'X-Site-URL' => $this->get_domain(),
        ];
        if ($is_ai && empty($this->license_key) && $this->trial_token !== '') {
            $headers['X-Trial-Token'] = $this->trial_token;
        }
        $start = microtime(true);
        $response = wp_remote_post($this->api_url . $endpoint, [
            'timeout' => 60,
            'headers' => $headers,
            'body' => wp_json_encode($body),
        ]);
        $duration_ms = (int) round((microtime(true) - $start) * 1000);
        $action = isset($body['action']) ? (string) $body['action'] : '';

        if (is_wp_error($response)) {
            self::tel_err($endpoint, $action, 0, 'network', $response->get_error_message());
            return $response;
        }

        $code = wp_remote_retrieve_response_code($response);
        $data = json_decode(wp_remote_retrieve_body($response), true);

        if ($code === 402 && isset($data['error']) && $data['error'] === 'credits_exhausted') {
            self::tel_err($endpoint, $action, 402, 'credits_exhausted', $data['error'] ?? '');
            return new \WP_Error('credits_exhausted', __('Monthly AI credits exhausted.', 'formforge'), [
                'status' => 402,
                'remaining' => $data['remaining'] ?? 0,
                'reset_date' => $data['reset_date'] ?? '',
            ]);
        }

        if ($code < 400 && strpos($endpoint, '/ai/process') !== false
            && is_array($data) && isset($data['credits'])
            && class_exists('Forge_Credits_Widget')) {
            \Forge_Credits_Widget::push_credits($data['credits']);
        }

        if ($code >= 400) {
            $err_msg = is_array($data) ? (string) ($data['error'] ?? 'API error') : 'API error';
            self::tel_err($endpoint, $action, $code, self::tel_class($code, $err_msg), $err_msg);
            $err_data = ['status' => $code];
            if ($code === 429) {
                $retry_after = wp_remote_retrieve_header($response, 'retry-after');
                if ($retry_after) {
                    $secs = intval($retry_after);
                    if ($secs > 0) $err_data['retry_after'] = $secs;
                }
            }
            return new \WP_Error('forge_api_error', $err_msg, $err_data);
        }

        if (class_exists('Forge_Telemetry') && strpos($endpoint, '/ai/') !== false) {
            Forge_Telemetry::track('ai.request', [
                'endpoint' => $endpoint, 'action' => $action, 'status_code' => $code,
                'duration_bucket' => self::tel_dur($duration_ms), 'success' => true,
            ]);
        }
        return $data;
    }
    private static function tel_dur(int $ms): string {
        return $ms < 500 ? '0-500' : ($ms < 2000 ? '500-2k' : ($ms < 5000 ? '2k-5k' : ($ms < 15000 ? '5k-15k' : ($ms < 60000 ? '15k-60k' : '60k+'))));
    }
    private static function tel_class(int $code, string $msg): string {
        if ($code === 401 || $code === 403) return 'auth';
        if ($code === 429) return 'rate_limit';
        if ($code === 402) return 'credits_exhausted';
        if ($code >= 500)  return 'upstream_5xx';
        if (stripos($msg, 'country') !== false && stripos($msg, 'not supported') !== false) return 'geo_block';
        if ($code >= 400)  return 'client_4xx';
        return 'other';
    }
    private static function tel_err(string $ep, string $act, int $code, string $cls, string $msg): void {
        if (!class_exists('Forge_Telemetry')) return;
        Forge_Telemetry::track_error('forge_api', ['endpoint'=>$ep,'action'=>$act,'status_code'=>$code,'error_class'=>$cls,'message'=>$msg,'plugin_slug'=>'formforge']);
    }

    private function get_domain() {
        return parse_url(home_url(), PHP_URL_HOST);
    }

    /**
 * Connect Mailchimp API key (stored in Forge API D1, not in WordPress).
 */
    public function mailchimp_connect($api_key) {
        $license = $this->require_integration_site_token('Mailchimp');
        if (is_wp_error($license)) return $license;
        return $this->post('/integrations/mailchimp/connect', $this->integration_auth_body([
            'api_key' => $api_key,
        ]));
    }

    /**
 * Disconnect Mailchimp.
 */
    public function mailchimp_disconnect() {
        $license = $this->require_license_key('Mailchimp');
        if (is_wp_error($license)) return $license;
        return $this->post('/integrations/mailchimp/disconnect', $this->integration_auth_body());
    }

    /**
 * Get Mailchimp connection status.
 */
    public function mailchimp_status() {
        $license = $this->require_license_key('Mailchimp');
        if (is_wp_error($license)) return $license;
        return $this->post('/integrations/mailchimp/status', $this->integration_auth_body());
    }

    /**
 * Get Mailchimp lists for the connected account.
 */
    public function mailchimp_lists() {
        $license = $this->require_license_key('Mailchimp');
        if (is_wp_error($license)) return $license;
        return $this->post('/integrations/mailchimp/lists', $this->integration_auth_body());
    }

    /**
 * Subscribe email to a Mailchimp list.
 */
    public function mailchimp_subscribe($email, $list_id, $merge_fields = []) {
        $license = $this->require_integration_site_token('Mailchimp');
        if (is_wp_error($license)) return $license;
        return $this->post('/integrations/mailchimp/subscribe', $this->integration_auth_body([
            'email' => $email,
            'list_id' => $list_id,
            'merge_fields' => $merge_fields,
        ]));
    }

    /**
 * Connect HubSpot API key (stored in Forge API D1).
 */
    /**
 * FC-44 WhatsApp registration — mint a 10-minute /start token bound to
 * this site, returned to the FormForge Settings UI for the admin to
 * send from their personal WhatsApp.
 */
    public function whatsapp_register_token() {
        $license = $this->require_integration_site_token('WhatsApp');
        if (is_wp_error($license)) return $license;
        return $this->post('/notifications/whatsapp/register-token', $this->integration_auth_body());
    }

    /**
 * FC-44 WhatsApp status — admin UI polls this after issuing a register
 * token; flips to "Connected as <phone>" the moment the admin sends
 * "/start <token>" from their WhatsApp.
 */
    public function whatsapp_status() {
        $license = $this->require_license_key('WhatsApp');
        if (is_wp_error($license)) return $license;
        return $this->post('/notifications/whatsapp/status', $this->integration_auth_body());
    }

    public function hubspot_connect($api_key) {
        $license = $this->require_integration_site_token('HubSpot');
        if (is_wp_error($license)) return $license;
        return $this->post('/integrations/hubspot/connect', $this->integration_auth_body([
            'api_key' => $api_key,
        ]));
    }

    public function hubspot_disconnect() {
        $license = $this->require_license_key('HubSpot');
        if (is_wp_error($license)) return $license;
        return $this->post('/integrations/hubspot/disconnect', $this->integration_auth_body());
    }

    public function hubspot_status() {
        $license = $this->require_license_key('HubSpot');
        if (is_wp_error($license)) return $license;
        return $this->post('/integrations/hubspot/status', $this->integration_auth_body());
    }

    public function hubspot_pipelines() {
        $license = $this->require_license_key('HubSpot');
        if (is_wp_error($license)) return $license;
        return $this->post('/integrations/hubspot/pipelines', $this->integration_auth_body());
    }

    public function hubspot_properties() {
        $license = $this->require_license_key('HubSpot');
        if (is_wp_error($license)) return $license;
        return $this->post('/integrations/hubspot/properties', $this->integration_auth_body());
    }

    public function hubspot_submit($email, $contact_properties = [], $create_deal = false, $deal_name = '', $pipeline = '', $stage = '') {
        $license = $this->require_integration_site_token('HubSpot');
        if (is_wp_error($license)) return $license;
        return $this->post('/integrations/hubspot/submit', $this->integration_auth_body([
            'email' => $email,
            'contact_properties' => $contact_properties,
            'create_deal' => $create_deal,
            'deal_name' => $deal_name,
            'pipeline' => $pipeline,
            'stage' => $stage,
        ]));
    }

    /**
 * Google OAuth: get auth URL to start connection.
 *
 * Connect-flakiness fix: return_url tells the Worker exactly where in
 * WP admin to drop the user once OAuth succeeds. The Worker callback
 * 302s straight there with ?google_connected=1 appended, replacing
 * the unreliable postMessage / polling / visibilitychange dance for
 * the case where window.opener gets stripped during Google's
 * cross-origin redirect chain.
 */
    public function google_auth_url($return_url = null) {
        // Same-tab navigation: caller passes the admin URL the user is
        // currently on so they land back where they started after OAuth.
        // Default to Settings if the call site doesn't specify.
        if (empty($return_url)) {
            $return_url = admin_url('admin.php?page=formforge-settings');
        }

        // FF-GOOGLE-001 fix: use shared helper that refreshes the cached
        // site_token from Worker /licenses/validate when absent or stale,
        // rather than relying solely on whatever was stored at activation time.
        // Returns '' only when the license itself is invalid/absent.
        $token = '';
        if ($this->license_key !== '') {
            $response = $this->validate_license($this->license_key);
            if (is_array($response) && !empty($response['token'])) {
                $token = (string) $response['token'];
            }
        }
        if ($token === '' && class_exists('Forge_Connect')) {
            $token = \Forge_Connect::get_or_refresh_site_token('formforge', true);
        }
        if ($token === '') {
            $this->refresh_site_token();
            $token = $this->site_token;
        }

        // Sync instance state so integration_auth_body stays consistent.
        if ($token !== '') {
            $this->site_token = $token;
        }

        if ($token === '') {
            // No valid license — return '' so caller renders disabled button
            // with "Activate PRO license to connect Google" hint.
            return '';
        }

        // Mint a one-shot init token server-side so the license_key +
        // site_token NEVER ride in the browser URL (review #HIGH). The
        // browser only ever navigates to the opaque ?init=<token>.
        $init = $this->mint_google_init_token([
            'license_key' => $this->license_key,
            'product'     => 'formforge',
            'site_url'    => home_url('/'),
            'site_token'  => $token,
            'return_url'  => $return_url,
        ]);
        if ($init === '') {
            return '';
        }

        return $this->api_url . '/integrations/google/auth?init=' . rawurlencode($init);
    }

    /**
 * Mint a one-shot Google OAuth `init` token from the Worker.
 *
 * The license_key + site_token are POSTed in the request BODY, so they
 * never appear in the browser URL / history / Referer (review #HIGH,
 * 2026-06-07). Returns '' on any failure so the caller degrades to a
 * disabled "Connect Google" button instead of a broken link.
 *
 * @param array $fields license_key, product, site_url, site_token, return_url
 * @return string opaque init token, or '' on failure
 */
    private function mint_google_init_token($fields) {
        $response = wp_remote_post($this->api_url . '/integrations/google/auth/init', [
            'timeout' => 10,
            'headers' => ['Content-Type' => 'application/json'],
            'body'    => wp_json_encode($fields),
        ]);
        if (is_wp_error($response)) {
            return '';
        }
        if ((int) wp_remote_retrieve_response_code($response) !== 200) {
            return '';
        }
        $data = json_decode(wp_remote_retrieve_body($response), true);
        if (is_array($data) && !empty($data['init'])) {
            return (string) $data['init'];
        }
        return '';
    }

    public function google_status() {
        $license = $this->require_license_key(__('Google Sheets and Calendar', 'formforge'));
        if (is_wp_error($license)) return $license;
        return $this->post('/integrations/google/status', $this->integration_auth_body());
    }

    public function google_disconnect() {
        $license = $this->require_license_key(__('Google Sheets and Calendar', 'formforge'));
        if (is_wp_error($license)) return $license;
        return $this->post('/integrations/google/disconnect', $this->integration_auth_body());
    }

    public function sheets_list() {
        $license = $this->require_license_key(__('Google Sheets', 'formforge'));
        if (is_wp_error($license)) return $license;
        return $this->post('/integrations/sheets/list', $this->integration_auth_body());
    }

    public function sheets_append($spreadsheet_id, $sheet_name, $values) {
        $license = $this->require_license_key(__('Google Sheets', 'formforge'));
        if (is_wp_error($license)) return $license;
        return $this->post('/integrations/sheets/append', $this->integration_auth_body([
            'spreadsheet_id' => $spreadsheet_id,
            'sheet_name' => $sheet_name,
            'values' => $values,
        ]));
    }

    /**
 * Short-lived OAuth access token for Google Picker. Picker runs in the
 * browser and needs an access_token; refresh_token stays server-side.
 */
    public function sheets_picker_token() {
        $license = $this->require_license_key(__('Google Sheets', 'formforge'));
        if (is_wp_error($license)) return $license;
        return $this->post('/integrations/sheets/picker-token', $this->integration_auth_body([
            'site_url' => home_url('/'),
        ]));
    }

    public function calendar_event($calendar_id, $summary, $start, $end, $description = '', $attendee_email = '', $attendee_name = '', $time_zone = '') {
        $license = $this->require_license_key(__('Google Calendar', 'formforge'));
        if (is_wp_error($license)) return $license;
        $body = $this->integration_auth_body([
            'calendar_id' => $calendar_id,
            'summary' => $summary,
            'start' => $start,
            'end' => $end,
        ]);
        if ($description) $body['description'] = $description;
        if ($attendee_email) $body['attendee_email'] = $attendee_email;
        if ($attendee_name) $body['attendee_name'] = $attendee_name;
        if ($time_zone) $body['time_zone'] = $time_zone;
        return $this->post('/integrations/calendar/event', $body);
    }

    public function calendar_list() {
        $license = $this->require_license_key(__('Google Calendar', 'formforge'));
        if (is_wp_error($license)) return $license;
        return $this->post('/integrations/calendar/list', $this->integration_auth_body());
    }

    public function calendar_freebusy($calendar_id, $time_min, $time_max, $slot_duration = 30, $work_start = 9, $work_end = 17, $time_zone = '') {
        $license = $this->require_license_key(__('Google Calendar', 'formforge'));
        if (is_wp_error($license)) return $license;
        $body = $this->integration_auth_body([
            'calendar_id' => $calendar_id,
            'time_min' => $time_min,
            'time_max' => $time_max,
            'slot_duration' => $slot_duration,
            'work_start' => $work_start,
            'work_end' => $work_end,
        ]);
        if (!empty($time_zone)) $body['time_zone'] = $time_zone;
        return $this->post('/integrations/calendar/freebusy', $body);
    }

    /**
 * Generate PDF from submission data.
 */
    public function generate_pdf($form_title, $submission_id, $form_id, $fields, $format = 'html') {
        return $this->post('/integrations/pdf/generate', $this->integration_auth_body([
            'form_title' => $form_title,
            'submission_id' => $submission_id,
            'form_id' => $form_id,
            'fields' => $fields,
            'site_url' => home_url(),
            'site_name' => get_bloginfo('name'),
            'template' => 'basic',
            'format' => $format,
        ]));
    }

    private function get($endpoint) {
        $response = wp_remote_get($this->api_url . $endpoint, [
            'timeout' => 30,
            'headers' => [
                'Content-Type' => 'application/json',
                'X-Site-URL' => $this->get_domain(),
                'X-License-Key' => $this->license_key,
            ],
        ]);

        if (is_wp_error($response)) return $response;

        $code = wp_remote_retrieve_response_code($response);
        $data = json_decode(wp_remote_retrieve_body($response), true);

        if ($code === 402 && isset($data['error']) && $data['error'] === 'credits_exhausted') {
            return new \WP_Error('credits_exhausted', __('Monthly AI credits exhausted.', 'formforge'), [
                'status' => 402,
                'remaining' => $data['remaining'] ?? 0,
                'reset_date' => $data['reset_date'] ?? '',
            ]);
        }

        if ($code >= 400) {
            return new \WP_Error('forge_api_error', $data['error'] ?? 'API error', ['status' => $code]);
        }

        return $data;
    }

    public function get_license_key() { return $this->license_key; }
    public function set_license_key($key) { $this->license_key = $key; }
}
