<?php
/**
 * Onboarding Wizard (CF11 - client side).
 *
 * 3-step wizard that walks a first-time operator through:
 *   Step 1: Welcome + license status check
 *   Step 2: Pick your audience (sub_niches + geo_buckets + traffic_ranges)
 *   Step 3: Done + next-steps guidance
 *
 * State persists between steps via the WP 'user_meta' option.
 *
 * All render paths go through Admin\Renderer (CF14 build-for-change rule).
 *
 * @package SwapAds\Client\Admin
 * @since   1.1.0
 */

declare(strict_types=1);

namespace SwapAds\Client\Admin;

use SwapAds\Client\Admin\OperatorDashboardHubPage;
use SwapAds\Client\Admin\ClientErrorRenderer;
use SwapAds\Client\Api\RestClient;
use SwapAds\Client\License\LicenseManager;
use SwapAds\Client\Http\Redirect;
use SwapAds\Client\Http\Request;

/**
 * Class OnboardingWizardPage.
 *
 * @since 1.1.0
 */
final class OnboardingWizardPage
{
    public const MENU_SLUG        = 'swapads-client-onboarding';
    public const USER_META_DONE   = 'swapads_client_onboarding_complete';
    public const ERROR_CONTEXT    = 'swapads_client_onboarding';
    public const STEP_WELCOME     = 1;
    public const STEP_AUDIENCE    = 2;
    public const STEP_DONE        = 3;

    /**
     * Register WP hooks.
     */
    public static function register(): void
    {
        add_action('admin_menu', [self::class, 'addMenu']);
        add_action('admin_post_swapads_client_onboarding_step', [self::class, 'handleStepSubmit']);
    }

    /**
     * Register sub-menu page.
     */
    public static function addMenu(): void
    {
        // F233 (2026-07-31): hide Onboarding menu once the current user has
        // completed onboarding. Operators who bookmarked ?page=...onboarding
        // still land here (render() shows "already done") — only the menu
        // entry disappears.
        //
        // TODO-CLIENT-008 (2026-08-02): make Setup a standalone TOP-LEVEL
        // page (not a submenu of SwapAds). Operators get to it via the
        // dashboard link or the WP Admin sidebar.
        $userId = get_current_user_id();
        $done   = (bool) get_user_meta($userId, self::USER_META_DONE, true);
        if ($done) {
            return;
        }
        add_menu_page(
            'SwapAds Setup',
            'SwapAds Setup',
            'manage_options',
            self::MENU_SLUG,
            [self::class, 'render'],
            'dashicons-admin-generic',
            81  // just below "Users" (70) for visibility
        );
    }

    /**
     * Render the wizard for the current step.
     */
    public static function render(): void
    {
        if (!current_user_can('manage_options')) {
            wp_die('Insufficient permissions', 'Forbidden', ['response' => 403]);
        }

        $currentStep = self::currentStep();
        $userId = get_current_user_id();
        $done   = (bool) get_user_meta($userId, self::USER_META_DONE, true);

        echo Renderer::pageHeader(
            'Welcome to SwapAds',
            'Get started in 3 quick steps. Set your audience preferences and we will match you with relevant backlink opportunities.'
        );

        // F198: surface any persisted error from a previous audience-save attempt.
        $persisted = get_transient(self::ERROR_CONTEXT);
        if (is_array($persisted)) {
            echo ClientErrorRenderer::render(self::ERROR_CONTEXT, $persisted, 'save audience preferences');
            delete_transient(self::ERROR_CONTEXT);
        }

        // Show progress bar (1/3, 2/3, 3/3)
        echo self::renderProgress($currentStep, $done);

        // Render the current step
        if ($done) {
            echo self::renderAlreadyDone();
        } else {
            switch ($currentStep) {
                case self::STEP_WELCOME:
                    echo self::renderStepWelcome();
                    break;
                case self::STEP_AUDIENCE:
                    echo self::renderStepAudience();
                    break;
                case self::STEP_DONE:
                    echo self::renderStepDone();
                    break;
                default:
                    echo self::renderStepWelcome();
                    break;
            }
        }

        echo Renderer::pageFooter();
    }

