<?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 Block: class-custom-api-block.php
 * 
 * Phase 1 MVP: Custom API Content Block
 * - User provides any public API URL (no auth)
 * - User maps JSONPath fields to display tokens
 * - Roku fetches and renders data directly
 * 
 * @since 5.2.0
 */

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

require_once CASTCONDUCTOR_PLUGIN_DIR . 'includes/content-blocks/class-base-block.php';

class CastConductor_Custom_API_Block extends CastConductor_Base_Block {
	public $type = 'custom_api';
	public $display_name = 'Custom API';
	public $description = 'Connect to any public API and display dynamic data on your Roku channel.';

	/**
	 * Get field schema for Custom API block
	 * Extended with API configuration and JSONPath field mapping
	 */
	public function get_field_schema() {
		$schema = parent::get_field_schema();
		
		// === API Configuration Group ===
		$schema['api_config'] = [
			'type' => 'group',
			'label' => 'API Configuration',
			'fields' => [
				'api_url' => [
					'type' => 'url',
					'label' => 'API URL',
					'description' => 'Enter the full URL of the public API endpoint (e.g., https://api.coindesk.com/v1/bpi/currentprice.json)',
					'placeholder' => 'https://api.example.com/data.json',
					'default' => '',
					'required' => true
				],
				'http_method' => [
					'type' => 'select',
					'label' => 'HTTP Method',
					'default' => 'GET',
					'options' => [
						'GET' => 'GET (default)',
						'POST' => 'POST'
					]
				],
				'refresh_interval' => [
					'type' => 'number',
					'label' => 'Refresh Interval (seconds)',
					'description' => 'How often to refresh the data (minimum 30 seconds)',
					'default' => 300,
					'min' => 30,
					'max' => 86400
				],
				'timeout' => [
					'type' => 'number',
					'label' => 'Request Timeout (seconds)',
					'default' => 10,
					'min' => 5,
					'max' => 60
				],
				'cache_duration' => [
					'type' => 'number',
					'label' => 'Cache Duration (seconds)',
					'description' => 'How long to cache API responses (helps with rate limiting)',
					'default' => 1800,
					'min' => 0,
					'max' => 86400
				]
			]
		];
		
		// === Array/Data Path Configuration ===
		$schema['data_path'] = [
			'type' => 'group',
			'label' => 'Data Path (for arrays)',
			'description' => 'If the API returns an array, specify how to access and select items',
			'fields' => [
				'array_path' => [
					'type' => 'text',
					'label' => 'Array Path (JSONPath)',
					'description' => 'Path to the array in the response (e.g., "data.items" or "results")',
					'placeholder' => 'data.items',
					'default' => ''
				],
				'array_selection' => [
					'type' => 'select',
					'label' => 'Item Selection',
					'default' => 'first',
					'options' => [
						'first' => 'First item',
						'last' => 'Last item',
						'random' => 'Random item',
						'rotate' => 'Rotate through items'
					]
				]
			]
		];
		
		// === Field Mapping Group (JSONPath → Display) ===
		$schema['field_mapping'] = [
			'type' => 'group',
			'label' => 'Field Mapping',
			'description' => 'Map API response fields to display elements using dot notation (e.g., "bpi.USD.rate")',
			'fields' => [
				'primary_text' => [
					'type' => 'text',
					'label' => 'Primary Text Path',
					'description' => 'JSONPath to the main display text',
					'placeholder' => 'title or data.headline',
					'default' => ''
				],
				'secondary_text' => [
					'type' => 'text',
					'label' => 'Secondary Text Path',
					'description' => 'JSONPath to the secondary/subtitle text',
					'placeholder' => 'description or data.summary',
					'default' => ''
				],
				'image_url' => [
					'type' => 'text',
					'label' => 'Image URL Path',
					'description' => 'JSONPath to an image URL in the response',
					'placeholder' => 'image or data.thumbnail',
					'default' => ''
				],
				'number_value' => [
					'type' => 'text',
					'label' => 'Number/Value Path',
					'description' => 'JSONPath to a numeric value (price, count, etc.)',
					'placeholder' => 'price or bpi.USD.rate',
					'default' => ''
				],
				'date_value' => [
					'type' => 'text',
					'label' => 'Date/Time Path',
					'description' => 'JSONPath to a date or timestamp field',
					'placeholder' => 'updated or data.timestamp',
					'default' => ''
				],
				'link_url' => [
					'type' => 'text',
					'label' => 'Link URL Path',
					'description' => 'JSONPath to a link/URL in the response',
					'placeholder' => 'url or data.link',
					'default' => ''
				]
			]
		];
		
		// === Display Options ===
		$schema['display_options'] = [
			'type' => 'group',
			'label' => 'Display Options',
			'fields' => [
				'show_primary' => [
					'type' => 'checkbox',
					'label' => 'Show Primary Text',
					'default' => true
				],
				'show_secondary' => [
					'type' => 'checkbox',
					'label' => 'Show Secondary Text',
					'default' => true
				],
				'show_image' => [
					'type' => 'checkbox',
					'label' => 'Show Image',
					'default' => true
				],
				'show_number' => [
					'type' => 'checkbox',
					'label' => 'Show Number/Value',
					'default' => false
				],
				'number_prefix' => [
					'type' => 'text',
					'label' => 'Number Prefix',
					'placeholder' => '$',
					'default' => ''
				],
				'number_suffix' => [
					'type' => 'text',
					'label' => 'Number Suffix',
					'placeholder' => ' USD',
					'default' => ''
				],
				'text_truncate' => [
					'type' => 'number',
					'label' => 'Text Truncate Length',
					'description' => 'Maximum characters for text fields (0 = no limit)',
					'default' => 100,
					'min' => 0,
					'max' => 500
				]
			]
		];
		
		// === API Templates (Quick Setup) ===
		$schema['api_templates'] = [
			'type' => 'group',
			'label' => 'Quick Setup Templates',
			'description' => 'Pre-configured templates for popular APIs',
			'fields' => [
				'template' => [
					'type' => 'select',
					'label' => 'Template',
					'default' => 'custom',
					'options' => self::get_api_templates()
				]
			]
		];
		
		return $schema;
	}
	
