<?php
/**
 * Tool: design_page_preview
 *
 * Step 1 of the custom design workflow. Creates a NEW DRAFT page with a
 * fresh, custom-designed layout based on an existing page's content.
 * The original page is left untouched. Returns a preview link so the
 * user can review the design before committing.
 *
 * After approval, apply_page_design swaps the design into the original
 * page (preserving slug, ID, internal links, SEO).
 *
 * Plus: a custom minimal renderer (template_redirect) that serves the
 * preview at /?swcp_render_preview=ID with NO theme chrome, NO admin
 * bar, and NO rail — just the design itself, on a magical cream stage.
 */
if (!defined('ABSPATH')) exit;

// Daily cleanup of forgotten previews (older than 7 days, hard delete).
add_action('init', function () {
    if (!wp_next_scheduled('swcp_purge_old_previews')) {
        wp_schedule_event(time() + 3600, 'daily', 'swcp_purge_old_previews');
    }
});
add_action('swcp_purge_old_previews', function () {
    $cutoff = gmdate('Y-m-d H:i:s', time() - 7 * DAY_IN_SECONDS);
    $previews = get_posts([
        'post_type'      => 'page',
        'post_status'    => ['draft', 'trash'],
        'meta_key'       => '_swcp_is_preview',
        'meta_value'     => 1,
        'date_query'     => [['column' => 'post_modified_gmt', 'before' => $cutoff]],
        'posts_per_page' => 50,
        'fields'         => 'ids',
    ]);
    foreach ($previews as $id) {
        wp_delete_post($id, true);
    }
});

// Hide Co-pilot preview drafts from the admin Pages list. They are an
// implementation detail of the preview flow, not user-managed content —
// seeing "Home — Draft" alongside the real "Home" was confusing. Joost
// 2026-05-19 video feedback: he couldn't tell which Home was his real
// page. apply_page_design trashes the preview on success, but a preview
// generated and abandoned (e.g. user iterated then never applied) used
// to linger until the 7-day purge. We hide them at the list level now,
// and the new preview-flow code below also hard-deletes any existing
// previews for the same original before generating a new one.
add_action('pre_get_posts', function ($query) {
    if (!is_admin() || !$query->is_main_query()) return;
    if (!function_exists('get_current_screen')) return;
    $screen = function_exists('get_current_screen') ? get_current_screen() : null;
    if (!$screen || $screen->id !== 'edit-page') return;
    $existing = (array) $query->get('meta_query', []);
    $existing[] = [
        'key'     => '_swcp_is_preview',
        'compare' => 'NOT EXISTS',
    ];
    $query->set('meta_query', $existing);
});

/**
 * Hard-delete every existing preview draft for the given original post.
 * Called right before inserting a new preview so the system always
 * holds at most one preview per original — no pile-up in the Pages
 * list, no leftover after the user iterates without applying.
 */
function swcp_purge_existing_previews_for($original_id) {
    $existing = get_posts([
        'post_type'      => 'page',
        'post_status'    => 'any',
        'meta_query'     => [
            ['key' => '_swcp_is_preview', 'value' => 1],
            ['key' => '_swcp_preview_of', 'value' => (int) $original_id],
        ],
        'posts_per_page' => 50,
        'fields'         => 'ids',
    ]);
    foreach ($existing as $id) {
        wp_delete_post($id, true);
    }
}