    /**
     * Determine which step the current user is on.
     *
     * Reads from request first (?step=N), then user meta (last seen step).
     */
    public static function currentStep(): int
    {
        // ?step=2 — explicit nav
        if (isset($_GET['step'])) {
            $step = (int) wp_unslash($_GET['step']);
            if (in_array($step, [self::STEP_WELCOME, self::STEP_AUDIENCE, self::STEP_DONE], true)) {
                return $step;
            }
        }
        // User meta fallback
        $userId = get_current_user_id();
        $stored = (int) get_user_meta($userId, 'swapads_client_onboarding_step', true);
        if ($stored >= self::STEP_WELCOME && $stored <= self::STEP_DONE) {
            return $stored;
        }
        return self::STEP_WELCOME;
    }

    /**
     * Render progress indicator.
     */
    public static function renderProgress(int $step, bool $done): string
    {
        $stages = [
            self::STEP_WELCOME  => 'Welcome',
            self::STEP_AUDIENCE => 'Audience',
            self::STEP_DONE     => 'Done',
        ];
        $html = '<div class="swapads-wizard-progress">';
        foreach ($stages as $n => $label) {
            $state = $done ? 'done' : ($n < $step ? 'done' : ($n === $step ? 'current' : 'pending'));
            $html .= '<span class="swapads-wizard-step swapads-wizard-step-' . esc_attr($state) . '">';
            $html .= '<span class="swapads-wizard-step-num">' . esc_html((string) $n) . '</span> ';
            $html .= '<span class="swapads-wizard-step-label">' . esc_html($label) . '</span>';
            $html .= '</span>';
        }
        $html .= '</div>';
        return $html;
    }

    /**
     * Render step 1 (welcome).
     */
    public static function renderStepWelcome(): string
    {
        $licensed = LicenseManager::isLicensed();
        $body  = '<p>SwapAds is a 1:1 barter exchange for backlinks and banner ads.</p>';
        $body .= '<p>You will:</p>';
        $body .= '<ol>';
        $body .= '<li>Tell us about your audience (Step 2).</li>';
        $body .= '<li>Receive backlink suggestions that match.</li>';
        $body .= '<li>Approve them - and have your backlinks approved by others.</li>';
        $body .= '</ol>';
        if (!$licensed) {
            $body .= '<p><strong>License inactive.</strong> Activate your license before continuing (see Settings).</p>';
        }
        $body .= '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
        $body .= '<input type="hidden" name="action" value="swapads_client_onboarding_step">';
        $body .= '<input type="hidden" name="step" value="' . esc_attr((string) self::STEP_AUDIENCE) . '">';
        $body .= '<input type="hidden" name="swapads_nonce" value="' . esc_attr(wp_create_nonce('swapads_onboarding_step')) . '">';
        $body .= '<p><button type="submit" class="button button-primary"' . ($licensed ? '' : ' disabled') . '>Continue to Step 2: Audience</button></p>';
        $body .= '</form>';
        return $body;
    }

    /**
     * Render step 2 (audience selection).
     */
    public static function renderStepAudience(): string
    {
        $definitions = self::fetchDefinitions();
        $saved       = self::fetchSavedAudience();
        if (!is_array($definitions)) {
            return '<p>Could not reach the server to load audience options. Please refresh in a moment.</p>';
        }

        $subNiches = is_array($definitions['sub_niches'] ?? null) ? $definitions['sub_niches'] : [];
        $geos      = is_array($definitions['geo_buckets'] ?? null) ? $definitions['geo_buckets'] : [];
        $traffic   = is_array($definitions['traffic_ranges'] ?? null) ? $definitions['traffic_ranges'] : [];

        $savedSn = is_array($saved['sub_niches'] ?? null) ? $saved['sub_niches'] : [];
        $savedG  = is_array($saved['geo_buckets'] ?? null) ? $saved['geo_buckets'] : [];
        $savedT  = is_array($saved['traffic_ranges'] ?? null) ? $saved['traffic_ranges'] : [];

        $body  = '<p>Select all that apply. You can change these later in Settings.</p>';
        $body .= self::renderFormOpen();
        $body .= '<h3>Sub-niches</h3>';
        $body .= self::renderCheckboxGroup('sub_niches[]', $subNiches, $savedSn);
        $body .= '<h3>Geo buckets</h3>';
        $body .= self::renderCheckboxGroup('geo_buckets[]', $geos, $savedG);
        $body .= '<h3>Traffic ranges</h3>';
        $body .= self::renderCheckboxGroup('traffic_ranges[]', $traffic, $savedT);
        $body .= '<input type="hidden" name="step" value="' . esc_attr((string) self::STEP_DONE) . '">';
        $body .= '<p>';
        $body .= '<button type="submit" class="button button-primary">Save and continue to Step 3</button> ';
        $body .= '<a href="' . esc_url(add_query_arg('step', (string) self::STEP_WELCOME)) . '" class="button">Back</a>';
        $body .= '</p>';
        $body .= self::renderFormClose();
        return $body;
    }

