/// @file    Client.ino
/// @brief   Educational tutorial for FastLED HTTP fetch API with explicit types
/// @author  FastLED Community
///
/// This tutorial demonstrates network functionality in FastLED WASM builds, specifically
/// the fetch API for making HTTP requests. It shows two different approaches for handling
/// asynchronous operations with EXPLICIT TYPES for educational clarity.
///
/// EDUCATIONAL FOCUS: All types are explicitly declared instead of using 'auto'
/// to help you understand the FastLED type system and async patterns.
///
/// TWO ASYNC APPROACHES DEMONSTRATED:
/// 
/// APPROACH 1: Promise-based with .then() and .catch_() callbacks (JavaScript-like)
/// - Uses method chaining for async operations
/// - Callbacks handle success and error cases
/// - Non-blocking, event-driven pattern
///
/// APPROACH 2: fl::task::await_top_level() pattern for synchronous-style async code
/// - Uses explicit types: fl::task::Promise<T>, fl::task::PromiseResult<T>, fl::optional<T>
/// - Blocks until async operation completes (only safe in Arduino loop()!)
/// - More traditional imperative programming style
///
/// The example toggles between these approaches every 10 seconds to demonstrate
/// both patterns working with the same underlying fetch API.
///
/// FASTLED ASYNC TYPE SYSTEM TUTORIAL:
/// 
/// Key Types You'll Learn:
/// * fl::task::Promise<T>        - Represents a future value of type T
/// * fl::task::PromiseResult<T>  - Wraps either a successful T value or an Error
/// * fl::net::http::Response          - HTTP response with status, headers, and body
/// * fl::net::http::FetchOptions      - Configuration object for HTTP requests
/// * fl::optional<T>       - May or may not contain a value of type T
/// * fl::task::Error             - Error information with message and context
///
/// NEW FETCH API STRUCTURE:
/// * FetchOptions is a pure data configuration object
/// * fetch_get() returns fl::task::Promise<fl::net::http::Response> (not auto!)
/// * Promises can be handled with .then()/.catch_() OR await_top_level()
/// * All async operations integrate with FastLED's engine automatically
///
/// EXPLICIT TYPE EXAMPLES:
/// 
/// Promise-based approach:
/// ```cpp
/// fl::task::Promise<fl::net::http::Response> promise = fl::net::http::fetch_get("http://example.com");
/// promise.then([](const fl::net::http::Response& response) { /* handle success */ })
///        .catch_([](const fl::task::Error& error) { /* handle error */ });
/// ```
///
/// Await-based approach:
/// ```cpp
/// fl::task::Promise<fl::net::http::Response> promise = fl::net::http::fetch_get("http://example.com");
/// fl::task::PromiseResult<fl::net::http::Response> result = fl::task::await_top_level(promise);
/// if (result.ok()) {
///     const fl::net::http::Response& response = result.value();
///     // Use response...
/// }
/// ```
///
/// TO RUN THIS TUTORIAL:
/// 
/// For WASM (recommended for networking):
/// 1. Install FastLED: `pip install fastled`
/// 2. cd into this examples directory  
/// 3. Run: `fastled Client.ino`
/// 4. Open the web page and check browser console for detailed fetch results
///
/// For other platforms:
/// Uses mock responses for testing the API without network connectivity

#include "fl/net/http/fetch.h"     // FastLED HTTP fetch API
#include "fl/log/log.h"      // FastLED logging system  
#include "fl/task/executor.h"     // FastLED async utilities (await_top_level, etc.)
#include <FastLED.h>      // FastLED core library

// Use FastLED namespace for cleaner code

// LED CONFIGURATION
#define NUM_LEDS 10      // Number of LEDs in our strip
#define DATA_PIN 2       // Arduino pin connected to LED data line

fl::CRGB leds[NUM_LEDS];

// Timing and approach control variables for tutorial cycling
static fl::u32 last_request_time = 0;        // Track last request time for 10-second intervals
static int approach_counter = 0;                    // Cycle through 4 different approaches

void setup() {
    // Initialize LED strip
    FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, NUM_LEDS);

    // Set all LEDs to dark red initially (indicates waiting/starting state)
    fill_solid(leds, NUM_LEDS, fl::CRGB(64, 0, 0)); // Dark red = starting up
    FastLED.show();

    // Tutorial introduction messages
    FL_WARN("FastLED Networking Tutorial started - 10 LEDs set to dark red");
    FL_WARN("Learning HTTP fetch API with TWO different async patterns:");
    FL_WARN("  APPROACH 1: Promise-based (.then/.catch_) with explicit types");
    FL_WARN("  APPROACH 2: fl::task::await_top_level pattern with explicit types");
    FL_WARN("Toggles between approaches every 10 seconds for comparison...");
    FL_WARN("LED colors indicate status: Red=Error, Green=Promise Success, Blue=Await Success");
}

