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

class IDVE_AI {

    private $settings;
    private $blocks;

    /**
     * Valid column width fractions.
     */
    private static $valid_widths = array(
        '1/1', '1/2', '1/3', '2/3', '1/4', '3/4', '1/5', '2/5', '3/5', '4/5',
    );

    public function __construct( $settings, IDVE_Blocks $blocks ) {
        $this->settings = wp_parse_args( $settings, array(
            'provider'      => 'openai',
            'api_key'       => '',
            'model'         => '',
            'custom_prompt' => '',
        ) );
        $this->blocks = $blocks;
    }

    /**
     * Check if the AI feature is configured (has an API key).
     */
    public function is_configured() {
        return ! empty( $this->settings['api_key'] );
    }

    /**
     * Main generate method — orchestrates prompt building, API call, parsing, and validation.
     *
     * @param string $prompt  User's natural language prompt.
     * @param string $mode    One of: generate_page, add_section, edit_element, create_class.
     * @param array  $context Optional context (currentTree, selectedElement, selectedType).
     * @return array|WP_Error
     */
    public function generate( $prompt, $mode, $context = array() ) {
        if ( ! $this->is_configured() ) {
            return new WP_Error( 'ai_not_configured', __( 'AI is not configured. Please add an API key in Settings > IDVE.', 'insight-dezign-visual-editor' ), array( 'status' => 400 ) );
        }

        $valid_modes = array( 'generate_page', 'add_section', 'edit_element', 'refine_page', 'create_class' );
        if ( ! in_array( $mode, $valid_modes, true ) ) {
            return new WP_Error( 'ai_invalid_mode', __( 'Invalid AI mode.', 'insight-dezign-visual-editor' ), array( 'status' => 400 ) );
        }

        $system_prompt = $this->build_system_prompt( $mode, $context );
        $provider      = $this->settings['provider'];
        $model         = $this->settings['model'];

        // Default models if none specified.
        if ( empty( $model ) ) {
            $model = 'openai' === $provider ? 'gpt-4o' : 'claude-sonnet-4-20250514';
        }

        if ( 'anthropic' === $provider ) {
            $raw = $this->call_anthropic( $system_prompt, $prompt, $model );
        } else {
            $raw = $this->call_openai( $system_prompt, $prompt, $model );
        }

        if ( is_wp_error( $raw ) ) {
            return $raw;
        }

        $parsed = $this->parse_response( $raw );
        if ( is_wp_error( $parsed ) ) {
            return $parsed;
        }

        // Validate and auto-repair based on mode.
        if ( 'create_class' === $mode ) {
            if ( ! isset( $parsed['globalCss'] ) || ! is_string( $parsed['globalCss'] ) ) {
                return new WP_Error( 'ai_invalid_response', __( 'AI did not return valid CSS.', 'insight-dezign-visual-editor' ), array( 'status' => 502 ) );
            }
            return array( 'globalCss' => $parsed['globalCss'] );
        }

        if ( 'edit_element' === $mode ) {
            if ( ! isset( $parsed['element'] ) || ! is_array( $parsed['element'] ) ) {
                return new WP_Error( 'ai_invalid_response', __( 'AI did not return a valid element.', 'insight-dezign-visual-editor' ), array( 'status' => 502 ) );
            }
            $parsed['element'] = $this->validate_block( $parsed['element'] );
            return array( 'element' => $parsed['element'] );
        }

        if ( 'add_section' === $mode ) {
            $sections = isset( $parsed['sections'] ) ? $parsed['sections'] : array();
            if ( ! is_array( $sections ) || empty( $sections ) ) {
                return new WP_Error( 'ai_invalid_response', __( 'AI did not return valid sections.', 'insight-dezign-visual-editor' ), array( 'status' => 502 ) );
            }
            $validated = array();
            foreach ( $sections as $sec ) {
                $validated[] = $this->validate_section( $sec );
            }
            $result = array( 'sections' => $validated );
            if ( ! empty( $parsed['globalCss'] ) && is_string( $parsed['globalCss'] ) ) {
                $result['globalCss'] = wp_strip_all_tags( $parsed['globalCss'] );
            }
            return $result;
        }

        if ( 'refine_page' === $mode ) {
            if ( ! isset( $parsed['tree'] ) || ! is_array( $parsed['tree'] ) ) {
                if ( isset( $parsed['sections'] ) && is_array( $parsed['sections'] ) ) {
                    $parsed['tree'] = array( 'sections' => $parsed['sections'] );
                } else {
                    return new WP_Error( 'ai_invalid_response', __( 'AI did not return a valid page tree.', 'insight-dezign-visual-editor' ), array( 'status' => 502 ) );
                }
            }
            $tree   = $this->validate_tree( $parsed['tree'] );
            $result = array( 'tree' => $tree );
            if ( ! empty( $parsed['globalCss'] ) && is_string( $parsed['globalCss'] ) ) {
                $result['globalCss'] = wp_strip_all_tags( $parsed['globalCss'] );
            }
            return $result;
        }

        // generate_page
        if ( ! isset( $parsed['tree'] ) || ! is_array( $parsed['tree'] ) ) {
            // Maybe AI returned sections at top level.
            if ( isset( $parsed['sections'] ) && is_array( $parsed['sections'] ) ) {
                $parsed['tree'] = array( 'sections' => $parsed['sections'] );
            } else {
                return new WP_Error( 'ai_invalid_response', __( 'AI did not return a valid page tree.', 'insight-dezign-visual-editor' ), array( 'status' => 502 ) );
            }
        }

        $tree   = $this->validate_tree( $parsed['tree'] );
        $result = array( 'tree' => $tree );
        if ( ! empty( $parsed['globalCss'] ) && is_string( $parsed['globalCss'] ) ) {
            $result['globalCss'] = wp_strip_all_tags( $parsed['globalCss'] );
        }
        return $result;
    }

