<?php
/**
 * Pattern library — pre-built block sections users can insert via the "+"
 * between sections in the inline editor. Joost 2026-05-17: closest single
 * gap vs Elementor/Kadence/Divi is "I want a pricing table now, not in 30s
 * of AI gen". 15 hand-crafted patterns in SailWP brand language cover the
 * 80% — AI generation stays the safety net for anything unusual.
 *
 * v1 ships with 3 starter patterns to validate the surface; expand to
 * the full 15 in follow-up sprints based on actual gallery usage.
 */
if (!defined('ABSPATH')) exit;

class SailWP_Copilot_Patterns {

    public function __construct() {
        add_action('rest_api_init', [$this, 'register_routes']);
    }

    public function register_routes() {
        register_rest_route(SailWP_Copilot_REST::NAMESPACE_V1, '/patterns', [
            'methods'             => 'GET',
            'callback'            => [$this, 'list_patterns'],
            'permission_callback' => function () { return current_user_can('edit_posts'); },
        ]);
        register_rest_route(SailWP_Copilot_REST::NAMESPACE_V1, '/patterns/insert', [
            'methods'             => 'POST',
            'callback'            => [$this, 'insert_pattern'],
            'permission_callback' => function () { return current_user_can('edit_posts'); },
        ]);
    }