// APPROACH 1: Promise-based async pattern (JavaScript-like)
// This approach uses method chaining and callbacks - very common in web development
void test_promise_approach() {
    FL_WARN("APPROACH 1: Promise-based pattern with explicit types");
    
    // TUTORIAL: fetch_get() returns fl::task::Promise<fl::net::http::Response> (not auto!)
    // The promise represents a future HTTP response that may succeed or fail
    // Chain .then() for success handling and the lambda receives a
    // const fl::net::http::Response& when the fetch succeeds. error_ will handle network device
    // failures (no connection, DNS failure, etc, but not HTTP errors like 404, 500, etc.)
    fl::net::http::fetch_get("http://fastled.io").then([](const fl::net::http::Response& response) {
        // TUTORIAL: Check if HTTP request was successful
        if (response.ok()) {
            FL_WARN("SUCCESS [Promise] HTTP fetch successful! Status: "
                    << response.status() << " " << response.status_text());

            // TUTORIAL: get_content_type() returns fl::optional<fl::string>
            // Optional types may or may not contain a value - always check!
            fl::optional<fl::string> content_type = response.get_content_type();
            if (content_type.has_value()) {
                FL_WARN("CONTENT [Promise] Content-Type: " << *content_type);
            }

            // TUTORIAL: response.text() returns fl::string with response body
            const fl::string& response_body = response.text();
            if (response_body.length() >= 100) {
                FL_WARN("RESPONSE [Promise] First 100 characters: " << response_body.substr(0, 100));
            } else {
                FL_WARN("RESPONSE [Promise] Full response (" << response_body.length()
                                         << " chars): " << response_body);
            }

            // Visual feedback: Green LEDs indicate promise-based success
            fill_solid(leds, NUM_LEDS, fl::CRGB(0, 64, 0)); // Green for promise success
        } else {
            // HTTP error (like 404, 500, etc.) - still a valid response, just an error status
            FL_WARN("ERROR [Promise] HTTP Error! Status: "
                    << response.status() << " " << response.status_text());
            FL_WARN("CONTENT [Promise] Error content: " << response.text());

            // Visual feedback: Orange LEDs indicate HTTP error
            fill_solid(leds, NUM_LEDS, fl::CRGB(64, 32, 0)); // Orange for HTTP error
        }
    })
    // TUTORIAL: Chain .catch_() for network/connection error handling  
    // The lambda receives a const fl::task::Error& when the fetch fails completely
    .catch_([](const fl::task::Error& network_error) {
        // Network error (no connection, DNS failure, etc.)
        FL_WARN("ERROR [Promise] Network Error: " << network_error.message);

        // Visual feedback: Red LEDs indicate network failure
        fill_solid(leds, NUM_LEDS, fl::CRGB(64, 0, 0)); // Red for network error
    });
}

// APPROACH 2: fl::task::await_top_level() pattern (synchronous-style async code)  
// This approach blocks until completion - feels like traditional programming
void test_await_approach() {
    FL_WARN("APPROACH 2: await_top_level pattern with explicit types");
    
    // TUTORIAL: Create a FetchOptions object to configure the HTTP request
    // FetchOptions is a data container - you can set timeout, headers, etc.
    fl::net::http::FetchOptions request_config("");  // Empty URL - will use the URL passed to fetch_get()
    request_config.timeout(5000)          // 5 second timeout
                  .header("User-Agent", "FastLED/Client-Tutorial");  // Custom user agent
    
    // TUTORIAL: fetch_get() returns fl::task::Promise<fl::net::http::Response> (explicit type!)
    // This promise represents the future HTTP response
    fl::task::Promise<fl::net::http::Response> http_promise = fl::net::http::fetch_get("http://fastled.io", request_config);
    
    // TUTORIAL: await_top_level() returns fl::task::PromiseResult<fl::net::http::Response>
    // result wraps either a successful response OR an Error - never both!
    // CRITICAL: await_top_level() blocks until completion - ONLY safe in Arduino loop()!
    fl::task::PromiseResult<fl::net::http::Response> result = fl::task::await_top_level(http_promise);
    
    // TUTORIAL: Check if the result contains a successful response
    if (result.ok()) {
        // TUTORIAL: Extract the response from the result
        // result.value() returns const fl::net::http::Response& - the actual HTTP response
        const fl::net::http::Response& http_response = result.value();
        
        FL_WARN("SUCCESS [Await] HTTP fetch successful! Status: "
                << http_response.status() << " " << http_response.status_text());

        // TUTORIAL: Check for optional Content-Type header
        // get_content_type() returns fl::optional<fl::string> - may be empty!
        fl::optional<fl::string> content_type = http_response.get_content_type();
        if (content_type.has_value()) {
            FL_WARN("CONTENT [Await] Content-Type: " << *content_type);
        }

        // TUTORIAL: Get the response body as fl::string
        const fl::string& response_body = http_response.text();
        if (response_body.length() >= 100) {
            FL_WARN("RESPONSE [Await] First 100 characters: " << response_body.substr(0, 100));
        } else {
            FL_WARN("RESPONSE [Await] Full response (" << response_body.length()
                                     << " chars): " << response_body);
        }

        // Visual feedback: Blue LEDs indicate await-based success (different from promise)
        fill_solid(leds, NUM_LEDS, fl::CRGB(0, 0, 64)); // Blue for await success
    } else {
        // Either HTTP error OR network error - both end up here
        // TUTORIAL: result.error_message() is a convenience method for getting error text
        FL_WARN("ERROR [Await] Request failed: " << result.error_message());

        // Visual feedback: Red LEDs for any await error
        fill_solid(leds, NUM_LEDS, fl::CRGB(64, 0, 0)); // Red for any error
    }
}

