<?php
/*
Plugin Name:    Tabular 3
Description:    Integrates DataTables.net with WordPress for inline editing of posts and custom fields. 
                Display posts (with metadata and custom fields) in a DataTables editor UI.
Version:        1.0
Author:         dewolfe001 (shawn@shawndewolfe.com)

*/

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly.
}

class Tabular3 {
    private static $instance = null;
    public $plugin_path;
    public $plugin_url;
    public $class_name;
    

    private function __construct() {
        $this->plugin_path = plugin_dir_path( __FILE__ );
        $this->plugin_url  = plugin_dir_url( __FILE__ );

        $this->class_name = [
            'text' => 'text',
            'number' => 'number',
            'date' => 'date',
            'textarea' => 'textarea',
            'wysiwyg' => 'wysiwyg',
            'media_list' => 'media_list',
            'featured_image' => 'featured_image',
            'media_upload' => 'media_upload',
            'taxonomy_autocomplete' => 'taxonomy_autocomplete',
            'select' => 'select',
            'relationship' => 'relationship',
            'readonly' => 'readonly',
            'hidden' => 'hidden'           
        ];
        
        // Activation and deactivation hooks.
        register_activation_hook(__FILE__, array($this, 'activate'));
        register_deactivation_hook(__FILE__, array($this, 'deactivate'));

        // Admin menu and assets.
        add_action('admin_menu', array($this, 'register_admin_menu'));
        add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_assets'));
        add_action('admin_enqueue_scripts', array($this, 'enqueue_tabular_extensions'));

        // Ajax handlers.
        add_action('wp_ajax_tabular3_get_posts', array($this, 'ajax_get_posts'));
        add_action('wp_ajax_tabular3_update_posts', array($this, 'ajax_update_posts'));
        
        // Register the AJAX handler for logged-in and non-logged in users
        add_action( 'wp_ajax_tabular3_get_taxonomy_terms', array( $this, 'ajax_get_taxonomy_terms' ) );
        // add_action( 'wp_ajax_nopriv_tabular3_get_taxonomy_terms', array( $this, 'ajax_get_taxonomy_terms' ) );
        
        add_action( 'wp_ajax_tabular3_get_relationship_items', array( $this, 'ajax_get_relationship_items' ) );
        // add_action( 'wp_ajax_nopriv_tabular3_get_relationship_items', array( $this, 'ajax_get_relationship_items' ) );
        
        add_action( 'wp_ajax_search', array( $this, 'ajax_narrow' ) );        
        
        // add_action('wp_ajax_tabular3_upload_media', array($this, 'ajax_upload_media'));         
        add_action('wp_ajax_tabular3_handle_media_upload', array($this, 'ajax_handle_media_upload'));

        add_action('wp_ajax_tabular3_load_media_scripts', array($this, 'ajax_load_media_scripts'));
        add_action('wp_ajax_tabular3_get_attachment_url', array($this, 'ajax_get_attachment_url'));
        add_action('wp_ajax_tabular3_get_attachment_id_by_url', array($this, 'ajax_get_attachment_id_by_url'));

        add_action('admin_post_save_tabular3_settings', array($this, 'save_tabular3_settings'));
        

        // Allow extensions to hook in.
        do_action('tabular3_loaded');
    }

    public static function instance() {
        if ( self::$instance == null ) {
            self::$instance = new Tabular3();
        }
        return self::$instance;
    }

    public function activate() {
        // Create default options if needed.
        if ( false === get_option('tabular3_options') ) {
            update_option('tabular3_options', array());
        }
    }

    public function deactivate() {
        // Cleanup tasks if needed.
    }

    /**
     * Register the settings page under Settings and also add a submenu for each enabled post type.
     */
    public function register_admin_menu() {
        // Add main Tabular settings page.
        add_options_page(
            'Tabular Settings',
            'Tabular',
            'manage_options',
            'tabular3-settings',
            array($this, 'render_settings_page')
        );

        // For each public post type that is enabled for Tabular, add a submenu.
        $post_types = $this->get_post_types();
        foreach ( $post_types as $post_type ) {
            $enabled = get_option('tabular3_' . $post_type . '_enabled', false);
            if ( $enabled ) {
                if ($post_type == 'post') {
                    add_submenu_page(
                        'edit.php',
                        'Tabular View for Posts',
                        'Tabular',
                        'edit_posts',
                        'tabular3-view-' . $post_type,
                        array($this, 'render_tabular_view'),
                        -100
                    );             
                    continue;
                }
                if ($post_type == 'attachment') {
                    add_submenu_page(
                        'upload.php',
                        'Tabular View for Media',
                        'Tabular',
                        'edit_posts',
                        'tabular3-view-' . $post_type,
                        array($this, 'render_tabular_view'),
                        -100
                    );             
                    continue;
                }
                
                add_submenu_page(
                    'edit.php?post_type=' . $post_type,
                    'Tabular View for '.ucwords($post_type),
                    'Tabular',
                    'edit_posts',
                    'tabular3-view-' . $post_type,
                    array($this, 'render_tabular_view'),
                    -100
                );
            }
        }
    }
        
