/*
 * DLM PDF.js Proxy Counter
 * Contributor: Ron Fredericks, BiophysicsLab.com
 * Snippet: DLM PDF.js Proxy Counter
 * Rev: 2
 * Last updated: 3/30/2026
 *
 * Tracks:
 * - PDF proxy opens via shared function call from Proxy Viewer snippet
 * - ZIP/other Download Monitor downloads via JavaScript click interception
 *
 * Counting architecture:
 * - PDF views: counted server-side via ron_increment_file_event('proxy')
 *   called from the Proxy Viewer snippet during the proxy request.
 * - ZIP downloads: counted client-side via JavaScript click handler that
 *   calls the ron_count_dlm_download AJAX endpoint on every link click.
 *   This approach is fully cache-independent — works with LiteSpeed,
 *   WP Rocket, Cloudflare, or any other cache system without exclusion rules.
 *
 * Shared meta keys:
 * - _ron_file_event_count
 * - _ron_file_event_last_gmt
 *
 * Shortcodes:
 * - [file_event_count id="123"]
 * - [file_event_date id="123"]
 * - [file_event_count_live id="123"]
 * - [file_event_date_live id="123"]
 *
 * Admin panel:
 * - WordPress Admin → Downloads → DLM PDF.js Stats
 */

/*
 * Core increment function.
 * Called from:
 * - Proxy Viewer snippet for PDF views (source = 'proxy')
 * - AJAX endpoint for ZIP/DLM downloads (source = 'dlm')
 *
 * Source-aware locking:
 * - 'proxy': shared Redis lock per download_id prevents PDF.js byte-range
 *   requests from double-counting a single PDF view across simultaneous
 *   PHP processes within a 10-second window.
 * - 'dlm': unique lock key per click so every legitimate ZIP download
 *   always counts regardless of timing.
 */
function ron_increment_file_event($download_id, $source = 'proxy') {
    $download_id = absint($download_id);
    if (!$download_id) {
        return false;
    }

    if ($source === 'proxy') {
        $lock_key = 'ron_file_event_lock_' . $download_id;
    } else {
        $lock_key = 'ron_file_event_lock_' . $download_id . '_' . uniqid();
    }

    /*
     * Layer 1: Static PHP array catches duplicate calls within
     * the same PHP process for both sources.
     */
    static $processed = array();
    if ( isset($processed[$lock_key]) ) {
        return false;
    }
    $processed[$lock_key] = true;

    /*
     * Layer 2: Redis wp_cache lock for PDF proxy only.
     * Prevents double counts from PDF.js simultaneous byte-range requests
     * arriving as separate PHP processes within the 10-second window.
     * Not applied to ZIP/DLM downloads — each click must always count.
     */
    if ( $source === 'proxy' ) {
        if ( wp_cache_get($lock_key, 'ron_file_event') ) {
            return false;
        }
        wp_cache_set($lock_key, 1, 'ron_file_event', 10);
    }

    $count = (int) get_post_meta($download_id, '_ron_file_event_count', true);
    update_post_meta($download_id, '_ron_file_event_count', $count + 1);
    update_post_meta($download_id, '_ron_file_event_last_gmt', current_time('mysql', true));

    return true;
}

/*
 * AJAX endpoint for ZIP/DLM download counting.
 * Called by JavaScript click handler when user clicks a Download Monitor link.
 * This approach is cache-independent — admin-ajax.php is never cached.
 * The dlm_downloading server-side hook is NOT used for ZIP counting.
 */
add_action('wp_ajax_ron_count_dlm_download', 'ron_ajax_count_dlm_download');
add_action('wp_ajax_nopriv_ron_count_dlm_download', 'ron_ajax_count_dlm_download');

function ron_ajax_count_dlm_download() {
    nocache_headers();

    $download_id = isset($_POST['download_id']) ? absint($_POST['download_id']) : 0;

    if (!$download_id) {
        wp_send_json_error('Missing download ID.');
    }

    /*
     * Verify the download exists in Download Monitor.
     */
    $post = get_post($download_id);
    if (!$post || $post->post_type !== 'dlm_download') {
        wp_send_json_error('Invalid download ID.');
    }

    ron_increment_file_event($download_id, 'dlm');

    wp_send_json_success();
}

/*
 * Public helper to read unified count.
 */
function ron_get_file_event_count($download_id) {
    return (int) get_post_meta((int) $download_id, '_ron_file_event_count', true);
}

/*
 * Public helper to read unified last GMT date.
 */
function ron_get_file_event_date($download_id) {
    $value = get_post_meta((int) $download_id, '_ron_file_event_last_gmt', true);
    return is_string($value) ? $value : '';
}

