<?php
declare(strict_types=1);

/**
 * @var \Magento\Framework\View\Element\Template $block
 * @var \Magento\Framework\Escaper $escaper
 * @var \ClawRock\Debug\Model\View\Profiler $viewModel
 * @var \ClawRock\Debug\Model\Collector\TimeCollector $collector
 */
$viewModel = $block->getViewModel();
$collector = $block->getCollector();
$profile = $viewModel->getProfile();
$events = $collector->getEvents();
?>
<h2><?= $escaper->escapeHtml(__('Performance metrics')) ?></h2>
<div class="metrics">
    <div class="metric">
        <span class="value">
            <?= /* @noEscape */ $collector->getDuration() ?>
            <span class="unit">ms</span>
        </span>
        <span class="label"><?= $escaper->escapeHtml(__('Total execution time')) ?></span>
    </div>
    <div class="metric">
        <span class="value">
            <?= /* @noEscape */$profile->getCollector('memory')->getMemoryUsage() ?>
            <span class="unit">MB</span>
        </span>
        <span class="label"><?= $escaper->escapeHtml(__('Peak memory usage')) ?></span>
    </div>
</div>
<?php if (count($events) === 0): ?>
    <div class="empty status-error">
        <p><?= $escaper->escapeHtml(__('No detailed performance data is available.')) ?></p>
    </div>
