<?php declare(strict_types=1);

/**
 * This file is part of the Tracy (https://tracy.nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */

namespace Tracy;

use function array_slice, count;
use const PHP_OUTPUT_HANDLER_FINAL;


/**
 * Debugger for outputs.
 */
final class OutputDebugger
{
	private const BOM = "\xEF\xBB\xBF";

	/** @var list<array{string, int, string, list<array<string, mixed>>}> */
	private array $list = [];


	public static function enable(): void
	{
		$me = new self;
		$me->start();
	}


	public function start(): void
	{
		foreach (get_included_files() as $file) {
			if (file_get_contents($file, length: 3) === self::BOM) {
				$this->list[] = [$file, 1, self::BOM, []];
			}
		}

		ob_start($this->handler(...), 1);
	}


	private function handler(string $s, int $phase): string
	{
		$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
		if (isset($trace[0]['file'], $trace[0]['line'])) {
			$stack = $trace;
			unset($stack[0]['line'], $stack[0]['args']);
			$i = count($this->list);
			if ($i && $this->list[$i - 1][3] === $stack) {
				$this->list[$i - 1][2] .= $s;
			} else {
				$this->list[] = [$trace[0]['file'], $trace[0]['line'], $s, $stack];
			}
		}

		return $phase === PHP_OUTPUT_HANDLER_FINAL
			? $this->renderHtml()
			: '';
	}


	private function renderHtml(): string
	{
		$res = '<style>code, pre {white-space:nowrap} a {text-decoration:none} pre {color:gray;display:inline} big {color:red}</style><code>';
		foreach ($this->list as $item) {
			$stack = [];
			foreach (array_slice($item[3], 1) as $t) {
				$t += ['class' => '', 'type' => '', 'function' => ''];
				$stack[] = "$t[class]$t[type]$t[function]()"
					. (isset($t['file'], $t['line']) ? ' in ' . basename($t['file']) . ":$t[line]" : '');
			}

			$res .= '<span title="' . Helpers::escapeHtml(implode("\n", $stack)) . '">'
				. Helpers::editorLink($item[0], $item[1]) . ' '
				. str_replace(self::BOM, '<big>BOM</big>', Dumper::toHtml($item[2]))
				. "</span><br>\n";
		}

		return $res . '</code>';
	}
}
