<?php

/**
 * @package   bdk/debug
 * @author    Brad Kent <bkfake-github@yahoo.com>
 * @license   http://opensource.org/licenses/MIT MIT
 * @copyright 2014-2025 Brad Kent
 * @since     2.0
 */

namespace bdk\Debug\Dump\Base;

use bdk\Debug\Abstraction\Abstracter;
use bdk\Debug\Abstraction\Abstraction;
use bdk\Debug\Abstraction\Object\Abstraction as ObjectAbstraction;
use bdk\Debug\Abstraction\Type;
use bdk\Debug\Dump\AbstractValue;
use bdk\Debug\Dump\Base\BaseObject;
use bdk\Debug\Utility\Utf8;

/**
 * Dump values
 */
class Value extends AbstractValue
{
    const TYPE_ABSTRACTION = 'bdk\Debug\Abstraction\Abstraction|null';

    /** @var BaseObject */
    protected $lazyObject;

    /**
     * Extend me to format classname/constant, etc
     *
     * @param string|array $val classname or classname(::|->)name (method/property/const)
     *
     * @return string
     */
    public function markupIdentifier($val)
    {
        if (\is_array($val)) {
            $val = $val[0] . '::' . $val[1];
        }
        return $this->highlightChars($val);
    }

    /**
     * {@inheritDoc}
     */
    protected function dumpArray(array $array, $abs = null)
    {
        \bdk\Debug\Utility\PhpType::assertType($abs, self::TYPE_ABSTRACTION, 'abs');

        if ($this->optionGet('isMaxDepth')) {
            return 'array *' . $this->debug->i18n->trans('abs.max-depth') . '*';
        }
        $arrayNew = array();
        $absKeys = isset($abs['keys'])
            ? $abs['keys']
            : array();
        foreach ($array as $key => $val) {
            if (isset($absKeys[$key])) {
                $key = $absKeys[$key];
            }
            $key = $this->dump($key, array('addQuotes' => false));
            $arrayNew[$key] = $this->dump($val);
        }
        return $arrayNew;
    }

    /**
     * {@inheritDoc}
     */
    protected function dumpBool($val)
    {
        return $val;
    }

    /**
     * Dump callable
     *
     * @param Abstraction $abs array/callable abstraction
     *
     * @return string
     */
    protected function dumpCallable(Abstraction $abs)
    {
        return 'callable: '
            . $this->markupIdentifier($abs['value']);
    }

    /**
     * {@inheritDoc}
     */
    protected function dumpFloat($val, $abs = null)
    {
        \bdk\Debug\Utility\PhpType::assertType($abs, self::TYPE_ABSTRACTION, 'abs');

        $date = $this->checkTimestamp($val, $abs);
        return $date
            ? $val . ' (' . $date . ')'
            : $val;
    }

    /**
     * Dump identifier
     *
     * @param Abstraction $abs constant abstraction
     *
     * @return string
     */
    protected function dumpIdentifier(Abstraction $abs)
    {
        return $abs['value'];
    }

    /**
     * {@inheritDoc}
     */
    protected function dumpInt($val, $abs = null)
    {
        \bdk\Debug\Utility\PhpType::assertType($abs, self::TYPE_ABSTRACTION, 'abs');

        $val = $this->dumpFloat($val, $abs);
        return \is_string($val)
            ? $val
            : (int) $val;
    }

    /**
     * Dump non-inspected value (likely object)
     *
     * @return string
     */
    protected function dumpNotInspected()
    {
        return 'NOT INSPECTED';
    }

    /**
     * {@inheritDoc}
     */
    protected function dumpNull()
    {
        return null;
    }

    /**
     * Dump object
     *
     * @param ObjectAbstraction $abs Object Abstraction instance
     *
     * @return string|array
     */
    protected function dumpObject(ObjectAbstraction $abs)
    {
        return $this->object->dump($abs);
    }

    /**
     * Dump recursion (array recursion)
     *
     * @return string
     */
    protected function dumpRecursion()
    {
        return 'array *' . $this->debug->i18n->trans('abs.recursion') . '*';
    }

    /**
     * Dump resource
     *
     * @param Abstraction $abs resource abstraction
     *
     * @return string
     */
    protected function dumpResource(Abstraction $abs)
    {
        return $abs['value'];
    }