    public function enqueue_admin_assets($hook) {

        if ($_GET['page'] == 'tabular3-settings') {
            // For draggable (sortable) fields.
            wp_enqueue_script('jquery');            
            wp_enqueue_style('jquery-ui-css', 'https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css');
            wp_enqueue_script('jquery-ui-sortable');          
    
            // ✅ Load DataTables Core AFTER Bootstrap
            wp_enqueue_script(
                'datatables-settings-js',
                $this->plugin_url . 'assets/js/tabular3-settings.js',
                ['jquery', 'jquery-ui-sortable'],
                '2.2.2',
                true
            );
    
            // ✅ Load Custom Admin CSS
            wp_enqueue_style(
                'tabular3-admin-css',
                $this->plugin_url . 'assets/css/tabular3-admin.css',
                [],
                '1.0'
            );    
            
            // get outta here
            return;
        }

        if (strpos($hook, 'tabular3-') === false && strpos($hook, 'edit.php') === false) {
            return;
        }

        wp_enqueue_script('jquery');
    
        wp_enqueue_script('bootstrap-js', 'https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js', ['jquery'], '4.6.0', false);
    
        // ✅ Ensure Bootstrap 4 is loaded FIRST
        wp_enqueue_style(
            'bootstrap4-css',
            'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.2/css/bootstrap.min.css',
            [],
            '4.5.2'
        );
        wp_enqueue_script(
            'bootstrap4-js',
            'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.2/js/bootstrap.bundle.min.js',
            ['jquery'],
            '4.5.2',
            true
        );
    
        // ✅ Load DataTables Core AFTER Bootstrap
        wp_enqueue_script(
            'datatables-js',
            $this->plugin_url . 'assets/datatables/datatables.js',
            ['jquery', 'bootstrap4-js'],
            '2.2.2',
            true
        );
        wp_enqueue_style(
            'datatables-css',
            $this->plugin_url . 'assets/datatables/datatables.css',
            [],
            '2.2.2'
        );
    
        // ✅ Load DataTables Editor JS AFTER DataTables Core
        wp_enqueue_script(
            'datatables-editor-js',
            $this->plugin_url . 'assets/datatables/js/dataTables.editor.min.js',
            ['datatables-js'],
            '2.2.2',
            true
        );
    
        // ✅ Load Editor Bootstrap 4 AFTER DataTables Editor
        wp_enqueue_script(
            'editor-bootstrap4-js',
            $this->plugin_url . 'assets/datatables/js/editor.bootstrap4.min.js',
            ['datatables-editor-js'],
            '2.2.2',
            true
        );
        wp_enqueue_style(
            'editor-bootstrap4-css',
            $this->plugin_url . 'assets/datatables/css/editor.bootstrap4.min.css',
            ['bootstrap4-css'],
            '2.2.2'
        );
    
        // ✅ Load Custom Admin JavaScript (LAST)
        wp_enqueue_script(
            'tabular3-admin',
            $this->plugin_url . 'assets/js/tabular3-admin.js',
            ['jquery', 'datatables-js', 'datatables-editor-js', 'editor-bootstrap4-js'],
            '1.0',
            true
        );
    
        // ✅ Load Custom Admin CSS
        wp_enqueue_style(
            'tabular3-admin-css',
            $this->plugin_url . 'assets/css/tabular3-admin.css',
            [],
            '1.0'
        );
    
        // Add jQuery UI for dialogs
        wp_enqueue_script('jquery-ui-dialog');
        wp_enqueue_style('wp-jquery-ui-dialog');
        
        // ✅ Localize script for AJAX with additional data
        wp_localize_script('tabular3-admin', 'tabular3', [
            'ajax_url' => admin_url('admin-ajax.php'),
            'admin_url' => admin_url(),
            'plugin_url' => $this->plugin_url,
            'nonce' => wp_create_nonce('tabular3_nonce')
        ]);
    }

    
    public function enqueue_tabular_extensions() {

        wp_enqueue_media();

        // Enqueue base admin script
        wp_enqueue_script(
            'tabular3-admin', 
            plugin_dir_url(__FILE__) . 'js/tabular3-admin.js', 
            ['jquery', 'datatables', 'datatables-editor'], 
            '1.0.0', 
            true
        );
    
        // Enqueue media extensions
        wp_enqueue_script(
            'tabular3-media-extensions', 
            plugin_dir_url(__FILE__) . 'js/tabular3-media-extensions.js', 
            ['tabular3-admin', 'jquery', 'datatables', 'datatables-editor', 'media-upload', 'wp-media'], 
            '1.0.0', 
            true
        );
    
        // Enqueue custom field extensions
        wp_enqueue_script(
            'custom-field-extensions', 
            plugin_dir_url(__FILE__) . 'js/custom-field-extensions.js', 
            ['tabular3-admin'], 
            '1.0.0', 
            true
        );

        
        // Optional: Add more extension scripts
        // wp_enqueue_script(
        //     'tabular3-custom-extensions', 
        //     plugin_dir_url(__FILE__) . 'js/custom-extensions.js', 
        //     ['tabular3-admin'], 
        //     '1.0.0', 
        //     true
        // );
    }
        
    public function save_tabular3_settings() {
        // Check if the form is submitted

        if (!isset($_POST['tabular3_nonce']) || !wp_verify_nonce($_POST['tabular3_nonce'], 'tabular3_save_settings')) {
            return; // Security check failed, exit early
        }
    
        if (!current_user_can('manage_options')) {
            return; // Ensure only admins can save settings
        }
    
        // Iterate over each post type
        if (isset($_POST['fields']) && is_array($_POST['fields'])) {
            foreach ($_POST['fields'] as $post_type => $fields) {
    
                error_log(__LINE__." - working with $post_type ");

                if (!post_type_exists($post_type)) {
                    continue; // Skip invalid post types
                }
    
                $field_keys = [];
                foreach ($fields as $field_key => $settings) {
                    // Ensure a valid field key
                    $field_key = sanitize_key($field_key);
                    if (empty($field_key)) {
                        continue;
                    }
    
                    // Sanitize common settings
                    $sanitized_settings = [
                        'label' => sanitize_text_field($settings['label']),
                        'input_format' => sanitize_text_field($settings['input_format']),
                        'priority' => sanitize_text_field($settings['priority']),                        
                    ];
    
                    // Handle dynamic options
                    if ($settings['input_format'] === 'taxonomy_autocomplete') {
                        $sanitized_settings['taxonomy'] = sanitize_key($settings['taxonomy']);
                    }
                    if ($settings['input_format'] === 'select') {
                        $sanitized_settings['select_options'] = sanitize_textarea_field($settings['select_options']);
                    }
                    if ($settings['input_format'] === 'relationship') {
                        $sanitized_settings['relationship_type'] = sanitize_key($settings['relationship_type']);
                    }
    
                    error_log(__LINE__." - to stow ".print_r($sanitized_settings, TRUE));
    
                    // Save the settings in wp_options
                    update_option('tabular3_' . $post_type . '_field_' . $field_key, $sanitized_settings);
                    $field_keys[] = $field_key;
                }
                update_option('tabular3_' . $post_type . '_fields', $field_keys);
                                        
                error_log('compared '.print_r(get_option('tabular3_' . $post_type . '_fields'), TRUE));
                
            }
        }
    
        // Redirect back to the settings page with a success message
        wp_redirect(add_query_arg('tabular3_saved', 'true', admin_url('options-general.php?page=tabular3-settings')));
        exit;
    }


