<?php
/**
 * Cast Conductor Proprietary License v5
 * SPDX-License-Identifier: LicenseRef-CastConductor-Proprietary-v5
 * 
 * Copyright (c) 2025 CastConductor.com. All Rights Reserved.
 * 
 * This file is part of Cast Conductor ("Software"). The Software and its source
 * code constitute proprietary, confidential, and trade secret information of
 * CastConductor.com ("Company"). Any access or use is governed strictly by the
 * Cast Conductor Proprietary License v5 ("License"). By installing, copying,
 * accessing, compiling, or otherwise using the Software you agree to be bound by
 * all terms of the License. If you do not agree, you must cease use immediately.
 * 
 * Key Terms (Summary – see full License for binding terms):
 *  1. No Redistribution: You may not publish, distribute, sublicense, rent,
 *     lease, transfer, sell, or otherwise make the Software (or any derivative)
 *     available to any third party without prior written consent of Company.
 *  2. No Modification: Modification, reverse engineering, decompilation, or
 *     disassembly is prohibited except to the limited extent expressly permitted
 *     by applicable law that cannot be contractually waived.
 *  3. Confidentiality: Treat all source code and related artifacts as Company
 *     Confidential Information. Maintain at least the same degree of care as for
 *     your own confidential materials, and not less than reasonable care.
 *  4. No Patent License: No express or implied patent rights are granted. Future
 *     patents (if any) are fully reserved.
 *  5. No Trademark License: Company names, marks, and logos may not be used
 *     without prior written permission.
 *  6. Limited Internal Use: Use is limited solely to internal evaluation and
 *     operation of licensed Cast Conductor deployments. Commercial hosting or
 *     resale as a service requires a separate written agreement.
 *  7. Telemetry & License Validation: The Software may periodically transmit a
 *     hashed installation identifier, domain (or site ID), plugin/app version,
 *     and a truncated (non-reversible) fragment of the license key solely to
 *     validate activation status and enforce licensing. This minimal "phone home"
 *     check contains no personal or content data. If optional telemetry is later
 *     introduced it will be limited to aggregate operational metrics (no PII),
 *     fully documented, and optionally disableable per published instructions.
 *  8. Third-Party Components: The Software may include open source components
 *     covered by their own licenses. See THIRD-PARTY-NOTICES.md. Those licenses
 *     govern their respective components; this License governs all remaining code.
 *  9. Export Compliance: You are responsible for compliance with all applicable
 *     export control and sanctions laws.
 * 10. Warranty Disclaimer: THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF
 *     ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO MERCHANTABILITY,
 *     FITNESS FOR A PARTICULAR PURPOSE, TITLE, AND NON-INFRINGEMENT.
 * 11. Limitation of Liability: IN NO EVENT WILL COMPANY OR AUTHORS BE LIABLE FOR
 *     ANY INDIRECT, SPECIAL, INCIDENTAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE
 *     DAMAGES, OR LOST PROFITS, EVEN IF ADVISED OF THE POSSIBILITY.
 * 12. Acceptance: Use of the Software constitutes acceptance of the License.
 * 13. Enforcement: Unauthorized reproduction or distribution may result in civil
 *     and criminal penalties and will be prosecuted to the maximum extent allowed
 *     by law.
 * 
 * Authoritative EULA: EULA-v5.1.md (repository root – private) and https://castconductor.com/eula
 * Precedence: If this summary conflicts with the EULA, the EULA governs.
 * Revision: Current EULA revision v5.1 (subject to update; check EULA for current enterprise thresholds).
 * 
 * Full License text available from: licensing@castconductor.com
 * Security reports: security@castconductor.com
 * Commercial inquiries: licensing@castconductor.com
 * 
 * END OF HEADER
 */

/**
 * Content Blocks API Controller - CRUD Operations for Content Blocks
 * 
 * Provides REST API endpoints for managing content blocks:
 * - GET /wp-json/castconductor/v5/content-blocks (list all)
 * - GET /wp-json/castconductor/v5/content-blocks/{id} (get single)
 * - POST /wp-json/castconductor/v5/content-blocks (create)
 * - PUT /wp-json/castconductor/v5/content-blocks/{id} (update)
 * - DELETE /wp-json/castconductor/v5/content-blocks/{id} (delete)
 */

if (!defined('ABSPATH')) {
    exit;
}

class CastConductor_Content_Blocks_Controller extends WP_REST_Controller {
    
    protected $namespace = 'castconductor/v5';
    protected $rest_base = 'content-blocks';
    