/*
 * Shortcode: [file_event_count id="123"]
 */
add_shortcode('file_event_count', 'ron_file_event_count_shortcode');
function ron_file_event_count_shortcode($atts) {
    $atts = shortcode_atts(
        array(
            'id' => 0,
        ),
        $atts,
        'file_event_count'
    );

    $download_id = absint($atts['id']);
    if (!$download_id) {
        return '';
    }

    return (string) ron_get_file_event_count($download_id);
}

/*
 * Shortcode: [file_event_date id="123"]
 */
add_shortcode('file_event_date', 'ron_file_event_date_shortcode');
function ron_file_event_date_shortcode($atts) {
    $atts = shortcode_atts(
        array(
            'id' => 0,
        ),
        $atts,
        'file_event_date'
    );

    $download_id = absint($atts['id']);
    if (!$download_id) {
        return '';
    }

    $value = ron_get_file_event_date($download_id);
    if (!$value) {
        return '';
    }

    return esc_html($value . ' GMT');
}

/*
 * Live shortcode: [file_event_count_live id="123"]
 */
add_shortcode('file_event_count_live', 'ron_file_event_count_live_shortcode');
function ron_file_event_count_live_shortcode($atts) {
    global $ron_file_event_live_used;
    $ron_file_event_live_used = true;

    $atts = shortcode_atts(
        array(
            'id' => 0,
        ),
        $atts,
        'file_event_count_live'
    );

    $download_id = absint($atts['id']);
    if (!$download_id) {
        return '';
    }

    $count = ron_get_file_event_count($download_id);

    return '<span class="ron-file-event-count" data-id="' . esc_attr($download_id) . '">' . esc_html($count) . '</span>';
}

/*
 * Live shortcode: [file_event_date_live id="123"]
 */
add_shortcode('file_event_date_live', 'ron_file_event_date_live_shortcode');
function ron_file_event_date_live_shortcode($atts) {
    global $ron_file_event_live_used;
    $ron_file_event_live_used = true;

    $atts = shortcode_atts(
        array(
            'id' => 0,
        ),
        $atts,
        'file_event_date_live'
    );

    $download_id = absint($atts['id']);
    if (!$download_id) {
        return '';
    }

    $date = ron_get_file_event_date($download_id);
    $text = $date ? $date . ' GMT' : '';

    return '<span class="ron-file-event-date" data-id="' . esc_attr($download_id) . '">' . esc_html($text) . '</span>';
}

/*
 * AJAX endpoint for live count/date display refresh.
 */
add_action('wp_ajax_ron_get_file_event', 'ron_ajax_get_file_event');
add_action('wp_ajax_nopriv_ron_get_file_event', 'ron_ajax_get_file_event');

function ron_ajax_get_file_event() {
    nocache_headers();

    $download_id = isset($_GET['id']) ? absint($_GET['id']) : 0;

    if (!$download_id) {
        wp_send_json_error();
    }

    $count = ron_get_file_event_count($download_id);
    $date  = ron_get_file_event_date($download_id);

    wp_send_json_success(array(
        'count' => $count,
        'date'  => $date ? $date . ' GMT' : '',
    ));
}

/*
 * Built-in CSS to prevent layout jitter.
 * Only outputs on pages that use a live shortcode.
 */
add_action('wp_head', 'ron_file_event_live_css');
function ron_file_event_live_css() {
    global $ron_file_event_live_used;
    if ( ! $ron_file_event_live_used ) {
        return;
    }
    ?>
    <style>
        .ron-file-event-count,
        .ron-file-event-date {
            display: inline-block;
            vertical-align: baseline;
        }

        .ron-file-event-count {
            min-width: 4ch;
        }

        .ron-file-event-date {
            min-width: 22ch;
        }
    </style>
    <?php
}

/*
 * Built-in JS for:
 * 1. ZIP/DLM download click counting — intercepts all Download Monitor
 *    links (URL pattern /download/) and fires an AJAX count request.
 *    Cache-independent: works with any cache system without exclusion rules.
 * 2. Live counter display refresh — polls every 5 seconds and updates
 *    count/date spans when values change.
 *
 * Always loaded on every page because ZIP download links can appear anywhere.
 * The live counter polling only runs if live shortcode spans are present.
 */