	/**
	 * Get pre-configured API templates for quick setup
	 * 
	 * @return array Template options for select field
	 */
	public static function get_api_templates() {
		return [
			'custom' => '— Custom API —',
			'bitcoin_price' => '💰 Bitcoin Price (CoinDesk)',
			'random_joke' => '😂 Random Joke (JokeAPI)',
			'weather' => '🌤️ Weather (Open-Meteo)',
			'quotes' => '💬 Random Quotes (Quotable)',
			'trivia' => '🎯 Trivia Question (Open Trivia)',
			'cat_facts' => '🐱 Cat Facts',
			'space' => '🚀 NASA APOD (Astronomy Picture)',
			'news' => '📰 News Headlines'
		];
	}
	
	/**
	 * Get template configuration for a specific template ID
	 * 
	 * @param string $template_id Template identifier
	 * @return array Template configuration or empty array
	 */
	public static function get_template_config($template_id) {
		$templates = [
			'bitcoin_price' => [
				'api_url' => 'https://api.coindesk.com/v1/bpi/currentprice.json',
				'refresh_interval' => 60,
				'field_mapping' => [
					'primary_text' => 'chartName',
					'secondary_text' => 'disclaimer',
					'number_value' => 'bpi.USD.rate'
				],
				'display_options' => [
					'number_prefix' => '$',
					'number_suffix' => ' USD'
				]
			],
			'random_joke' => [
				'api_url' => 'https://v2.jokeapi.dev/joke/Any?safe-mode&type=single',
				'refresh_interval' => 300,
				'field_mapping' => [
					'primary_text' => 'joke',
					'secondary_text' => 'category'
				]
			],
			'weather' => [
				'api_url' => 'https://api.open-meteo.com/v1/forecast?latitude=40.7128&longitude=-74.0060&current_weather=true',
				'refresh_interval' => 600,
				'field_mapping' => [
					'primary_text' => 'current_weather.weathercode',
					'number_value' => 'current_weather.temperature'
				],
				'display_options' => [
					'number_suffix' => '°F'
				]
			],
			'quotes' => [
				'api_url' => 'https://api.quotable.io/random',
				'refresh_interval' => 300,
				'field_mapping' => [
					'primary_text' => 'content',
					'secondary_text' => 'author'
				]
			],
			'trivia' => [
				'api_url' => 'https://opentdb.com/api.php?amount=1&type=multiple',
				'refresh_interval' => 300,
				'data_path' => [
					'array_path' => 'results'
				],
				'field_mapping' => [
					'primary_text' => 'question',
					'secondary_text' => 'category'
				]
			],
			'cat_facts' => [
				'api_url' => 'https://catfact.ninja/fact',
				'refresh_interval' => 300,
				'field_mapping' => [
					'primary_text' => 'fact'
				]
			],
			'space' => [
				'api_url' => 'https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY',
				'refresh_interval' => 3600,
				'field_mapping' => [
					'primary_text' => 'title',
					'secondary_text' => 'explanation',
					'image_url' => 'url',
					'date_value' => 'date'
				]
			]
		];
		
		return $templates[$template_id] ?? [];
	}
	