    /**
     * {@inheritDoc}
     */
    protected function dumpString($val, $abs = null)
    {
        \bdk\Debug\Utility\PhpType::assertType($abs, self::TYPE_ABSTRACTION, 'abs');

        if (\is_numeric($val)) {
            $date = $this->checkTimestamp($val, $abs);
            return $date
                ? $val . ' (' . $date . ')'
                : $val;
        }
        if ($abs) {
            return $this->dumpStringAbs($abs);
        }
        $val = $this->escapeEscapeSequences($val);
        return $this->highlightChars($val);
    }

    /**
     * Dump string abstraction
     *
     * @param Abstraction $abs Abstraction instance
     *
     * @return string
     */
    protected function dumpStringAbs(Abstraction $abs)
    {
        if ($abs['prettified']) {
            $this->optionSet('addQuotes', false);
        }
        $val = $abs['typeMore'] === Type::TYPE_STRING_BINARY
            ? $this->dumpStringBinary($abs)
            : (string) $abs;
        $strLenDiff = 0;
        if ($abs['strlenValue']) {
            $abs['strlen'] = $abs['strlen'] ?: \strlen((string) $abs['value']);
            $strLenDiff = $abs['strlen'] - $abs['strlenValue'];
        }
        if ($strLenDiff) {
            $val .= '[' . $this->debug->i18n->trans('string.more-bytes', array('bytes' => $strLenDiff))  . ']';
        }
        return $val;
    }

    /**
     * Get binary value
     *
     * @param Abstraction $abs Abstraction instance
     *
     * @return string
     */
    protected function dumpStringBinary(Abstraction $abs)
    {
        if ($abs['brief'] && $abs['contentType']) {
            return 'string(' . $abs['contentType'] . '): '
                . $this->debug->utility->getBytes($abs['strlen']);
        }
        if ($abs['value']) {
            return $abs['value'];
        }
        return \implode('', \array_map(function ($chunk) {
            return $chunk[0] === Utf8::TYPE_OTHER
                ? '\\x' . \str_replace(' ', ' \\x', $chunk[1])
                : $this->dumpString($chunk[1]);
        }, $abs['chunks'] ?: array()));
    }

    /**
     * Dump undefined
     *
     * @return string
     */
    protected function dumpUndefined()
    {
        return Abstracter::UNDEFINED;
    }

    /**
     * Dump Type::TYPE_UNKNOWN
     *
     * @param Abstraction $abs resource abstraction
     *
     * @return array
     */
    protected function dumpUnknown(Abstraction $abs)
    {
        $values = \array_diff_key($abs->getValues(), \array_flip(['brief', 'typeMore']));
        \ksort($values);
        return $values;
    }

    /**
     * Getter for this->object
     *
     * @return BaseObject
     */
    protected function getObject()
    {
        if (!$this->lazyObject) {
            $this->lazyObject = new BaseObject($this);
        }
        return $this->lazyObject;
    }

    /**
     * Get replacement for character
     *
     * @param string $char        single multi-byte character
     * @param bool   $charReplace whether to replace char with code-point
     *                            control chars are always replaced
     *
     * @return string \x##, \u{####}, or original char
     */
    protected function charReplacement($char, $charReplace)
    {
        if (\ord($char) < 0x80) {
            // always replace control chars
            return '\\x' . \str_pad(\dechex(\ord($char)), 2, '0', STR_PAD_LEFT);
        }
        if (isset($this->charData[$char]['class']) && \strpos($this->charData[$char]['class'], 'char-ws') !== false) {
            // always replace "whitespace" chars
            $charReplace = true;
        }
        if ($charReplace) {
            return '\\u{' . \str_pad(\dechex(Utf8::ord($char)), 4, '0', STR_PAD_LEFT) . '}';
        }
        return $char;
    }

    /**
     * Highlight confusable and other characters
     *
     * @param string $str HTML String to update
     *
     * @return string
     */
    protected function highlightChars($str)
    {
        $chars = $this->findChars($str);
        $charReplace = $this->optionGet('charReplace');
        foreach ($chars as $char) {
            $replacement = $this->charReplacement($char, $charReplace);
            $str = \str_replace($char, $replacement, $str);
        }
        return $str;
    }
}
