<?php

class agile_tix_custom_support
{
    private $agile_tix;
    private $version;

    public function __construct($agile_tix, $version)
    {
        $this->agile_tix = $agile_tix;
        $this->version = $version;
    }
    
    static function agile_user_custom_post_creator($event, $commentStat, $postStat, $isNewEvent, $postID, $functions, $comingSoonEventData = null)
    {
        
        $postType = get_option('agile_field_theme_custom_map_post_type');
        //agile_tix::agile_tix_log_event("agile_user_custom_post_creator postType : $postType");
        if ($postType == '' || $postType == false) {
            //if we don't have a known post type, escape this func, something went wrong
            agile_tix::agile_tix_log_event("Custom Post Creator: missing post type while processing event " . ($event['ID'] ?? 'UNKNOWN_ID'));
            return;
        }
        
        $template = get_option('agile_field_theme_custom_map_template') ?? '';
        $thumbnailID = 0;
        $EventImage = $event['EventImage'] ?? '';
        if (!empty($EventImage)) {
            $mediaLibImg = agile_tix_post_creator::add_image_to_media_library_from_uri($EventImage);
            if ($mediaLibImg && array_key_exists('url', $mediaLibImg) && array_key_exists('attachment_id', $mediaLibImg)) {
                $thumbnailID = $mediaLibImg['attachment_id'];
            }
        }

        $showingIDs = [];
        // Coming soon events never have CurrentShowings
        if (isset($event["CurrentShowings"]) && is_array($event["CurrentShowings"])) {
            foreach ($event["CurrentShowings"] as $showing){
                array_push($showingIDs, $showing["ID"]);
            }
        }


        $meta_input = agile_tix_post_creator::agile_create_universal_meta_fields($event, $thumbnailID, $comingSoonEventData);
        //agile_tix::agile_tix_log_event("agile_user_custom_post_creator line by line 1");
        //$meta_input = array();
        //$meta_input["ATS_ModifiedDate"] = agile_tix_post_creator::agile_get_most_recent_lastUpdate($event) ?? '';
        //agile_tix::agile_tix_log_event("agile_user_custom_post_creator line by line 2");
        //$meta_input["ATS_EventID"] = $event['ID'] ?? '';
        //$meta_input["ATS_MediaLib_Img_ID"] = $thumbnailID ?? '';
        //$meta_input["ATS_firstEndDate"] = $event['CurrentShowings'][0]['EndDate'] ?? '';
        //$meta_input["ATS_showingIDs"] = $showingIDs ?? [];

        //$meta_input["ATS_HideFeatImg"] = get_option('agile_field_render_carousel') ?? 0;
        $custom_fields = get_option('agile_custom_fields', []);
        $custom_meta_input = [];
        //$functions = agile_tix_custom_support::agile_collect_user_functions();
        //agile_tix::agile_tix_log_event("agile_user_custom_post_creator line by line 3");
        $needsFunctions = false;

        $pattern = '/' . implode('|', array_map('preg_quote', $functions)) . '/';


        //agile_tix::agile_tix_log_event("agile_user_custom_post_creator pre-loop");
        foreach ($custom_fields as $index => $value){
            //$metaFunction could be actual code or a simple array traversal path
            $metaName = $value[0];
            $metaFunction = $value[1];
            //agile_tix::agile_tix_log_event("custom_fields loop metaName:" . $metaName);
            //agile_tix::agile_tix_log_event("custom_fields loop metaFunction:" . $metaFunction);
            if ($functions && !empty($functions)) {
                $needsFunctions = preg_match($pattern, $metaFunction) > 0;
            }
            //agile_tix::agile_tix_log_event("needsFunctions : $needsFunctions");

            if ($needsFunctions){
                [$functionName, $argArray] = agile_tix_custom_support::extract_function_and_args($metaFunction);
                $customResult = agile_tix_custom_support::execute_user_code($event, $functionName, $argArray);
                //$customResult = agile_tix_custom_support::execute_user_code($event, $metaFunction);
                //agile_tix::agile_tix_log_event("custom_fields loop needsFunctions customResult:" . $customResult);
                $custom_meta_input[$metaName] = $customResult;
            } else {
                //Horray! the user isn't using any custom functions, so we can use safe array taversal, thank god
                //This assumes $code is a simple array traversal path in dot or bracket notation
                $custom_meta_input[$metaName] = agile_tix_custom_support::agile_safe_Array_Transversal($event, $metaFunction);
            }
        }

        if (!empty($custom_meta_input)){
            //agile_tix::agile_tix_log_event("custom_meta_input: $custom_meta_input");
            //agile_tix::agile_tix_log_event("meta_input before push:" . json_encode($meta_input));
            $meta_input += $custom_meta_input;
            //agile_tix::agile_tix_log_event("meta_input after push:" . json_encode($meta_input));
        }

        $post_data = array(
            'ID' => $postID,
            'post_title' => $event['Name'],
            'post_content' => agile_tix_post_creator::agile_create_universal_content_body($event) . '<div class="hiddenTags">' . $event['CorpOrgName'] . '</div> ',
            'post_status' => $postStat,
            'post_type' => $postType,
            'page_template' => $template,
            'comment_status' => $commentStat,
            'meta_input' => $meta_input,
        );


        if ($isNewEvent) {
            $postID = wp_insert_post($post_data);
        } else {
            $postID = wp_update_post($post_data);
        }

        if (is_wp_error($postID)) {
            $error_message = $postID->get_error_message();
            agile_tix::agile_tix_log_event("Custom Post Creator: wp_insert_post/wp_update_post failed for event " . ($event['ID'] ?? 'UNKNOWN_ID') . " - " . $error_message);
            return;
        }

        //agile_tix::agile_tix_log_event("Custom Post Creator: post " . ($isNewEvent ? 'created' : 'updated') . " for event " . ($event['ID'] ?? 'UNKNOWN_ID') . " with WP post ID " . $postID);

        $taxonomies = get_option('agile_term_fields', []);
        $needsFunctions = false;
        //agile_tix::agile_tix_log_event("taxonomies loop begin:");
        foreach ($taxonomies as $index => $value) {
            //$metaFunction could be actual code or a simple array traversal path
            $termName = $value[0];
            $termFunction = $value[1];
            if ($functions && !empty($functions)) {
                $needsFunctions = preg_match($pattern, $termFunction) > 0;
            }
            //agile_tix::agile_tix_log_event("tax loop termName: " . $termName);
            //agile_tix::agile_tix_log_event("custom_terms loop termFunction: " . $termFunction);

            if ($needsFunctions) {
                //agile_tix::agile_tix_log_event("tax loop needsFunctions pre-execute_user_code");
                //$customResult = agile_tix_custom_support::execute_user_code($event, $termFunction)
                [$functionName, $argArray] = agile_tix_custom_support::extract_function_and_args($termFunction);
                $customResult = agile_tix_custom_support::execute_user_code($event, $functionName, $argArray);
                //agile_tix::agile_tix_log_event("tax loop needsFunctions customResult:" . $customResult);
                wp_set_object_terms($postID, $customResult , $termName);
            } else {
                //agile_tix::agile_tix_log_event("tax loop needsFunctions pre-safe_Array_Transversal");
                $customResult = agile_tix_custom_support::agile_safe_Array_Transversal($event, $termFunction);
                //agile_tix::agile_tix_log_event("tax loop !needsFunctions customResult:" . $customResult);
                wp_set_object_terms($postID, $customResult, $termName);
            }
        }

        if ($thumbnailID != '') {
            set_post_thumbnail($postID, $thumbnailID);
        }
        //agile_tix::agile_tix_log_event("agile_user_custom_post_creator end");
    }


