<?php

declare(strict_types=1);

namespace Jfcherng\Diff\Renderer\Text;

use Jfcherng\Diff\Exception\UnsupportedFunctionException;
use Jfcherng\Diff\Renderer\AbstractRenderer;
use Jfcherng\Diff\Renderer\RendererConstant;
use Jfcherng\Utility\CliColor;

/**
 * Base renderer for rendering text-based diffs.
 */
abstract class AbstractText extends AbstractRenderer
{
    /**
     * @var bool is this renderer pure text?
     */
    public const IS_TEXT_RENDERER = true;

    /**
     * @var string the diff output representing there is no EOL at EOF in the GNU diff tool
     */
    public const GNU_OUTPUT_NO_EOL_AT_EOF = '\ No newline at end of file';

    /**
     * @var bool controls whether cliColoredString() is enabled or not
     */
    protected $isCliColorEnabled = false;

    /**
     * {@inheritdoc}
     */
    public function setOptions(array $options): AbstractRenderer
    {
        parent::setOptions($options);

        // determine $this->isCliColorEnabled
        if ($this->options['cliColorization'] === RendererConstant::CLI_COLOR_ENABLE) {
            $this->isCliColorEnabled = true;
        } elseif ($this->options['cliColorization'] === RendererConstant::CLI_COLOR_DISABLE) {
            $this->isCliColorEnabled = false;
        } else {
            $this->isCliColorEnabled = \PHP_SAPI === 'cli' && $this->hasColorSupport(\STDOUT);
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function getResultForIdenticalsDefault(): string
    {
        return '';
    }

    /**
     * {@inheritdoc}
     */
    protected function renderArrayWorker(array $differArray): string
    {
        throw new UnsupportedFunctionException(__METHOD__);

        return ''; // make IDE not complain
    }

    /**
     * Colorize the string for CLI output.
     *
     * @param string      $str    the string
     * @param null|string $symbol the symbol
     *
     * @return string the (maybe) colorized string
     */
    protected function cliColoredString(string $str, ?string $symbol): string
    {
        static $symbolToStyles = [
            '@' => ['f_purple', 'bold'], // header
            '-' => ['f_red', 'bold'], // deleted
            '+' => ['f_green', 'bold'], // inserted
            '!' => ['f_yellow', 'bold'], // replaced
        ];

        $styles = $symbolToStyles[$symbol] ?? [];

        if (!$this->isCliColorEnabled || empty($styles)) {
            return $str;
        }

        return CliColor::color($str, $styles);
    }

    /**
     * Returns true if the stream supports colorization.
     *
     * Colorization is disabled if not supported by the stream:
     *
     * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo
     * terminals via named pipes, so we can only check the environment.
     *
     * Reference: Composer\XdebugHandler\Process::supportsColor
     * https://github.com/composer/xdebug-handler
     *
     * @see https://github.com/symfony/console/blob/647c51ff073300a432a4a504e29323cf0d5e0571/Output/StreamOutput.php#L81-L124
     *
     * @param resource $stream
     *
     * @return bool true if the stream supports colorization, false otherwise
     *
     * @suppress PhanUndeclaredFunction
     */
    protected function hasColorSupport($stream): bool
    {
        // Follow https://no-color.org/
        if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) {
            return false;
        }

        if ('Hyper' === getenv('TERM_PROGRAM')) {
            return true;
        }

        if (\DIRECTORY_SEPARATOR === '\\') {
            return (\function_exists('sapi_windows_vt100_support')
                && @sapi_windows_vt100_support($stream))
                || false !== getenv('ANSICON')
                || 'ON' === getenv('ConEmuANSI')
                || 'xterm' === getenv('TERM');
        }

        if (\function_exists('stream_isatty')) {
            return @stream_isatty($stream);
        }

        if (\function_exists('posix_isatty')) {
            return @posix_isatty($stream);
        }

        $stat = @fstat($stream);
        // Check if formatted mode is S_IFCHR
        return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
    }
}