    /**
     * Render the settings page where post types and field options are managed.
     */
    public function render_settings_page() {
        ?>
        <div class="wrap">
            <h1>Tabular3 Settings</h1>
    
            <?php if (isset($_GET['tabular3_saved']) && $_GET['tabular3_saved'] === 'true') : ?>
                <div class="updated notice is-dismissible">
                    <p>Settings saved successfully.</p>
                </div>
            <?php endif; ?>
    
            <form method="post" action="<?php echo admin_url('admin-post.php'); ?>">
                <input type="hidden" name="action" value="save_tabular3_settings">
                <?php wp_nonce_field('tabular3_save_settings', 'tabular3_nonce'); ?>
    
                <h2>Field Settings</h2>
    
                <?php
                // Get all registered post types
                $post_types = get_post_types(['public' => true], 'objects');
                foreach ($post_types as $post_type_obj) {
                    
                    $post_type = $post_type_obj->name;
                    echo '<h3>' . esc_html($post_type_obj->label) . '</h3>';
                    $enabled = get_option('tabular3_' . $post_type . '_enabled', false);    
                    ?>
                    <tr>
                        <td colspan="2">
                            <label>
                                <input type="checkbox" name="tabular3_<?php echo esc_attr($post_type); ?>_enabled" value="1" <?php checked(1, $enabled, true); ?> />
                                Enable Tabular for <?php echo esc_html($post_type); ?>
                            </label>
                        </td>
                    </tr>
                    <?php
        
                    if ($enabled != 1) {
                        continue;
                    }
        
                    // Retrieve stored settings for this post type
                    // $fields_option = get_option('tabular3_' . $post_type . '_fields', []);
                        
                    // Get the current saved fields
                    $fields_option = get_option('tabular3_' . $post_type . '_fields', []);
                    
                    // Get the latest detected fields dynamically
                    $detected_fields = $this->get_custom_fields_for_post_type($post_type);
                    
                    // If no fields are saved or if the schema has changed, update the option
                    if (empty($fields_option) || array_diff($detected_fields, $fields_option)) {
                        update_option('tabular3_' . $post_type . '_fields', $detected_fields);
                        $fields_option = $detected_fields;
                    }

                    if (empty($fields_option)) {
                        echo '<p>No custom fields found for this post type.</p>';
                        continue;
                    }
    
                    echo '<p>Drag to reorder fields:</p>';
                    echo '<ul class="tabular3-fields-list">';
    
                    foreach ($fields_option as $field_key) {
                        $stored_settings = get_option('tabular3_' . $post_type . '_field_' . sanitize_key($field_key), []);
                        $label = isset($stored_settings['label']) ? esc_attr($stored_settings['label']) : '';
                        $priority = isset($stored_settings['priority']) ? esc_attr($stored_settings['priority']) : '';
                        $input_format = isset($stored_settings['input_format']) ? esc_attr($stored_settings['input_format']) : '';
                        $taxonomy = isset($stored_settings['taxonomy']) ? esc_attr($stored_settings['taxonomy']) : '';
                        $select_options = isset($stored_settings['select_options']) ? esc_textarea($stored_settings['select_options']) : '';
                        $relationship_type = isset($stored_settings['relationship_type']) ? esc_attr($stored_settings['relationship_type']) : '';
    
                        ?>
    
                        <li class="tabular3-field-item">
                            <strong><?php echo esc_html($field_key); ?></strong>
                            <label><span>Label:</span>
                                <input type="text" name="fields[<?php echo esc_attr($post_type); ?>][<?php echo esc_attr($field_key); ?>][label]" 
                                       value="<?php echo esc_attr($label); ?>" />
                            </label>
                            <label><span>Priority:</span>
                                <select class="tabular-priority" data-field-key="<?php echo esc_attr($field_key); ?>" 
                                        name="fields[<?php echo esc_attr($post_type); ?>][<?php echo esc_attr($field_key); ?>][priority]">
                                    <option value="" <?php selected($priority, ''); ?>>Default</option>
                                    <?php for ($p = 1; $p <= 10; $p++) { ?>
                                    <option value="<?php echo $p; ?>" <?php selected($priority, $p); ?>>Priority <?php echo $p; ?></option>
                                    <?php } ?>
                                </select>
                            </label>
                            <label><span>Input Format:</span>
                                <select class="tabular-input-format" data-field-key="<?php echo esc_attr($field_key); ?>" 
                                        name="fields[<?php echo esc_attr($post_type); ?>][<?php echo esc_attr($field_key); ?>][input_format]">
                                    <option value="text" <?php selected($input_format, 'text'); ?>>Text</option>
                                    <option value="number" <?php selected($input_format, 'number'); ?>>Number</option>
                                    <option value="date" <?php selected($input_format, 'date'); ?>>Date</option>
                                    <option value="textarea" <?php selected($input_format, 'textarea'); ?>>Textarea</option>
                                    <option value="wysiwyg" <?php selected($input_format, 'wysiwyg'); ?>>WYSIWYG</option>
                                    <option value="media_list" <?php selected($input_format, 'media_list'); ?>>Media List</option>                                    
                                    <option value="featured_image" <?php selected($input_format, 'featured_image'); ?>>Featured Image</option>
                                    <option value="media_upload" <?php selected($input_format, 'media_upload'); ?>>Media Upload</option>
                                    <option value="taxonomy_autocomplete" <?php selected($input_format, 'taxonomy_autocomplete'); ?>>Taxonomy Autocomplete</option>
                                    <option value="select" <?php selected($input_format, 'select'); ?>>Select List</option>
                                    <option value="relationship" <?php selected($input_format, 'relationship'); ?>>Relationship</option>
                                    <option value="readonly" <?php selected($input_format, 'readonly'); ?>>Read Only</option>
                                    <option value="hidden" <?php selected($input_format, 'hidden'); ?>>Hidden</option>
                                </select>
                            </label>
                            <!-- Conditional Sections -->
                            <div class="conditional-settings" id="conditional-<?php echo esc_attr($field_key); ?>">
                                <!-- Taxonomy Autocomplete -->
                                <div class="conditional-section taxonomy_autocomplete" style="display:none;">
                                    <label>Select Taxonomy for Autocomplete:
                                        <select name="fields[<?php echo esc_attr($post_type); ?>][<?php echo esc_attr($field_key); ?>][taxonomy]">
                                            <?php foreach (get_taxonomies([], 'objects') as $tax) : ?>
                                                <option value="<?php echo esc_attr($tax->name); ?>" <?php selected($taxonomy, $tax->name); ?>>
                                                    <?php echo esc_html($tax->label); ?>
                                                </option>
                                            <?php endforeach; ?>
                                        </select>
                                    </label>
                                </div>
    
                                <!-- Select List -->
                                <div class="conditional-section select" style="display:none;">
                                    <label>Enter select options (one per line):
                                        <textarea name="fields[<?php echo esc_attr($post_type); ?>][<?php echo esc_attr($field_key); ?>][select_options]"><?php echo esc_textarea($select_options); ?></textarea>
                                    </label>
                                </div>
    
                                <!-- Relationship -->
                                <div class="conditional-section relationship" style="display:none;">
                                    <label>Select Relationship Type:
                                        <select name="fields[<?php echo esc_attr($post_type); ?>][<?php echo esc_attr($field_key); ?>][relationship_type]">
                                            <option value="">-- Select Relationship --</option>
                                
                                            <!-- Post Types -->
                                            <optgroup label="Post Types">
                                                <?php foreach (get_post_types(['public' => true], 'objects') as $post_type_obj) : ?>
                                                    <option value="post_type_<?php echo esc_attr($post_type_obj->name); ?>" 
                                                        <?php selected($relationship_type, 'post_type_' . $post_type_obj->name); ?>>
                                                        <?php echo esc_html($post_type_obj->label); ?>
                                                    </option>
                                                <?php endforeach; ?>
                                            </optgroup>
                                
                                            <!-- Taxonomies -->
                                            <optgroup label="Taxonomies">
                                                <?php foreach (get_taxonomies([], 'objects') as $taxonomy_obj) : ?>
                                                    <option value="taxonomy_<?php echo esc_attr($taxonomy_obj->name); ?>" 
                                                        <?php selected($relationship_type, 'taxonomy_' . $taxonomy_obj->name); ?>>
                                                        <?php echo esc_html($taxonomy_obj->label); ?>
                                                    </option>
                                                <?php endforeach; ?>
                                            </optgroup>
                                
                                            <!-- User Roles -->
                                            <optgroup label="User Roles">
                                                <?php 
                                                global $wp_roles;
                                                foreach ($wp_roles->roles as $role_key => $role) : ?>
                                                    <option value="user_role_<?php echo esc_attr($role_key); ?>" 
                                                        <?php selected($relationship_type, 'user_role_' . $role_key); ?>>
                                                        <?php echo esc_html($role['name']); ?>
                                                    </option>
                                                <?php endforeach; ?>
                                            </optgroup>
                                
                                        </select>
                                    </label>
                                </div>

                            </div>
                        </li>
    
                        <?php
                    }
                    echo '</ul>';
                    
                }
                ?>
                <p>
                    <button type="submit" class="button-primary">Save Settings</button>                    
                </p>
            </form>
        </div>
        <?php
        
    }







