<?php

declare(strict_types=1);

namespace Phan\Library;

use Phan\AST\ASTReverter;

/**
 * StringUtil contains methods to simplify working with strings in Phan and its plugins.
 */
class StringUtil
{
    /**
     * Encode a scalar value in a compact, unambiguous representation for emitted issues.
     * The encoder used by encodeValue may change.
     * This aims to fit on a single line.
     *
     * @param string|int|float|bool|null $value
     */
    public static function encodeValue($value): string
    {
        if (\is_string($value) && \preg_match('/([\0-\15\16-\37])/', $value)) {
            // Use double quoted strings if this contains newlines, tabs, control characters, etc.
            return '"' . ASTReverter::escapeInnerString($value, '"') . '"';
        }
        return self::varExportPretty($value);
    }

    /**
     * Same as var_export($value, true), but converts top-level NULL to null.
     * Other changes may be made in the future, e.g. shorter list representations.
     *
     * @param ?(int|bool|string|array|float) $value
     */
    public static function varExportPretty($value): string
    {
        if ($value === null) {
            return 'null';  // return lowercase instead of uppercase 'NULL'
        }
        return \var_export($value, true);
    }

    /**
     * JSON encodes a value - Guaranteed to return a string.
     * @param string|int|float|bool|null|array|object $value
     */
    public static function jsonEncode($value): string
    {
        $result = \json_encode($value, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE | \JSON_PARTIAL_OUTPUT_ON_ERROR);
        return \is_string($result) ? $result : '(invalid data)';
    }

    /**
     * Encode a list of values in a compact, unambiguous representation for emitted issues.
     * @param list<string|int|float|bool> $values
     */
    public static function encodeValueList(string $separator, array $values): string
    {
        return \implode(
            $separator,
            \array_map([self::class, 'encodeValue'], $values)
        );
    }

    /**
     * Coerce $str to valid utf-8
     */
    public static function asUtf8(string $str): string
    {
        return \mb_convert_encoding($str, 'UTF-8', 'UTF-8') ?: $str;
    }

    /**
     * Coerce $str to valid utf-8 and replace newlines with placeholders
     */
    public static function asSingleLineUtf8(string $str): string
    {
        if (!\preg_match("@[\\n\\r\x80-\xff]@", $str)) {
            // Around 5x faster for the common case of being ASCII without newlines.
            return $str;
        }
        return \str_replace(["\n", "\r"], "�", self::asUtf8($str));
    }

    /**
     * Returns true if the provided argument is a non-zero length string.
     * Unlike (bool)$str, this allows the literal string '0', and rejects types other than strings.
     *
     * @param ?string|?false $str typical inputs are nullable/falsable strings
     * @phan-assert string $str TODO: This unconditionally sets the type of $str to string - add an equivalent that only sets the type when true.
     * @psalm-assert-if-true string $str
     */
    public static function isNonZeroLengthString($str): bool
    {
        return \is_string($str) && $str !== '';
    }
}