	/**
	 * Fetch and process API data
	 * Called by content blocks controller for preview and live data
	 * 
	 * @param array $config Block configuration
	 * @return array Processed API data or error
	 */
	public function fetch_api_data($config) {
		// Support both nested (api_config.api_url) and flat (api_url) formats
		$api_config = $config['api_config'] ?? [];
		$api_url = $api_config['api_url'] ?? ($config['api_url'] ?? '');
		
		if (empty($api_url)) {
			return [
				'error' => true,
				'message' => 'No API URL configured'
			];
		}
		
		// Check if URL is valid
		if (!filter_var($api_url, FILTER_VALIDATE_URL)) {
			return [
				'error' => true,
				'message' => 'Invalid API URL format'
			];
		}
		
		// Check cache first
		$cache_key = 'cc_api_' . md5($api_url);
		$cache_duration = (int) ($api_config['cache_duration'] ?? ($config['cache_duration'] ?? 1800));
		$cached = get_transient($cache_key);
		
		if ($cached !== false) {
			return $cached;
		}
		
		// Make the API request
		$timeout = (int) ($api_config['timeout'] ?? ($config['timeout'] ?? 10));
		$method = $api_config['http_method'] ?? ($config['http_method'] ?? 'GET');
		
		$args = [
			'timeout' => $timeout,
			'headers' => [
				'Accept' => 'application/json',
				'User-Agent' => 'CastConductor/5.2'
			]
		];
		
		if ($method === 'GET') {
			$response = wp_remote_get($api_url, $args);
		} else {
			$response = wp_remote_post($api_url, $args);
		}
		
		if (is_wp_error($response)) {
			return [
				'error' => true,
				'message' => 'API request failed: ' . $response->get_error_message()
			];
		}
		
		$status_code = wp_remote_retrieve_response_code($response);
		if ($status_code !== 200) {
			return [
				'error' => true,
				'message' => "API returned HTTP {$status_code}"
			];
		}
		
		$body = wp_remote_retrieve_body($response);
		$data = json_decode($body, true);
		
		if (json_last_error() !== JSON_ERROR_NONE) {
			return [
				'error' => true,
				'message' => 'Invalid JSON response from API'
			];
		}
		
		// Process array path if specified
		$array_path = $config['data_path']['array_path'] ?? '';
		if (!empty($array_path)) {
			$data = $this->extract_by_path($data, $array_path);
			
			if (is_array($data) && isset($data[0])) {
				// It's an array - select an item
				$selection = $config['data_path']['array_selection'] ?? 'first';
				$data = $this->select_array_item($data, $selection);
			}
		}
		
		// Extract mapped fields
		$result = $this->extract_mapped_fields($data, $config['field_mapping'] ?? []);
		
		// Apply display options (prefix/suffix for numbers, truncation)
		$result = $this->apply_display_options($result, $config['display_options'] ?? []);
		
		// Add metadata
		$result['_meta'] = [
			'api_url' => $api_url,
			'fetched_at' => current_time('c'),
			'cached' => false
		];
		
		// Cache the result
		if ($cache_duration > 0) {
			set_transient($cache_key, $result, $cache_duration);
		}
		
		return $result;
	}
	
	/**
	 * Extract value from data using dot-notation path
	 * 
	 * @param array $data Source data
	 * @param string $path Dot-notation path (e.g., "bpi.USD.rate")
	 * @return mixed Extracted value or null
	 */
	private function extract_by_path($data, $path) {
		if (empty($path) || !is_array($data)) {
			return $data;
		}
		
		$parts = explode('.', $path);
		$current = $data;
		
		foreach ($parts as $part) {
			if (is_array($current) && isset($current[$part])) {
				$current = $current[$part];
			} else {
				return null;
			}
		}
		
		return $current;
	}
	
	/**
	 * Select an item from an array based on selection method
	 * 
	 * @param array $array Source array
	 * @param string $method Selection method
	 * @return mixed Selected item
	 */
	private function select_array_item($array, $method) {
		if (empty($array)) {
			return null;
		}
		
		switch ($method) {
			case 'last':
				return end($array);
			case 'random':
				return $array[array_rand($array)];
			case 'rotate':
				// Use current time to rotate through items
				$index = time() % count($array);
				return $array[$index];
			case 'first':
			default:
				return reset($array);
		}
	}
	
	/**
	 * Extract mapped fields from API data
	 * 
	 * @param array $data Source data
	 * @param array $mapping Field mapping configuration
	 * @return array Extracted fields
	 */
	private function extract_mapped_fields($data, $mapping) {
		$result = [
			'primary_text' => '',
			'secondary_text' => '',
			'image_url' => '',
			'number_value' => '',
			'date_value' => '',
			'link_url' => ''
		];
		
		foreach ($result as $field => $default) {
			$path = $mapping[$field] ?? '';
			if (!empty($path)) {
				$value = $this->extract_by_path($data, $path);
				if ($value !== null) {
					$result[$field] = is_scalar($value) ? (string) $value : json_encode($value);
				}
			}
		}
		
		// Store raw data for debugging
		$result['_raw'] = $data;
		
		return $result;
	}
	
	/**
	 * Apply display options to extracted data
	 * 
	 * @param array $data Extracted data
	 * @param array $options Display options
	 * @return array Processed data
	 */
	private function apply_display_options($data, $options) {
		// Apply number prefix/suffix
		if (!empty($data['number_value'])) {
			$prefix = $options['number_prefix'] ?? '';
			$suffix = $options['number_suffix'] ?? '';
			$data['number_display'] = $prefix . $data['number_value'] . $suffix;
		}
		
		// Apply text truncation
		$truncate = (int) ($options['text_truncate'] ?? 100);
		if ($truncate > 0) {
			foreach (['primary_text', 'secondary_text'] as $field) {
				if (!empty($data[$field]) && strlen($data[$field]) > $truncate) {
					$data[$field] = substr($data[$field], 0, $truncate) . '…';
				}
			}
		}
		
		return $data;
	}
}
