<?php

/*
 * This file is part of Psy Shell.
 *
 * (c) 2012-2025 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Psy\Formatter;

use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;

/**
 * Utility for creating terminal hyperlinks (OSC 8).
 */
class LinkFormatter
{
    /** @var array<string, string> */
    private static $styles = [];

    /**
     * Set styles for formatting hyperlinks.
     *
     * @param array $styles Map of style name to inline style string
     */
    public static function setStyles(array $styles): void
    {
        self::$styles = $styles;
    }

    /**
     * Check if the current Symfony Console version supports hyperlinks.
     */
    public static function supportsLinks(): bool
    {
        static $supports = null;

        if ($supports === null) {
            $supports = \method_exists(OutputFormatterStyle::class, 'setHref');
        }

        return $supports;
    }

    /**
     * Wrap text in a style tag, optionally including an href.
     *
     * @param string      $style The style name (e.g., 'class', 'function', 'info')
     * @param string      $text  The text to wrap
     * @param string|null $href  Optional hyperlink
     *
     * @return string Formatted text with style and optional href
     */
    public static function styleWithHref(string $style, string $text, ?string $href = null): string
    {
        $escapedText = OutputFormatter::escape($text);

        if ($href !== null && self::supportsLinks()) {
            $href = self::encodeHrefForOsc8($href);
            $inline = self::$styles[$style] ?? '';
            $combinedStyle = $inline !== '' ? \sprintf('%s;href=%s', $inline, $href) : \sprintf('href=%s', $href);

            return \sprintf('<%s>%s</>', $combinedStyle, $escapedText);
        }

        return \sprintf('<%s>%s</%s>', $style, $escapedText, $style);
    }

    /**
     * Get the php.net manual URL for a given item.
     *
     * @param string $item Function or class name
     *
     * @return string URL to php.net manual
     */
    public static function getPhpNetUrl(string $item): string
    {
        // Normalize the item name for URL (lowercase, replace :: with . and _ with -)
        $normalized = \str_replace('::', '.', $item);
        $normalized = \str_replace('_', '-', $normalized);
        $normalized = \strtolower($normalized);

        return 'https://php.net/'.$normalized;
    }

    /**
     * Encode a string for use in OSC 8 hyperlink URIs.
     *
     * Per OSC 8 spec, URIs must only contain bytes in the 32-126 range.
     *
     * @param string $str String to encode
     *
     * @return string URI-encoded string safe for OSC 8
     */
    public static function encodeHrefForOsc8(string $str): string
    {
        // Encode any character outside printable ASCII range (32-126)
        return \preg_replace_callback('/[^\x20-\x7E]/', function ($matches) {
            return \rawurlencode($matches[0]);
        }, $str);
    }
}