/// APPROACH 3: JSON Response Handling with FastLED's ideal JSON API
/// This demonstrates fetch responses with automatic JSON parsing
void test_json_response() {
    FL_WARN("APPROACH 3: JSON Response handling with fl::json integration");
    
    // TUTORIAL: Fetch a JSON API endpoint (httpbin.org provides test JSON)
    // This endpoint returns JSON with request information
    fl::net::http::fetch_get("https://httpbin.org/json").then([](const fl::net::http::Response& response) {
        if (response.ok()) {
            FL_WARN("SUCCESS [JSON Promise] HTTP fetch successful! Status: "
                    << response.status() << " " << response.status_text());
            
            // TUTORIAL: Check if response contains JSON content
            // is_json() checks Content-Type header and body content
            if (response.is_json()) {
                FL_WARN("DETECTED [JSON Promise] Response contains JSON data");
                
                // TUTORIAL: response.json() returns fl::json with FastLED's ideal API
                // Automatic parsing, caching, and safe access with defaults using operator|
                fl::json data = response.json();
                
                // TUTORIAL: Safe JSON access with defaults - never crashes!
                // Uses FastLED's proven pattern: json["path"]["to"]["key"] | default_value
                fl::string slideshow_url = data["slideshow"]["author"] | fl::string("unknown");
                fl::string slideshow_title = data["slideshow"]["title"] | fl::string("untitled");
                int slide_count = data["slideshow"]["slides"].size();
                
                FL_WARN("JSON [Promise] Slideshow Author: " << slideshow_url);
                FL_WARN("JSON [Promise] Slideshow Title: " << slideshow_title);
                FL_WARN("JSON [Promise] Slide Count: " << slide_count);
                
                // TUTORIAL: Access nested arrays safely
                if (data.contains("slideshow") && data["slideshow"].contains("slides")) {
                    fl::json slides = data["slideshow"]["slides"];
                    if (slides.is_array() && slides.size() > 0) {
                        // Get first slide information with safe defaults
                        fl::string first_slide_title = slides[0]["title"] | fl::string("no title");
                        fl::string first_slide_type = slides[0]["type"] | fl::string("unknown");
                        FL_WARN("JSON [Promise] First slide: " << first_slide_title << " (" << first_slide_type << ")");
                    }
                }
                
                // Visual feedback: Blue LEDs for successful JSON parsing
                fill_solid(leds, NUM_LEDS, fl::CRGB(0, 0, 128)); // Blue for JSON success
                
            } else {
                FL_WARN("INFO [JSON Promise] Response is not JSON format");
                // Visual feedback: Yellow for non-JSON response
                fill_solid(leds, NUM_LEDS, fl::CRGB(64, 64, 0)); // Yellow for non-JSON
            }
        } else {
            FL_WARN("ERROR [JSON Promise] HTTP error: " << response.status() 
                    << " " << response.status_text());
            // Visual feedback: Red LEDs for HTTP error
            fill_solid(leds, NUM_LEDS, fl::CRGB(64, 0, 0)); // Red for HTTP error
        }
    }).catch_([](const fl::task::Error& error) {
        FL_WARN("ERROR [JSON Promise] Network error: " << error.message);
        // Visual feedback: Purple LEDs for network error
        fill_solid(leds, NUM_LEDS, fl::CRGB(64, 0, 64)); // Purple for network error
    });
    
    FastLED.show();
}