    /**
     * Build the system prompt based on mode and context.
     */
    public function build_system_prompt( $mode, $context = array() ) {
        $parts = array();

        // A. Base rules
        $parts[] = $this->get_base_rules();

        // B. Block type schemas
        $parts[] = $this->get_block_schemas();

        // C. Design best practices
        $parts[] = $this->get_design_practices();

        // D. Available global CSS classes
        $parts[] = $this->get_global_classes();

        // E. Mode-specific instructions
        $parts[] = $this->get_mode_instructions( $mode, $context );

        // F. Custom prompt
        if ( ! empty( $this->settings['custom_prompt'] ) ) {
            $parts[] = "## Additional Instructions\n" . $this->settings['custom_prompt'];
        }

        return implode( "\n\n", array_filter( $parts ) );
    }

    /**
     * Call OpenAI Chat Completions API.
     */
    private function call_openai( $system, $user, $model ) {
        $response = wp_remote_post( 'https://api.openai.com/v1/chat/completions', array(
            'timeout' => 120,
            'headers' => array(
                'Content-Type'  => 'application/json',
                'Authorization' => 'Bearer ' . $this->settings['api_key'],
            ),
            'body' => wp_json_encode( array(
                'model'           => $model,
                'response_format' => array( 'type' => 'json_object' ),
                'messages'        => array(
                    array( 'role' => 'system', 'content' => $system ),
                    array( 'role' => 'user', 'content' => $user ),
                ),
                'temperature' => 0.4,
                'max_tokens'  => 16384,
            ) ),
        ) );

        return $this->handle_api_response( $response, 'openai' );
    }

    /**
     * Call Anthropic Messages API.
     *
     * Uses assistant prefill trick: start with `{` to force JSON-only output.
     */
    private function call_anthropic( $system, $user, $model ) {
        $response = wp_remote_post( 'https://api.anthropic.com/v1/messages', array(
            'timeout' => 120,
            'headers' => array(
                'Content-Type'      => 'application/json',
                'x-api-key'         => $this->settings['api_key'],
                'anthropic-version' => '2023-06-01',
            ),
            'body' => wp_json_encode( array(
                'model'      => $model,
                'system'     => $system,
                'messages'   => array(
                    array( 'role' => 'user', 'content' => $user . "\n\nRespond with ONLY the JSON object. No explanation, no markdown fences, no text before or after the JSON." ),
                    array( 'role' => 'assistant', 'content' => '{' ),
                ),
                'max_tokens'  => 16384,
                'temperature' => 0.4,
            ) ),
        ) );

        return $this->handle_api_response( $response, 'anthropic' );
    }