    /**
     * Render the DataTables view for a given post type.
     */

    public function render_tabular_view() {
        // Determine the post type either from the query string or from the page slug.
        $post_type = isset($_GET['post_type']) ? sanitize_key($_GET['post_type']) : '';
        $page = isset($_GET['page']) ? sanitize_key($_GET['page']) : '';
        if ( empty($post_type) ) {
            if ( !empty($page) ) {
                // Explode the page slug (e.g. "tabular3-view-post") and use the last part.
                $parts = explode('-', $page);
                $post_type = array_pop($parts);
            }
            if ( empty($post_type) ) {
                echo '<div class="error"><p>Invalid post type.</p></div>';
                return;
            }
        }
        ?>
        <div class="wrap">
            <h1>Tabular View for <?php echo ucwords(esc_html($post_type)); ?></h1>
            <table id="tabular3-table" class="display" style="width:100%;">
                <thead>
                    <tr>
                        <?php
                        // Get the list of field keys for this post type.
                        $fields_option = get_option('tabular3_' . $post_type . '_fields', array());
                        $prima = true;
                        foreach ( $fields_option as $field_key ) {
                            $field_settings = get_option('tabular3_' . $post_type . '_field_' . sanitize_key($field_key), array());
                            // Skip fields marked as hidden.
                            
                            if (isset($field_settings['input_format']) && $field_settings['input_format'] === 'hidden') continue;
                            $priority = '';
                            
                            if ($prima) {
                                    $priority = ' data-priority="1"';                                
                            }
                            else {
                                if ((isset($field_settings['priority'])) && ($field_settings['priority'] != '')) {
                                    $priority = sprintf(' data-priority="%d"', $field_settings['priority']);
                                }                                                           
                            }
                        
                            echo sprintf('<th%s>', $priority) . esc_html($field_settings['label']) . '</th>';
                            $prima = false;
                        }
                        ?>
                    </tr>
                </thead>
            </table>
            <script>
                // Output a configuration object for tabular3-admin.js to use.
                
                // ROADMAP / TODO
                /*
                
                the src is where it comes from the main db, the post custom fields, the user custom fields, the taxonomy or something else
                the type of field use to edit the data-- maybe pack in edit relevant supporting data
                    
                */

                window.tabular3_config = {
                    postType: '<?php echo esc_js($post_type); ?>',
                    editorFields: [
                        <?php foreach ($fields_option as $field_key):
                            $field_settings = get_option('tabular3_' . $post_type . '_field_' . sanitize_key($field_key), []);
                            if (isset($field_settings['input_format']) && $field_settings['input_format'] === 'hidden') continue;
                        ?>
            {
                label: "<?php echo esc_js($field_settings['label']); ?>",
                name: "<?php echo esc_js($field_key); ?>",
                inputFormat: "<?php echo esc_js($field_settings['input_format']); ?>",
                selectOptions: "<?php echo isset($field_settings['select_options']) ? esc_js($field_settings['select_options']) : ''; ?>",
                taxonomy: "<?php echo isset($field_settings['taxonomy']) ? esc_js($field_settings['taxonomy']) : ''; ?>",
                relationshipType: "<?php echo isset($field_settings['relationship_type']) ? esc_js($field_settings['relationship_type']) : ''; ?>"
            },
                        <?php endforeach; ?>
                    ],
                    dtColumns: [
                        <?php foreach ($fields_option as $field_key):
                            $field_settings = get_option('tabular3_' . $post_type . '_field_' . sanitize_key($field_key), []);
                            if (isset($field_settings['input_format']) && $field_settings['input_format'] === 'hidden') continue;
                        ?>
            {
                data: "<?php echo esc_js($field_key); ?>",
                title: "<?php echo esc_js($field_settings['label']); ?>",
                className: "<?php echo $this->get_class_name($field_settings['input_format']); ?>",
                editorClass: "<?php echo $this->get_class_name($field_settings['input_format']); ?>",
                inputFormat: "<?php echo esc_js($field_settings['input_format']); ?>"
            },
                        <?php endforeach; ?>
                    ]
                };

            </script>
        </div>
        <?php
    }