/// APPROACH 4: JSON Response with await pattern  
/// Same JSON handling but using await_top_level for synchronous-style code
void test_json_await() {
    FL_WARN("APPROACH 4: JSON Response with await pattern");
    
    // TUTORIAL: Using await pattern with JSON responses
    // fl::net::http::fetch_get() returns fl::task::Promise<fl::net::http::Response>
    fl::task::Promise<fl::net::http::Response> json_promise = fl::net::http::fetch_get("https://httpbin.org/get");
    
    // TUTORIAL: await_top_level() converts promise to result
    // fl::task::PromiseResult<fl::net::http::Response> contains either response or error
    fl::task::PromiseResult<fl::net::http::Response> result = fl::task::await_top_level(json_promise);
    
    if (result.ok()) {
        // TUTORIAL: Extract the response from the result
        const fl::net::http::Response& http_response = result.value();
        
        FL_WARN("SUCCESS [JSON Await] HTTP fetch successful! Status: "
                << http_response.status() << " " << http_response.status_text());
        
        // TUTORIAL: Check for JSON content and parse if available
        if (http_response.is_json()) {
            FL_WARN("DETECTED [JSON Await] Response contains JSON data");
            
            // TUTORIAL: Parse JSON with automatic caching
            fl::json data = http_response.json();
            
            // TUTORIAL: httpbin.org/get returns information about the request
            // Extract data with safe defaults using FastLED's ideal JSON API
            fl::string origin_ip = data["origin"] | fl::string("unknown");
            fl::string request_url = data["url"] | fl::string("unknown");
            
            FL_WARN("JSON [Await] Request Origin IP: " << origin_ip);
            FL_WARN("JSON [Await] Request URL: " << request_url);
            
            // TUTORIAL: Access nested headers object safely
            if (data.contains("headers")) {
                fl::json headers = data["headers"];
                fl::string user_agent = headers["User-Agent"] | fl::string("unknown");
                fl::string accept = headers["Accept"] | fl::string("unknown");
                
                FL_WARN("JSON [Await] User-Agent: " << user_agent);
                FL_WARN("JSON [Await] Accept: " << accept);
            }
            
            // TUTORIAL: Access query parameters (if any)
            if (data.contains("args")) {
                fl::json args = data["args"];
                if (args.size() > 0) {
                    FL_WARN("JSON [Await] Query parameters found: " << args.size());
                } else {
                    FL_WARN("JSON [Await] No query parameters in request");
                }
            }
            
            // Visual feedback: Cyan LEDs for successful await JSON processing
            fill_solid(leds, NUM_LEDS, fl::CRGB(0, 128, 128)); // Cyan for await JSON success
            
        } else {
            FL_WARN("INFO [JSON Await] Response is not JSON format");
            // Visual feedback: Orange for non-JSON with await
            fill_solid(leds, NUM_LEDS, fl::CRGB(128, 32, 0)); // Orange for non-JSON await
        }
        
    } else {
        // TUTORIAL: Handle request failures (network or HTTP errors)
        FL_WARN("ERROR [JSON Await] Request failed: " << result.error_message());
        // Visual feedback: Red LEDs for any await error
        fill_solid(leds, NUM_LEDS, fl::CRGB(128, 0, 0)); // Red for await error
    }
    
    FastLED.show();
}

void loop() {
    // TUTORIAL: Cycle between different async approaches every 10 seconds
    // This allows you to see both promise-based and await-based patterns in action
    // The LEDs provide visual feedback about which approach succeeded

    unsigned long current_time = fl::millis();
    
    // Switch approaches every 10 seconds
    // 4 different approaches: Promise, Await, JSON Promise, JSON Await
    if (current_time - last_request_time >= 10000) {
        last_request_time = current_time;
        
        // Cycle through 4 different approaches
        if (approach_counter % 4 == 0) {
            test_promise_approach();
            FL_WARN("CYCLE: Demonstrated Promise-based pattern (Green LEDs on success)");
        } else if (approach_counter % 4 == 1) {
            test_await_approach();
            FL_WARN("CYCLE: Demonstrated Await-based pattern (Blue LEDs on success)");
        } else if (approach_counter % 4 == 2) {
            test_json_response();
            FL_WARN("CYCLE: Demonstrated JSON Promise pattern (Blue LEDs on success)");
        } else if (approach_counter % 4 == 3) {
            test_json_await();
            FL_WARN("CYCLE: Demonstrated JSON Await pattern (Cyan LEDs on success)");
        }
        
        approach_counter++;
        
        FL_WARN("NEXT: Will switch to next approach in 10 seconds...");
    }

    // TUTORIAL NOTE: Async operations are automatically managed!
    // * On WASM: delay() pumps async tasks every 1ms automatically
    // * On all platforms: FastLED.show() triggers async updates via engine events
    // * No manual async updates needed - everything happens behind the scenes!


    // TUTORIAL: This delay automatically pumps async tasks on WASM!
    // The delay is broken into 1ms chunks with async processing between chunks.
    // This isn't necessary when calling the await approach, but is critical
    // the standard promise.then() approach.
    // Note: In the future loop() may become a macro to inject auto pumping
    // of the async tasks.
    delay(10);
}