add_action('wp_footer', 'ron_file_event_live_script');
function ron_file_event_live_script() {
?>
<script>
(function() {

    /*
     * ZIP/DLM download click counting.
     * Intercepts clicks on any Download Monitor link (href contains /download/)
     * and fires an AJAX POST to increment the count for that download ID.
     * The download ID is extracted from the URL pattern /download/NNNN/.
     * The file download proceeds normally — the AJAX call does not block it.
     */
    function ronAttachDownloadCounters() {
        document.querySelectorAll('a[href*="/download/"]').forEach(function(link) {
            if (link.dataset.ronCounted) return;
            link.dataset.ronCounted = '1';

            link.addEventListener('click', function() {
                var href = this.href || '';
                var match = href.match(/\/download\/(\d+)\//);
                if (!match) return;

                var downloadId = match[1];
                var data = new FormData();
                data.append('action', 'ron_count_dlm_download');
                data.append('download_id', downloadId);

                fetch('/wp-admin/admin-ajax.php', {
                    method: 'POST',
                    body: data
                }).catch(function() {
                    // Ignore errors — download proceeds regardless
                });
            });
        });
    }

    /*
     * Live counter display refresh.
     * Only runs if live shortcode spans are present on the page.
     * Polls every 5 seconds and updates DOM only when value has changed.
     */
    function ronUpdateFileEvents() {
        var countEls = document.querySelectorAll('.ron-file-event-count');
        var dateEls  = document.querySelectorAll('.ron-file-event-date');
        var ids      = new Set();

        countEls.forEach(function(el) { if (el.dataset.id) ids.add(el.dataset.id); });
        dateEls.forEach(function(el)  { if (el.dataset.id) ids.add(el.dataset.id); });

        if (ids.size === 0) return;

        ids.forEach(function(id) {
            fetch('/wp-admin/admin-ajax.php?action=ron_get_file_event&id='
                + encodeURIComponent(id) + '&_=' + Date.now())
                .then(function(res) { return res.json(); })
                .then(function(data) {
                    if (!data.success) return;

                    document.querySelectorAll(
                        '.ron-file-event-count[data-id="' + id + '"]'
                    ).forEach(function(el) {
                        var newText = String(data.data.count);
                        if (el.textContent !== newText) el.textContent = newText;
                    });

                    document.querySelectorAll(
                        '.ron-file-event-date[data-id="' + id + '"]'
                    ).forEach(function(el) {
                        var newText = data.data.date || '';
                        if (el.textContent !== newText) el.textContent = newText;
                    });
                })
                .catch(function() {
                    // Ignore transient fetch errors
                });
        });
    }

    document.addEventListener('DOMContentLoaded', function() {
        ronAttachDownloadCounters();
        ronUpdateFileEvents();
        setInterval(ronUpdateFileEvents, 5000);
    });

})();
</script>
<?php
}

/*
 * Admin panel: DLM PDF.js Stats
 * Shows all Download Monitor files with their event counts and last event date.
 * Accessible at: WordPress Admin → Downloads → DLM PDF.js Stats
 */
add_action('admin_menu', 'ron_file_event_stats_menu');
function ron_file_event_stats_menu() {
    add_submenu_page(
        'edit.php?post_type=dlm_download',
        'DLM PDF.js Stats',
        'DLM PDF.js Stats',
        'manage_options',
        'ron-file-event-stats',
        'ron_file_event_stats_page'
    );
}

/*
 * Admin panel page renderer.
 */
function ron_file_event_stats_page() {
    if (!current_user_can('manage_options')) {
        wp_die('Access denied.');
    }

    // Get WordPress timezone label
    $tz_string = get_option('timezone_string');
    if (!$tz_string) {
        $offset    = get_option('gmt_offset');
        $tz_string = timezone_name_from_abbr('', $offset * 3600, false);
    }
    $tz_label = $tz_string ? $tz_string : 'UTC';

    // Short timezone abbreviation for display (e.g. PST, EST)
    try {
        $tz_obj  = new DateTimeZone($tz_label);
        $dt      = new DateTime('now', $tz_obj);
        $tz_abbr = $dt->format('T');
    } catch (Exception $e) {
        $tz_abbr = $tz_label;
    }

    // Handle Reset All
    if (
        isset($_POST['ron_reset_all']) &&
        check_admin_referer('ron_file_event_reset_all', 'ron_nonce')
    ) {
        $all = get_posts(array(
            'post_type'      => 'dlm_download',
            'posts_per_page' => -1,
            'post_status'    => 'any',
            'fields'         => 'ids',
        ));
        foreach ($all as $id) {
            update_post_meta($id, '_ron_file_event_count', 0);
            update_post_meta($id, '_ron_file_event_last_gmt', '');
        }
        echo '<div class="notice notice-success"><p>All counts have been reset to zero.</p></div>';
    }

    // Handle individual count update
    if (
        isset($_POST['ron_update_count']) &&
        check_admin_referer('ron_file_event_update', 'ron_nonce')
    ) {
        $update_id    = absint($_POST['ron_update_id']);
        $update_count = absint($_POST['ron_update_count_value']);
        if ($update_id) {
            update_post_meta($update_id, '_ron_file_event_count', $update_count);
            echo '<div class="notice notice-success"><p>Count updated successfully.</p></div>';
        }
    }

    // Sorting
    $valid_orderby = array('title', 'count', 'date');
    $orderby       = isset($_GET['orderby']) && in_array($_GET['orderby'], $valid_orderby)
                     ? $_GET['orderby'] : 'count';
    $order         = isset($_GET['order']) && $_GET['order'] === 'asc' ? 'asc' : 'desc';
    $new_order     = $order === 'asc' ? 'desc' : 'asc';

    // Pagination
    $per_page     = 50;
    $current_page = isset($_GET['paged']) ? max(1, absint($_GET['paged'])) : 1;

    // Fetch all downloads
    $all_downloads = get_posts(array(
        'post_type'      => 'dlm_download',
        'posts_per_page' => -1,
        'post_status'    => 'any',
    ));

    // Build rows with meta data
    $rows = array();
    foreach ($all_downloads as $dl) {
        $count    = (int) get_post_meta($dl->ID, '_ron_file_event_count', true);
        $last_gmt = get_post_meta($dl->ID, '_ron_file_event_last_gmt', true);

        // Convert GMT to local time
        $last_local = '';
        if ($last_gmt) {
            try {
                $dt = new DateTime($last_gmt, new DateTimeZone('UTC'));
                $dt->setTimezone(new DateTimeZone($tz_label));
                $last_local = $dt->format('M j, Y g:i A') . ' ' . $tz_abbr;
            } catch (Exception $e) {
                $last_local = $last_gmt . ' GMT';
            }
        }

        // Detect file type - first try PDF proxy cache, then DLM version metadata
        $relative = get_post_meta($dl->ID, '_ron_pdfjs_relative_path', true);
        if ($relative) {
            $ext = strtoupper(pathinfo($relative, PATHINFO_EXTENSION));
        } else {
            $versions = get_posts(array(
                'post_type'      => 'dlm_download_version',
                'post_parent'    => $dl->ID,
                'posts_per_page' => 1,
                'post_status'    => 'any',
            ));
            if (!empty($versions)) {
                $files = get_post_meta($versions[0]->ID, '_files', true);
                if (is_string($files) && $files !== '') {
                    $decoded = json_decode($files, true);
                    if (is_array($decoded) && !empty($decoded)) {
                        $first_file = reset($decoded);
                        $ext = strtoupper(pathinfo(parse_url($first_file, PHP_URL_PATH), PATHINFO_EXTENSION));
                    } else {
                        $ext = strtoupper(pathinfo(parse_url($files, PHP_URL_PATH), PATHINFO_EXTENSION));
                    }
                } elseif (is_array($files) && !empty($files)) {
                    $first_file = reset($files);
                    $ext = strtoupper(pathinfo(parse_url($first_file, PHP_URL_PATH), PATHINFO_EXTENSION));
                } else {
                    $ext = '—';
                }
            } else {
                $ext = '—';
            }
        }

        $rows[] = array(
            'id'         => $dl->ID,
            'title'      => $dl->post_title,
            'type'       => $ext,
            'count'      => $count,
            'last_gmt'   => $last_gmt,
            'last_local' => $last_local,
            'edit_url'   => get_edit_post_link($dl->ID),
        );
    }

    // Sort rows
    usort($rows, function($a, $b) use ($orderby, $order) {
        if ($orderby === 'title') {
            $cmp = strcasecmp($a['title'], $b['title']);
        } elseif ($orderby === 'date') {
            $cmp = strcmp($a['last_gmt'], $b['last_gmt']);
        } else {
            $cmp = $a['count'] - $b['count'];
        }
        return $order === 'asc' ? $cmp : -$cmp;
    });

    // Paginate
    $total        = count($rows);
    $total_pages  = max(1, ceil($total / $per_page));
    $offset       = ($current_page - 1) * $per_page;
    $rows         = array_slice($rows, $offset, $per_page);

    // Base URL for sorting/pagination links
    $base_url = admin_url('edit.php?post_type=dlm_download&page=ron-file-event-stats');

    ?>
    <div class="wrap">
        <h1>DLM PDF.js Stats</h1>
        <p>All times shown in <?php echo esc_html($tz_abbr); ?>
            (<?php echo esc_html($tz_label); ?>).
            Showing <?php echo $total; ?> download(s).
        </p>
        <p style="color:#999; font-size:12px;">
            DLM PDF.js Stats &mdash; developed by
            <a href="https://www.biophysicslab.com" target="_blank">
                Ron Fredericks, BiophysicsLab.com
            </a>
        </p>

        <?php /* Reset All Form */ ?>
        <form method="post" id="ron-reset-form" style="margin-bottom:20px;">
            <?php wp_nonce_field('ron_file_event_reset_all', 'ron_nonce'); ?>
            <button type="submit" name="ron_reset_all" value="1"
                class="button button-secondary"
                onclick="return confirm('⚠️ Are you sure you want to reset ALL counts to zero?\n\nThis cannot be undone.');">
                Reset All Counts
            </button>
        </form>

        <?php /* Stats Table */ ?>
        <table class="wp-list-table widefat fixed striped">
            <colgroup>
                <col style="width:40px;">
                <col>
                <col style="width:60px;">
                <col style="width:160px;">
                <col style="width:220px;">
                <col style="width:120px;">
            </colgroup>
            <thead>
                <tr>
                    <th>#</th>
                    <th>
                        <a href="<?php echo esc_url(add_query_arg(array(
                            'orderby' => 'title',
                            'order'   => $orderby === 'title' ? $new_order : 'asc',
                        ), $base_url)); ?>">
                            Title
                            <?php if ($orderby === 'title'): ?>
                                <?php echo $order === 'asc' ? '▲' : '▼'; ?>
                            <?php else: ?>
                                <span style="color:#ccc;">▲▼</span>
                            <?php endif; ?>
                        </a>
                    </th>
                    <th>Type</th>
                    <th>
                        <a href="<?php echo esc_url(add_query_arg(array(
                            'orderby' => 'count',
                            'order'   => $orderby === 'count' ? $new_order : 'desc',
                        ), $base_url)); ?>">
                            Count
                            <?php if ($orderby === 'count'): ?>
                                <?php echo $order === 'asc' ? '▲' : '▼'; ?>
                            <?php else: ?>
                                <span style="color:#ccc;">▲▼</span>
                            <?php endif; ?>
                        </a>
                    </th>
                    <th style="padding-left:15px;">
                        <a href="<?php echo esc_url(add_query_arg(array(
                            'orderby' => 'date',
                            'order'   => $orderby === 'date' ? $new_order : 'desc',
                        ), $base_url)); ?>">
                            Last Event
                            <?php if ($orderby === 'date'): ?>
                                <?php echo $order === 'asc' ? '▲' : '▼'; ?>
                            <?php else: ?>
                                <span style="color:#ccc;">▲▼</span>
                            <?php endif; ?>
                        </a>
                    </th>
                    <th>Actions</th>
                </tr>
            </thead>
            <tbody>
                <?php foreach ($rows as $i => $row): ?>
                <tr>
                    <td><?php echo $offset + $i + 1; ?></td>
                    <td><?php echo esc_html($row['title']); ?></td>
                    <td><?php echo esc_html($row['type']); ?></td>
                    <td>
                        <form method="post" style="display:flex;gap:6px;align-items:center;">
                            <?php wp_nonce_field('ron_file_event_update', 'ron_nonce'); ?>
                            <input type="hidden" name="ron_update_id"
                                value="<?php echo esc_attr($row['id']); ?>"/>
                            <input type="number" name="ron_update_count_value"
                                value="<?php echo esc_attr($row['count']); ?>"
                                style="width:70px;" min="0"/>
                            <input type="submit" name="ron_update_count"
                                value="Save" class="button button-small"/>
                        </form>
                    </td>
                    <td style="padding-left:15px;"><?php echo esc_html($row['last_local'] ?: '—'); ?></td>
                    <td>
                        <a href="<?php echo esc_url($row['edit_url']); ?>"
                            target="_blank" class="button button-small">
                            Edit in DLM
                        </a>
                    </td>
                </tr>
                <?php endforeach; ?>
            </tbody>
        </table>

        <?php /* Pagination */ ?>
        <?php if ($total_pages > 1): ?>
        <div style="margin-top:15px;">
            <?php for ($p = 1; $p <= $total_pages; $p++): ?>
                <?php if ($p === $current_page): ?>
                    <strong style="margin-right:5px;">[<?php echo $p; ?>]</strong>
                <?php else: ?>
                    <a style="margin-right:5px;"
                        href="<?php echo esc_url(add_query_arg(array(
                            'paged'   => $p,
                            'orderby' => $orderby,
                            'order'   => $order,
                        ), $base_url)); ?>">
                        <?php echo $p; ?>
                    </a>
                <?php endif; ?>
            <?php endfor; ?>
        </div>
        <?php endif; ?>

    </div>
    <?php
}