    /**
     * Handle the raw HTTP response from either provider.
     */
    private function handle_api_response( $response, $provider ) {
        if ( is_wp_error( $response ) ) {
            return new WP_Error( 'ai_network_error', __( 'Could not connect to AI provider. Please try again.', 'insight-dezign-visual-editor' ), array( 'status' => 502 ) );
        }

        $code = wp_remote_retrieve_response_code( $response );
        $body = wp_remote_retrieve_body( $response );

        if ( 401 === $code ) {
            return new WP_Error( 'ai_auth_error', __( 'Invalid API key. Please check your key in Settings > IDVE.', 'insight-dezign-visual-editor' ), array( 'status' => 401 ) );
        }
        if ( 429 === $code ) {
            return new WP_Error( 'ai_rate_limit', __( 'Rate limited by AI provider. Please wait a moment and try again.', 'insight-dezign-visual-editor' ), array( 'status' => 429 ) );
        }
        if ( $code >= 500 ) {
            return new WP_Error( 'ai_provider_error', __( 'AI provider is experiencing issues. Please try again later.', 'insight-dezign-visual-editor' ), array( 'status' => 502 ) );
        }
        if ( $code < 200 || $code >= 300 ) {
            // Log the raw body for debugging non-standard errors.
            error_log( '[IDVE AI] API error HTTP ' . $code . ': ' . substr( $body, 0, 500 ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            // translators: %d is the HTTP status code returned by the AI provider.
            return new WP_Error( 'ai_api_error', sprintf( __( 'AI API error (HTTP %d).', 'insight-dezign-visual-editor' ), $code ), array( 'status' => 502 ) );
        }

        $data = json_decode( $body, true );
        if ( ! $data ) {
            error_log( '[IDVE AI] Could not decode provider response: ' . substr( $body, 0, 500 ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            return new WP_Error( 'ai_invalid_response', __( 'Could not parse AI provider response.', 'insight-dezign-visual-editor' ), array( 'status' => 502 ) );
        }

        // Extract text content from response.
        if ( 'anthropic' === $provider ) {
            // Check for truncation — stop_reason "max_tokens" means output was cut off.
            $stop_reason = isset( $data['stop_reason'] ) ? $data['stop_reason'] : '';
            if ( 'max_tokens' === $stop_reason ) {
                error_log( '[IDVE AI] Anthropic response was truncated (max_tokens reached).' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            }

            if ( isset( $data['content'][0]['text'] ) ) {
                $text = $data['content'][0]['text'];
                // Prepend the `{` we used as assistant prefill.
                return '{' . $text;
            }
            error_log( '[IDVE AI] Unexpected Anthropic format: ' . substr( $body, 0, 500 ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            return new WP_Error( 'ai_invalid_response', __( 'Unexpected Anthropic response format.', 'insight-dezign-visual-editor' ), array( 'status' => 502 ) );
        }

        // OpenAI
        if ( isset( $data['choices'][0]['message']['content'] ) ) {
            return $data['choices'][0]['message']['content'];
        }
        error_log( '[IDVE AI] Unexpected OpenAI format: ' . substr( $body, 0, 500 ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
        return new WP_Error( 'ai_invalid_response', __( 'Unexpected OpenAI response format.', 'insight-dezign-visual-editor' ), array( 'status' => 502 ) );
    }

    /**
     * Parse JSON from the AI response text.
     */
    public function parse_response( $raw ) {
        // Trim whitespace.
        $raw = trim( $raw );

        // 1. Try direct decode.
        $decoded = json_decode( $raw, true );
        if ( is_array( $decoded ) && ! empty( $decoded ) ) {
            return $decoded;
        }

        // 2. Try extracting from the LAST code fence (the actual output, not examples).
        if ( preg_match_all( '/```(?:json)?\s*([\s\S]*?)```/', $raw, $all_matches ) ) {
            // Try from last match backwards.
            for ( $i = count( $all_matches[1] ) - 1; $i >= 0; $i-- ) {
                $decoded = json_decode( trim( $all_matches[1][ $i ] ), true );
                if ( is_array( $decoded ) && ! empty( $decoded ) ) {
                    return $decoded;
                }
            }
        }

        // 3. Try extracting from outermost braces — find the first `{` and the last `}`.
        $first_brace = strpos( $raw, '{' );
        $last_brace  = strrpos( $raw, '}' );
        if ( false !== $first_brace && false !== $last_brace && $last_brace > $first_brace ) {
            $json_str = substr( $raw, $first_brace, $last_brace - $first_brace + 1 );
            $decoded  = json_decode( $json_str, true );
            if ( is_array( $decoded ) && ! empty( $decoded ) ) {
                return $decoded;
            }
        }

        // 4. Log what we received for debugging.
        error_log( '[IDVE AI] Failed to parse JSON. Raw response (first 1000 chars): ' . substr( $raw, 0, 1000 ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
        $json_error = json_last_error_msg();
        error_log( '[IDVE AI] json_decode error: ' . $json_error ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log

        return new WP_Error(
            'ai_invalid_response',
            // translators: %s is the JSON parsing error message.
            sprintf( __( 'AI response was not valid JSON (%s). Please try again.', 'insight-dezign-visual-editor' ), $json_error ),
            array( 'status' => 502 )
        );
    }

    /**
     * Validate and auto-repair a full tree.
     */
    public function validate_tree( $tree ) {
        if ( ! isset( $tree['sections'] ) || ! is_array( $tree['sections'] ) ) {
            $tree['sections'] = array();
        }

        $validated = array();
        foreach ( $tree['sections'] as $sec ) {
            $validated[] = $this->validate_section( $sec );
        }
        $tree['sections'] = $validated;

        // Deduplicate IDs — AI models sometimes reuse IDs across sections.
        $seen = array();
        $this->dedup_ids( $tree, $seen );

        return $tree;
    }

    /**
     * Walk the tree and replace any duplicate IDs with fresh ones.
     */
    private function dedup_ids( &$tree, &$seen ) {
        if ( empty( $tree['sections'] ) ) {
            return;
        }
        foreach ( $tree['sections'] as &$sec ) {
            $this->dedup_element_id( $sec, 'idve_sec', $seen );
            if ( empty( $sec['rows'] ) ) {
                continue;
            }
            foreach ( $sec['rows'] as &$row ) {
                $this->dedup_element_id( $row, 'idve_row', $seen );
                if ( empty( $row['columns'] ) ) {
                    continue;
                }
                foreach ( $row['columns'] as &$col ) {
                    $this->dedup_element_id( $col, 'idve_col', $seen );
                    if ( ! empty( $col['blocks'] ) ) {
                        $this->dedup_block_ids( $col['blocks'], $seen );
                    }
                }
            }
        }
    }

    /**
     * Replace an element's ID if it was already seen.
     */
    private function dedup_element_id( &$el, $prefix, &$seen ) {
        if ( ! empty( $el['id'] ) ) {
            if ( isset( $seen[ $el['id'] ] ) ) {
                $el['id'] = $this->generate_id( $prefix );
            }
            $seen[ $el['id'] ] = true;
        }
    }

    /**
     * Recursively deduplicate block IDs (handles group children).
     */
    private function dedup_block_ids( &$blocks, &$seen ) {
        foreach ( $blocks as &$block ) {
            $this->dedup_element_id( $block, 'idve_blk', $seen );
            if ( ! empty( $block['blocks'] ) ) {
                $this->dedup_block_ids( $block['blocks'], $seen );
            }
        }
    }

    /**
     * Validate a section.
     */
    private function validate_section( $sec ) {
        if ( ! is_array( $sec ) ) {
            $sec = array();
        }
        if ( empty( $sec['id'] ) ) {
            $sec['id'] = $this->generate_id( 'idve_sec' );
        }
        $sec['settings'] = $this->ensure_section_settings( isset( $sec['settings'] ) ? $sec['settings'] : array() );

        if ( ! isset( $sec['rows'] ) || ! is_array( $sec['rows'] ) ) {
            $sec['rows'] = array();
        }

        $rows = array();
        foreach ( $sec['rows'] as $row ) {
            $rows[] = $this->validate_row( $row );
        }
        $sec['rows'] = $rows;
        return $sec;
    }

    /**
     * Validate a row.
     */
    private function validate_row( $row ) {
        if ( ! is_array( $row ) ) {
            $row = array();
        }
        if ( empty( $row['id'] ) ) {
            $row['id'] = $this->generate_id( 'idve_row' );
        }
        $row['settings'] = $this->ensure_row_settings( isset( $row['settings'] ) ? $row['settings'] : array() );

        if ( ! isset( $row['columns'] ) || ! is_array( $row['columns'] ) ) {
            $row['columns'] = array();
        }

        $cols = array();
        foreach ( $row['columns'] as $col ) {
            $cols[] = $this->validate_column( $col );
        }
        $row['columns'] = $cols;
        return $row;
    }

    /**
     * Validate a column.
     */
    private function validate_column( $col ) {
        if ( ! is_array( $col ) ) {
            $col = array();
        }
        if ( empty( $col['id'] ) ) {
            $col['id'] = $this->generate_id( 'idve_col' );
        }
        if ( empty( $col['width'] ) || ! in_array( $col['width'], self::$valid_widths, true ) ) {
            $col['width'] = '1/1';
        }
        $col['settings'] = $this->ensure_column_settings( isset( $col['settings'] ) ? $col['settings'] : array() );

        if ( ! isset( $col['blocks'] ) || ! is_array( $col['blocks'] ) ) {
            $col['blocks'] = array();
        }

        $blocks = array();
        foreach ( $col['blocks'] as $block ) {
            $validated = $this->validate_block( $block );
            if ( $validated ) {
                $blocks[] = $validated;
            }
        }
        $col['blocks'] = $blocks;
        return $col;
    }

    /**
     * Validate a block. Returns null for unrecognized types.
     */
    private function validate_block( $block ) {
        if ( ! is_array( $block ) ) {
            return null;
        }

        $type = isset( $block['type'] ) ? $block['type'] : '';
        $block_def = $this->blocks->get( $type );
        if ( ! $block_def ) {
            return null;
        }

        if ( empty( $block['id'] ) ) {
            $block['id'] = $this->generate_id( 'idve_blk' );
        }

        // Ensure data has all default fields.
        if ( ! isset( $block['data'] ) || ! is_array( $block['data'] ) ) {
            $block['data'] = array();
        }
        foreach ( $block_def['fields'] as $name => $field ) {
            if ( ! array_key_exists( $name, $block['data'] ) ) {
                $block['data'][ $name ] = isset( $field['default'] ) ? $field['default'] : '';
            }
        }

        // Ensure settings.
        $block['settings'] = $this->ensure_block_settings( isset( $block['settings'] ) ? $block['settings'] : array() );

        // Handle group block children.
        if ( 'group' === $type && isset( $block['blocks'] ) && is_array( $block['blocks'] ) ) {
            $children = array();
            foreach ( $block['blocks'] as $child ) {
                $v = $this->validate_block( $child );
                if ( $v ) {
                    $children[] = $v;
                }
            }
            $block['blocks'] = $children;
        }

        return $block;
    }

    /**
     * Generate a unique ID with prefix.
     */
    private function generate_id( $prefix ) {
        return $prefix . '_' . bin2hex( random_bytes( 4 ) );
    }

    /**
     * Ensure section settings have all required keys.
     */
    private function ensure_section_settings( $s ) {
        return wp_parse_args( $s, array(
            'backgroundColor' => '',
            'backgroundImage' => '',
            'padding'         => array( 'top' => 0, 'right' => 0, 'bottom' => 0, 'left' => 0 ),
            'margin'          => array( 'top' => 0, 'right' => 0, 'bottom' => 0, 'left' => 0 ),
            'border'          => array(
                'width'  => array( 'top' => 0, 'right' => 0, 'bottom' => 0, 'left' => 0 ),
                'color'  => '#000000',
                'style'  => 'solid',
                'radius' => array( 'topLeft' => 0, 'topRight' => 0, 'bottomRight' => 0, 'bottomLeft' => 0 ),
            ),
            'maxWidth'        => '',
            'stretch'         => 'default',
            'contentMaxWidth' => '',
            'cssClass'        => '',
            'cssId'           => '',
        ) );
    }

    /**
     * Ensure row settings have all required keys.
     */
    private function ensure_row_settings( $s ) {
        return wp_parse_args( $s, array(
            'backgroundColor' => '',
            'padding'         => array( 'top' => 0, 'right' => 0, 'bottom' => 0, 'left' => 0 ),
            'margin'          => array( 'top' => 0, 'right' => 0, 'bottom' => 0, 'left' => 0 ),
            'border'          => array(
                'width'  => array( 'top' => 0, 'right' => 0, 'bottom' => 0, 'left' => 0 ),
                'color'  => '#000000',
                'style'  => 'solid',
                'radius' => array( 'topLeft' => 0, 'topRight' => 0, 'bottomRight' => 0, 'bottomLeft' => 0 ),
            ),
            'equalHeight'    => false,
            'gap'            => '',
            'cssClass'       => '',
            'cssId'          => '',
        ) );
    }

    /**
     * Ensure column settings have all required keys.
     */
    private function ensure_column_settings( $s ) {
        return wp_parse_args( $s, array(
            'padding'         => array( 'top' => 0, 'right' => 0, 'bottom' => 0, 'left' => 0 ),
            'margin'          => array( 'top' => 0, 'right' => 0, 'bottom' => 0, 'left' => 0 ),
            'backgroundColor' => '',
            'verticalAlign'   => 'top',
            'cssClass'        => '',
            'cssId'           => '',
            'border'          => array(
                'width'  => array( 'top' => 0, 'right' => 0, 'bottom' => 0, 'left' => 0 ),
                'color'  => '#000000',
                'style'  => 'solid',
                'radius' => array( 'topLeft' => 0, 'topRight' => 0, 'bottomRight' => 0, 'bottomLeft' => 0 ),
            ),
        ) );
    }

    /**
     * Ensure block settings have all required keys.
     */
    private function ensure_block_settings( $s ) {
        return wp_parse_args( $s, array(
            'margin'          => array( 'top' => 0, 'right' => 0, 'bottom' => 0, 'left' => 0 ),
            'padding'         => array( 'top' => 0, 'right' => 0, 'bottom' => 0, 'left' => 0 ),
            'backgroundColor' => '',
            'border'          => array(
                'width'  => array( 'top' => 0, 'right' => 0, 'bottom' => 0, 'left' => 0 ),
                'color'  => '#000000',
                'style'  => 'solid',
                'radius' => array( 'topLeft' => 0, 'topRight' => 0, 'bottomRight' => 0, 'bottomLeft' => 0 ),
            ),
            'cssClass' => '',
            'cssId'    => '',
            'visible'  => true,
        ) );
    }

    /**
     * Base rules section of the system prompt.
     *
     * Avoids markdown code fences so the AI response parser isn't confused.
     */
    private function get_base_rules() {
        return <<<'PROMPT'
# IDVE Page Builder — AI Generation Rules

You are a page builder assistant for the Insight Dezign Visual Editor (IDVE) WordPress plugin.

CRITICAL: You output ONLY valid JSON. No markdown, no code fences, no explanation text, no text before or after the JSON object. Your entire response must be a single parseable JSON object.

## Tree Structure

The IDVE page data is a JSON tree:
- Root has "sections" array
- Each section has: id, settings, rows[]
- Each row has: id, settings, columns[]
- Each column has: id, width (fraction string), settings, blocks[]
- Each block has: id, type, data (block-specific), settings (shared)
- Group blocks also have a "blocks" array containing child blocks

## ID Format
Every element needs a unique ID. Format: idve_PREFIX_XXXXXXXX (8 random hex chars).
Prefixes: sec (sections), row (rows), col (columns), blk (blocks).
Example: "idve_sec_a1b2c3d4", "idve_blk_f9e8d7c6"
IMPORTANT: Every ID must be unique across the entire tree. Never reuse IDs.

## Column Widths
Column "width" must be one of these fraction strings (columns in a row MUST add to 100%):
"1/1" (100%), "1/2" (50%), "1/3" (33.3%), "2/3" (66.7%), "1/4" (25%), "3/4" (75%), "1/5" (20%), "2/5" (40%), "3/5" (60%), "4/5" (80%)

## Settings — Complete Reference

### Section settings
- backgroundColor (string, default ""): CSS color value (hex, rgba)
- backgroundImage (string, default ""): URL to background image
- backgroundSize (string, default "cover"): CSS background-size (cover, contain, auto)
- backgroundPosition (string, default "center"): CSS background-position (center, top, bottom, left, right, or combinations)
- backgroundRepeat (string, default "no-repeat"): CSS background-repeat (no-repeat, repeat, repeat-x, repeat-y)
- padding (object, default {top:0,right:0,bottom:0,left:0}): pixel values
- margin (object, same format)
- border (object): {width:{top:0,right:0,bottom:0,left:0}, color:"#000000", style:"solid", radius:{topLeft:0,topRight:0,bottomRight:0,bottomLeft:0}}
- maxWidth (string, default ""): constrains section content width (e.g., "1200px")
- stretch (string, default "default"): "default" = normal width, "full" = edge-to-edge (content stretches too), "full-bg" = background stretches edge-to-edge but content is constrained to contentMaxWidth
- contentMaxWidth (string, default ""): max-width for inner content when stretch is "full-bg" (e.g., "1200px")
- cssClass (string, default ""): custom CSS class added to the section element
- cssId (string, default ""): custom HTML id attribute for anchor links or CSS targeting

### Row settings
- backgroundColor (string, default ""): CSS color value
- backgroundImage (string, default ""): URL to background image
- backgroundSize (string, default "cover")
- backgroundPosition (string, default "center")
- backgroundRepeat (string, default "no-repeat")
- padding (object, same format as section)
- margin (object, same format)
- border (object, same format)
- equalHeight (boolean, default false): when true, all columns in the row stretch to match the tallest column — essential for card layouts
- gap (string, default ""): CSS gap value between columns (e.g., "20px", "1.5rem") — replaces margin hacks for uniform column spacing
- cssClass (string, default ""): custom CSS class
- cssId (string, default ""): custom HTML id

### Column settings
- backgroundColor (string, default ""): CSS color value — combined with border-radius creates card-like appearance
- backgroundImage (string, default ""): URL to background image
- backgroundSize (string, default "cover")
- backgroundPosition (string, default "center")
- backgroundRepeat (string, default "no-repeat")
- padding (object, same format)
- margin (object, same format)
- border (object, same format) — use border.radius for rounded corners on cards
- verticalAlign (string, default "top"): "top", "center", or "bottom" — controls vertical positioning of content within the column. When equalHeight is on, this determines where content sits in stretched columns. When off, controls the column's alignment relative to sibling columns.
- cssClass (string, default ""): custom CSS class
- cssId (string, default ""): custom HTML id

### Block settings (shared by ALL block types)
- margin (object, same format)
- padding (object, same format)
- backgroundColor (string, default "")
- border (object, same format)
- cssClass (string, default ""): custom CSS class
- cssId (string, default ""): custom HTML id (applied to the wrapper div — useful for anchor links)
- visible (boolean, default true)

## cssClass Rendering Behavior

IMPORTANT: cssClass placement differs by block type:
- Button blocks: cssClass is applied to the inner <a> element, NOT the wrapper div. The wrapper only handles alignment.
- Image blocks: cssClass is applied to the inner <img> element, NOT the wrapper div. The wrapper only handles alignment.
- Icon blocks: cssClass is applied to the inner <i> element, NOT the wrapper div. The wrapper only handles alignment.
- All other blocks (text, heading, spacer, divider, video, audio, html, group): cssClass is applied to the wrapper div.

cssId is ALWAYS applied to the wrapper div for all block types.

For sections, rows, and columns: cssClass and cssId are both applied to the element div.

When writing globalCss rules, keep this in mind — a class on a button targets the <a>, not the wrapper.

## Group Blocks — Container Elements

Group blocks are containers that hold child blocks. They are useful for:
- Applying a shared background color, background image, or border around multiple blocks
- Creating card-like containers within a column
- Adding padding around a set of blocks

Structure: A group block has "type":"group", a "data" object with minHeight and optional verticalAlign in settings, and a "blocks" array containing child blocks.

## Section Stretch Modes — Detail

- "default": Section width follows normal page flow. Use maxWidth to constrain content.
- "full": Section AND content stretch edge-to-edge across the viewport. Content has no max-width constraint. Good for full-bleed layouts where content should be wide.
- "full-bg": ONLY the background (color/image) stretches edge-to-edge. Content is constrained by contentMaxWidth (defaults to "1200px"). This is the most common choice for colored/image sections — the background spans the viewport but text and blocks stay readable.

## IMPORTANT: Settings Optimization

You can omit settings objects entirely — the system will fill in defaults. Only include settings when you need non-default values. This keeps your JSON output smaller and avoids hitting token limits.

For example, a simple text block only needs: {"id":"idve_blk_...","type":"text","data":{"content":"<p>Hello</p>"}}
The system will add default settings automatically.

## Global CSS Classes

Include a "globalCss" key in your response with valid CSS for styling that inline settings cannot achieve: box-shadow, text-shadow, gradients, transforms, transitions, hover effects, backdrop-filter, animations, etc.

Then reference those classes in element cssClass settings.

GUIDELINES for globalCss:
- Use descriptive, hyphenated class names (e.g., card-hover-lift, gradient-blue-purple)
- Keep selectors simple — single class only (e.g., .card-shadow), no compound selectors
- You may include pseudo-selectors like .card-hover-lift:hover { transform: translateY(-4px); }
- Always include transition on the base class when defining hover effects (e.g., .card-hover-lift { transition: transform 0.3s ease; })
- globalCss is optional — omit it when inline settings are sufficient
PROMPT;
    }

    /**
     * Get block schemas for the system prompt.
     */
    public function get_block_schemas() {
        $all    = $this->blocks->get_all();
        $lines  = array( '## Block Types' );
        $lines[] = 'Each block has a "type" string and a "data" object with block-specific fields:';
        $lines[] = '';

        foreach ( $all as $type => $block ) {
            $fields_desc = array();
            foreach ( $block['fields'] as $name => $field ) {
                $ftype   = isset( $field['type'] ) ? $field['type'] : 'text';
                $default = isset( $field['default'] ) ? $field['default'] : '';
                $desc    = $name . ' (' . $ftype;

                if ( isset( $field['options'] ) && is_array( $field['options'] ) ) {
                    $desc .= ', options: ' . implode( '|', $field['options'] );
                }

                if ( is_bool( $default ) ) {
                    $desc .= ', default: ' . ( $default ? 'true' : 'false' );
                } elseif ( is_numeric( $default ) ) {
                    $desc .= ', default: ' . $default;
                } elseif ( '' !== $default ) {
                    $desc .= ', default: "' . $default . '"';
                }

                $desc .= ')';
                $fields_desc[] = $desc;
            }

            $lines[] = '### ' . $type . ' — ' . $block['label'];
            $lines[] = 'Data fields: ' . implode( ', ', $fields_desc );

            // Special notes for specific block types.
            if ( 'text' === $type ) {
                $lines[] = 'Note: "content" should contain HTML (wrap text in <p> tags). Supports <strong>, <em>, <a>, <ul>, <ol>, <li>.';
            } elseif ( 'image' === $type ) {
                $lines[] = 'Note: For placeholder images use imageUrl: "https://placehold.co/800x400" with imageId: 0.';
            } elseif ( 'group' === $type ) {
                $lines[] = 'Note: Group blocks have a "blocks" array (child blocks) in addition to "data".';
            } elseif ( 'icon' === $type ) {
                $lines[] = 'Note: "iconClass" is a FontAwesome 6 class string, e.g. "fa-solid fa-star", "fa-regular fa-heart", "fa-brands fa-github". Use "fa-solid" prefix for filled icons, "fa-regular" for outlined, "fa-brands" for brand logos.';
            }
            $lines[] = '';
        }

        return implode( "\n", $lines );
    }

    /**
     * Design best practices for the system prompt.
     */
    private function get_design_practices() {
        return <<<'PROMPT'
## Design Best Practices

### Layout and Spacing
- Use section padding (40-80px top/bottom) for breathing room between page sections.
- Use section stretch "full-bg" on sections with background colors or images so they span the viewport while keeping content readable.
- Use stretch "full" only for true edge-to-edge hero layouts where content should also be wide.
- Use row gap (e.g., "20px" or "30px") for uniform spacing between columns instead of column margins.
- Add spacer blocks between content blocks for vertical spacing (20-40px).
- Create varied layouts: alternate between 1-column, 2-column, and 3-column rows across sections.

### Card Layouts
- Enable equalHeight on the row so all columns stretch to match the tallest.
- Give each column: backgroundColor, padding (20-30px all sides), and border.radius (8-12px all corners).
- Set verticalAlign to "center" on columns to center content vertically within equal-height cards.
- Add a shadow globalCss class for depth. Example: .card-shadow { box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1); }
- Apply the shadow class via cssClass on each column.
- Use gap on the row (e.g., "30px") to space cards evenly.

### Drop Shadows (via globalCss)
- Subtle: box-shadow: 0 1px 3px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06)
- Medium: box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1)
- Strong: box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1), 0 4px 6px -4px rgba(0,0,0,0.1)
- Elevated: box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.1)

### Gradient Backgrounds (via globalCss)
- Apply gradient classes to sections or columns via cssClass.
- Linear: background: linear-gradient(135deg, #667eea 0%, #764ba2 100%)
- Radial: background: radial-gradient(circle at top right, #4facfe 0%, #00f2fe 100%)
- Subtle overlay: background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.6) 100%) — works over background images for text readability.
- When applying a gradient class to a section/column, do NOT also set backgroundColor (inline backgroundColor would override the gradient).

### Hover Effects and Transitions (via globalCss)
- Always define the transition on the base class, and the transform on the :hover pseudo-selector.
- Lift effect: .card-hover-lift { transition: transform 0.3s ease, box-shadow 0.3s ease; } .card-hover-lift:hover { transform: translateY(-4px); box-shadow: 0 12px 20px rgba(0,0,0,0.15); }
- Scale effect: .img-hover-scale { transition: transform 0.3s ease; overflow: hidden; } .img-hover-scale:hover { transform: scale(1.05); }
- Apply hover classes to columns (cards), buttons (via cssClass on the button block), or image wrappers.

### Typography
- Use headings hierarchically: h1 for page title (one per page), h2 for section headings, h3 for sub-sections, h4 for card titles.
- Use darker heading colors (#1a1a2e, #16213e, #0f3460) for contrast against light backgrounds.
- Use lighter heading colors (#ffffff, #f0f0f0) on dark or gradient background sections.
- Set appropriate font sizes: h2 at 28-36px, h3 at 22-26px, body text at 16-18px.
- Use text blocks with textColor for readable body text (#333333 or #555555 on light, #e0e0e0 on dark).

### Content Quality
- Write realistic, contextual text — NOT lorem ipsum.
- Wrap text content in <p> tags. Use <strong> and <em> for emphasis.
- Buttons should have descriptive action labels ("Get Started", "Learn More", "Contact Us") and use "#" for unknown URLs.
- Use placeholder images (https://placehold.co/WIDTHxHEIGHT) with imageId: 0. Match dimensions to the layout context.

### Background Images
- Available on sections, rows, and columns via backgroundImage, backgroundSize, backgroundPosition, backgroundRepeat.
- For hero sections: use a background image on the section with stretch "full-bg", and add a gradient overlay class for text readability.
- For layered effects: set backgroundImage on a section/column AND a semi-transparent backgroundColor (e.g., "rgba(0,0,0,0.5)") — the background color renders on top of the image as an overlay.

### Color Consistency
- Pick a primary color and 1-2 accent colors. Use them consistently for backgrounds, buttons, headings, and borders.
- Use white (#ffffff) or very light gray (#f8f9fa, #f0f4f8) for alternating section backgrounds.
- Dark sections (#1a1a2e, #0d1b2a, #1b1b2f) create visual contrast between light sections.

### globalCss Patterns — Ready-to-Use Examples
- Shadow card: .card-shadow { box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1); }
- Gradient hero: .hero-gradient { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
- Hover lift: .hover-lift { transition: transform 0.3s ease, box-shadow 0.3s ease; } .hover-lift:hover { transform: translateY(-4px); box-shadow: 0 12px 24px rgba(0,0,0,0.15); }
- Text glow: .text-glow { text-shadow: 0 0 20px rgba(59,130,246,0.5); }
- Smooth image zoom: .img-zoom { overflow: hidden; } .img-zoom img { transition: transform 0.4s ease; } .img-zoom img:hover { transform: scale(1.08); }
PROMPT;
    }

    /**
     * Get global CSS classes for the system prompt.
     */
    public function get_global_classes() {
        $css     = get_option( 'idve_global_styles', '' );
        $classes = array();

        if ( ! empty( $css ) ) {
            preg_match_all( '/(?:^|[},])\s*\.([a-zA-Z_-][a-zA-Z0-9_-]*)\s*\{/m', $css, $matches );
            if ( ! empty( $matches[1] ) ) {
                foreach ( $matches[1] as $name ) {
                    if ( strpos( $name, 'idve-' ) !== 0 ) {
                        $classes[] = $name;
                    }
                }
                $classes = array_unique( $classes );
            }
        }

        if ( empty( $classes ) ) {
            return "## Available Global CSS Classes\nNo global CSS classes are defined yet.";
        }

        return "## Available Global CSS Classes\nYou can assign these to any element's cssClass setting: " . implode( ', ', $classes );
    }

    /**
     * Mode-specific instructions for the system prompt.
     */
    private function get_mode_instructions( $mode, $context ) {
        switch ( $mode ) {
            case 'generate_page':
                return <<<'PROMPT'
## Your Task: Generate Full Page
Return a JSON object: {"tree":{"sections":[...]},"globalCss":"..."}
Create multiple sections with varied layouts to build a complete page.
Each section should have at least one row with columns and blocks.
Use stretch "full-bg" on sections with background colors so they span the full viewport.
Include a "globalCss" string with CSS classes for shadows, gradients, hover effects, or other advanced styling used in the page. Omit globalCss if no custom classes are needed.
PROMPT;

            case 'add_section':
                $count = 0;
                if ( isset( $context['currentTree']['sections'] ) ) {
                    $count = count( $context['currentTree']['sections'] );
                }
                return "## Your Task: Add Section(s)\n" .
                    "Return a JSON object: {\"sections\":[...],\"globalCss\":\"...\"}\n" .
                    "These sections will be appended to an existing page that currently has {$count} section(s).\n" .
                    "Create one or more new sections as requested.\n" .
                    "Use stretch \"full-bg\" on sections with background colors so they span the full viewport.\n" .
                    "Include a \"globalCss\" string with CSS classes for shadows, gradients, hover effects, or other advanced styling. Omit globalCss if no custom classes are needed.";

            case 'edit_element':
                $element_json = '';
                if ( isset( $context['selectedElement'] ) ) {
                    $element_json = wp_json_encode( $context['selectedElement'] );
                }
                $type_label = isset( $context['selectedType'] ) ? $context['selectedType'] : 'element';
                return "## Your Task: Edit Selected Element\n" .
                    "Return a JSON object with this exact structure: {\"element\":{...}}\n" .
                    "You are editing a {$type_label}. Here is the current element JSON:\n" .
                    "{$element_json}\n" .
                    "Modify the data and/or settings fields as requested. Preserve id and type.";

            case 'refine_page':
                $tree_json = '';
                if ( isset( $context['currentTree'] ) ) {
                    $tree_json = wp_json_encode( $context['currentTree'] );
                }
                return "## Your Task: Refine Existing Page\n" .
                    "You are modifying an existing page. Here is the current page tree JSON:\n" .
                    $tree_json . "\n\n" .
                    "Return a JSON object: {\"tree\":{\"sections\":[...]},\"globalCss\":\"...\"}\n" .
                    "Apply the requested changes to the existing tree. Preserve the overall structure, IDs, and content that the user did NOT ask to change.\n" .
                    "Only modify what the user specifically requests. Do NOT remove sections, rows, blocks, or content unless explicitly asked.\n" .
                    "Include globalCss if new classes are needed for the changes. Omit globalCss if no new classes are required.";

            case 'create_class':
                $existing = $this->get_global_classes();
                return "## Your Task: Create CSS Class\n" .
                    "Return a JSON object with this exact structure: {\"globalCss\":\".class-name { property: value; }\"}\n" .
                    "Use descriptive, hyphenated class names (e.g., card-shadow, text-gradient).\n" .
                    "Return valid CSS that can be appended to the global stylesheet.\n" .
                    $existing;

            default:
                return '';
        }
    }
}