    /**
     * Ajax handler to fetch posts data for DataTables.
     */
    public function ajax_get_posts() {
        // Verify the nonce.
        if ( ! isset($_POST['nonce']) || ! wp_verify_nonce($_POST['nonce'], 'tabular3_nonce') ) {
            wp_send_json_error('Invalid nonce');
        }
        $post_type = isset($_POST['post_type']) ? sanitize_key($_POST['post_type']) : 'post';
    
        // DataTables parameters.
        $start  = isset($_POST['start']) ? intval($_POST['start']) : 0;
        $length = isset($_POST['length']) ? intval($_POST['length']) : 10;
        $search = isset($_POST['search']['value']) ? sanitize_text_field($_POST['search']['value']) : '';
    
        $args = array(
            'post_type'      => $post_type,
            'posts_per_page' => $length,
            'offset'         => $start,
            's'              => $search,
        );
    
        $query = new WP_Query($args);
        $data  = array();
    
        // Retrieve the field settings for this post type.
        $fields_option = get_option('tabular3_' . $post_type . '_fields', array());
        $field_settings = [];
        
        foreach ( $query->posts as $post ) {
            $row = array();
            // Loop through each field in the settings.
            foreach ( $fields_option as $field_key ) {
                $san_key = sanitize_key($field_key);
                if (!isset($field_settings[$san_key])) {
                    $field_settings[$san_key] = get_option('tabular3_' . $post_type . '_field_' . sanitize_key($field_key), array());                    
                }

                if ( isset($field_settings[$san_key]['visibility']) && $field_settings[$san_key]['visibility'] == 'hidden' ) {
                    continue;
                }

                // $field_settings = get_option('tabular3_' . $post_type . '_field_' . sanitize_key($field_key), array());
                if ( isset($field_settings[$san_key]['visibility']) && $field_settings[$san_key]['visibility'] == 'hidden' ) {
                    continue;
                }
                $value = "";

                $post_properties = get_object_vars($post);
                foreach ($post_properties as $property_name => $property_value) {
                    if ( $field_key == $property_name ) {
                        $value = $property_value;
                        break;
                    }
                    if (in_array($property_name, ['post_type', 'post_password'])) {
                        continue;
                    }
                }

                if ( !empty($value) ) { 
                    if ($field_settings[$san_key]['input_format'] == 'relationship') {
                        $value = $this->get_relationship_display( $value, $field_settings[$san_key]['relationship_type'] ); 
                    }
                    if ($field_settings[$san_key]['input_format'] == 'media_list') {
                        $value = $this->get_image_display( $value, $field_settings[$san_key]['input_format'] ); 
                    }
                    if ($field_settings[$san_key]['input_format'] == 'featured_image') {
                        $value = $this->get_image_display( $value, $field_settings[$san_key]['input_format'] ); 
                    }
                    if ($field_settings[$san_key]['input_format'] == 'media_upload') {
                        $value = $this->get_image_display( $value, $field_settings[$san_key]['input_format'] ); 
                    }                    
                }
                
                // Assume the field is a custom meta; if empty, check for taxonomy terms.
                if ( empty($value) ) {                
                    $value = get_post_meta($post->ID, $san_key, true);
                }
                if ( empty($value) ) {
                    $taxonomies = get_object_taxonomies($post_type);
                    if ( in_array($field_key, $taxonomies) ) {
                        $terms = wp_get_post_terms($post->ID, $san_key, array("fields" => "names"));
                        $value = implode(', ', $terms);
                    }
                }                
                
                // Always define the key, even if empty.
                $row[$field_key] = !empty($value) ? $value : '';
            }
            if (in_array('_thumbnail_id', $fields_option)) {
                $thumbnail_id = get_post_meta($post->ID, '_thumbnail_id', true);
                $row['_thumbnail_id'] = $thumbnail_id ? wp_get_attachment_url($thumbnail_id) : '';
            }
            
            // Optionally include the post ID.
            $row['ID'] = $post->ID;
            $data[] = $row;
        }
    
        // Build the response for DataTables.
        $response = array(
            "draw"            => isset($_POST['draw']) ? intval($_POST['draw']) : 0,
            "recordsTotal"    => intval($query->found_posts),
            "recordsFiltered" => intval($query->found_posts),
            "data"            => $data,
        );
        wp_send_json($response);
    }

    /**
     * Ajax handler to update one or more posts when edited via DataTables Editor.
     */
     