add_action('template_redirect', 'swcp_maybe_render_preview', 1);
function swcp_maybe_render_preview() {
    if (!isset($_GET['swcp_render_preview'])) return;
    $preview_id = (int) $_GET['swcp_render_preview'];
    if (!$preview_id) return;
    if (!current_user_can('edit_posts')) {
        wp_die('You need editor permissions to view a co-pilot preview.', 'Forbidden', ['response' => 403]);
    }
    $post = get_post($preview_id);
    if (!$post || $post->post_type !== 'page' || !get_post_meta($preview_id, '_swcp_is_preview', true)) {
        wp_die('That preview is not here anymore.', 'Not found', ['response' => 404]);
    }

    // Two preview kinds:
    //   - 'blocks'  : Gutenberg-block preview from redesign_page. Render
    //                 server-side via do_blocks() so the iframe shows the
    //                 reshuffled sections without any host-theme chrome.
    //   - default   : design_page_preview's Opus-designed raw HTML. Strip
    //                 the wp:html markers and dump the design as-is.
    $preview_kind = (string) get_post_meta($preview_id, '_swcp_preview_kind', true);
    if ($preview_kind === 'blocks') {
        $content = do_blocks((string) $post->post_content);
    } else {
        $content = (string) $post->post_content;
        $content = preg_replace('/<!--\s*\/?wp:html\s*-->/', '', $content);
    }

    nocache_headers();
    header('Content-Type: text/html; charset=UTF-8');
    header('X-Frame-Options: SAMEORIGIN');

    $title = esc_html(get_the_title($post));
    $locale = esc_attr(get_locale());
    $tool_styles_url = SAILWP_COPILOT_URL . 'assets/css/tool-styles.css?v=' . SAILWP_COPILOT_VERSION;
    ?>
<!DOCTYPE html>
<html lang="<?php echo $locale; ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex,nofollow">
<title><?php echo $title; ?> · SailWP Preview</title>
<?php if ($preview_kind === 'blocks'): ?>
<link rel="stylesheet" href="<?php echo esc_url($tool_styles_url); ?>">
<?php endif; ?>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
    min-height: 100vh;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, "Helvetica Neue", sans-serif;
    color: #3a2e24;
    background: radial-gradient(circle at 18% -10%, #fff5e8 0%, #fbf6ee 38%, #f5ecde 100%);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}
body {
    padding: clamp(20px, 4vw, 56px) clamp(12px, 3vw, 40px);
    overflow-x: hidden;
}
.swcp-stage-glow {
    position: fixed;
    inset: 0;
    pointer-events: none;
    background:
        radial-gradient(900px circle at 20% 0%,   rgba(232, 132, 92, 0.20), transparent 60%),
        radial-gradient(1000px circle at 100% 100%, rgba(232, 184, 122, 0.18), transparent 60%);
    z-index: 0;
    animation: swcp-stage-glow-breath 9s ease-in-out infinite alternate;
}
@keyframes swcp-stage-glow-breath {
    from { opacity: 0.8; transform: translate(-4px, -4px); }
    to   { opacity: 1;   transform: translate(8px, 6px);   }
}
.swcp-stage {
    position: relative;
    z-index: 1;
    max-width: 1080px;
    margin: 0 auto;
    background: #ffffff;
    border-radius: 20px;
    box-shadow:
        0 60px 120px rgba(232, 132, 92, 0.18),
        0 20px 50px rgba(122, 81, 56, 0.12),
        0 0 0 1px rgba(122, 81, 56, 0.05);
    overflow: hidden;
    animation: swcp-stage-entrance 0.6s cubic-bezier(0.2, 0.7, 0.2, 1);
}
@keyframes swcp-stage-entrance {
    from { opacity: 0; transform: translateY(12px) scale(0.985); }
    to   { opacity: 1; transform: translateY(0)    scale(1); }
}
/* The design's own scoped CSS (under .swcp-design-root) drives layout
   inside the stage. We don't override anything here. */
<?php if ($preview_kind === 'blocks'): ?>
/* Block-preview baseline: minimal typography so Gutenberg blocks read
   sensibly without the host theme. The redesigned-section / lead-image
   classes from tool-styles.css handle the rest. */
.swcp-stage {
    padding: clamp(32px, 6vw, 64px) clamp(20px, 5vw, 56px);
    color: #2b2018;
}
.swcp-stage > * + * { margin-top: 0.9rem; }
.swcp-stage h1 { font-size: clamp(2rem, 4vw, 2.8rem); line-height: 1.2; margin-top: 1.5rem; font-weight: 700; }
.swcp-stage h2 { font-size: clamp(1.5rem, 2.8vw, 2rem); line-height: 1.3; margin-top: 2.2rem; font-weight: 700; }
.swcp-stage h3 { font-size: clamp(1.2rem, 2.1vw, 1.5rem); line-height: 1.35; margin-top: 1.6rem; font-weight: 600; }
.swcp-stage p, .swcp-stage li { font-size: 1.05rem; line-height: 1.7; }
.swcp-stage ul, .swcp-stage ol { padding-inline-start: 1.5rem; }
.swcp-stage img { max-width: 100%; height: auto; display: block; }
.swcp-stage figure { margin: 1.5rem 0; }
.swcp-stage a { color: #c4593a; text-decoration: underline; text-underline-offset: 3px; }
.swcp-stage .wp-block-group.alignfull {
    margin-inline: calc(-1 * clamp(20px, 5vw, 56px));
    padding-inline: clamp(20px, 5vw, 56px);
}
<?php endif; ?>
</style>
</head>
<body>
<div class="swcp-stage-glow" aria-hidden="true"></div>
<div class="swcp-stage">
<?php echo $content; ?>
</div>
</body>
</html>
    <?php
    exit;
}

function swcp_tool_design_page_preview($input, $ctx) {
    $page_arg   = isset($input['page']) ? $input['page'] : 'current';
    $style_hint = isset($input['style_hint']) ? (string) $input['style_hint'] : '';
    $additional = isset($input['additional_paragraphs']) && is_array($input['additional_paragraphs'])
        ? array_values(array_filter(array_map('strval', $input['additional_paragraphs']), 'strlen'))
        : [];
    $extra_image_urls = isset($input['additional_image_urls']) && is_array($input['additional_image_urls'])
        ? array_values(array_filter(array_map('strval', $input['additional_image_urls']), 'strlen'))
        : [];
    $preserve_text = !empty($input['preserve_text']);
    $resolved   = swcp_resolve_or_error($page_arg, $ctx['post_id'] ?? 0);
    if (is_array($resolved) && isset($resolved['ok']) && !$resolved['ok']) return $resolved;
    $original = $resolved;

    $compat = swcp_check_builder_compat($original->ID);
    if ($compat !== null) return $compat;

    $blocks = swcp_get_blocks($original);
    if (empty($blocks) && empty($additional)) {
        return ['ok' => false, 'message' => 'The page "' . esc_html(get_the_title($original)) . '" is still empty. Add some text or images first, then I can design it.'];
    }

    list($text_pieces, $image_refs) = swcp_extract_content_for_design($blocks);

    // Sparse-content gate: if the page has barely any body text and the
    // caller has NOT supplied additional_paragraphs, ask Claude to gather
    // content from the user before invoking Opus on a near-empty page.
    if (empty($additional)) {
        $headings = [];
        $body_chars = 0;
        foreach ($text_pieces as $piece) {
            if (preg_match('/^H[1-6]:\s*(.*)$/', (string) $piece, $m)) {
                $headings[] = $m[1];
            } else {
                $body_chars += mb_strlen((string) $piece);
            }
        }
        $body_paragraphs = max(0, count($text_pieces) - count($headings));
        if ($body_chars < 200 || $body_paragraphs < 2) {
            return [
                'ok'             => false,
                'sparse'         => true,
                'message'        => 'The page "' . esc_html(get_the_title($original)) . '" only has section titles and barely any body text yet. Ask the user a few short questions to gather what they want each section to say, then call this tool again with their answers in the additional_paragraphs argument.',
                'page_title'     => get_the_title($original),
                'headings_found' => $headings,
                'body_chars'     => $body_chars,
                'image_count'    => count($image_refs),
            ];
        }
    } else {
        // Splice the gathered paragraphs into the text fed to Opus.
        $text_pieces = array_merge($text_pieces, $additional);
    }

    // Append any user-uploaded images (just-uploaded via the rail) so they
    // make it into the design alongside images already on the page.
    foreach ($extra_image_urls as $url) {
        $image_refs[] = ['src' => $url, 'alt' => '', 'caption' => 'User-uploaded photo'];
    }

    if (empty($text_pieces) && empty($image_refs)) {
        return ['ok' => false, 'message' => 'I could not find any text or images on "' . esc_html(get_the_title($original)) . '". There is nothing to design with yet.'];
    }

    // Gather a tight site-style-hints object so Opus can match the host
    // theme's colors/fonts instead of inventing a brand from scratch.
    $style_hints = swcp_collect_site_style_hints();

    // Kick off the Opus design as an ASYNC job. The proxy returns a job_id
    // immediately (<1s) so PHP-FPM/Nginx never see a long-running upstream
    // request. The rail polls /copilot/design-page-status until the design
    // is ready, then calls our /design-finalize REST endpoint to create
    // the preview draft. Robust against any upstream timeout.
    $proxy_root = SailWP_Copilot_Plugin::get_ai_proxy_url();
    $url  = $proxy_root . '/copilot/design-page-async';
    $body = wp_json_encode([
        'page_title'        => get_the_title($original),
        'site_name'         => get_bloginfo('name'),
        'site_url'          => home_url(),
        'style_hint'        => $style_hint,
        'site_style_hints'  => $style_hints,
        'brand_profile'     => class_exists('SailWP_Copilot_Brand_Profile')
            ? SailWP_Copilot_Brand_Profile::for_proxy()
            : null,
        'preserve_text'     => $preserve_text,
        'content'           => [
            'text'   => $text_pieces,
            'images' => $image_refs,
        ],
    ]);

    $response = wp_remote_post($url, [
        'timeout' => 15,
        'headers' => array_filter([
            'Content-Type'   => 'application/json',
            'X-License-Key'  => SailWP_Copilot_Plugin::get_license_key() ?: null,
            'X-Site-API-Key' => SailWP_Copilot_Plugin::get_site_api_key() ?: null,
        ]),
        'body' => $body,
    ]);
    if (is_wp_error($response)) {
        return ['ok' => false, 'message' => 'The design service did not respond. Try once more in a moment.'];
    }
    $code    = wp_remote_retrieve_response_code($response);
    $payload = json_decode(wp_remote_retrieve_body($response), true);
    if ($code === 402 && is_array($payload) && !empty($payload['limit_reached'])) {
        $upgrade = !empty($payload['upgrade_url']) ? $payload['upgrade_url'] : $payload['pricing_url'];
        return [
            'ok'              => false,
            'limit_reached'   => true,
            'message'         => $payload['message'],
            'upgrade_url'     => $upgrade,
            'pricing_url'     => $payload['pricing_url'],
            'plan'            => $payload['plan'] ?? 'free',
            'inline_upgrade'  => $payload['inline_upgrade'] ?? null,
        ];
    }
    if ($code >= 400 || empty($payload['ok']) || empty($payload['job_id'])) {
        $msg = is_array($payload) && !empty($payload['message']) ? $payload['message'] : 'Could not start the design job. Try again.';
        return ['ok' => false, 'message' => $msg];
    }

    // Hand the rail the job_id + the info needed to finalize. The rail
    // polls the proxy directly (CORS-open) and then calls our REST
    // /finalize-design endpoint to create the draft post.
    return [
        'ok'              => true,
        'pending'         => true,
        'job_id'          => $payload['job_id'],
        'status_endpoint' => $proxy_root . '/copilot/design-page-status',
        'original_id'     => (int) $original->ID,
        'page_title'      => get_the_title($original),
        'message'         => 'Designing your page now. This takes about a minute for interactive designs.',
    ];
}

/**
 * Finalize a previously started async design: fetch the rendered HTML
 * from the proxy job, create the preview draft post, and return the
 * usual preview_id / view_url / edit_url so the rail can slide it in.
 * Called by the rail once /copilot/design-page-status reports done.
 */
function swcp_finalize_design($job_id, $original_id) {
    $job_id      = (string) $job_id;
    $original_id = (int) $original_id;
    if ($job_id === '' || $original_id <= 0) {
        return ['ok' => false, 'message' => 'Missing finalize parameters.'];
    }
    $original = get_post($original_id);
    if (!$original instanceof WP_Post) {
        return ['ok' => false, 'message' => 'Original page disappeared.'];
    }

    $status_url = SailWP_Copilot_Plugin::get_ai_proxy_url() . '/copilot/design-page-status?job_id=' . rawurlencode($job_id);
    $resp = wp_remote_get($status_url, ['timeout' => 15]);
    if (is_wp_error($resp)) {
        return ['ok' => false, 'message' => 'Could not retrieve the finished design.'];
    }
    $data = json_decode(wp_remote_retrieve_body($resp), true);
    if (!is_array($data) || empty($data['ok'])) {
        return ['ok' => false, 'message' => 'Design status check failed.'];
    }
    if ($data['status'] !== 'done' || empty($data['result']['html'])) {
        return ['ok' => false, 'message' => 'Design not ready yet.'];
    }

    $design_html = (string) $data['result']['html'];
    $new_content =
        "<!-- wp:html -->\n" .
        $design_html . "\n" .
        "<!-- /wp:html -->";

    // Purge any leftover preview drafts for this original page so the
    // Pages list never accumulates duplicates. The hide-from-list filter
    // above already keeps them invisible, but hard-deleting them also
    // avoids stale state inside iterate_page_design's preview lookups.
    swcp_purge_existing_previews_for((int) $original->ID);

    // Create a NEW draft page so the original stays exactly as it is.
    $preview_title = get_the_title($original) . ' (preview)';
    $preview_slug  = $original->post_name . '-preview-' . wp_generate_uuid4();

    $preview_id = wp_insert_post([
        'post_type'    => 'page',
        'post_status'  => 'draft',
        'post_title'   => $preview_title,
        'post_name'    => $preview_slug,
        'post_content' => $new_content,
    ], true);
    if (is_wp_error($preview_id) || !$preview_id) {
        return ['ok' => false, 'message' => 'Designed the layout but could not save the preview.'];
    }

    // Link preview ↔ original so apply_page_design knows what to swap.
    update_post_meta($preview_id, '_swcp_preview_of', (int) $original->ID);
    update_post_meta($preview_id, '_swcp_is_preview', 1);

    // Custom-render URL: strips theme chrome, admin bar, and the rail itself
    // so the preview shows ONLY the design — no inception.
    $preview_url = add_query_arg('swcp_render_preview', $preview_id, home_url('/'));
    $edit_url    = swcp_edit_url_for($preview_id);

    return [
        'ok'           => true,
        'message'      => 'I built a preview of "' . esc_html(get_the_title($original)) . '". Open the link to see it. Your live page is unchanged. If you like the new design, tell me to apply it.',
        'preview_id'   => (int) $preview_id,
        'original_id'  => (int) $original->ID,
        'preview_url'  => $preview_url,
        'edit_url'     => $edit_url,
        'view_url'     => $preview_url,
    ];
}