<?php else: ?>
    <script>
        var requests = <?= /* @noEscape */ $collector->getJsTimeline(); ?>;
    </script>

    <h2><?= $escaper->escapeHtml(__('Execution timeline')) ?></h2>
    <?php if (!$events): ?>
        <p class="label status-warning">
            <strong>
                <?= $escaper->escapeHtml(__('You need to enable Varien Profiler to render timeline.')) ?>
            </strong>
        </p>
    <?php endif; ?>

    <form id="timeline-control" action="" method="get">
        <input type="hidden" name="panel" value="time">
        <label for="threshold"><?= $escaper->escapeHtml(__('Threshold')) ?></label>
        <input type="number" size="3" name="threshold" id="threshold" value="3" min="0"> ms
        <span class="help">
            <?= $escaper->escapeHtml(
                __('(timeline only displays events with a duration longer than this threshold)')
            ) ?>
        </span>
    </form>

    <div class="sf-profiler-timeline">
        <div class="legends" style="color: #777">
            <?php foreach ($collector->getColors() as $color => $hexCode): ?>
                <span data-color="<?= /* @noEscape */ $hexCode ?>"
                      style="border-left-color: <?= /* @noEscape */ $hexCode ?>;"><?= /* @noEscape */ $color ?></span>
            <?php endforeach; ?>
        </div>
        <canvas width="2120"
                height="1216"
                id="timeline_<?= /* @noEscape */ $viewModel->getProfile()->getToken() ?>"
                class="timeline"
                style="width: 1060px; height: 608px;"></canvas>
    </div>

    <script>
        /**
         * In-memory key-value cache manager
         */
        var cache = new function () {
            "use strict";
            var dict = {};
            this.get = function (key) {
                return dict.hasOwnProperty(key)
                    ? dict[key]
                    : null;
            };
            this.set = function (key, value) {
                dict[key] = value;
                return value;
            };
        };
        /**
         * Query an element with a CSS selector.
         *
         * @param {string} selector - a CSS-selector-compatible query string
         *
         * @return DOMElement|null
         */
        function query(selector) {
            "use strict";
            var key = 'SELECTOR: ' + selector;
            return cache.get(key) || cache.set(key, document.querySelector(selector));
        }
        /**
         * Canvas Manager
         */
        function CanvasManager(requests, maxRequestTime) {
            "use strict";
            var _drawingColors  = <?= /* @noEscape */ $collector->getJsColors(); ?>,
                _storagePrefix  = 'timeline/',
                _threshold      = 1,
                _requests       = requests,
                _maxRequestTime = maxRequestTime;

            /**
             * Check whether this event is a child event.
             *
             * @return true if it is
             */
            function isChildEvent(event) {
                return '__section__.child' === event.name;
            }

            /**
             * Check whether this event is categorized in 'section'.
             *
             * @return true if it is
             */
            function isSectionEvent(event) {
                return 'section' === event.category;
            }

            /**
             * Get the width of the container.
             */
            function getContainerWidth() {
                return query('#collector-content h2').clientWidth;
            }

            /**
             * Draw one canvas.
             *
             * @param request   the request object
             * @param max       <subjected for removal>
             * @param threshold the threshold (lower bound) of the length of the timeline (in milliseconds)
             * @param width     the width of the canvas
             */
            this.drawOne = function (request, max, threshold, width) {
                "use strict";
                var text,
                    ms,
                    xc,
                    drawableEvents,
                    mainEvents,
                    elementId       = 'timeline_' + request.id,
                    canvasHeight    = 0,
                    gapPerEvent     = 38,
                    colors          = _drawingColors,
                    space           = 10.5,
                    ratio           = (width - space * 2) / max,
                    h               = space,
                    x               = request.left * ratio + space, // position
                    canvas          = cache.get(elementId) || cache.set(elementId, document.getElementById(elementId)),
                    ctx             = canvas.getContext("2d"),
                    scaleRatio,
                    devicePixelRatio;
                // Filter events whose total time is below the threshold.
                drawableEvents      = request.events.filter(function (event) {
                    return event.duration >= threshold;
                });
                canvasHeight += gapPerEvent * drawableEvents.length;
                // For retina displays so text and boxes will be crisp
                devicePixelRatio    = window.devicePixelRatio == "undefined" ? 1 : window.devicePixelRatio;
                scaleRatio          = devicePixelRatio / 1;
                canvas.width        = width * scaleRatio;
                canvas.height       = canvasHeight * scaleRatio;
                canvas.style.width  = width + 'px';
                canvas.style.height = canvasHeight + 'px';
                ctx.scale(scaleRatio, scaleRatio);
                ctx.textBaseline = "middle";
                ctx.lineWidth    = 0;
                // For each event, draw a line.
                ctx.strokeStyle  = "#CCC";
                drawableEvents.forEach(function (event) {
                    event.periods.forEach(function (period) {
                        var timelineHeadPosition = x + period.start * ratio;
                        if (isChildEvent(event)) {
                            /* create a striped background dynamically */
                            var img       = new Image();
                            <?php // phpcs:ignore Generic.Files.LineLength.TooLong ?>
                            img.src       = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKBAMAAAB/HNKOAAAAIVBMVEX////w8PDd7h7d7h7d7h7d7h7w8PDw8PDw8PDw8PDw8PAOi84XAAAAKUlEQVQImWNI71zAwMBQMYuBgY0BxExnADErGEDMTgYQE8hnAKtCZwIAlcMNSR9a1OEAAAAASUVORK5CYII=';
                            var pattern   = ctx.createPattern(img, 'repeat');
                            ctx.fillStyle = pattern;
                            ctx.fillRect(timelineHeadPosition, 0, (period.end - period.start) * ratio, canvasHeight);
                        } else if (isSectionEvent(event)) {
                            var timelineTailPosition = x + period.end * ratio;
                            ctx.beginPath();
                            ctx.moveTo(timelineHeadPosition, 0);
                            ctx.lineTo(timelineHeadPosition, canvasHeight);
                            ctx.moveTo(timelineTailPosition, 0);
                            ctx.lineTo(timelineTailPosition, canvasHeight);
                            ctx.fill();
                            ctx.closePath();
                            ctx.stroke();
                        }
                    });
                });
                // Filter for main events.
                mainEvents = drawableEvents.filter(function (event) {
                    return !isChildEvent(event)
                });
                // For each main event, draw the visual presentation of timelines.
                mainEvents.forEach(function (event) {
                    h += 8;
                    // For each sub event, ...
                    event.periods.forEach(function (period) {
                        // Set the drawing style.
                        ctx.fillStyle   = colors['default'];
                        ctx.strokeStyle = colors['default'];
                        if (colors[event.name]) {
                            ctx.fillStyle   = colors[event.name];
                            ctx.strokeStyle = colors[event.name];
                        } else if (colors[event.category]) {
                            ctx.fillStyle   = colors[event.category];
                            ctx.strokeStyle = colors[event.category];
                        }
                        // Draw the timeline
                        var timelineHeadPosition = x + period.start * ratio;
                        if (!isSectionEvent(event)) {
                            ctx.fillRect(timelineHeadPosition, h + 3, 2, 8);
                            ctx.fillRect(timelineHeadPosition, h, (period.end - period.start) * ratio || 2, 6);
                        } else {
                            var timelineTailPosition = x + period.end * ratio;
                            ctx.beginPath();
                            ctx.moveTo(timelineHeadPosition, h);
                            ctx.lineTo(timelineHeadPosition, h + 11);
                            ctx.lineTo(timelineHeadPosition + 8, h);
                            ctx.lineTo(timelineHeadPosition, h);
                            ctx.fill();
                            ctx.closePath();
                            ctx.stroke();
                            ctx.beginPath();
                            ctx.moveTo(timelineTailPosition, h);
                            ctx.lineTo(timelineTailPosition, h + 11);
                            ctx.lineTo(timelineTailPosition - 8, h);
                            ctx.lineTo(timelineTailPosition, h);
                            ctx.fill();
                            ctx.closePath();
                            ctx.stroke();
                            ctx.beginPath();
                            ctx.moveTo(timelineHeadPosition, h);
                            ctx.lineTo(timelineTailPosition, h);
                            ctx.lineTo(timelineTailPosition, h + 2);
                            ctx.lineTo(timelineHeadPosition, h + 2);
                            ctx.lineTo(timelineHeadPosition, h);
                            ctx.fill();
                            ctx.closePath();
                            ctx.stroke();
                        }
                    });
                    h += 30;
                    ctx.beginPath();
                    ctx.strokeStyle = "#E0E0E0";
                    ctx.moveTo(0, h - 10);
                    ctx.lineTo(width, h - 10);
                    ctx.closePath();
                    ctx.stroke();
                });
                h = space;
                // For each event, draw the label.
                mainEvents.forEach(function (event) {
                    ctx.fillStyle = "#444";
                    ctx.font      = "12px sans-serif";
                    text          = event.name;
                    ms            = "  " + (
                        event.duration < 1 ? event.duration : parseInt(event.duration, 10)
                    ) + " ms / " + event.memory + " MB";
                    if (x + event.starttime * ratio + ctx.measureText(text + ms).width > width) {
                        ctx.textAlign = "end";
                        ctx.font      = "10px sans-serif";
                        ctx.fillStyle = "#777";
                        xc            = x + event.endtime * ratio - 1;
                        ctx.fillText(ms, xc, h);
                        xc -= ctx.measureText(ms).width;
                        ctx.font      = "12px sans-serif";
                        ctx.fillStyle = "#222";
                        ctx.fillText(text, xc, h);
                    } else {
                        ctx.textAlign = "start";
                        ctx.font      = "13px sans-serif";
                        ctx.fillStyle = "#222";
                        xc            = x + event.starttime * ratio + 1;
                        ctx.fillText(text, xc, h);
                        xc += ctx.measureText(text).width;
                        ctx.font      = "11px sans-serif";
                        ctx.fillStyle = "#777";
                        ctx.fillText(ms, xc, h);
                    }
                    h += gapPerEvent;
                });
            };
            this.drawAll      = function (width, threshold) {
                "use strict";
                width     = width || getContainerWidth();
                threshold = threshold || this.getThreshold();
                var self  = this;
                _requests.forEach(function (request) {
                    self.drawOne(request, _maxRequestTime, threshold, width);
                });
            };
            this.getThreshold = function () {
                var threshold = Sfjs.getPreference(_storagePrefix + 'threshold');
                if (null === threshold) {
                    return _threshold;
                }
                _threshold = parseInt(threshold);
                return _threshold;
            };
            this.setThreshold = function (threshold) {
                _threshold = threshold;
                Sfjs.setPreference(_storagePrefix + 'threshold', threshold);
                return this;
            };
        }
        function canvasAutoUpdateOnResizeAndSubmit(e) {
            e.preventDefault();
            canvasManager.drawAll();
        }
        function canvasAutoUpdateOnThresholdChange(e) {
            canvasManager
                .setThreshold(query('input[name="threshold"]').value)
                .drawAll();
        }

        var canvasManager                      = new CanvasManager(requests.data, requests.max);
        query('input[name="threshold"]').value = canvasManager.getThreshold();
        canvasManager.drawAll();
        // Update the colors of legends.
        var timelineLegends = document.querySelectorAll('.sf-profiler-timeline > .legends > span[data-color]');
        for (var i = 0; i < timelineLegends.length; ++i) {
            var timelineLegend                   = timelineLegends[i];
            timelineLegend.style.borderLeftColor = timelineLegend.getAttribute('data-color');
        }
        // Bind event handlers
        var elementTimelineControl       = query('#timeline-control'),
            elementThresholdControl      = query('input[name="threshold"]');
        window.onresize                  = canvasAutoUpdateOnResizeAndSubmit;
        elementTimelineControl.onsubmit  = canvasAutoUpdateOnResizeAndSubmit;
        elementThresholdControl.onclick  = canvasAutoUpdateOnThresholdChange;
        elementThresholdControl.onchange = canvasAutoUpdateOnThresholdChange;
        elementThresholdControl.onkeyup  = canvasAutoUpdateOnThresholdChange;
        window.setTimeout(function () {
            canvasAutoUpdateOnThresholdChange(null);
        }, 50);
        //]]>
    </script>
<?php endif; ?>