    public function ajax_update_posts() {
        if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'tabular3_nonce' ) ) {
            wp_send_json_error( 'Invalid nonce' );
        }
    
        $post_type = isset( $_POST['post_type'] ) ? sanitize_key( $_POST['post_type'] ) : 'post';
        $data = isset( $_POST['data'] ) ? $_POST['data'] : [];
    
        $results = [];
    
        foreach ( $data as $post_id => $fields ) {
            $post_id = intval( $post_id );
            $updated_fields = [];
            $post_updates   = [ 'ID' => $post_id ];
            $meta_updates   = [];
            $taxonomy_updates = [];
    
            foreach ( $fields as $key => $value ) {
                if ( in_array( $key, [ 'post_title', 'post_content', 'post_status', 'post_date' ], true ) ) {
                    $post_updates[ $key ] = sanitize_text_field( $value );
                } elseif ( taxonomy_exists( $key ) ) {
                    // Process taxonomy fields.
                    $terms = array_map( 'sanitize_text_field', explode( ',', $value ) );
                    // Allow modification of taxonomy terms via filter.
                    $terms = apply_filters( 'tabular3_process_taxonomy', $terms, $post_id, $key );
                    $taxonomy_updates[ $key ] = $terms;
                } elseif ( $key === 'featured_image' ) {
                    // Process the featured image as a media file.
                    $mediaData = json_decode( $value, true );
                    // Filter the featured image data.
                    $mediaData = apply_filters( 'tabular3_process_featured_image', $mediaData, $post_id, $key );
                    if ( ! empty( $mediaData ) && isset( $mediaData[0]['id'] ) ) {
                        set_post_thumbnail( $post_id, $mediaData[0]['id'] );
                    }
                } else {
                    $mediaData = json_decode( $value, true );
                    if ( ! empty( $mediaData ) ) {
                        // Process media files stored in custom fields.
                        $mediaData = apply_filters( 'tabular3_process_media_files', $mediaData, $post_id, $key );
                        $imgHtml = '<ul>';
                        foreach ( $mediaData as $media ) {
                            $imgHtml .= '<li><img src="' . esc_url( $media['url'] ) . '" data-id="' . intval( $media['id'] ) . '"></li>';
                        }
                        $imgHtml .= '</ul>';
                        $meta_updates[ $key ] = $imgHtml;
                    } else {
                        // Process regular custom fields.
                        $clean_value = sanitize_text_field( $value );
                        $meta_updates[ $key ] = apply_filters( 'tabular3_process_custom_field', $clean_value, $post_id, $key );
                    }
                }
            }
    
            // Update the post if any core fields were modified.
            if ( count( $post_updates ) > 1 ) {
                wp_update_post( $post_updates );
            }
    
            // Process taxonomy updates.
            foreach ( $taxonomy_updates as $taxonomy => $terms ) {
                wp_set_object_terms( $post_id, $terms, $taxonomy );
            }
            do_action( 'tabular3_after_taxonomy_processed', $post_id, $taxonomy_updates );
    
            // Process meta updates.
            foreach ( $meta_updates as $meta_key => $meta_value ) {
                update_post_meta( $post_id, $meta_key, $meta_value );
            }
            do_action( 'tabular3_after_custom_fields_processed', $post_id, $meta_updates );
    
            $results[ $post_id ] = $updated_fields;
        }
    
        wp_send_json_success( $results );
    }
         
    public function ajax_narrow() {
        if ($_GET['action'] == 'tabular3_get_taxonomy_terms') {
            $this->ajax_get_taxonomy_terms();
        }
        if ($_GET['action'] == 'tabular3_get_relationship_items') {
            $this->ajax_get_relationship_items();            
        }        
        
    }     
    
    public function ajax_get_taxonomy_terms() {
        // Verify the nonce for security
        if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'tabular3_nonce' ) ) {
            wp_send_json_error( 'Invalid nonce' );
        }
        
        // Retrieve and sanitize the input values
        $taxonomy = isset( $_POST['taxonomy'] ) ? sanitize_key( $_POST['taxonomy'] ) : '';
        $search   = isset( $_POST['search'] )   ? sanitize_text_field( $_POST['search'] ) : '';
        
        // Check if the taxonomy exists
        if ( empty( $taxonomy ) || ! taxonomy_exists( $taxonomy ) ) {
            wp_send_json_error( 'Invalid taxonomy' );
        }
        
        // Query for terms that match the search string
        $terms = get_terms( array(
            'taxonomy'   => $taxonomy,
            'hide_empty' => false,
            'search'     => $search,
        ) );
        
        // Check for errors or no results
        if ( is_wp_error( $terms ) ) {
            wp_send_json_error( $terms->get_error_message() );
        }
        
        // Build an array of terms with term_id and name
        $results = array();
        foreach ( $terms as $term ) {
            $results[] = array(
                'term_id' => $term->term_id,
                'name'    => $term->name,
            );
        }
        
        // Return the results in JSON format
        wp_send_json_success( array( 'data' => $results ) );
    }
    
    public function ajax_get_relationship_items() {
        // Security check
        if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'tabular3_nonce' ) ) {
            wp_send_json_error( 'Invalid nonce' );
        }
        
        // Retrieve and sanitize input parameters
        $relationship_type = isset( $_POST['relationship_type'] ) ? sanitize_text_field( $_POST['relationship_type'] ) : '';
        $search = isset( $_POST['search'] ) ? sanitize_text_field( $_POST['search'] ) : '';
        
        $results = array();
        
        // Determine which type of relationship items to retrieve:
        if ( strpos( $relationship_type, 'post_type_' ) === 0 ) {
            // Relationship from a post type
            $post_type = str_replace( 'post_type_', '', $relationship_type );
            // Query posts from the given post type matching the search term
            $args = array(
                'post_type'      => $post_type,
                'posts_per_page' => 10,
                's'              => $search,
                'post_status'    => 'publish'
            );
            $posts = get_posts( $args );
            foreach ( $posts as $post ) {
                $results[] = array(
                    'id'    => $post->ID,
                    'title' => $post->post_title
                );
            }
        }
        elseif ( strpos( $relationship_type, 'taxonomy_' ) === 0 ) {
            // Relationship from a taxonomy
            $taxonomy = str_replace( 'taxonomy_', '', $relationship_type );
            if ( taxonomy_exists( $taxonomy ) ) {
                $terms = get_terms( array(
                    'taxonomy'   => $taxonomy,
                    'hide_empty' => false,
                    'search'     => $search,
                    'number'     => 10
                ) );
                if ( ! is_wp_error( $terms ) ) {
                    foreach ( $terms as $term ) {
                        $results[] = array(
                            'id'    => $term->term_id,
                            'title' => $term->name
                        );
                    }
                }
            }
        }
        elseif ( strpos( $relationship_type, 'user_role_' ) === 0 ) {
            // Relationship from user roles

            // Make sure we're in WordPress context.
            global $wp_roles;
            
            // 1. Get and sanitize the posted values.
            // $relationship_type = isset($_POST['relationship_type']) ? sanitize_text_field($_POST['relationship_type']) : '';
            $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
            
            // 2. Loop through all roles to see if the submitted relationship_type matches (partial match).
            $matched_role = '';
            foreach ($wp_roles->roles as $role_key => $role_data) {
                // Check if $relationship_type is in the role name or the role key
                
                // print "checking $role_key vs. $relationship_type  gets " . print_r($role_data, TRUE) ."\n";
                
                if (stripos($relationship_type, $role_data['name']) !== false || stripos($relationship_type, $role_key) !== false) {
                    $matched_role = $role_data['name'];
                    break;  // Found a match; no need to keep checking
                }
            }

            // print "-- $matched_role --";            
            // print print_r($wp_roles->roles, TRUE);
            
            // Prepare an array to hold our final results.
            $results = array();
            
            // 3. If we found a matching role, do a user query to find matching users.
            if ( ! empty( $matched_role ) ) {
                // Build our user query arguments
                if (strpos($search, '(ID:') > 0) {
                    list($keep, $toss) = explode('(ID:', $search);
                    $search = trim($keep);
                }
                
                $args = array(
                    'search'         => "*".sanitize_text_field($search)."*", // No manual wildcards
                    'search_columns' => array('user_login', 'user_email'),
                    'role'           => $matched_role
                );
                
                // Optional: Add empty role check
                if (empty($matched_role)) {
                    unset($args['role']);
                }
                
                $user_query = new WP_User_Query($args);
                $users = $user_query->get_results();
            
                // 5. Build $results from user data
                foreach ( $users as $user ) {
                    $results[] = array(
                        'value' => sprintf('%s (ID: %s)', $user->user_login, $user->ID),
                        'label' => sprintf('%s (ID: %s)', $user->user_login, $user->ID),
                        // 'email'    => $user->user_email,
                        // 'role'     => $matched_role,
                    );
                }
            }

        }
        else {
            wp_send_json_error( 'Invalid relationship type.' );
        }
        
        // Return the results in the expected JSON format
        print wp_json_encode( array( 'data' => $results ) );
        exit;
        // wp_send_json_success( array( 'data' => $results ) );
    }
         
    public function ajax_handle_media_upload() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'tabular3_nonce')) {
            wp_send_json_error('Invalid nonce');
        }
    
        if (!current_user_can('upload_files')) {
            wp_send_json_error('Permission denied');
        }
    
        if (empty($_FILES['file'])) {
            wp_send_json_error('No file uploaded.');
        }
    
        $file = $_FILES['file'];
        $attachment_id = media_handle_upload('file', 0);
    
        if (is_wp_error($attachment_id)) {
            wp_send_json_error($attachment_id->get_error_message());
        }
    
        $attachment_url = wp_get_attachment_url($attachment_id);
    
        wp_send_json_success([
            'id'  => $attachment_id,
            'url' => $attachment_url
        ]);
    }
     
    /**
     * AJAX handler to load WordPress media scripts when needed
     */
    public function ajax_load_media_scripts() {
        // Check permissions
        if (!current_user_can('upload_files')) {
            wp_send_json_error('Permission denied');
            return;
        }
    
        // WordPress media scripts
        wp_enqueue_media();
        
        // These are the core scripts needed for the media library
        $scripts = array(
            'media-editor',
            'media-models',
            'media-views',
            'media-audiovideo',
            'mce-view',
            'wp-api-request',
            'wp-i18n'
        );
    
        foreach ($scripts as $script) {
            // Get the registered script object
            $wp_script = wp_scripts()->query($script, 'registered');
            
            if (!$wp_script) {
                continue;
            }
    
            // Construct the full URL 
            $script_url = $wp_script->src;
            if (strpos($script_url, '://') === false) {
                $script_url = site_url($script_url);
            }
            
            // Include in response
            $response[$script] = $script_url;
        }
        
        // Send the script URLs back to the client
        wp_send_json_success($response);
    }
    
    
    /**
     * AJAX handler to get attachment URL by ID
     */
    public function ajax_get_attachment_url() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'tabular3_nonce')) {
            wp_send_json_error('Invalid nonce');
        }
        
        if (!isset($_POST['attachment_id']) || empty($_POST['attachment_id'])) {
            wp_send_json_error('Missing attachment ID');
        }
        
        $attachment_id = intval($_POST['attachment_id']);
        $attachment_url = wp_get_attachment_url($attachment_id);
        
        if (!$attachment_url) {
            wp_send_json_error('Attachment not found');
        }
        
        wp_send_json_success($attachment_url);
    }
    
    /**
     * AJAX handler to get attachment ID by URL
     */
    public function ajax_get_attachment_id_by_url() {
        if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'tabular3_nonce')) {
            wp_send_json_error('Invalid nonce');
        }
        
        if (!isset($_POST['url']) || empty($_POST['url'])) {
            wp_send_json_error('Missing URL');
        }
        
        $url = esc_url_raw($_POST['url']);
        $attachment_id = attachment_url_to_postid($url);
        
        wp_send_json_success($attachment_id);
    }

    /**
     * Get a list of public post types.
     */
    private function get_post_types() {
        $args = array(
            'public' => true,
        );
        $post_types = get_post_types($args, 'names');
        return $post_types;
    }

    /**
     * Scan all posts of a given post type to return an array of distinct custom field keys.
     */
    private function get_custom_fields_for_post_type($post_type) {
        // Get all posts (just the IDs) of the given post type.
        $posts = get_posts(array(
            'post_type'      => $post_type,
            'posts_per_page' => 1
        ));
        $custom_fields = array();
    
        // Add post object properties (excluding unwanted ones)
        foreach ($posts as $post) {
            $post_properties = get_object_vars($post);
            
            foreach ($post_properties as $property_name => $property_value) {
                if (in_array($property_name, ['ID', 'post_type', 'post_password'])) {
                    continue;
                }
                $custom_fields[] = $property_name;    
            }                
        }
    
        // ✅ Add Taxonomies associated with this post type.
        $taxonomies = get_object_taxonomies($post_type);
        if (!empty($taxonomies)) {
            foreach ($taxonomies as $taxonomy) {
                if (!in_array($taxonomy, $custom_fields)) {
                    $custom_fields[] = $taxonomy;
                }
            }
        }
        
        // ✅ Check if the post type supports a Featured Image (Thumbnail)
        if (post_type_supports($post_type, 'thumbnail')) {
            $custom_fields[] = '_thumbnail_id';
        }
        
        // ✅ Add Custom Fields (Meta Keys) from postmeta table.
        $distinct_meta_keys = $this->get_distinct_meta_keys_for_post_type($post_type);
        foreach ($distinct_meta_keys as $distinct_key) {
            if (substr($distinct_key, 0, 1) === '_') {
                continue; // Skip hidden meta fields
            }
            if (!in_array($distinct_key, $custom_fields)) {
                $custom_fields[] = $distinct_key;
            }
        }
    
        return $custom_fields;
    }

    public function get_relationship_display( $value, $relationship_type ) {
        // Safety check: if no value, return an empty string.
        if ( empty( $value ) ) {
            return '';
        }

        // Handle post type relationships: e.g., "post_type_post", "post_type_page", "post_type_attachment"
        if ( strpos( $relationship_type, 'post_type_' ) === 0 ) {
            $post_obj = get_post( $value );
            if ( $post_obj ) {
                return sprintf( '%s (ID: %s)', $post_obj->post_title, $value );
            }
        }
        // Handle taxonomy relationships: e.g., "taxonomy_category", "taxonomy_post_tag", etc.
        elseif ( strpos( $relationship_type, 'taxonomy_' ) === 0 ) {
            // Extract the taxonomy slug (e.g., "category", "post_tag") from the relationship type.
            $taxonomy = substr( $relationship_type, strlen( 'taxonomy_' ) );
            $term_obj = get_term( $value, $taxonomy );
            if ( $term_obj && ! is_wp_error( $term_obj ) ) {
                return sprintf( '%s (ID: %s)', $term_obj->name, $value );
            }
        }
        // Handle user role relationships: e.g., "user_role_administrator", "user_role_editor", etc.
        elseif ( strpos( $relationship_type, 'user_role_' ) === 0 ) {
            $user_obj = get_user_by( 'id', $value );
            if ( $user_obj ) {
                return sprintf( '%s (ID: %s)', $user_obj->display_name, $value );
            }
        }

        // Fallback: if none of the above worked, simply return the ID.
        return sprintf( '(ID: %s)', $value );
    }

    public function image_display( $value, $display_type ) {
        // Sanity check: if empty, return nothing.
        if ( empty( $value ) ) {
            return '';
        }
        
        // Process based on the display type.
        switch ( $display_type ) {

            case 'media_list':
                // If $value is not an array, assume it's a comma-separated list.
                if ( ! is_array( $value ) ) {
                    $value = array_map( 'trim', explode( ',', $value ) );
                }
                // Begin building the unordered list.
                $output = '<ul class="media-list">';
                foreach ( $value as $attachment_id ) {
                    $attachment_id = intval( $attachment_id );
                    // Only process valid attachment IDs.
                    if ( $attachment_id > 0 ) {
                        // Get the image HTML using the thumbnail size, adding a custom "media" attribute.
                        $img_html = wp_get_attachment_image( $attachment_id, 'thumbnail', false, array( 'media' => $attachment_id ) );
                        if ( $img_html ) {
                            $output .= '<li>' . $img_html . '</li>';
                        }
                    }
                }
                $output .= '</ul>';
                return $output;
                break;

            case 'featured_image':
                // For featured image, expect a single numeric attachment id.
                $attachment_id = intval( $value );
                if ( $attachment_id > 0 ) {
                    // Get the image HTML using a thumbnail size and a custom class.
                    $img_html = wp_get_attachment_image( $attachment_id, 'thumbnail', false, array( 'media' => $attachment_id, 'class' => 'featured-image' ) );
                    if ( $img_html ) {
                        // Append the attachment id in a span for display purposes.
                        return $img_html . ' <span class="attachment-id">ID:[' . $attachment_id . ']</span>';
                    }
                }
                return '';
                break;

            case 'media_upload':
                // For media_upload, expect a single numeric attachment id and output the image.
                $attachment_id = intval( $value );
                if ( $attachment_id > 0 ) {
                    $img_html = wp_get_attachment_image( $attachment_id, 'thumbnail', false, array( 'media' => $attachment_id ) );
                    if ( $img_html ) {
                        return $img_html;
                    }
                }
                return '';
                break;

            default:
                // If an unknown display type is passed, return an empty string.
                return '';
        }
    }

    public function get_class_name($input_format) {
        return $this->class_name[$input_format];
    }


    public function get_distinct_meta_keys_for_post_type($post_type) {
        global $wpdb;
    
        // Prepare the SQL query
        $query = $wpdb->prepare(
            "SELECT DISTINCT pm.meta_key
             FROM {$wpdb->postmeta} pm
             INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID
             WHERE p.post_type = %s",
            $post_type
        );
    
        // Execute the query and fetch results
        $meta_keys = $wpdb->get_col($query);
    
        return $meta_keys;
    }

}

Tabular3::instance();