    static function execute_user_code($event, $functionName, $argArray)
    {
        if (empty($functionName)) {
            return ['error' => 'No function provided'];
        }

        $disable_functions = ['exec', 'system', 'shell_exec', 'passthru', 'eval', 'fopen', 'file_put_contents', 'unlink', 'curl_exec'];
        foreach ($disable_functions as $func) {
            if (stripos($functionName, $func) !== false) {
                return ['error' => 'Unsafe function detected.'];
            }
        }

        try {
            $args = agile_tix_custom_support::parse_arguments($argArray);

            if (isset($args['error'])) {
                return $args;
            }

            if (function_exists($functionName)) {
                return call_user_func_array($functionName, array_merge([$event], $args));
            } else {
                agile_tix::agile_tix_log_event("execute_user_code failed - $functionName");
                return ['error' => 'Function not found: ' . $functionName];
            }
        } catch (Throwable $e) {
            agile_tix::agile_tix_log_event("execute_user_code failed -" . $e->getMessage());
            return ['error' => 'Execution failed: ' . $e->getMessage()];
        }
    }


    /*
     * Parse our user defined string of (hopefully) php code
     * Scan against black-listed functions
     * Doesn't actually execute any thing, just eval, just creates an array of function names to execute after the code is actually eval'd
     * */
    static function agile_collect_user_functions() {

        //agile_tix::agile_tix_log_event("agile_collect_user_functions begin");
        $userDefinedCode = get_option('agile_field_theme_custom_code', '');


        // Forbidden functions
        $disable_functions = ['exec', 'system', 'shell_exec', 'passthru', 'eval', 'fopen', 'file_put_contents', 'unlink', 'curl_exec'];

        // Check for dangerous functions
        foreach ($disable_functions as $func) {
            if (stripos($userDefinedCode, $func) !== false) {
                //agile_tix::agile_tix_log_event("agile_collect_user_functions disabled func found");
                return false;
                //return "Error: Forbidden function ($func) detected!";
            }
        }
        //token_get_all cannot take strings that explicitly contain the <?php starting string
        //Based on error messages: I think this is because the Zend engine's lexical scanner inserts its own tag at string[0] to parse tokens
        $tokens = token_get_all("<?php " . $userDefinedCode);
        $functions = [];
        $capture = false;
        $functionName = '';
        //agile_tix::agile_tix_log_event("agile_collect_user_functions loop begin");
        // Loop through the tokens to find function definitions
        foreach ($tokens as $token) {
            if (is_array($token) && $token[0] == T_FUNCTION) {
                $capture = true;  // Start capturing the function name
            } elseif ($capture && is_array($token) && $token[0] == T_STRING) {
                $functionName = $token[1];  
                $functions[] = $functionName;  
                $capture = false;
            }
        }
        //agile_tix::agile_tix_log_event("agile_collect_user_functions loop end");
        // Check if any functions were found
        if (empty($functions)) {
            //agile_tix::agile_tix_log_event("agile_collect_user_functions (empty(functions))");
            return false;
            //return "Error: No valid functions found in the provided code.";
        }

        $evalNeeded = false;
        foreach ($functions as $function){
            if (!function_exists($function)){
                $evalNeeded = true;
                break;
            }
        }
        if (!$evalNeeded){
            //if all the included functions already exist in scope, no point in doing it again
            //agile_tix::agile_tix_log_event("agile_collect_user_functions functions unneeded");
            return $functions;
        }

        // Execute user-defined functions safely
        //TODO: even more sanitization/ saftey checks before eval??
        //agile_tix::agile_tix_log_event("agile_collect_user_functions invoked. userDefinedCode = " . $userDefinedCode);
        ob_start();
        try {
            //agile_tix::agile_tix_log_event("agile_collect_user_functions pre-eval .");
            eval ($userDefinedCode);
            //agile_tix::agile_tix_log_event("agile_collect_user_functions post-eval.");
            $output = ob_get_clean(); // Capture any warnings/errors
            //agile_tix::agile_tix_log_event("agile_collect_user_functions eval worked." );
        } catch (ParseError $e) {
            ob_end_clean(); 
            agile_tix::agile_tix_log_event("agile_collect_user_functions eval error: ." . $e->getMessage());
            throw new Exception("Syntax error in evaluated code: " . $e->getMessage());
        }

        //agile_tix::agile_tix_log_event("agile_collect_user_functions return.");
        return $functions;  // Return functions indexed by name
    }