    /**
     * Register routes
     */
    public function register_routes() {
        // List all content blocks
        register_rest_route($this->namespace, '/' . $this->rest_base, [
            [
                'methods' => WP_REST_Server::READABLE,
                'callback' => [$this, 'get_items'],
                'permission_callback' => [$this, 'check_admin_permission'],
                'args' => $this->get_collection_params()
            ],
            [
                'methods' => WP_REST_Server::CREATABLE,
                'callback' => [$this, 'create_item'],
                'permission_callback' => [$this, 'check_admin_permission'],
                'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::CREATABLE)
            ]
        ]);
        
        // Single content block operations
        register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', [
            [
                'methods' => WP_REST_Server::READABLE,
                'callback' => [$this, 'get_item'],
                'permission_callback' => [$this, 'check_admin_permission'],
                'args' => [
                    'id' => [
                        'description' => 'Content block ID',
                        'type' => 'integer'
                    ]
                ]
            ],
            [
                'methods' => WP_REST_Server::EDITABLE,
                'callback' => [$this, 'update_item'],
                'permission_callback' => [$this, 'check_admin_permission'],
                'args' => $this->get_endpoint_args_for_item_schema(WP_REST_Server::EDITABLE)
            ],
            [
                'methods' => WP_REST_Server::DELETABLE,
                'callback' => [$this, 'delete_item'],
                'permission_callback' => [$this, 'check_admin_permission'],
                'args' => [
                    'id' => [
                        'description' => 'Content block ID',
                        'type' => 'integer'
                    ]
                ]
            ]
        ]);
        
        // Content block types endpoint
        register_rest_route($this->namespace, '/' . $this->rest_base . '/types', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_content_block_types'],
            'permission_callback' => [$this, 'check_admin_permission']
        ]);
        
        // Content block preview endpoint
        register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)/preview', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'preview_content_block'],
            'permission_callback' => [$this, 'check_admin_permission'],
            'args' => [
                'id' => [
                    'description' => 'Content block ID',
                    'type' => 'integer'
                ]
            ]
        ]);
        
        // Content block live data endpoint
        register_rest_route($this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)/live-data', [
            'methods' => WP_REST_Server::READABLE,
            'callback' => [$this, 'get_live_content_data'],
            'permission_callback' => [$this, 'check_admin_permission'],
            'args' => [
                'id' => [
                    'description' => 'Content block ID',
                    'type' => 'integer'
                ]
            ]
        ]);
    }
    
    /**
     * Get multiple content blocks
     */
    public function get_items($request) {
        global $wpdb;
        
        $table_name = $wpdb->prefix . 'castconductor_content_blocks';
        $per_page = (int) $request->get_param('per_page') ?: 20;
        $page = (int) $request->get_param('page') ?: 1;
        $offset = ($page - 1) * $per_page;
        $search = $request->get_param('search');
        $type = $request->get_param('type');
        $enabled = $request->get_param('enabled');
        
        // Build WHERE clause
        $where_conditions = ['1=1'];
        $where_values = [];
        
        if (!empty($search)) {
            $where_conditions[] = '(name LIKE %s OR type LIKE %s)';
            $where_values[] = '%' . $wpdb->esc_like($search) . '%';
            $where_values[] = '%' . $wpdb->esc_like($search) . '%';
        }
        
        if (!empty($type)) {
            $where_conditions[] = 'type = %s';
            $where_values[] = $type;
        }
        
        if ($enabled !== null) {
            $where_conditions[] = 'enabled = %d';
            $where_values[] = (int) $enabled;
        }
        
        $where_clause = implode(' AND ', $where_conditions);
        
        // Get total count
        $count_query = "SELECT COUNT(*) FROM {$table_name} WHERE {$where_clause}";
        if (!empty($where_values)) {
            $count_query = $wpdb->prepare($count_query, $where_values);
        }
        $total = (int) $wpdb->get_var($count_query);
        
        // Get items
        $items_query = "SELECT * FROM {$table_name} WHERE {$where_clause} ORDER BY created_at DESC LIMIT %d OFFSET %d";
        $query_values = array_merge($where_values, [$per_page, $offset]);
        $items_query = $wpdb->prepare($items_query, $query_values);
        $items = $wpdb->get_results($items_query);
        
        // Format items
        $formatted_items = [];
        foreach ($items as $item) {
            $formatted_items[] = $this->prepare_item_for_response($item, $request);
        }
        
        $response = rest_ensure_response($formatted_items);
        $response->header('X-WP-Total', $total);
        $response->header('X-WP-TotalPages', ceil($total / $per_page));
        
        return $response;
    }
    
    /**
     * Get single content block
     */
    public function get_item($request) {
        $id = (int) $request['id'];
        $item = $this->get_content_block($id);
        
        if (!$item) {
            return new WP_Error('content_block_not_found', 'Content block not found', ['status' => 404]);
        }
        
        return rest_ensure_response($this->prepare_item_for_response($item, $request));
    }
    
    /**
     * Create content block
     */
    public function create_item($request) {
        global $wpdb;
        
        $table_name = $wpdb->prefix . 'castconductor_content_blocks';
        $name = sanitize_text_field($request->get_param('name'));
        $type = sanitize_text_field($request->get_param('type'));
        $visual_config = $request->get_param('visual_config');
        $data_config = $request->get_param('data_config');
        $enabled = (bool) $request->get_param('enabled');
        
        // Validate required fields
        if (empty($name) || empty($type)) {
            return new WP_Error('missing_required_fields', 'Name and type are required', ['status' => 400]);
        }
        
        // Validate content block type format (alphanumeric, underscores, hyphens allowed)
        // This allows user-created custom types like "track_info_hero", "my_custom_block", etc.
        if (!preg_match('/^[a-zA-Z][a-zA-Z0-9_-]*$/', $type)) {
            return new WP_Error('invalid_type_format', 'Content block type must start with a letter and contain only letters, numbers, underscores, or hyphens', ['status' => 400]);
        }
        
        // Prepare visual config JSON
        $visual_config_json = null;
        if ($visual_config) {
            $visual_config_json = wp_json_encode($visual_config);
            if (json_last_error() !== JSON_ERROR_NONE) {
                return new WP_Error('invalid_visual_config', 'Invalid visual configuration JSON', ['status' => 400]);
            }
        }
        
        // Prepare data config JSON (for duplicate/copy operations)
        $data_config_json = null;
        if ($data_config) {
            $data_config_json = wp_json_encode($data_config);
            if (json_last_error() !== JSON_ERROR_NONE) {
                return new WP_Error('invalid_data_config', 'Invalid data configuration JSON', ['status' => 400]);
            }
        }
        
        // Insert content block
        $result = $wpdb->insert(
            $table_name,
            [
                'name' => $name,
                'type' => $type,
                'visual_config' => $visual_config_json,
                'data_config' => $data_config_json,
                'enabled' => $enabled ? 1 : 0,
                'created_at' => current_time('mysql'),
                'updated_at' => current_time('mysql')
            ],
            ['%s', '%s', '%s', '%s', '%d', '%s', '%s']
        );
        
        if ($result === false) {
            return new WP_Error('create_failed', 'Failed to create content block', ['status' => 500]);
        }
        
        $item_id = $wpdb->insert_id;
        $item = $this->get_content_block($item_id);
        
        $response = rest_ensure_response($this->prepare_item_for_response($item, $request));
        $response->set_status(201);
        
        return $response;
    }
    
    /**
     * Update content block
     */
    public function update_item($request) {
        global $wpdb;
        
        $id = (int) $request['id'];
        $table_name = $wpdb->prefix . 'castconductor_content_blocks';
        
        // Check if content block exists
        $existing_item = $this->get_content_block($id);
        if (!$existing_item) {
            return new WP_Error('content_block_not_found', 'Content block not found', ['status' => 404]);
        }
        
        // Prepare update data
        $update_data = [];
        $update_format = [];
        
        $name = $request->get_param('name');
        if ($name !== null) {
            $update_data['name'] = sanitize_text_field($name);
            $update_format[] = '%s';
        }
        
        $type = $request->get_param('type');
        if ($type !== null) {
            // Validate format only - allow any valid type string (user-created types)
            if (!preg_match('/^[a-zA-Z][a-zA-Z0-9_-]*$/', $type)) {
                return new WP_Error('invalid_type_format', 'Content block type must start with a letter and contain only letters, numbers, underscores, or hyphens', ['status' => 400]);
            }
            $update_data['type'] = sanitize_text_field($type);
            $update_format[] = '%s';
        }
        
        $visual_config = $request->get_param('visual_config');
        if ($visual_config !== null) {
            $visual_config_json = wp_json_encode($visual_config);
            if (json_last_error() !== JSON_ERROR_NONE) {
                return new WP_Error('invalid_visual_config', 'Invalid visual configuration JSON', ['status' => 400]);
            }
            $update_data['visual_config'] = $visual_config_json;
            $update_format[] = '%s';
        }
        
        $enabled = $request->get_param('enabled');
        if ($enabled !== null) {
            $update_data['enabled'] = (bool) $enabled ? 1 : 0;
            $update_format[] = '%d';
        }
        
        if (empty($update_data)) {
            return new WP_Error('no_updates', 'No valid fields to update', ['status' => 400]);
        }
        
        // Add updated timestamp
        $update_data['updated_at'] = current_time('mysql');
        $update_format[] = '%s';
        
        // Update content block
        $result = $wpdb->update(
            $table_name,
            $update_data,
            ['id' => $id],
            $update_format,
            ['%d']
        );
        
        if ($result === false) {
            return new WP_Error('update_failed', 'Failed to update content block', ['status' => 500]);
        }
        
        $item = $this->get_content_block($id);
        return rest_ensure_response($this->prepare_item_for_response($item, $request));
    }
    
    /**
     * Delete content block
     */
    public function delete_item($request) {
        global $wpdb;
        
        $id = (int) $request['id'];
        $table_name = $wpdb->prefix . 'castconductor_content_blocks';
        
        // Check if content block exists
        $existing_item = $this->get_content_block($id);
        if (!$existing_item) {
            return new WP_Error('content_block_not_found', 'Content block not found', ['status' => 404]);
        }
        
        // Check if content block is in use by containers
        $assignments_table = $wpdb->prefix . 'castconductor_container_blocks';
        $assignments_count = $wpdb->get_var($wpdb->prepare(
            "SELECT COUNT(*) FROM {$assignments_table} WHERE content_block_id = %d",
            $id
        ));
        
        if ($assignments_count > 0) {
            return new WP_Error('content_block_in_use', 
                sprintf('Cannot delete content block that is assigned to %d container(s)', $assignments_count),
                ['status' => 409]);
        }
        
        // Delete content block
        $result = $wpdb->delete(
            $table_name,
            ['id' => $id],
            ['%d']
        );
        
        if ($result === false) {
            return new WP_Error('delete_failed', 'Failed to delete content block', ['status' => 500]);
        }
        
        return rest_ensure_response([
            'deleted' => true,
            'previous' => $this->prepare_item_for_response($existing_item, $request)
        ]);
    }
    
    /**
     * Get available content block types
     */
    public function get_content_block_types($request) {
        $types = [
            'track_info' => [
                'name' => 'Track Info',
                'description' => 'Display current track information with album artwork',
                'supports' => ['album_artwork', 'metadata_api', 'custom_styling'],
                'default_config' => [
                    'album_artwork' => ['enabled' => true, 'size' => '600x600'],
                    'typography' => ['font_size' => 24, 'color' => '#ffffff'],
                    'background' => ['type' => 'solid', 'color' => '#1e1e1e']
                ]
            ],
            'shoutout' => [
                'name' => 'Shoutout',
                'description' => 'Display user-submitted shoutouts with moderation',
                'supports' => ['moderation', 'lifecycle_management', 'custom_styling'],
                'default_config' => [
                    'typography' => ['font_size' => 20, 'color' => '#ffffff'],
                    'background' => ['type' => 'solid', 'color' => '#2c2c2c'],
                    'moderation' => ['enabled' => true, 'auto_approve' => false]
                ]
            ],
            'sponsor' => [
                'name' => 'Sponsor',
                'description' => 'Display sponsor content with scheduling',
                'supports' => ['scheduling', 'qr_codes', 'custom_styling'],
                'default_config' => [
                    'typography' => ['font_size' => 22, 'color' => '#ffffff'],
                    'background' => ['type' => 'solid', 'color' => '#1a472a'],
                    'scheduling' => ['enabled' => true]
                ]
            ],
            'promo' => [
                'name' => 'Promo',
                'description' => 'Display promotional content with scheduling',
                'supports' => ['scheduling', 'custom_styling'],
                'default_config' => [
                    'typography' => ['font_size' => 22, 'color' => '#ffffff'],
                    'background' => ['type' => 'solid', 'color' => '#47471a'],
                    'scheduling' => ['enabled' => true]
                ]
            ],
            'weather' => [
                'name' => 'Weather',
                'description' => 'Display weather based on viewer IP geolocation',
                'supports' => ['geolocation', 'api_integration', 'custom_styling'],
                'default_config' => [
                    'typography' => ['font_size' => 20, 'color' => '#ffffff'],
                    'background' => ['type' => 'solid', 'color' => '#1a3a47']
                ]
            ],
            'location_time' => [
                'name' => 'Location/Time',
                'description' => 'Display viewer location and local time',
                'supports' => ['geolocation', 'timezone_detection', 'custom_styling'],
                'default_config' => [
                    'typography' => ['font_size' => 18, 'color' => '#ffffff'],
                    'background' => ['type' => 'solid', 'color' => '#3a1a47']
                ]
            ],
            // SHELVED (Dec 2025): Custom API content blocks hidden from UI - code intact for future development
            // 'custom_api' => [
            //     'name' => 'Custom API',
            //     'description' => 'Connect to external APIs for dynamic content',
            //     'supports' => ['api_configuration', 'field_mapping', 'custom_styling'],
            //     'default_config' => [
            //         'typography' => ['font_size' => 20, 'color' => '#ffffff'],
            //         'background' => ['type' => 'solid', 'color' => '#471a1a']
            //     ]
            // ],
            'custom' => [
                'name' => 'Custom',
                'description' => 'User-defined content with manual entry',
                'supports' => ['manual_content', 'custom_styling'],
                'default_config' => [
                    'typography' => ['font_size' => 20, 'color' => '#ffffff'],
                    'background' => ['type' => 'solid', 'color' => '#2c2c2c']
                ]
            ]
        ];
        
        return rest_ensure_response([
            'success' => true,
            'data' => $types,
            'total' => count($types),
            'timestamp' => current_time('c'),
            'version' => '5.0.0'
        ]);
    }
    
    /**
     * Preview content block with live data
     */
    public function preview_content_block($request) {
        $id = (int) $request['id'];
        $item = $this->get_content_block($id);
        
        if (!$item) {
            return new WP_Error('content_block_not_found', 'Content block not found', ['status' => 404]);
        }
        
        // Fetch LIVE DATA for preview (NO DUMMY DATA)
        $live_data = $this->fetch_live_data($item);
        
        return rest_ensure_response([
            'success' => true,
            'data' => [
                'content_block' => $this->prepare_item_for_response($item, $request),
                'live_data' => $live_data,
                'preview_html' => $this->render_preview_html($item, $live_data),
                'data_source' => $this->get_data_source_info($item)
            ],
            'timestamp' => current_time('c'),
            'version' => '5.0.0'
        ]);
    }
    
    /**
     * Get live content data for content block
     */
    public function get_live_content_data($request) {
        $id = (int) $request['id'];
        $item = $this->get_content_block($id);
        
        if (!$item) {
            return new WP_Error('content_block_not_found', 'Content block not found', ['status' => 404]);
        }
        
        // Fetch live data from configured source
        $live_data = $this->fetch_live_data($item);
        
        return rest_ensure_response([
            'success' => true,
            'data' => [
                'content_block' => $this->prepare_item_for_response($item, $request),
                'live_data' => $live_data,
                'data_source' => $this->get_data_source_info($item),
                'last_updated' => current_time('c')
            ],
            'timestamp' => current_time('c'),
            'version' => '5.0.0'
        ]);
    }
    
    /**
     * Fetch live data from configured source
     * Made public for Canvas Editor integration (V5 Philosophy: Share real data)
     */
    public function fetch_live_data($content_block) {
        // Parse data configuration
        $data_config = null;
        if (!empty($content_block->data_config)) {
            $data_config = json_decode($content_block->data_config, true);
        }
        
        // CUSTOM API BLOCK: Handle first since it uses api_config instead of data_source
        // This must be checked BEFORE the data_source check below
        $block_type = $content_block->type;
        if ($block_type === 'custom_api' && $data_config) {
            $class_name = 'CastConductor_Custom_API_Block';
            if (!class_exists($class_name)) {
                $file_path = CASTCONDUCTOR_PLUGIN_DIR . 'includes/content-blocks/class-custom-api-block.php';
                if (file_exists($file_path)) {
                    require_once $file_path;
                }
            }
            if (class_exists($class_name)) {
                $block_instance = new $class_name();
                if (method_exists($block_instance, 'fetch_api_data')) {
                    return $block_instance->fetch_api_data($data_config);
                }
            }
        }
        
        // BLOCK TYPE OVERRIDES: These block types need special handling regardless of data_source.
        // Move BEFORE the data_source check so they work even with empty/missing data_source.
        
        // TRACK INFO HERO: Hybrid block that combines track metadata + weather + location tokens.
        // Must merge data from all three sources for Scenes & Containers preview to work correctly.
        // The block uses {{track.artist}}, {{weather.temperature}}, {{location.time}}, etc. in its layers.
        if ($block_type === 'track_info_hero') {
            // Start with track metadata (primary data source)
            $track_data = $this->fetch_metadata_live_data($data_config ?: []);
            
            // Merge in weather fallback data for admin preview
            $weather_data = $this->fetch_weather_live_data($data_config ?: []);
            if (is_array($weather_data) && !isset($weather_data['error'])) {
                $track_data['temperature'] = $weather_data['temperature'] ?? '';
                $track_data['condition'] = $weather_data['condition'] ?? '';
                $track_data['unit'] = $weather_data['unit'] ?? '°F';
            }
            
            // Merge in location/time fallback data for admin preview
            $location_data = $this->fetch_location_time_live_data($data_config ?: []);
            if (is_array($location_data) && !isset($location_data['error'])) {
                $track_data['time'] = $location_data['time'] ?? '';
                $track_data['date'] = $location_data['date'] ?? '';
                $track_data['city'] = $location_data['city'] ?? '';
                $track_data['state'] = $location_data['state'] ?? '';
            }
            
            return $track_data;
        }
        
        if (!$data_config || empty($data_config['data_source'])) {
            return $this->get_no_config_message($content_block->type);
        }

        // FRAMEWORK: Dynamic dispatch to block-specific classes
        // This allows new content blocks to define their own data fetching logic
        // without modifying the core controller.
        $class_name = 'CastConductor_' . $this->dashes_to_camel_case($block_type) . '_Block';
        
        // Ensure class is loaded
        if (!class_exists($class_name)) {
            $file_path = CASTCONDUCTOR_PLUGIN_DIR . 'includes/content-blocks/class-' . str_replace('_', '-', $block_type) . '-block.php';
            if (file_exists($file_path)) {
                require_once $file_path;
            }
        }

        // Delegate to block class if it exists and implements fetch_data
        if (class_exists($class_name)) {
            $block_instance = new $class_name();
            if (method_exists($block_instance, 'fetch_data')) {
                // Check if this is a legacy data source that needs special handling?
                // For now, we trust the block class to handle its data config.
                // We only delegate for the types we've refactored or new types.
                if (in_array($block_type, ['shoutout', 'sponsor', 'promo'])) {
                    return $block_instance->fetch_data($data_config);
                }
            }
            // Note: custom_api handled earlier in this function before data_source check
        }
        
        // UNIFIED FETCH ARCHITECTURE - Single data path as documented in strategy
        $endpoint = $data_config['endpoint'] ?? null;
        $data_source = $data_config['data_source'];
        
        // BLOCK TYPE OVERRIDE: For weather and location_time blocks, always use their
        // proper live data functions regardless of data_source setting. This handles
        // legacy blocks that may still have 'manual_entry' in their data_config.
        // These blocks render client-side on Roku using viewer GeoIP, but in admin
        // preview we use server geolocation fallback for realistic display.
        if ($block_type === 'weather') {
            return $this->fetch_weather_live_data($data_config);
        }
        if ($block_type === 'location_time') {
            return $this->fetch_location_time_live_data($data_config);
        }
        
        // Handle data sources with unified architecture (Legacy/Standard)
        switch ($data_source) {
            case 'metadata_api':
                return $this->fetch_metadata_live_data($data_config);
                
            case 'openweathermap_api_roku_viewer_ip':
                return $this->fetch_weather_live_data($data_config);
                
            case 'roku_ip_geolocation_api':
                return $this->fetch_location_time_live_data($data_config);
                
            case 'external_api':
                return $this->fetch_external_api_live_data($data_config);
                
            case 'manual_entry':
                return $this->fetch_manual_live_data($data_config);
                
            default:
                return $this->get_no_config_message($content_block->type);
        }
    }

    /**
     * Itemized live data fetch to support server-side cycling in previews.
     * For list-based sources (shoutout/sponsor/promo), picks a deterministic item.
     * - item_index: integer index into the returned list (modulo list length)
     * - shuffle: when true, uses a seed to permute order deterministically if provided
     * - seed: optional string/number used for deterministic shuffling
     */
    public function fetch_live_data_itemized($content_block, $item_index = 0, $shuffle = false, $seed = null) {
        // Default to existing fetch for non-list sources
        $type = $content_block->type;
        $data_config = null;
        if (!empty($content_block->data_config)) {
            $data_config = json_decode($content_block->data_config, true);
        }

        // FRAMEWORK: Dynamic dispatch to block-specific classes for list fetching
        $class_name = 'CastConductor_' . $this->dashes_to_camel_case($type) . '_Block';
        
        // Ensure class is loaded
        if (!class_exists($class_name)) {
            $file_path = CASTCONDUCTOR_PLUGIN_DIR . 'includes/content-blocks/class-' . str_replace('_', '-', $type) . '-block.php';
            if (file_exists($file_path)) {
                require_once $file_path;
            }
        }

        // Delegate to block class if it exists and implements fetch_items
        if (class_exists($class_name)) {
            $block_instance = new $class_name();
            if (method_exists($block_instance, 'fetch_items')) {
                // Fetch list of items directly from block class
                $items = $block_instance->fetch_items($data_config, 20); // Fetch enough for rotation
                
                if (empty($items)) {
                    // Fallback to single fetch (which returns the "No Content" message)
                    return $this->fetch_live_data($content_block);
                }

                // Deterministic shuffle if requested
                if ($shuffle) {
                    $items = $this->shuffle_with_seed($items, (string)($seed ?? 'castconductor'));
                }
                
                $idx = 0;
                if (is_numeric($item_index)) {
                    $n = count($items);
                    if ($n > 0) { $idx = ((int)$item_index) % $n; if ($idx < 0) $idx += $n; }
                }
                
                return $items[$idx];
            }
        }

        // Non-list sources default to single fetch
        return $this->fetch_live_data($content_block);
    }

    /** Deterministic shuffle using seed (xorshift32 on hash of seed) */
    private function shuffle_with_seed($arr, $seed) {
        $n = count($arr);
        if ($n <= 1) return $arr;
        $state = $this->seed_to_int($seed);
        $out = array_values($arr);
        for ($i = $n - 1; $i > 0; $i--) {
            // xorshift32
            $state ^= ($state << 13) & 0xFFFFFFFF;
            $state ^= ($state >> 17);
            $state ^= ($state << 5) & 0xFFFFFFFF;
            $j = $state & 0x7FFFFFFF; // positive
            $j = $j % ($i + 1);
            $tmp = $out[$i];
            $out[$i] = $out[$j];
            $out[$j] = $tmp;
        }
        return $out;
    }

    private function seed_to_int($seed) {
        if (is_numeric($seed)) {
            return (int)$seed;
        }
        // simple string hash
        $h = 2166136261;
        $len = strlen($seed);
        for ($i = 0; $i < $len; $i++) {
            $h ^= ord($seed[$i]);
            $h = ($h * 16777619) & 0xFFFFFFFF;
        }
        return $h;
    }

    /**
     * Helper to convert dashes/underscores to CamelCase
     */
    private function dashes_to_camel_case($string) {
        return str_replace(' ', '_', ucwords(str_replace(['-', '_'], ' ', $string)));
    }


    
    /**
     * Get data source information
     */
    private function get_data_source_info($content_block) {
        $data_config = null;
        if (!empty($content_block->data_config)) {
            $data_config = json_decode($content_block->data_config, true);
        }
        
        if (!$data_config || empty($data_config['data_source'])) {
            return [
                'source' => 'none',
                'status' => 'not_configured',
                'message' => 'Data source not configured'
            ];
        }
        
        return [
            'source' => $data_config['data_source'],
            'status' => 'configured',
            'refresh_interval' => $data_config['refresh_interval'] ?? 30
        ];
    }
    
    /**
     * Fetch live metadata from audio streaming API
     * FOLLOWS: ENDPOINTS.md documented architecture using existing fields
     * ARCHITECTURE: WordPress Metadata URL → Toaster Fallback → Live API requests
     */
    private function fetch_metadata_live_data($data_config) {
        // Follow existing architecture: WordPress Settings → Toaster Fallback
        $metadata_url = $this->get_metadata_url_with_variable_substitution($data_config);
        
        if (empty($metadata_url)) {
            return [
                'error' => 'No metadata URL configured - please configure Metadata URL in WordPress settings',
                'artist' => 'Configuration Required',
                'title' => 'Set Metadata URL in CastConductor Settings',
                'album' => 'Setup Required',
                'artwork_url' => '',
                'duration' => '0:00',
                'status' => 'configuration_required'
            ];
        }
        
        // Make actual HTTP request to live metadata API
        $response = wp_remote_get($metadata_url, [
            'timeout' => 10,
            'headers' => [
                'User-Agent' => 'CastConductor/5.0 WordPress Plugin'
            ]
        ]);
        
        if (is_wp_error($response)) {
            return [
                'error' => 'Failed to fetch metadata: ' . $response->get_error_message(),
                'artist' => 'API Connection Failed',
                'title' => $response->get_error_message(),
                'album' => 'Check metadata URL configuration',
                'artwork_url' => '',
                'duration' => '0:00',
                'status' => 'connection_failed',
                'endpoint' => $metadata_url
            ];
        }
        
        $http_code = wp_remote_retrieve_response_code($response);
        if ($http_code !== 200) {
            return [
                'error' => "HTTP Error {$http_code}",
                'artist' => 'API Error',
                'title' => "HTTP {$http_code} - Check endpoint configuration",
                'album' => 'Endpoint Issue',
                'artwork_url' => '',
                'duration' => '0:00',
                'status' => 'http_error',
                'endpoint' => $metadata_url
            ];
        }
        
        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);
        
        if (!$data) {
            return [
                'error' => 'Invalid JSON response',
                'artist' => 'Invalid Response',
                'title' => 'API returned invalid JSON',
                'album' => 'Check endpoint format',
                'artwork_url' => '',
                'duration' => '0:00',
                'status' => 'invalid_json',
                'endpoint' => $metadata_url
            ];
        }
        
        // Parse real JSON structure from live API (Phase 3 Blueprint format)
        return $this->parse_live_metadata_response($data, $metadata_url);
    }
    
    /**
     * Get metadata URL following ENDPOINTS.md documented architecture
     * FOLLOWS: Existing Stream URL and Metadata URL fields with Toaster pattern
     * ARCHITECTURE: WordPress Settings → Toaster Fallback → Code Implementation
     */
    private function get_metadata_url_with_variable_substitution($data_config) {
        // 1. Content Block Override (Highest Priority)
        $endpoint_override = $data_config['endpoint_override'] ?? '';
        if (!empty($endpoint_override)) {
            return $endpoint_override;
        }
        
        // 2. WordPress Global Settings (User-Configured Metadata URL)
        $metadata_url = get_option('castconductor_metadata_url', '');
        if (!empty($metadata_url)) {
            return $metadata_url;
        }
        
        // 3. Toaster Default (Initial Setup Fallback)
        $toaster_metadata_url = get_option('castconductor_toaster_metadata_url', '');
        if (!empty($toaster_metadata_url)) {
            return $toaster_metadata_url;
        }
        
        // No URL configured
        return '';
    }

    /**
     * Parse live metadata response (Phase 3 Blueprint JSON structure)
     * IMPLEMENTS: Real JSON structure from live test API
     */
    private function parse_live_metadata_response($data, $endpoint) {
        // Handle Phase 3 Blueprint JSON structure
        if (isset($data['now_playing']['song'])) {
            $song = $data['now_playing']['song'];
            
            // PRIORITY: Separate artist/title fields > Combined text field
            $artist = !empty($song['artist']) ? $song['artist'] : '';
            $title = !empty($song['title']) ? $song['title'] : '';
            
            // Fallback: Parse combined "text" field if artist/title not available
            if (empty($artist) && empty($title) && !empty($song['text'])) {
                $parsed = $this->parse_combined_text_field($song['text']);
                $artist = $parsed['artist'];
                $title = $parsed['title'];
            }
            
            // Enhanced artwork discovery using CastConductor's album artwork search API
            $enhanced_artwork = $this->get_enhanced_artwork($artist, $song['album'] ?? '', $title);
            $artwork_url = $enhanced_artwork ?: ($song['art'] ?? '');
            
            return [
                'artist' => $artist ?: 'Unknown Artist',
                'title' => $title ?: 'Unknown Title',
                'album' => $song['album'] ?? '',
                'artwork_url' => $artwork_url,
                'elapsed' => $data['now_playing']['elapsed'] ?? 0,
                'duration' => $data['now_playing']['duration'] ?? 0,
                'text' => $song['text'] ?? '',
                'is_live' => $data['live']['is_live'] ?? false,
                'listeners' => $data['listeners']['current'] ?? 0,
                'station_name' => $data['station']['name'] ?? '',
                'status' => 'live_data',
                'endpoint' => $endpoint
            ];
        }
        
        // Handle other potential JSON formats (e.g., simple artist/title format)
        if (isset($data['artist']) && isset($data['title'])) {
            $artist = $data['artist'];
            $title = $data['title'];
            $album = $data['album'] ?? '';
            
            // Enhanced artwork discovery using CastConductor's album artwork search API
            $enhanced_artwork = $this->get_enhanced_artwork($artist, $album, $title);
            $artwork_url = $enhanced_artwork ?: ($data['artwork_url'] ?? $data['image'] ?? '');
            
            return [
                'artist' => $artist,
                'title' => $title,
                'album' => $album,
                'artwork_url' => $artwork_url,
                'duration' => $data['duration'] ?? '0:00',
                'status' => 'live_data',
                'endpoint' => $endpoint
            ];
        }
        
        // Fallback for unknown format
        return [
            'error' => 'Unsupported metadata format',
            'artist' => 'Format Not Supported',
            'title' => 'Check API response format',
            'album' => 'Endpoint returned unexpected structure',
            'artwork_url' => '',
            'duration' => '0:00',
            'status' => 'format_error',
            'endpoint' => $endpoint,
            'raw_response' => json_encode($data)
        ];
    }
    
    /**
     * Parse combined "text" field (Phase 3 Blueprint)
     * Example: "Mau P - Dress Code (Extended Mix)" → artist: "Mau P", title: "Dress Code (Extended Mix)"
     */
    private function parse_combined_text_field($text) {
        $separators = [' - ', ' – ', ' — ', ' / '];
        
        foreach ($separators as $separator) {
            if (strpos($text, $separator) !== false) {
                $parts = explode($separator, $text, 2);
                return [
                    'artist' => trim($parts[0]),
                    'title' => trim($parts[1] ?? '')
                ];
            }
        }
        
        // No separator found, treat entire text as title
        return [
            'artist' => '',
            'title' => trim($text)
        ];
    }
    
    /**
     * Get enhanced artwork using CastConductor's Album Artwork Search API
     * Integrates with iTunes, MusicBrainz, and Deezer APIs
     */
    private function get_enhanced_artwork($artist, $album, $title) {
        // Skip if no search terms available
        if (empty($artist) && empty($album) && empty($title)) {
            return null;
        }
        
        // Initialize album artwork controller
        $artwork_controller = new CastConductor_Album_Artwork_Controller();
        
        // Create a mock request object for the search
    $request = new WP_REST_Request('POST', '/castconductor/v5/album-artwork/search');
        $request->set_param('artist', $artist);
        $request->set_param('album', $album);
        $request->set_param('title', $title);
        $request->set_param('force_refresh', false); // Use cache for performance
        
        try {
            $response = $artwork_controller->search_artwork($request);
            
            // Handle WP_Error
            if (is_wp_error($response)) {
                error_log('CastConductor: Album artwork search error: ' . $response->get_error_message());
                return null;
            }
            
            // Extract response data
            $response_data = $response->get_data();
            
            if (isset($response_data['success']) && $response_data['success'] && !empty($response_data['artwork_url'])) {
                return $response_data['artwork_url'];
            }
            
            return null;
            
        } catch (Exception $e) {
            error_log('CastConductor: Album artwork search exception: ' . $e->getMessage());
            return null;
        }
    }
    
    /**
     * Fetch live weather data
     * 
     * Returns admin preview fallback values for Canvas/Scenes stage rendering.
     * Actual Roku devices will use their own GeoIP data client-side.
     * The fallback data allows content blocks to render visually in WordPress admin
     * without showing raw {{token}} placeholders.
     */
    private function fetch_weather_live_data($data_config) {
        $api_key = get_option('castconductor_openweather_api_key', '');
        
        // Try to get real weather data using server's geolocation for admin preview
        $geo_data = $this->get_server_geolocation();
        
        if (!empty($api_key) && $geo_data && isset($geo_data['lat']) && isset($geo_data['lon'])) {
            // Attempt real weather fetch using server location
            $weather_url = sprintf(
                'https://api.openweathermap.org/data/2.5/weather?lat=%s&lon=%s&appid=%s&units=imperial',
                $geo_data['lat'],
                $geo_data['lon'],
                $api_key
            );
            
            $response = wp_remote_get($weather_url, array('timeout' => 5));
            
            if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
                $weather_data = json_decode(wp_remote_retrieve_body($response), true);
                if ($weather_data && isset($weather_data['main']['temp'])) {
                    $temp = round($weather_data['main']['temp']);
                    $condition = ucfirst($weather_data['weather'][0]['description'] ?? 'Clear');
                    $city = $geo_data['city'] ?? 'Unknown';
                    
                    return [
                        'temperature' => (string)$temp,
                        'condition' => $condition,
                        'unit' => '°F',
                        'location' => $city,
                        'status' => 'admin_preview',
                        'preview_note' => 'Server location weather (Roku shows viewer\'s local weather)'
                    ];
                }
            }
        }
        
        // Fallback: Return styled placeholder data for admin preview
        // This ensures content blocks render visually instead of showing raw tokens
        return [
            'temperature' => '72',
            'condition' => 'Sunny',
            'unit' => '°F',
            'location' => $geo_data['city'] ?? 'Los Angeles',
            'status' => 'admin_preview_fallback',
            'preview_note' => 'Sample weather (Roku shows viewer\'s local weather)'
        ];
    }
    
    /**
     * Fetch live location and time data
     * 
     * Returns admin preview fallback values for Canvas/Scenes stage rendering.
     * Actual Roku devices will use their own GeoIP data client-side.
     */
    private function fetch_location_time_live_data($data_config) {
        // Get server geolocation for realistic admin preview
        $geo_data = $this->get_server_geolocation();
        
        // Get current server time
        $timezone = wp_timezone();
        $now = new DateTime('now', $timezone);
        
        return [
            'time' => $now->format('g:i A'),
            'date' => $now->format('l, F j'),
            'city' => $geo_data['city'] ?? 'Los Angeles',
            'state' => $geo_data['region'] ?? 'CA',
            'country' => $geo_data['country'] ?? 'US',
            'timezone' => $timezone->getName(),
            'status' => 'admin_preview',
            'preview_note' => 'Server location/time (Roku shows viewer\'s local data)'
        ];
    }
    
    /**
     * Get server geolocation using free IP geolocation API
     * Used for admin preview fallback data in location/weather blocks.
     * Results are cached for 1 hour to minimize API calls.
     * 
     * @return array|null Geolocation data with city, region, country, lat, lon or null on failure
     */
    private function get_server_geolocation() {
        $cache_key = 'castconductor_server_geolocation';
        $cached = get_transient($cache_key);
        
        if ($cached !== false) {
            return $cached;
        }
        
        // Use ip-api.com (free, no API key required, 45 requests/minute limit)
        $response = wp_remote_get('http://ip-api.com/json/?fields=status,city,regionName,country,lat,lon', array(
            'timeout' => 3,
            'sslverify' => false
        ));
        
        if (is_wp_error($response)) {
            // Return fallback on error
            return array(
                'city' => 'Los Angeles',
                'region' => 'CA',
                'country' => 'US',
                'lat' => 34.0522,
                'lon' => -118.2437
            );
        }
        
        $data = json_decode(wp_remote_retrieve_body($response), true);
        
        if (!$data || !isset($data['status']) || $data['status'] !== 'success') {
            // Return fallback
            return array(
                'city' => 'Los Angeles',
                'region' => 'CA',
                'country' => 'US',
                'lat' => 34.0522,
                'lon' => -118.2437
            );
        }
        
        $geo_data = array(
            'city' => $data['city'] ?? 'Unknown',
            'region' => $data['regionName'] ?? '',
            'country' => $data['country'] ?? '',
            'lat' => $data['lat'] ?? null,
            'lon' => $data['lon'] ?? null
        );
        
        // Cache for 1 hour
        set_transient($cache_key, $geo_data, HOUR_IN_SECONDS);
        
        return $geo_data;
    }

    /**
     * Fetch manual entry live data
     * For custom blocks with manually entered content
     */
    private function fetch_manual_live_data($data_config) {
        // Manual entry blocks store their content in data_config['content']
        // But for custom blocks with layers, the content is in the layers themselves
        // Return empty array to let the layer renderer handle it
        return [
            'content' => $data_config['content'] ?? '',
            'status' => 'manual_entry',
            'source' => 'Manual Content'
        ];
    }

    /**
     * Fetch external API live data
     * For blocks pulling from custom external APIs
     */
    private function fetch_external_api_live_data($data_config) {
        $endpoint = $data_config['endpoint'] ?? '';
        
        if (empty($endpoint)) {
            return [
                'error' => 'External API endpoint not configured',
                'status' => 'configuration_required'
            ];
        }
        
        $response = wp_remote_get($endpoint);
        
        if (is_wp_error($response)) {
            return [
                'error' => 'Failed to fetch external API data: ' . $response->get_error_message(),
                'status' => 'error'
            ];
        }
        
        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body, true);
        
        return [
            'data' => $data,
            'status' => 'live',
            'source' => 'External API'
        ];
    }
    


    /**
     * UNIFIED CONTENT DATA FETCHER - Strategy Document Compliant Architecture
     * Single method to fetch content from REST endpoints with exact field mapping
     * Eliminates architectural violations from separate fetch_*_live_data() methods
     */
    private function fetch_unified_content_data($endpoint, $content_type) {
        $response = wp_remote_get($endpoint);
        
        if (is_wp_error($response)) {
            return [
                'error' => 'Failed to fetch ' . $content_type . ' data: ' . $response->get_error_message()
            ];
        }
        
        $body = wp_remote_retrieve_body($response);
        $api_data = json_decode($body, true);
        
        if (!empty($api_data['data']) && is_array($api_data['data'])) {
            $item = $api_data['data'][0]; // Get first active item
            
            // EXACT FIELD MAPPING - Use field names from strategy document JSON structures
            switch ($content_type) {
                case 'shoutout':
                    return [
                        'name' => $item['name'] ?? 'Anonymous',
                        'location' => $item['location'] ?? 'Unknown Location', 
                        'message' => $item['message'] ?? 'No message',
                        'timestamp' => isset($item['timestamp']) ? strtotime($item['timestamp']) : current_time('timestamp'),
                        'artwork_url' => $this->get_square_branding_fallback(),
                        'status' => 'live_data',
                        'source' => 'WordPress Content Manager'
                    ];
                    
                case 'sponsor':
                    return [
                        'title' => $this->strip_quotes($item['title'] ?? 'Untitled Sponsor'),
                        'content' => $this->strip_quotes($item['content'] ?? 'No sponsor content'),
                        'featured_media_url' => $item['featured_media_url'] ?? $this->get_square_branding_fallback(),
                        'artwork_url' => $item['featured_media_url'] ?? $this->get_square_branding_fallback(),
                        'status' => 'live_data',
                        'source' => 'WordPress Content Manager'
                    ];
                    
                case 'promo':
                    return [
                        'title' => $this->strip_quotes($item['title'] ?? 'Untitled Promo'),
                        'content' => $this->strip_quotes($item['content'] ?? 'No promo content'),
                        'featured_media_url' => $item['featured_media_url'] ?? $this->get_square_branding_fallback(),
                        'artwork_url' => $item['featured_media_url'] ?? $this->get_square_branding_fallback(),
                        'status' => 'live_data',
                        'source' => 'WordPress Content Manager'
                    ];
            }
        }
        
        // Return appropriate "no content" messages
        switch ($content_type) {
            case 'shoutout':
                return [
                    'name' => 'No Shoutouts Yet',
                    'location' => 'WordPress Integration Ready',
                    'message' => 'Shoutouts will appear here when submitted',
                    'timestamp' => current_time('timestamp'),
                    'artwork_url' => $this->get_square_branding_fallback(),
                    'status' => 'awaiting_submissions',
                    'source' => 'WordPress Content Manager'
                ];
                
            case 'sponsor':
                return [
                    'title' => 'No Active Sponsors',
                    'content' => 'Sponsor campaigns will appear here when active',
                    'featured_media_url' => $this->get_square_branding_fallback(),
                    'artwork_url' => $this->get_square_branding_fallback(),
                    'status' => 'awaiting_campaigns',
                    'source' => 'WordPress Content Manager'
                ];
                
            case 'promo':
                return [
                    'title' => 'No Active Promos',
                    'content' => 'Promotional content will display here when scheduled',
                    'featured_media_url' => $this->get_square_branding_fallback(),
                    'artwork_url' => $this->get_square_branding_fallback(),
                    'status' => 'awaiting_promos',
                    'source' => 'WordPress Content Manager'
                ];
        }
        
        return [
            'error' => 'Unknown content type: ' . $content_type
        ];
    }

    /**
     * Get message when no configuration is available
     * Type-specific messaging for better UX
     */
    private function get_no_config_message($content_type) {
        // Device-rendered content blocks (location, weather) - not an error, just can't preview
        if ($content_type === 'location_time') {
            return [
                'device_rendered' => true,
                'message' => 'This content block uses GeoIP data to render client-side on the end-user\'s Roku device.',
                'type' => $content_type,
                'status' => 'device_runtime',
                'privacy_note' => 'No personally identifiable information is collected.',
                'preview_note' => 'Live preview unavailable – renders on Roku with viewer\'s local time and location.'
            ];
        }
        
        if ($content_type === 'weather') {
            return [
                'device_rendered' => true,
                'message' => 'This content block uses GeoIP data to render client-side on the end-user\'s Roku device.',
                'type' => $content_type,
                'status' => 'device_runtime',
                'privacy_note' => 'No personally identifiable information is collected.',
                'preview_note' => 'Live preview unavailable – renders on Roku with viewer\'s local weather.'
            ];
        }
        
        // Default: actual configuration error
        return [
            'error' => 'Data Source Not Configured',
            'message' => 'This content block needs a data source configuration',
            'type' => $content_type,
            'status' => 'configuration_required',
            'action' => 'Configure data source in content block settings'
        ];
    }

    /**
     * Helper: Get content block from database
     */
    private function get_content_block($id) {
        global $wpdb;
        
        $table_name = $wpdb->prefix . 'castconductor_content_blocks';
        return $wpdb->get_row($wpdb->prepare(
            "SELECT * FROM {$table_name} WHERE id = %d",
            $id
        ));
    }
    
    /**
     * Helper: Get default content block types (for documentation/schema - not used for validation)
     * Users can create any custom type; this list shows common/default types.
     */
    private function get_valid_content_block_types() {
        return [
            'track_info',
            'track_info_hero',
            'shoutout', 
            'sponsor',
            'promo',
            'weather',
            'location_time',
            'custom_api',
            'custom'
        ];
    }
    
    /**
     * Helper: Prepare item for response
     */
    public function prepare_item_for_response($item, $request) {
        $visual_config = null;
        if (!empty($item->visual_config)) {
            $visual_config = json_decode($item->visual_config, true);
        }

        $data_config = null;
        if (!empty($item->data_config)) {
            $data_config = json_decode($item->data_config, true);
        }
        
        $artwork_cache = null;
        if (!empty($item->artwork_cache)) {
            $artwork_cache = json_decode($item->artwork_cache, true);
        }
        
        return [
            'id' => (int) $item->id,
            'name' => $item->name,
            'type' => $item->type,
            'visual_config' => $visual_config,
            'data_config' => $data_config,
            'artwork_cache' => $artwork_cache,
            'enabled' => (bool) $item->enabled,
            'created_at' => $item->created_at,
            'updated_at' => $item->updated_at
        ];
    }
    
    /**
     * Helper: Generate sample data for preview
     * NOTE: This should only be used for design previews, not live content display
     */
    private function generate_sample_data($type) {
        // This method should only be used for design/preview purposes
        // Live content blocks should fetch real data from their configured sources
        
        switch ($type) {
            case 'track_info':
                return [
                    'artist' => 'LIVE DATA REQUIRED - Configure metadata API',
                    'title' => 'LIVE DATA REQUIRED - Configure metadata API',
                    'album' => 'Sample Album',
                    'artwork_url' => 'https://via.placeholder.com/600x600/1e1e1e/ffffff?text=Album+Art',
                    'duration' => '3:45',
                    'elapsed' => '1:23'
                ];
                
            case 'shoutout':
                return [
                    'message' => 'Great music tonight! Love listening during my commute.',
                    'author' => 'Sarah M.',
                    'location' => 'Portland, OR',
                    'submitted' => '2 minutes ago'
                ];
                
            case 'sponsor':
                return [
                    'sponsor_name' => 'Acme Electronics',
                    'message' => 'Visit our showroom for the latest tech deals!',
                    'website' => 'https://acme-electronics.com',
                    'logo_url' => 'https://via.placeholder.com/200x100/1a472a/ffffff?text=ACME',
                    'campaign_end' => '2025-12-31'
                ];
                
            case 'promo':
                return [
                    'title' => 'Summer Concert Series',
                    'message' => 'Join us every Friday night for live music in the park!',
                    'start_date' => '2025-06-01',
                    'end_date' => '2025-08-31'
                ];
                
            case 'weather':
                return [
                    'location' => 'Portland, OR',
                    'temperature' => '72°F',
                    'condition' => 'Partly Cloudy',
                    'humidity' => '65%',
                    'wind' => '8 mph NW'
                ];
                
            case 'location_time':
                return [
                    'city' => 'Portland',
                    'state' => 'Oregon',
                    'time' => '2:30 PM',
                    'timezone' => 'PDT',
                    'date' => 'August 8, 2025'
                ];
                
            case 'custom_api':
                return [
                    'title' => 'Custom API Data',
                    'description' => 'This would display data from your configured API',
                    'status' => 'Connected',
                    'last_update' => '30 seconds ago'
                ];
                
            case 'custom':
                return [
                    'title' => 'Custom Content',
                    'message' => 'Your custom message would appear here',
                    'note' => 'Configure your content in the Canvas Editor'
                ];
                
            default:
                return [
                    'message' => 'Sample content for ' . $type . ' content block'
                ];
        }
    }
    
    /**
     * Helper: Render preview HTML
     */
    private function render_preview_html($item, $sample_data) {
        $visual_config = json_decode($item->visual_config, true) ?: [];
        
        // Basic HTML structure for preview
        $html = '<div class="castconductor-preview castconductor-' . esc_attr($item->type) . '">';
        $html .= '<h3>' . esc_html($item->name) . '</h3>';
        
        switch ($item->type) {
            case 'track_info':
                $html .= '<div class="track-info">';
                if (isset($sample_data['artwork_url'])) {
                    $html .= '<img src="' . esc_url($sample_data['artwork_url']) . '" alt="Album Art" class="album-artwork" />';
                }
                $html .= '<div class="track-details">';
                $html .= '<div class="artist">' . esc_html($sample_data['artist']) . '</div>';
                $html .= '<div class="title">' . esc_html($sample_data['title']) . '</div>';
                $html .= '<div class="album">' . esc_html($sample_data['album']) . '</div>';
                $html .= '</div></div>';
                break;
                
            case 'shoutout':
                $html .= '<div class="shoutout">';
                $html .= '<div class="message">"' . esc_html($sample_data['message']) . '"</div>';
                $html .= '<div class="author">- ' . esc_html($sample_data['author']) . ', ' . esc_html($sample_data['location']) . '</div>';
                $html .= '</div>';
                break;
                
            default:
                $html .= '<div class="content">';
                foreach ($sample_data as $key => $value) {
                    if (is_string($value)) {
                        $html .= '<div class="' . esc_attr($key) . '">' . esc_html($value) . '</div>';
                    }
                }
                $html .= '</div>';
        }
        
        $html .= '</div>';
        
        return $html;
    }
    
    /**
     * Get collection parameters
     */
    public function get_collection_params() {
        return [
            'page' => [
                'description' => 'Current page of the collection',
                'type' => 'integer',
                'default' => 1,
                'sanitize_callback' => 'absint'
            ],
            'per_page' => [
                'description' => 'Maximum number of items to be returned in result set',
                'type' => 'integer',
                'default' => 20,
                'minimum' => 1,
                'maximum' => 100,
                'sanitize_callback' => 'absint'
            ],
            'search' => [
                'description' => 'Limit results to those matching a string',
                'type' => 'string',
                'sanitize_callback' => 'sanitize_text_field'
            ],
            'type' => [
                'description' => 'Limit results to content blocks of a specific type',
                'type' => 'string',
                'enum' => $this->get_valid_content_block_types(),
                'sanitize_callback' => 'sanitize_text_field'
            ],
            'enabled' => [
                'description' => 'Limit results to enabled or disabled content blocks',
                'type' => 'boolean'
            ]
        ];
    }
    
    /**
     * Check admin permissions
     * Allows: Administrators, castconductor_admin role, and Editors (edit_posts)
     */
    public function check_admin_permission() {
        return current_user_can('manage_options') || current_user_can('castconductor_admin') || current_user_can('edit_posts');
    }

    /**
     * Get square branding fallback for content blocks without custom artwork
     */
    private function get_square_branding_fallback() {
        // 1) Explicit override URL if provided (backward compatible)
        $square_branding_url = get_option('castconductor_square_branding_url', '');
        if (!empty($square_branding_url)) {
            return $square_branding_url;
        }

        // 2) Use wizard-provisioned uploads asset (current → default)
        $current_id = get_option('castconductor_current_square_logo_600x600');
        if (!empty($current_id)) {
            $url = wp_get_attachment_image_url($current_id, 'full');
            if (!empty($url)) {
                return $url;
            }
        }

        $default_id = get_option('castconductor_default_square_logo_600x600');
        if (!empty($default_id)) {
            $url = wp_get_attachment_image_url($default_id, 'full');
            if (!empty($url)) {
                return $url;
            }
        }

        // 3) Theme logo as a soft fallback
        $site_logo = get_theme_mod('custom_logo');
        if ($site_logo) {
            $url = wp_get_attachment_image_url($site_logo, 'medium');
            if (!empty($url)) {
                return $url;
            }
        }

        // 4) Final fallback to bundled plugin asset
        return plugin_dir_url(__FILE__) . '../../wizard-content/default-square-logo-600x600.png';
    }

    /**
     * Strip quotes from content - Required by strategy document for sponsors/promos
     * Removes both single and double quotes from beginning and end of strings
     */
    private function strip_quotes($content) {
        if (empty($content)) {
            return $content;
        }
        
        return trim($content, '\'"');
    }
}