    /**
     * Render step 3 (done + next steps).
     */
    public static function renderStepDone(): string
    {
        $body  = '<p><strong>All set!</strong> Your audience preferences are saved.</p>';
        $body .= '<h3>Next steps</h3>';
        $body .= '<ol>';
        $body .= '<li><a href="' . esc_url(admin_url('admin.php?page=' . OperatorDashboardPage::MENU_SLUG)) . '">View your dashboard</a> to see credit balance and activity.</li>';
        $body .= '<li><a href="' . esc_url(admin_url('admin.php?page=swapads-client-backlinks')) . '">Create a backlink offer</a> describing the page you want inbound links to.</li>';
        $body .= '<li><a href="' . esc_url(admin_url('admin.php?page=swapads-client-approval')) . '">Review suggested backlinks</a> from operators in your niche.</li>';
        $body .= '</ol>';
        $body .= '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
        $body .= '<input type="hidden" name="action" value="swapads_client_onboarding_step">';
        $body .= '<input type="hidden" name="step" value="complete">';
        $body .= '<input type="hidden" name="swapads_nonce" value="' . esc_attr(wp_create_nonce('swapads_onboarding_step')) . '">';
        $body .= '<p><button type="submit" class="button button-primary">Finish onboarding</button></p>';
        $body .= '</form>';
        return $body;
    }

    /**
     * Render a fallback message when the user is already onboarded.
     */
    public static function renderAlreadyDone(): string
    {
        $body  = '<p>You have already completed onboarding.</p>';
        $body .= '<p><a class="button" href="' . esc_url(admin_url('admin.php?page=' . OperatorDashboardPage::MENU_SLUG)) . '">Go to Dashboard</a></p>';
        return $body;
    }

    /**
     * Render form-open tag with nonce.
     */
    public static function renderFormOpen(): string
    {
        return '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">'
             . '<input type="hidden" name="action" value="swapads_client_onboarding_step">'
             . '<input type="hidden" name="swapads_nonce" value="' . esc_attr(wp_create_nonce('swapads_onboarding_step')) . '">';
    }

    /**
     * Render form-close tag.
     */
    public static function renderFormClose(): string
    {
        return '</form>';
    }

    /**
     * Render a multi-checkbox group.
     *
     * @param string            $name   Form field name (must end with []).
     * @param array<int,string> $values Available options.
     * @param array<int,string> $saved  Selected values.
     */
    public static function renderCheckboxGroup(string $name, array $values, array $saved): string
    {
        if (count($values) === 0) {
            return '<p class="swapads-muted">No options available.</p>';
        }
        $html = '<fieldset class="swapads-checkbox-group">';
        foreach ($values as $v) {
            $v = (string) $v;
            $checked = in_array($v, $saved, true) ? 'checked="checked"' : '';
            $html .= '<label><input type="checkbox" name="' . esc_attr($name) . '" value="' . esc_attr($v) . '" ' . $checked . '> ' . esc_html($v) . '</label>';
        }
        $html .= '</fieldset>';
        return $html;
    }

    /**
     * Fetch audience taxonomy from server.
     *
     * @return array<string, mixed>|null Null on error.
     */
    public static function fetchDefinitions(): ?array
    {
        // F198: RestClient::request() no longer throws for REST/transport
        // failures — read lastError() and persist so render() can show it.
        $client = RestClient::fromOption();
        try {
            $response = $client->get('/v1/audience/definitions');
        } catch (\Throwable $e) {
            set_transient(self::ERROR_CONTEXT, [
                'error_code'  => 'FATAL',
                'message'     => 'Unexpected fatal error: ' . $e->getMessage(),
                'http_status' => 0,
                'when'        => time(),
            ], MINUTE_IN_SECONDS);
            return null;
        }
        if (!is_array($response)) {
            if (($err = $client->lastError()) !== null) {
                set_transient(self::ERROR_CONTEXT, $err, MINUTE_IN_SECONDS);
            }
            return null;
        }
        if (empty($response['success']) && ($err = $client->lastError()) !== null) {
            set_transient(self::ERROR_CONTEXT, $err, MINUTE_IN_SECONDS);
            return null;
        }
        $data = $response['data'] ?? [];
        return is_array($data) ? $data : null;
    }