    /*
     * Basic array traversal to avoid using eval() when possible, ideally supports bracket and dot notation, bracket notation can be kind of funky
     * 
     * */
    static function agile_safe_Array_Transversal($array, $path)
    {
        //agile_tix::agile_tix_log_event("agile_safe_Array_Transversal invoked. path = " . $path);

        if (!is_array($array) || empty($path)) {
            return null;
        }

        // Normalize path: Convert bracket notation ['key'] to dot notation key
        $path = preg_replace("/\['?([a-zA-Z0-9_]+)'?\]/", '.$1', $path);

        // Remove leading dots (in case path starts with bracket notation)
        $path = ltrim($path, '.');
        $keys = explode('.', $path);

        //agile_tix::agile_tix_log_event('$array before traversal = ' . json_encode($array));

        foreach ($keys as $key) {
            //agile_tix::agile_tix_log_event("Traversing key: $key");

            if (is_array($array) && array_key_exists($key, $array)) {
                $array = $array[$key]; 
            } else {
                //agile_tix::agile_tix_log_event("Key $key not found. Returning null.");
                return null;
            }
        }

        //agile_tix::agile_tix_log_event("Final value found: " . json_encode($array));
        return $array; 
    }


    //I love dynamically typed languages (:
    static function parse_arguments($argArray)
    {
        $args = [];

        if (!is_array($argArray)) {
            return ['error' => 'Invalid argument format. Expected an array.'];
        }

        foreach ($argArray as $token) {
            if (is_numeric($token)) {
                $args[] = $token + 0; // Convert to int or float
            } elseif (is_string($token)) {
                $lowerToken = strtolower($token);
                if ($lowerToken === "true") {
                    $args[] = true;
                } elseif ($lowerToken === "false") {
                    $args[] = false;
                } elseif (in_array($lowerToken, ["null", "nil"], true)) {
                    $args[] = null;
                } else {
                    $args[] = $token; 
                }
            } else {
                $args[] = $token; // Already valid
            }
        }

        return $args;
    }

    static function extract_function_and_args($code)
    {
        if (preg_match('/^([a-zA-Z_][a-zA-Z0-9_]*)\((.*?)\)$/', $code, $matches)) {
            $functionName = $matches[1];
            $argString = trim($matches[2]);
            $argArray = $argString !== '' ? json_decode("[$argString]", true) : [];

            return [$functionName, $argArray];
        }
        return [$code, []]; // If no function syntax detected, treat it as a traversal path
    }

}