    /**
     * Pattern definitions. Each pattern is a self-contained chunk of
     * inline-styled HTML (no theme dependency, no plugin block deps) so
     * it renders the same in the gallery preview, in the live page, and
     * on any theme the user later switches to. Joost brand language:
     * DM Sans, coral / dark-navy / warm-cream, generous whitespace,
     * outcome-first copy.
     */
    public static function patterns() {
        // Brand palette
        $coral = '#e8845c';
        $dark  = '#1a1a2e';
        $cream = '#f5ecde';
        $warm  = '#e8b87a';
        // CSS font-family WITHOUT quotes — `DM Sans` and `Segoe UI` parse
        // fine as custom-idents. Quotes here would collide with the outer
        // style="…" attribute and corrupt the whole HTML (every nested "
        // ends the attribute), which is exactly what broke the gallery
        // previews until 2026-05-17 v0.23.5.
        $sans  = 'DM Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif';
        $serif = 'Georgia, Iowan Old Style, Palatino Linotype, serif';

        $patterns = [];

        // ============================================================
        // SIGNATURE PATTERN 1 — Editorial Hero (light + dark variants)
        // ============================================================
        // Typography-driven hero. No buttons (anti-SaaS), one underlined
        // link, eyebrow + italic accent in the headline, optional stat
        // ribbon. Copy is real — "Small websites, made slowly." — to
        // model the kind of voice Joost's audience CAN have, not generic
        // filler. Users see this and think "oh, I could be that warm".
        // ============================================================

        $editorial_hero = function ($variant) use ($coral, $dark, $cream, $warm, $sans, $serif) {
            $is_dark = ($variant === 'dark');
            $bg          = $is_dark ? $dark : $cream;
            $text_color  = $is_dark ? $cream : $dark;
            $eyebrow     = $is_dark ? $warm  : $coral;
            $lede_color  = $is_dark ? 'rgba(245,236,222,0.72)' : 'rgba(26,26,46,0.68)';
            $rule_color  = $is_dark ? 'rgba(245,236,222,0.18)' : 'rgba(26,26,46,0.14)';
            $link_color  = $is_dark ? $warm  : $coral;
            $stat_color  = $is_dark ? 'rgba(245,236,222,0.55)' : 'rgba(26,26,46,0.55)';

            return '<!-- wp:html -->' .
                '<section style="font-family: ' . $sans . '; padding: clamp(80px, 12vw, 160px) clamp(20px, 5vw, 56px); background: ' . $bg . '; color: ' . $text_color . ';">' .
                    '<div style="max-width: 1080px; margin: 0 auto;">' .
                        '<div style="font-size: 11px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; color: ' . $eyebrow . '; margin: 0 0 28px;">Amsterdam · since 2019</div>' .
                        '<h1 style="font-size: clamp(2.6rem, 6vw, 4.6rem); line-height: 1.05; font-weight: 700; margin: 0 0 28px; max-width: 920px; letter-spacing: -0.01em;">Small websites,<br>made <em style="font-style: italic; font-family: ' . $serif . '; font-weight: 500; color: ' . $link_color . ';">slowly</em>.</h1>' .
                        '<p style="font-size: clamp(1.05rem, 1.7vw, 1.2rem); line-height: 1.65; color: ' . $lede_color . '; margin: 0 0 36px; max-width: 600px;">Studio of two. We build calm, well-made sites for people who would rather not call a developer. No retainers, no jargon, no calls before ten.</p>' .
                        '<a href="#" style="display: inline-block; font-size: 1.05rem; font-weight: 500; color: ' . $link_color . '; text-decoration: underline; text-underline-offset: 6px; text-decoration-thickness: 1.5px;">See last year&rsquo;s projects <span aria-hidden="true">&rarr;</span></a>' .
                        '<div style="margin-top: clamp(64px, 10vw, 104px); padding-top: 28px; border-top: 1px solid ' . $rule_color . '; display: flex; flex-wrap: wrap; gap: clamp(24px, 5vw, 72px); font-size: 13px; color: ' . $stat_color . ';">' .
                            '<div><span style="display: block; font-size: 22px; font-weight: 600; color: ' . $text_color . '; margin-bottom: 4px;">12</span>projects this year</div>' .
                            '<div><span style="display: block; font-size: 22px; font-weight: 600; color: ' . $text_color . '; margin-bottom: 4px;">0</span>sales calls placed</div>' .
                            '<div><span style="display: block; font-size: 22px; font-weight: 600; color: ' . $text_color . '; margin-bottom: 4px;">3wk</span>average from brief to live</div>' .
                        '</div>' .
                    '</div>' .
                '</section>' .
                '<!-- /wp:html -->';
        };

        $patterns[] = [
            'id'          => 'hero-editorial-light',
            'name'        => 'Editorial hero · light',
            'category'    => 'Hero',
            'description' => 'Typography-driven hero with eyebrow, italic accent, and a quiet stat strip. Cream background, dark text.',
            'markup'      => $editorial_hero('light'),
        ];
        $patterns[] = [
            'id'          => 'hero-editorial-dark',
            'name'        => 'Editorial hero · dark',
            'category'    => 'Hero',
            'description' => 'Same composition on a dark navy background. For pages with a darker palette.',
            'markup'      => $editorial_hero('dark'),
        ];

        // ============================================================
        // SIGNATURE PATTERN 2 — Quiet Quote (light + dark variants)
        // ============================================================
        // One big quote, generous whitespace, small avatar with initials
        // (no stock photo). Copy is from a fictional but real-feeling
        // baker in Utrecht — voice, location, profession all model the
        // kind of attribution that earns trust. Decorative oversized
        // opening quote mark in coral for visual rhythm.
        // ============================================================

        $quiet_quote = function ($variant) use ($coral, $dark, $cream, $warm, $sans, $serif) {
            $is_dark = ($variant === 'dark');
            $bg            = $is_dark ? $dark : '#ffffff';
            $text_color    = $is_dark ? $cream : $dark;
            $meta_color    = $is_dark ? 'rgba(245,236,222,0.62)' : 'rgba(26,26,46,0.60)';
            $name_color    = $is_dark ? $cream : $dark;
            $mark_color    = $is_dark ? $warm  : $coral;
            $avatar_bg     = $coral;
            $avatar_text   = '#ffffff';

            // Self-contained SVG avatar — initials "MB" on coral disc.
            // No external image dependency. Brand-controlled.
            $avatar = '<svg width="56" height="56" viewBox="0 0 56 56" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style="flex-shrink: 0;">' .
                '<circle cx="28" cy="28" r="28" fill="' . $avatar_bg . '"/>' .
                '<text x="28" y="28" font-family="' . $sans . '" font-size="20" font-weight="600" fill="' . $avatar_text . '" text-anchor="middle" dominant-baseline="central" letter-spacing="0.02em">MB</text>' .
                '</svg>';

            return '<!-- wp:html -->' .
                '<section style="font-family: ' . $sans . '; padding: clamp(80px, 10vw, 128px) clamp(20px, 5vw, 56px); background: ' . $bg . '; color: ' . $text_color . ';">' .
                    '<figure style="max-width: 760px; margin: 0 auto; text-align: center;">' .
                        '<div aria-hidden="true" style="font-family: ' . $serif . '; font-size: clamp(4rem, 7vw, 6rem); line-height: 1; color: ' . $mark_color . '; margin: 0 0 -12px;">&ldquo;</div>' .
                        '<blockquote style="margin: 0 0 32px; padding: 0; border: 0; font-family: ' . $serif . '; font-style: italic; font-size: clamp(1.35rem, 2.4vw, 1.85rem); line-height: 1.45; font-weight: 400; color: ' . $text_color . ';">' .
                            'I emailed in a small panic on a Sunday. By Wednesday the new site was live and people were finding the right page on the first try. I am a baker. I should not have to think this hard about technology.' .
                        '</blockquote>' .
                        '<figcaption style="display: inline-flex; align-items: center; gap: 14px; text-align: left;">' .
                            $avatar .
                            '<div>' .
                                '<div style="font-size: 0.95rem; font-weight: 600; color: ' . $name_color . '; line-height: 1.3;">Marije van Beek</div>' .
                                '<div style="font-size: 0.85rem; color: ' . $meta_color . '; line-height: 1.4; margin-top: 2px;">Bakker Marije · Utrecht</div>' .
                            '</div>' .
                        '</figcaption>' .
                    '</figure>' .
                '</section>' .
                '<!-- /wp:html -->';
        };

        $patterns[] = [
            'id'          => 'quote-quiet-light',
            'name'        => 'Quiet quote · light',
            'category'    => 'Testimonial',
            'description' => 'One large italic quote, soft attribution. White background.',
            'markup'      => $quiet_quote('light'),
        ];
        $patterns[] = [
            'id'          => 'quote-quiet-dark',
            'name'        => 'Quiet quote · dark',
            'category'    => 'Testimonial',
            'description' => 'Same quote on a dark navy background, for pages with a darker palette.',
            'markup'      => $quiet_quote('dark'),
        ];

        // ============================================================
        // SIGNATURE PATTERN 3 — Honest Pricing (light + dark)
        // ============================================================
        // Two columns, Free vs Pro. Real positioning copy, no checkmark
        // walls, no "Most popular" badge gimmick. Joost design-philosophy
        // applied: radicale eerlijkheid ("cancel any time, no setup fee,
        // no salesperson"), concrete differentiators.
        // ============================================================

        $honest_pricing = function ($variant) use ($coral, $dark, $cream, $sans, $serif) {
            $is_dark = ($variant === 'dark');
            $bg          = $is_dark ? $dark : $cream;
            $text_color  = $is_dark ? $cream : $dark;
            $sub_color   = $is_dark ? 'rgba(245,236,222,0.65)' : 'rgba(26,26,46,0.65)';
            $card_bg     = $is_dark ? 'rgba(245,236,222,0.04)' : '#ffffff';
            $card_border = $is_dark ? 'rgba(245,236,222,0.12)' : 'rgba(26,26,46,0.08)';
            $accent      = $coral;

            $card_free = '<div style="background: ' . $card_bg . '; border: 1px solid ' . $card_border . '; border-radius: 18px; padding: 36px 32px;">' .
                '<div style="font-size: 11px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; color: ' . $sub_color . '; margin: 0 0 14px;">Free</div>' .
                '<div style="font-family: ' . $serif . '; font-size: 2.6rem; line-height: 1; font-weight: 500; margin: 0 0 14px; color: ' . $text_color . ';">€0</div>' .
                '<p style="font-size: 0.98rem; line-height: 1.55; color: ' . $sub_color . '; margin: 0 0 24px;">Everything you need to put a small site together. Bring your own time.</p>' .
                '<ul style="list-style: none; padding: 0; margin: 0 0 28px; font-size: 0.95rem; line-height: 1.7; color: ' . $text_color . ';">' .
                    '<li style="padding: 4px 0;">Up to 5 pages</li>' .
                    '<li style="padding: 4px 0;">Theme + page templates</li>' .
                    '<li style="padding: 4px 0;">SSL + daily backups included</li>' .
                    '<li style="padding: 4px 0;">Community Discord</li>' .
                '</ul>' .
                '<a href="#" style="display: inline-block; font-size: 0.98rem; font-weight: 500; color: ' . $text_color . '; text-decoration: underline; text-underline-offset: 5px;">Start free <span aria-hidden="true">&rarr;</span></a>' .
            '</div>';

            $card_pro = '<div style="background: ' . $card_bg . '; border: 2px solid ' . $accent . '; border-radius: 18px; padding: 36px 32px; position: relative;">' .
                '<div style="font-size: 11px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; color: ' . $accent . '; margin: 0 0 14px;">Pro</div>' .
                '<div style="font-family: ' . $serif . '; font-size: 2.6rem; line-height: 1; font-weight: 500; margin: 0 0 6px; color: ' . $text_color . ';">€19<span style="font-family: ' . $sans . '; font-size: 1rem; font-weight: 500; color: ' . $sub_color . '; margin-left: 4px;">/month</span></div>' .
                '<div style="font-size: 0.85rem; color: ' . $sub_color . '; margin: 0 0 18px;">or €190/year — two months on us.</div>' .
                '<p style="font-size: 0.98rem; line-height: 1.55; color: ' . $sub_color . '; margin: 0 0 24px;">For folks who want it all polished, without the time or the patience to fiddle.</p>' .
                '<ul style="list-style: none; padding: 0; margin: 0 0 28px; font-size: 0.95rem; line-height: 1.7; color: ' . $text_color . ';">' .
                    '<li style="padding: 4px 0;">Everything in Free</li>' .
                    '<li style="padding: 4px 0;">Unlimited pages + posts</li>' .
                    '<li style="padding: 4px 0;">AI co-pilot inside the editor</li>' .
                    '<li style="padding: 4px 0;">Email support within a day</li>' .
                '</ul>' .
                '<a href="#" style="display: inline-block; padding: 12px 22px; background: ' . $accent . '; color: #ffffff; text-decoration: none; border-radius: 999px; font-size: 0.98rem; font-weight: 600;">Choose Pro</a>' .
            '</div>';

            // Pro button uses a token that gets swapped at render-time
            // with the user's Stripe Payment Link from Settings →
            // Integrations. Defaults to "#" if unset.
            $card_pro = str_replace('href="#"', 'href="__SWCP_STRIPE_PRO_URL__"', $card_pro);

            return '<!-- wp:html -->' .
                '<section style="font-family: ' . $sans . '; padding: clamp(72px, 10vw, 128px) clamp(20px, 5vw, 56px); background: ' . $bg . '; color: ' . $text_color . ';">' .
                    '<div style="max-width: 920px; margin: 0 auto;">' .
                        '<div style="text-align: center; margin-bottom: clamp(40px, 6vw, 64px);">' .
                            '<h2 style="font-size: clamp(2rem, 3.4vw, 2.8rem); line-height: 1.15; font-weight: 700; margin: 0 0 14px; letter-spacing: -0.01em;">Pick what fits.</h2>' .
                            '<p style="font-size: 1.05rem; line-height: 1.6; color: ' . $sub_color . '; margin: 0; max-width: 480px; margin-left: auto; margin-right: auto;">Cancel any time. No setup fee. No salesperson on a follow-up call.</p>' .
                        '</div>' .
                        '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; align-items: stretch;">' .
                            $card_free .
                            $card_pro .
                        '</div>' .
                    '</div>' .
                '</section>' .
                '<!-- /wp:html -->';
        };

        $patterns[] = [
            'id'          => 'pricing-honest-light',
            'name'        => 'Honest pricing · light',
            'category'    => 'Pricing',
            'description' => 'Free vs Pro, two cards. Real positioning copy, no checkmark walls.',
            'markup'      => $honest_pricing('light'),
        ];
        $patterns[] = [
            'id'          => 'pricing-honest-dark',
            'name'        => 'Honest pricing · dark',
            'category'    => 'Pricing',
            'description' => 'Same two-card pricing on a dark navy background.',
            'markup'      => $honest_pricing('dark'),
        ];

        // ============================================================
        // SIGNATURE PATTERN 4 — Contact (light + dark)
        // ============================================================
        // Split layout: editorial intro on the left, form on the right.
        // Form uses mailto: as a no-backend default; users can wire it
        // to WPForms / Fluent / Contact Form 7 later. Single Send link
        // (no oversized button), small reassurance line below.
        // ============================================================

        $contact_form = function ($variant) use ($coral, $dark, $cream, $sans, $serif) {
            $is_dark = ($variant === 'dark');
            $bg            = $is_dark ? $dark : '#ffffff';
            $text_color    = $is_dark ? $cream : $dark;
            $sub_color     = $is_dark ? 'rgba(245,236,222,0.70)' : 'rgba(26,26,46,0.70)';
            $input_bg      = $is_dark ? 'rgba(245,236,222,0.06)' : '#fbfaf6';
            $input_border  = $is_dark ? 'rgba(245,236,222,0.16)' : 'rgba(26,26,46,0.12)';
            $input_color   = $is_dark ? $cream : $dark;

            return '<!-- wp:html -->' .
                '<section style="font-family: ' . $sans . '; padding: clamp(72px, 10vw, 128px) clamp(20px, 5vw, 56px); background: ' . $bg . '; color: ' . $text_color . ';">' .
                    '<div style="max-width: 1080px; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: clamp(36px, 6vw, 80px); align-items: start;">' .
                        '<div>' .
                            '<div style="font-size: 11px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; color: ' . $coral . '; margin: 0 0 18px;">Get in touch</div>' .
                            '<h2 style="font-size: clamp(2rem, 3.6vw, 2.8rem); line-height: 1.15; font-weight: 700; margin: 0 0 18px; letter-spacing: -0.01em;">Tell us what you&rsquo;re after.</h2>' .
                            '<p style="font-size: 1.05rem; line-height: 1.65; color: ' . $sub_color . '; margin: 0 0 14px;">We answer every email within a day, sometimes within an hour. If it&rsquo;s urgent, write &ldquo;urgent&rdquo; in the subject and we&rsquo;ll know.</p>' .
                            '<p style="font-size: 0.92rem; line-height: 1.6; color: ' . $sub_color . '; margin: 0;">Or just write to <a href="__SWCP_FORM_MAILTO__" style="color: ' . $coral . '; text-decoration: underline; text-underline-offset: 4px;">__SWCP_FORM_EMAIL__</a>.</p>' .
                        '</div>' .
                        '<form action="__SWCP_FORM_ENDPOINT__" method="post" enctype="text/plain" style="display: flex; flex-direction: column; gap: 14px;">' .
                            '<label style="display: flex; flex-direction: column; gap: 6px;"><span style="font-size: 0.85rem; font-weight: 600; color: ' . $sub_color . ';">Your name</span><input type="text" name="name" required style="font: inherit; padding: 12px 14px; background: ' . $input_bg . '; border: 1px solid ' . $input_border . '; border-radius: 10px; color: ' . $input_color . ';"></label>' .
                            '<label style="display: flex; flex-direction: column; gap: 6px;"><span style="font-size: 0.85rem; font-weight: 600; color: ' . $sub_color . ';">Your email</span><input type="email" name="email" required style="font: inherit; padding: 12px 14px; background: ' . $input_bg . '; border: 1px solid ' . $input_border . '; border-radius: 10px; color: ' . $input_color . ';"></label>' .
                            '<label style="display: flex; flex-direction: column; gap: 6px;"><span style="font-size: 0.85rem; font-weight: 600; color: ' . $sub_color . ';">What can we help with</span><textarea name="message" rows="5" required style="font: inherit; padding: 12px 14px; background: ' . $input_bg . '; border: 1px solid ' . $input_border . '; border-radius: 10px; color: ' . $input_color . '; resize: vertical; min-height: 120px;"></textarea></label>' .
                            '<button type="submit" style="margin-top: 4px; padding: 13px 22px; background: ' . $coral . '; color: #ffffff; border: none; border-radius: 999px; font: inherit; font-weight: 600; font-size: 0.98rem; cursor: pointer; align-self: flex-start;">Send <span aria-hidden="true">&rarr;</span></button>' .
                        '</form>' .
                    '</div>' .
                '</section>' .
                '<!-- /wp:html -->';
        };

        $patterns[] = [
            'id'          => 'contact-form-light',
            'name'        => 'Contact form · light',
            'category'    => 'Contact',
            'description' => 'Split layout, intro left, form right. mailto: by default — wire to WPForms / Fluent later.',
            'markup'      => $contact_form('light'),
        ];
        $patterns[] = [
            'id'          => 'contact-form-dark',
            'name'        => 'Contact form · dark',
            'category'    => 'Contact',
            'description' => 'Same form layout on a dark background.',
            'markup'      => $contact_form('dark'),
        ];

        // ============================================================
        // SIGNATURE PATTERN 5 — Email signup (light + dark)
        // ============================================================
        // Newsletter signup with Buttondown-compatible form action. Works
        // out of the box with Buttondown, Mailchimp, MailerLite and any
        // other service that accepts a POST with `email` field — user
        // just swaps the action URL. Inline input + button, centred copy
        // above. No fake "join 10,000 readers" social proof.
        // ============================================================

        $email_signup = function ($variant) use ($coral, $dark, $cream, $sans, $serif) {
            $is_dark = ($variant === 'dark');
            $bg            = $is_dark ? $dark : $cream;
            $text_color    = $is_dark ? $cream : $dark;
            $sub_color     = $is_dark ? 'rgba(245,236,222,0.70)' : 'rgba(26,26,46,0.65)';
            $input_bg      = $is_dark ? 'rgba(245,236,222,0.08)' : '#ffffff';
            $input_border  = $is_dark ? 'rgba(245,236,222,0.18)' : 'rgba(26,26,46,0.12)';
            $input_color   = $is_dark ? $cream : $dark;
            $faint_color   = $is_dark ? 'rgba(245,236,222,0.45)' : 'rgba(26,26,46,0.45)';

            return '<!-- wp:html -->' .
                '<section style="font-family: ' . $sans . '; padding: clamp(72px, 10vw, 120px) clamp(20px, 5vw, 56px); background: ' . $bg . '; color: ' . $text_color . ';">' .
                    '<div style="max-width: 640px; margin: 0 auto; text-align: center;">' .
                        '<div style="font-size: 11px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; color: ' . $coral . '; margin: 0 0 22px;">Newsletter</div>' .
                        '<h2 style="font-family: ' . $serif . '; font-style: italic; font-size: clamp(2.2rem, 4vw, 3.4rem); line-height: 1.1; font-weight: 500; margin: 0 0 18px; letter-spacing: -0.01em;">Once a month.</h2>' .
                        '<p style="font-size: 1.05rem; line-height: 1.65; color: ' . $sub_color . '; margin: 0 0 36px;">A short note about what we shipped and what we learned. No tracking, no upsells, unsubscribe any time.</p>' .
                        '<form action="__SWCP_NEWSLETTER_URL__" method="post" style="display: flex; flex-wrap: wrap; gap: 8px; max-width: 480px; margin: 0 auto;">' .
                            '<input type="email" name="email" placeholder="your@email.com" required style="flex: 1 1 240px; min-width: 0; font: inherit; padding: 14px 18px; background: ' . $input_bg . '; border: 1px solid ' . $input_border . '; border-radius: 999px; color: ' . $input_color . ';">' .
                            '<button type="submit" style="padding: 14px 26px; background: ' . $coral . '; color: #ffffff; border: none; border-radius: 999px; font: inherit; font-weight: 600; font-size: 0.98rem; cursor: pointer;">Subscribe</button>' .
                        '</form>' .
                        '<p style="font-size: 0.78rem; color: ' . $faint_color . '; margin: 22px 0 0;">__SWCP_NEWSLETTER_HINT__</p>' .
                    '</div>' .
                '</section>' .
                '<!-- /wp:html -->';
        };

        $patterns[] = [
            'id'          => 'email-signup-light',
            'name'        => 'Email signup · light',
            'category'    => 'Newsletter',
            'description' => 'Single-column centred signup. Form action is Buttondown-style — swap to Mailchimp / MailerLite by editing the URL.',
            'markup'      => $email_signup('light'),
        ];
        $patterns[] = [
            'id'          => 'email-signup-dark',
            'name'        => 'Email signup · dark',
            'category'    => 'Newsletter',
            'description' => 'Same signup form on a dark navy background.',
            'markup'      => $email_signup('dark'),
        ];

        // ============================================================
        // SIGNATURE PATTERN 6 — Image gallery (light + dark)
        // ============================================================
        // 3x2 grid of six photos. Placeholders are SVG data-URIs (subtle
        // brand-coloured gradients) so the pattern renders even without
        // a media library. Users dblclick each one to upload their own.
        // ============================================================

        $image_gallery = function ($variant) use ($coral, $dark, $cream, $warm, $sans) {
            $is_dark = ($variant === 'dark');
            $bg          = $is_dark ? $dark : '#ffffff';
            $text_color  = $is_dark ? $cream : $dark;
            $sub_color   = $is_dark ? 'rgba(245,236,222,0.65)' : 'rgba(26,26,46,0.60)';

            // Six SVG gradient placeholders, each base64-encoded as a
            // data-URI. Differentiated colour scheme per cell so the
            // grid feels intentional even before the user swaps in real
            // photos. Light theme uses warm tones, dark theme cooler.
            $placeholders = [];
            $palettes = $is_dark
                ? [['#1f2247','#3a3f6e'], ['#2a3a4a','#1f5a6c'], ['#4a2a3a','#7a3f5a'], ['#2a4a3a','#4a7a5a'], ['#3a2a4a','#5a3a6e'], ['#4a3a2a','#7a5a3a']]
                : [['#f3d9c1','#e8845c'], ['#e8e1d4','#bfb3a0'], ['#e6dac8','#c4a382'], ['#f5dcb8','#e8b87a'], ['#dde3e8','#a8b4be'], ['#ecdcd5','#c9a99c']];
            foreach ($palettes as $i => $pair) {
                $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4 3"><defs><linearGradient id="g' . $i . '" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="' . $pair[0] . '"/><stop offset="1" stop-color="' . $pair[1] . '"/></linearGradient></defs><rect width="4" height="3" fill="url(#g' . $i . ')"/></svg>';
                $b64 = base64_encode($svg);
                $placeholders[] = 'data:image/svg+xml;base64,' . $b64;
            }

            $cells = '';
            foreach ($placeholders as $i => $url) {
                $cells .= '<figure style="margin: 0; aspect-ratio: 4/3; overflow: hidden; border-radius: 14px; background: rgba(26,26,46,0.04);">' .
                    '<img src="' . $url . '" alt="Photo " loading="lazy" style="width: 100%; height: 100%; object-fit: cover; display: block;">' .
                    '</figure>';
            }

            return '<!-- wp:html -->' .
                '<section style="font-family: ' . $sans . '; padding: clamp(72px, 10vw, 120px) clamp(20px, 5vw, 56px); background: ' . $bg . '; color: ' . $text_color . ';">' .
                    '<div style="max-width: 1200px; margin: 0 auto;">' .
                        '<div style="margin-bottom: clamp(32px, 5vw, 56px);">' .
                            '<h2 style="font-size: clamp(1.8rem, 3vw, 2.4rem); line-height: 1.2; font-weight: 700; margin: 0 0 12px; letter-spacing: -0.01em;">A few moments from last year.</h2>' .
                            '<p style="font-size: 1.05rem; line-height: 1.6; color: ' . $sub_color . '; margin: 0; max-width: 540px;">Double-click any photo to swap it for your own.</p>' .
                        '</div>' .
                        '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px;">' .
                            $cells .
                        '</div>' .
                    '</div>' .
                '</section>' .
                '<!-- /wp:html -->';
        };

        $patterns[] = [
            'id'          => 'gallery-grid-light',
            'name'        => 'Image gallery · light',
            'category'    => 'Gallery',
            'description' => 'Six-photo grid with placeholder gradients. Double-click each to replace with your own image.',
            'markup'      => $image_gallery('light'),
        ];
        $patterns[] = [
            'id'          => 'gallery-grid-dark',
            'name'        => 'Image gallery · dark',
            'category'    => 'Gallery',
            'description' => 'Same six-photo grid on a dark navy background.',
            'markup'      => $image_gallery('dark'),
        ];

        return $patterns;

        // === LEGACY v0.23.0 patterns below — pulled 2026-05-17 (generic,
        // didn't meet design bar). Kept commented purely for reference of
        // the markup shape; do NOT re-enable as-is. =====================
        /*
        $coral = '#e8845c';
        $dark  = '#1a1a2e';
        $cream = '#f5ecde';
        $warm  = '#e8b87a';
        $base  = 'font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;';

        $patterns = [];

        // -- Hero: minimal headline + lead + 2 buttons -------------------
        $hero_minimal_html = '<!-- wp:html -->' .
            '<section class="swcp-pattern-hero-minimal" style="' . $base . ' padding: clamp(48px, 8vw, 96px) clamp(20px, 5vw, 56px); background: ' . $cream . '; color: ' . $dark . ';">' .
                '<div style="max-width: 920px; margin: 0 auto; text-align: left;">' .
                    '<h1 style="font-size: clamp(2.2rem, 5vw, 3.6rem); line-height: 1.1; font-weight: 700; margin: 0 0 18px;">A headline that earns the click.</h1>' .
                    '<p style="font-size: clamp(1.05rem, 1.8vw, 1.25rem); line-height: 1.6; color: rgba(26,26,46,0.75); margin: 0 0 32px; max-width: 640px;">One short paragraph that tells someone what you do and why it matters. Plain words, no jargon, ends with a comma — not a full stop.</p>' .
                    '<div style="display: flex; gap: 12px; flex-wrap: wrap;">' .
                        '<a href="#" style="display: inline-block; padding: 14px 26px; background: ' . $coral . '; color: #fff; text-decoration: none; border-radius: 999px; font-weight: 600; font-size: 15px;">Get started</a>' .
                        '<a href="#" style="display: inline-block; padding: 14px 26px; background: transparent; color: ' . $dark . '; text-decoration: none; border: 1.5px solid ' . $dark . '; border-radius: 999px; font-weight: 600; font-size: 15px;">See how it works</a>' .
                    '</div>' .
                '</div>' .
            '</section>' .
            '<!-- /wp:html -->';
        $patterns[] = [
            'id'         => 'hero-minimal',
            'name'       => 'Minimal hero',
            'category'   => 'Hero',
            'description'=> 'Headline + lead + two buttons. Quiet and confident.',
            'markup'     => $hero_minimal_html,
        ];

        // -- Features: 3-column with simple emoji icons ------------------
        $features_html = '<!-- wp:html -->' .
            '<section class="swcp-pattern-features-3col" style="' . $base . ' padding: clamp(56px, 8vw, 96px) clamp(20px, 5vw, 56px); background: #ffffff; color: ' . $dark . ';">' .
                '<div style="max-width: 1080px; margin: 0 auto;">' .
                    '<h2 style="font-size: clamp(1.8rem, 3vw, 2.4rem); line-height: 1.2; font-weight: 700; margin: 0 0 12px; text-align: center;">Three things you get out of the box</h2>' .
                    '<p style="font-size: 1.05rem; line-height: 1.6; color: rgba(26,26,46,0.65); margin: 0 auto 56px; text-align: center; max-width: 600px;">A short subtitle that frames what the three columns below add up to.</p>' .
                    '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 32px;">' .
                        '<div>' .
                            '<div style="width: 48px; height: 48px; border-radius: 12px; background: ' . $cream . '; display: flex; align-items: center; justify-content: center; font-size: 24px; margin-bottom: 18px;">✨</div>' .
                            '<h3 style="font-size: 1.25rem; line-height: 1.3; font-weight: 600; margin: 0 0 8px;">First benefit</h3>' .
                            '<p style="font-size: 0.98rem; line-height: 1.6; color: rgba(26,26,46,0.7); margin: 0;">One or two sentences explaining what this means for the reader. Keep it grounded.</p>' .
                        '</div>' .
                        '<div>' .
                            '<div style="width: 48px; height: 48px; border-radius: 12px; background: ' . $cream . '; display: flex; align-items: center; justify-content: center; font-size: 24px; margin-bottom: 18px;">⚡</div>' .
                            '<h3 style="font-size: 1.25rem; line-height: 1.3; font-weight: 600; margin: 0 0 8px;">Second benefit</h3>' .
                            '<p style="font-size: 0.98rem; line-height: 1.6; color: rgba(26,26,46,0.7); margin: 0;">Another concrete thing the reader will notice once they start using it.</p>' .
                        '</div>' .
                        '<div>' .
                            '<div style="width: 48px; height: 48px; border-radius: 12px; background: ' . $cream . '; display: flex; align-items: center; justify-content: center; font-size: 24px; margin-bottom: 18px;">🌊</div>' .
                            '<h3 style="font-size: 1.25rem; line-height: 1.3; font-weight: 600; margin: 0 0 8px;">Third benefit</h3>' .
                            '<p style="font-size: 0.98rem; line-height: 1.6; color: rgba(26,26,46,0.7); margin: 0;">Round it off — three usually feels complete without padding.</p>' .
                        '</div>' .
                    '</div>' .
                '</div>' .
            '</section>' .
            '<!-- /wp:html -->';
        $patterns[] = [
            'id'         => 'features-3col',
            'name'       => 'Three benefits',
            'category'   => 'Features',
            'description'=> 'Three columns with icon, title, short paragraph.',
            'markup'     => $features_html,
        ];

        // -- Pricing: 3-tier card grid -----------------------------------
        $pricing_card_basic = '<div style="background: #ffffff; border: 1px solid rgba(26,26,46,0.10); border-radius: 16px; padding: 32px 28px;">' .
            '<div style="font-size: 0.85rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: rgba(26,26,46,0.55); margin-bottom: 12px;">Starter</div>' .
            '<div style="font-size: 2.5rem; font-weight: 700; line-height: 1; margin-bottom: 4px;">€9<span style="font-size: 1rem; font-weight: 500; color: rgba(26,26,46,0.55);">/mo</span></div>' .
            '<p style="font-size: 0.95rem; line-height: 1.5; color: rgba(26,26,46,0.65); margin: 0 0 24px;">For getting started without thinking too hard about it.</p>' .
            '<ul style="list-style: none; padding: 0; margin: 0 0 28px;">' .
                '<li style="padding: 8px 0; font-size: 0.95rem; color: ' . $dark . ';">✓ Core feature one</li>' .
                '<li style="padding: 8px 0; font-size: 0.95rem; color: ' . $dark . ';">✓ Core feature two</li>' .
                '<li style="padding: 8px 0; font-size: 0.95rem; color: ' . $dark . ';">✓ Core feature three</li>' .
            '</ul>' .
            '<a href="#" style="display: block; padding: 12px; text-align: center; background: transparent; color: ' . $dark . '; text-decoration: none; border: 1.5px solid ' . $dark . '; border-radius: 999px; font-weight: 600; font-size: 14px;">Choose Starter</a>' .
        '</div>';

        $pricing_card_pro = '<div style="background: ' . $dark . '; color: #ffffff; border-radius: 16px; padding: 32px 28px; position: relative; transform: translateY(-8px); box-shadow: 0 20px 40px rgba(26,26,46,0.18);">' .
            '<div style="position: absolute; top: -12px; left: 50%; transform: translateX(-50%); background: ' . $coral . '; color: #fff; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; padding: 5px 12px; border-radius: 999px;">Most picked</div>' .
            '<div style="font-size: 0.85rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: rgba(255,255,255,0.6); margin-bottom: 12px;">Pro</div>' .
            '<div style="font-size: 2.5rem; font-weight: 700; line-height: 1; margin-bottom: 4px;">€29<span style="font-size: 1rem; font-weight: 500; color: rgba(255,255,255,0.6);">/mo</span></div>' .
            '<p style="font-size: 0.95rem; line-height: 1.5; color: rgba(255,255,255,0.7); margin: 0 0 24px;">For folks who want everything and a real edge.</p>' .
            '<ul style="list-style: none; padding: 0; margin: 0 0 28px;">' .
                '<li style="padding: 8px 0; font-size: 0.95rem; color: #ffffff;">✓ Everything in Starter</li>' .
                '<li style="padding: 8px 0; font-size: 0.95rem; color: #ffffff;">✓ Pro feature one</li>' .
                '<li style="padding: 8px 0; font-size: 0.95rem; color: #ffffff;">✓ Pro feature two</li>' .
                '<li style="padding: 8px 0; font-size: 0.95rem; color: #ffffff;">✓ Priority support</li>' .
            '</ul>' .
            '<a href="#" style="display: block; padding: 12px; text-align: center; background: ' . $coral . '; color: #fff; text-decoration: none; border-radius: 999px; font-weight: 600; font-size: 14px;">Choose Pro</a>' .
        '</div>';

        $pricing_card_team = '<div style="background: #ffffff; border: 1px solid rgba(26,26,46,0.10); border-radius: 16px; padding: 32px 28px;">' .
            '<div style="font-size: 0.85rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; color: rgba(26,26,46,0.55); margin-bottom: 12px;">Team</div>' .
            '<div style="font-size: 2.5rem; font-weight: 700; line-height: 1; margin-bottom: 4px;">€79<span style="font-size: 1rem; font-weight: 500; color: rgba(26,26,46,0.55);">/mo</span></div>' .
            '<p style="font-size: 0.95rem; line-height: 1.5; color: rgba(26,26,46,0.65); margin: 0 0 24px;">For small teams who collaborate on the same site.</p>' .
            '<ul style="list-style: none; padding: 0; margin: 0 0 28px;">' .
                '<li style="padding: 8px 0; font-size: 0.95rem; color: ' . $dark . ';">✓ Everything in Pro</li>' .
                '<li style="padding: 8px 0; font-size: 0.95rem; color: ' . $dark . ';">✓ Up to 5 editors</li>' .
                '<li style="padding: 8px 0; font-size: 0.95rem; color: ' . $dark . ';">✓ Shared workspace</li>' .
            '</ul>' .
            '<a href="#" style="display: block; padding: 12px; text-align: center; background: transparent; color: ' . $dark . '; text-decoration: none; border: 1.5px solid ' . $dark . '; border-radius: 999px; font-weight: 600; font-size: 14px;">Choose Team</a>' .
        '</div>';

        $pricing_html = '<!-- wp:html -->' .
            '<section class="swcp-pattern-pricing-3tier" style="' . $base . ' padding: clamp(64px, 9vw, 112px) clamp(20px, 5vw, 56px); background: ' . $cream . '; color: ' . $dark . ';">' .
                '<div style="max-width: 1100px; margin: 0 auto;">' .
                    '<h2 style="font-size: clamp(1.9rem, 3vw, 2.5rem); line-height: 1.2; font-weight: 700; margin: 0 0 14px; text-align: center;">Pick what fits.</h2>' .
                    '<p style="font-size: 1.05rem; line-height: 1.6; color: rgba(26,26,46,0.65); margin: 0 auto 56px; text-align: center; max-width: 540px;">Three honest tiers. Cancel any time, no setup fee, no salesperson.</p>' .
                    '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 24px; align-items: start;">' .
                        $pricing_card_basic .
                        $pricing_card_pro .
                        $pricing_card_team .
                    '</div>' .
                '</div>' .
            '</section>' .
            '<!-- /wp:html -->';
        $patterns[] = [
            'id'         => 'pricing-3tier',
            'name'       => 'Three-tier pricing',
            'category'   => 'Pricing',
            'description'=> 'Starter / Pro (highlighted) / Team with feature lists.',
            'markup'     => $pricing_html,
        ];

        return $patterns;
        */
    }

    /**
     * GET /patterns — list metadata + rendered HTML for gallery preview.
     * We send the rendered HTML (post do_blocks) so the gallery can show
     * an accurate scaled-down preview without a per-card iframe.
     */
    public function list_patterns(WP_REST_Request $request) {
        $patterns = self::patterns();
        $out = [];
        foreach ($patterns as $p) {
            $markup = self::swap_integration_tokens((string) $p['markup']);
            $out[] = [
                'id'          => $p['id'],
                'name'        => $p['name'],
                'category'    => $p['category'],
                'description' => $p['description'],
                'html'        => do_blocks($markup),
            ];
        }
        return rest_ensure_response([
            'ok'       => true,
            'patterns' => $out,
        ]);
    }

    /**
     * Replace __SWCP_*__ tokens in pattern markup with stored Integration
     * values from Settings → Integrations. Falls back to sensible
     * defaults when an option is empty so the pattern still works
     * out of the box. Joost 2026-05-17 v0.24.1.
     */
    public static function swap_integration_tokens($markup) {
        $stripe_pro_url = trim((string) get_option('sailwp_copilot_stripe_pro_url', ''));
        if ($stripe_pro_url === '') $stripe_pro_url = '#';

        $newsletter_url = trim((string) get_option('sailwp_copilot_newsletter_url', ''));
        if ($newsletter_url === '') {
            $newsletter_url = 'https://buttondown.email/api/emails/embed-subscribe/YOUR_USERNAME';
            $newsletter_hint = 'Set your Buttondown / Mailchimp / MailerLite URL in Settings → Integrations.';
        } else {
            $newsletter_hint = '';
        }

        $form_endpoint = trim((string) get_option('sailwp_copilot_form_endpoint', ''));
        if ($form_endpoint === '') {
            $form_endpoint = 'mailto:hello@example.com';
            $form_mailto   = 'mailto:hello@example.com';
            $form_email    = 'hello@example.com';
        } else if (stripos($form_endpoint, 'mailto:') === 0) {
            $email = substr($form_endpoint, 7);
            $form_mailto = $form_endpoint;
            $form_email  = $email;
        } else {
            // Formspree / Basin / Getform — keep endpoint as the form
            // action, swap the visible "or just write to" mailto/email
            // pair to placeholders so the user can repoint manually.
            $form_mailto = 'mailto:hello@example.com';
            $form_email  = 'hello@example.com';
        }

        return str_replace(
            [
                '__SWCP_STRIPE_PRO_URL__',
                '__SWCP_NEWSLETTER_URL__',
                '__SWCP_NEWSLETTER_HINT__',
                '__SWCP_FORM_ENDPOINT__',
                '__SWCP_FORM_MAILTO__',
                '__SWCP_FORM_EMAIL__',
            ],
            [
                esc_url($stripe_pro_url),
                esc_url($newsletter_url),
                esc_html($newsletter_hint),
                esc_url($form_endpoint),
                esc_url($form_mailto),
                esc_html($form_email),
            ],
            (string) $markup
        );
    }

    /**
     * POST /patterns/insert
     * Body: { post_id, pattern_id, anchor_block_index?, position? }
     *   - position: 'before' | 'after' | 'end' (default 'end')
     *   - anchor_block_index: zero-based index into top-level parsed blocks
     *     where the insert should happen (only when position != 'end').
     * Returns: { ok, html, save_index }
     *   - html: rendered HTML of the newly-inserted pattern (for client
     *     DOM injection)
     *   - save_index: where in the post the new block sits, so the client
     *     can locate it for hover / inline-edit wiring
     */
    public function insert_pattern(WP_REST_Request $request) {
        $post_id    = (int) $request->get_param('post_id');
        $pattern_id = (string) $request->get_param('pattern_id');
        $position   = (string) ($request->get_param('position') ?: 'end');
        $anchor     = (int) $request->get_param('anchor_block_index');
        if (!$post_id || !$pattern_id) {
            return new WP_Error('missing_params', 'post_id and pattern_id are required', ['status' => 400]);
        }
        $post = get_post($post_id);
        if (!$post) {
            return new WP_Error('not_found', 'That page is not here anymore.', ['status' => 404]);
        }
        $patterns = self::patterns();
        $found = null;
        foreach ($patterns as $p) {
            if ($p['id'] === $pattern_id) { $found = $p; break; }
        }
        if (!$found) {
            return new WP_Error('pattern_unknown', 'I do not have a pattern with that id.', ['status' => 404]);
        }

        // Parse + splice. Top-level blocks only — the "+" between sections
        // lives in inline-edit which operates at the top level.
        $blocks = parse_blocks((string) $post->post_content);
        $resolved_markup = self::swap_integration_tokens((string) $found['markup']);
        $new_blocks = parse_blocks($resolved_markup);

        $insert_at = count($blocks); // default: append
        if ($position === 'before' || $position === 'after') {
            $insert_at = max(0, min(count($blocks), $anchor + ($position === 'after' ? 1 : 0)));
        }
        array_splice($blocks, $insert_at, 0, $new_blocks);

        $new_content = serialize_blocks($blocks);
        $update = wp_update_post([
            'ID'           => $post_id,
            'post_content' => $new_content,
        ], true);
        if (is_wp_error($update)) {
            return new WP_Error('save_failed', $update->get_error_message(), ['status' => 500]);
        }

        return rest_ensure_response([
            'ok'         => true,
            'pattern_id' => $pattern_id,
            'html'       => do_blocks($resolved_markup),
            'save_index' => $insert_at,
        ]);
    }
}