    /**
     * Fetch the current site's saved audience (best effort).
     *
     * @return array<string, mixed>
     */
    public static function fetchSavedAudience(): array
    {
        try {
            $response = RestClient::fromOption()->get('/v1/audience/get');
        } catch (\Throwable $e) {
            return [];
        }
        if (!is_array($response)) {
            return [];
        }
        $out = [];
        foreach (['sub_niches', 'geo_buckets', 'traffic_ranges'] as $key) {
            $out[$key] = is_array($response[$key] ?? null) ? array_map('strval', $response[$key]) : [];
        }
        return $out;
    }

    /**
     * POST step form to admin-post.php handler.
     */
    public static function handleStepSubmit(): void
    {
        if (!current_user_can('manage_options')) {
            wp_die('Forbidden', '', ['response' => 403]);
        }
        $nonce = isset($_POST['swapads_nonce']) ? (string) wp_unslash($_POST['swapads_nonce']) : '';
        if (!wp_verify_nonce($nonce, 'swapads_onboarding_step')) {
            wp_die('Invalid nonce', '', ['response' => 403]);
        }

        $step = isset($_POST['step']) ? (string) wp_unslash($_POST['step']) : '';
        $userId = get_current_user_id();

        if ($step === 'complete') {
            update_user_meta($userId, self::USER_META_DONE, '1');
            Redirect::to(admin_url('admin.php?page=' . OperatorDashboardPage::MENU_SLUG));
        }

        $target = (int) $step;
        if ($target === self::STEP_AUDIENCE) {
            // Forward from welcome -> audience (no save)
            update_user_meta($userId, 'swapads_client_onboarding_step', (string) self::STEP_AUDIENCE);
            Redirect::to(admin_url('admin.php?page=' . self::MENU_SLUG . '&step=' . self::STEP_AUDIENCE));
        }
        if ($target === self::STEP_DONE) {
            // Save audience + advance
            $subNiches    = isset($_POST['sub_niches'])    ? (array) wp_unslash($_POST['sub_niches'])    : [];
            $geoBuckets   = isset($_POST['geo_buckets'])   ? (array) wp_unslash($_POST['geo_buckets'])   : [];
            $trafficRanges = isset($_POST['traffic_ranges']) ? (array) wp_unslash($_POST['traffic_ranges']) : [];
            self::saveAudience(
                array_values(array_filter($subNiches, 'is_string')),
                array_values(array_filter($geoBuckets, 'is_string')),
                array_values(array_filter($trafficRanges, 'is_string'))
            );
            update_user_meta($userId, 'swapads_client_onboarding_step', (string) self::STEP_DONE);
            Redirect::to(admin_url('admin.php?page=' . self::MENU_SLUG . '&step=' . self::STEP_DONE));
        }

        // Unknown step - bounce to welcome
        Redirect::to(admin_url('admin.php?page=' . self::MENU_SLUG));
    }

    /**
     * Save the audience to the server (POST /v1/audience/set).
     *
     * @param array<int,string> $subNiches
     * @param array<int,string> $geoBuckets
     * @param array<int,string> $trafficRanges
     */
    public static function saveAudience(array $subNiches, array $geoBuckets, array $trafficRanges): bool
    {
        // F198: RestClient::request() never throws for REST/transport
        // failures. On failure, persist the normalized error so render()
        // can show the operator what went wrong.
        $client = RestClient::fromOption();
        $response = $client->post('/v1/audience/set', [
            'sub_niches'     => array_values(array_unique(array_filter($subNiches, 'is_string'))),
            'geo_buckets'    => array_values(array_unique(array_filter($geoBuckets, 'is_string'))),
            'traffic_ranges' => array_values(array_unique(array_filter($trafficRanges, 'is_string'))),
        ]);
        if (!is_array($response) || empty($response['success'])) {
            $err = $client->lastError() ?? $response;
            if (is_array($err)) {
                set_transient(self::ERROR_CONTEXT, $err, MINUTE_IN_SECONDS);
            }
            return false;
        }
        return true;
    }

    /**
     * Is the user finished with onboarding?
     */
    public static function isComplete(int $userId): bool
    {
        return (bool) get_user_meta($userId, self::USER_META_DONE, true);
    }
}
