<?php

declare(strict_types=0);

/**
 * vim:set softtabstop=4 shiftwidth=4 expandtab:
 *
 * LICENSE: GNU Affero General Public License, version 3 (AGPL-3.0-or-later)
 * Copyright Ampache.org, 2001-2024
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

namespace Ampache\Module\Util;

use Ampache\Config\ConfigContainerInterface;
use Ampache\Module\Api\Api;
use Ampache\Module\Playback\Localplay\LocalPlay;
use Ampache\Module\Playback\Localplay\LocalPlayTypeEnum;
use Ampache\Module\System\Plugin\PluginTypeEnum;
use Ampache\Module\Util\Rss\Type\RssFeedTypeEnum;
use Ampache\Plugin\PluginDisplayOnFooterInterface;
use Ampache\Repository\MetadataFieldRepositoryInterface;
use Ampache\Repository\Model\Playlist;
use Ampache\Repository\Model\Plugin;
use Ampache\Repository\Model\Search;
use Ampache\Config\AmpConfig;
use Ampache\Module\System\Core;
use Ampache\Module\System\Dba;
use Ampache\Repository\Model\Preference;
use Ampache\Repository\Model\User;

/**
 * A collection of methods related to the user interface
 */
class Ui implements UiInterface
{
    private static int $_ticker = 0;

    /** @var array<string, string> $_icon_cache */
    private static array $_icon_cache = [];

    /** @var array<string, string> $_image_cache */
    private static array $_image_cache = [];

    public function __construct(
        private readonly ConfigContainerInterface $configContainer
    ) {
    }

    /**
     * This dumps out some html and an icon for the type of rss that we specify
     *
     * @param array<string, string>|null $params
     */
    public static function getRssLink(
        RssFeedTypeEnum $type,
        ?User $user = null,
        string $title = '',
        ?array $params = null
    ): string {
        $strparams = "";
        if (is_array($params)) {
            foreach ($params as $key => $value) {
                $strparams .= "&" . scrub_out($key) . "=" . scrub_out($value);
            }
        }

        $rsstoken = '';
        if ($user !== null) {
            $rsstoken = "&rsstoken=" . $user->getRssToken();
        }

        $string = (
            '<a class="nohtml" href="' . AmpConfig::get('web_path') .
            '/rss.php?type=' . $type->value .
            $rsstoken . $strparams . '" target="_blank">' .
            self::get_material_symbol(
                'rss_feed',
                T_('RSS Feed')
            )
        );
        if (!empty($title)) {
            $string .= '&nbsp;' . $title;
        }
        $string .= '</a>';

        return $string;
    }

    /**
     * find_template
     *
     * Return the path to the template file wanted. The file can be overwritten
     * by the theme if it's not a php file, or if it is and if option
     * allow_php_themes is set to true.
     */
    public static function find_template(string $template, bool $extern = false): string
    {
        $path      = AmpConfig::get('theme_path', '/themes/reborn') . '/templates/' . $template;
        $realpath  = __DIR__ . '/../../../public/' . $path;
        $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
        if (($extension != 'php' || AmpConfig::get('allow_php_themes')) && file_exists($realpath) && is_file($realpath)) {
            return $path;
        } else {
            if ($extern === true) {
                return '/templates/' . $template;
            }

            return __DIR__ . '/../../../public/templates/' . $template;
        }
    }

    public function showObjectNotFound(): void
    {
        $this->showHeader();
        echo T_('You have requested an object that does not exist');
        $this->showQueryStats();
        $this->showFooter();
    }

    /**
     * Displays the default error page
     */
    public function accessDenied(string $error = 'Access Denied'): void
    {
        // Clear any buffered crap
        ob_end_clean();
        header("HTTP/1.1 403 $error");
        require_once self::find_template('show_denied.inc.php');
    }

    /**
     * Displays an error page when you can't write the config
     */
    public function permissionDenied(string $fileName): void
    {
        // Clear any buffered crap
        ob_end_clean();
        header("HTTP/1.1 403 Permission Denied");
        require_once self::find_template('show_denied_permission.inc.php');
    }

    /**
     * ajax_include
     *
     * Does some trickery with the output buffer to return the output of a
     * template.
     */
    public static function ajax_include(string $template): string
    {
        ob_start();
        require self::find_template('') . $template;
        $output = ob_get_contents();
        ob_end_clean();

        return $output ?: '';
    }

    /**
     * check_iconv
     *
     * Checks to see whether iconv is available.
     */
    public static function check_iconv(): bool
    {
        if (function_exists('iconv') && function_exists('iconv_substr')) {
            return true;
        }

        return false;
    }

    /**
     * check_ticker
     *
     * Stupid little cutesie thing to ratelimit output of long-running
     * operations.
     */
    public static function check_ticker(): bool
    {
        if (defined('SSE_OUTPUT') || defined('CLI') || defined('API')) {
            return false;
        }
        if (!isset(self::$_ticker) || (time() > self::$_ticker + 1)) {
            self::$_ticker = time();

            return true;
        }

        return false;
    }

    /**
     * clean_utf8
     *
     * Removes characters that aren't valid in XML
     * (which is a subset of valid UTF-8, but close enough for our purposes.)
     * See http://www.w3.org/TR/2006/REC-xml-20060816/#charsets
     */
    public static function clean_utf8(string $string): string
    {
        if ($string) {
            $clean = preg_replace(
                '/[^\x{9}\x{a}\x{d}\x{20}-\x{d7ff}\x{e000}-\x{fffd}\x{10000}-\x{10ffff}]|[\x{7f}-\x{84}\x{86}-\x{9f}\x{fdd0}-\x{fddf}\x{1fffe}-\x{1ffff}\x{2fffe}-\x{2ffff}\x{3fffe}-\x{3ffff}\x{4fffe}-\x{4ffff}\x{5fffe}-\x{5ffff}\x{6fffe}-\x{6ffff}\x{7fffe}-\x{7ffff}\x{8fffe}-\x{8ffff}\x{9fffe}-\x{9ffff}\x{afffe}-\x{affff}\x{bfffe}-\x{bffff}\x{cfffe}-\x{cffff}\x{dfffe}-\x{dffff}\x{efffe}-\x{effff}\x{ffffe}-\x{fffff}\x{10fffe}-\x{10ffff}]/u',
                '',
                $string
            );

            if ($clean) {
                return rtrim((string)$clean);
            }

            debug_event(self::class, 'Charset cleanup failed, something might break', 1);
        }

        return '';
    }

    /**
     * format_bytes
     *
     * Turns a size in bytes into the best human-readable value
     * @param int|float $value Size in bytes
     * @param int $precision Number of decimal places to show
     * @param int $pass Internal counter for recursion
     */
    public static function format_bytes(int|float $value, int $precision = 2, int $pass = 0): string
    {
        if (!$value) {
            return '';
        }
        while (strlen((string)floor($value)) > 3) {
            $value /= 1024;
            $pass++;
        }

        $unit = match ($pass) {
            1 => 'kB',
            2 => 'MB',
            3 => 'GB',
            4 => 'TB',
            5 => 'PB',
            default => 'B',
        };

        return ((string)round($value, $precision)) . ' ' . $unit;
    }

    /**
     * unformat_bytes
     *
     * Parses a human-readable size
     * @noinspection PhpMissingBreakStatementInspection
     */
    public static function unformat_bytes(int|string $value): string
    {
        if (preg_match('/^([0-9]+) *([[:alpha:]]+)$/', (string)$value, $matches)) {
            $value = (int)$matches[1];
            $unit  = strtolower(substr($matches[2], 0, 1));
        } else {
            return (string)$value;
        }

        switch ($unit) {
            case 'p':
                $value *= 1024;
                // Intentional break fall-through
            case 't':
                $value *= 1024;
                // Intentional break fall-through
            case 'g':
                $value *= 1024;
                // Intentional break fall-through
            case 'm':
                $value *= 1024;
                // Intentional break fall-through
            case 'k':
                $value *= 1024;
        }

        return (string)$value;
    }

    /**
     * get_icon
     *
     * Returns an <img> or <svg> tag for the specified icon
     */
    public static function get_icon(string $name, ?string $title = null, ?string $id_attrib = null, ?string $class_attrib = null): string
    {
        $title    = $title ?? T_(ucfirst($name));
        $icon_url = self::_find_icon($name);
        $icontype = pathinfo($icon_url, PATHINFO_EXTENSION);
        $tag      = '';
        if ($icontype == 'svg') {
            // load svg file
            $svgicon = simplexml_load_file($icon_url);
            if ($svgicon !== false) {
                if (empty($svgicon->title)) {
                    $svgicon->addChild('title', $title);
                } else {
                    $svgicon->title = $title;
                }
                if (empty($svgicon->desc)) {
                    $svgicon->addChild('desc', $title);
                } else {
                    $svgicon->desc = $title;
                }

                if (!empty($id_attrib)) {
                    $svgicon->addAttribute('id', $id_attrib);
                }
                if (empty($class_attrib)) {
                    $class_attrib = 'icon icon-' . $name;
                }
                $svgicon->addAttribute('class', $class_attrib);

                $tag = explode("\n", (string)$svgicon->asXML(), 2)[1];
            }
        } else {
            // fall back to png
            $tag = '<img src="' . $icon_url . '" ';
            $tag .= 'alt="' . $title . '" ';
            $tag .= 'title="' . $title . '" ';
            if ($id_attrib !== null) {
                $tag .= 'id="' . $id_attrib . '" ';
            }
            if ($class_attrib !== null) {
                $tag .= 'class="' . $class_attrib . '" ';
            }
            $tag .= '/>';
        }

        return $tag;
    }

    /**
     * get_material_symbol
     *
     * Returns an <svg> tag for the specified Material Symbol
     */
    public static function get_material_symbol(string $name, ?string $title = null, ?string $id_attrib = null, ?string $class_attrib = null): string
    {
        $title    = $title ?? T_(ucfirst($name));
        $filepath = __DIR__ . '/../../../resources/images/material-symbols/' . $name . '.svg';
        if (!is_file($filepath)) {
            // fall back to error icon if icon is missing
            debug_event(self::class, 'Runtime Error: icon ' . $name . ' not found.', 1);
            $filepath = __DIR__ . '/../../../resources/images/icon_error.svg';
        }
        $tag = '';
        // load svg file
        $svgicon = simplexml_load_file($filepath);
        if ($svgicon !== false) {
            if (empty($svgicon->title)) {
                $svgicon->addChild('title', $title);
            } else {
                $svgicon->title = $title;
            }
            if (empty($svgicon->desc)) {
                $svgicon->addChild('desc', $title);
            } else {
                $svgicon->desc = $title;
            }

            if (!empty($id_attrib)) {
                $svgicon->addAttribute('id', $id_attrib);
            }
            if (empty($class_attrib)) {
                $class_attrib = '';
            }
            $svgicon->addAttribute('class', 'material-symbol material-symbol-' . $name . " " . $class_attrib);

            $tag = explode("\n", (string)$svgicon->asXML(), 3)[1];
        }

        return $tag;
    }

    /**
     * _find_icon
     *
     * Does the finding icon thing. match svg first over png
     */
    private static function _find_icon(string $name): string
    {
        if (isset(self::$_icon_cache[$name])) {
            return self::$_icon_cache[$name];
        }

        $path = 'themes/' . AmpConfig::get('theme_name', 'reborn') . '/images/icons/';
        // Can't use GLOB_BRACE for Alpine compatibility https://github.com/ampache/ampache/issues/4008
        $filesearch = array_merge(
            glob(__DIR__ . '/../../../public/' . $path . 'icon_' . $name . '.svg') ?: [],
            glob(__DIR__ . '/../../../public/' . $path . 'icon_' . $name . '.png') ?: []
        );

        if (empty($filesearch)) {
            // if the theme is missing an icon, fall back to default images folder
            $path = 'images/';
            // check private resources folder for svg files
            $filesearch = glob(__DIR__ . '/../../../resources/' . $path . 'icon_' . $name . '.svg');
            if (empty($filesearch)) {
                // finally fall back to the public images folder
                // Can't use GLOB_BRACE for Alpine compatibility https://github.com/ampache/ampache/issues/4008
                $filesearch = array_merge(
                    glob(__DIR__ . '/../../../public/' . $path . 'icon_' . $name . '.svg') ?: [],
                    glob(__DIR__ . '/../../../public/' . $path . 'icon_' . $name . '.png') ?: []
                );
            }
        }

        if (!empty($filesearch) && is_file($filesearch[0])) {
            $filename = pathinfo($filesearch[0], PATHINFO_BASENAME);
        } else {
            // fall back to error icon if icon is missing
            debug_event(self::class, 'Runtime Error: icon ' . $name . ' not found.', 1);

            return __DIR__ . '/../../../resources/images/icon_error.svg';
        }
        if (pathinfo($filename, PATHINFO_EXTENSION) === 'svg') {
            $url = (string)$filesearch[0];
        } else {
            $url = AmpConfig::get_web_path() . '/' . $path . $filename;
        }
        // cache the url so you don't need to keep searching
        self::$_icon_cache[$name] = $url;

        return $url;
    }

    /**
     * get_image
     *
     * Returns an <img> or <svg> tag for the specified image
     */
    public static function get_image(string $name, ?string $title = null, ?string $id_attrib = null, ?string $class_attrib = null): string
    {
        $title     = $title ?? ucfirst($name);
        $image_url = self::_find_image($name);
        $imagetype = pathinfo($image_url, PATHINFO_EXTENSION);
        $tag       = '';
        if ($imagetype == 'svg') {
            // load svg file
            $svgimage = simplexml_load_file($image_url);
            if ($svgimage !== false) {
                if (empty($svgimage->title)) {
                    $svgimage->addChild('title', $title);
                } else {
                    $svgimage->title = $title;
                }
                if (empty($svgimage->desc)) {
                    $svgimage->addChild('desc', $title);
                } else {
                    $svgimage->desc = $title;
                }

                if (!empty($id_attrib)) {
                    $svgimage->addAttribute('id', $id_attrib);
                }

                $class_attrib = ($class_attrib) ?? 'image image-' . $name;
                $svgimage->addAttribute('class', $class_attrib);

                $tag = explode("\n", (string)$svgimage->asXML(), 2)[1];
            }
        } else {
            // fall back to png
            $tag = '<img src="' . $image_url . '" ';
            $tag .= 'alt="' . $title . '" ';
            $tag .= 'title="' . $title . '" ';
            if ($id_attrib !== null) {
                $tag .= 'id="' . $id_attrib . '" ';
            }
            if ($class_attrib !== null) {
                $tag .= 'class="' . $class_attrib . '" ';
            }
            $tag .= '/>';
        }

        return $tag;
    }

    /**
     * _find_image
     *
     * Does the finding image thing. match svg first over png
     */
    private static function _find_image(string $name): string
    {
        if (isset(self::$_image_cache[$name])) {
            return self::$_image_cache[$name];
        }

        // always check themes first
        $path = 'themes/' . AmpConfig::get('theme_name', 'reborn') . '/images/';
        // Can't use GLOB_BRACE for Alpine compatibility https://github.com/ampache/ampache/issues/4008
        $filesearch = array_merge(
            glob(__DIR__ . '/../../../public/' . $path . $name . '.svg') ?: [],
            glob(__DIR__ . '/../../../public/' . $path . $name . '.png') ?: []
        );

        if (empty($filesearch)) {
            $path = 'images/';
            // check private resources folder for svg files
            $filesearch = glob(__DIR__ . '/../../../resources/' . $path . $name . '.svg') ?: [];
            if (empty($filesearch)) {
                // finally fall back to the public images folder
                // Can't use GLOB_BRACE for Alpine compatibility https://github.com/ampache/ampache/issues/4008
                $filesearch = array_merge(
                    glob(__DIR__ . '/../../../public/' . $path . $name . '.svg') ?: [],
                    glob(__DIR__ . '/../../../public/' . $path . $name . '.png') ?: []
                );
            }
        }
        if (empty($filesearch)) {
            // if the theme is missing an image. fall back to default images folder
            $filename = $name . '.png';
            $path     = 'images/';
        } else {
            $filename = pathinfo($filesearch[0], PATHINFO_BASENAME);
        }
        if (
            $filesearch &&
            pathinfo($filename, PATHINFO_EXTENSION) === 'svg'
        ) {
            $url = $filesearch[0];
        } else {
            $url = AmpConfig::get_web_path() . '/' . $path . $filename;
        }
        // cache the url so you don't need to keep searching
        self::$_image_cache[$name] = $url;

        return $url;
    }

    /**
     * Show the requested template file
     */
    public function show(string $template, array $context = []): void
    {
        extract($context);

        require_once self::find_template($template);
    }

    public function showFooter(): void
    {
        static::show_footer();
    }

    public function showHeader(): void
    {
        require_once self::find_template('header.inc.php');
    }

    /**
     * show_footer
     *
     * Shows the footer template and possibly profiling info.
     *
     * @deprecated use non-static version
     */
    public static function show_footer(): void
    {
        if (!defined("TABLE_RENDERED")) {
            show_table_render();
        }
        $user = Core::get_global('user');
        if ($user instanceof User) {
            $plugins = Plugin::get_plugins(PluginTypeEnum::FOOTER_WIDGET);
            foreach ($plugins as $plugin_name) {
                $plugin = new Plugin($plugin_name);
                if ($plugin->_plugin instanceof PluginDisplayOnFooterInterface && $plugin->load($user)) {
                    $plugin->_plugin->display_on_footer();
                }
            }
        }

        require_once self::find_template('footer.inc.php');
        if (Core::get_request('profiling') !== '') {
            Dba::show_profile();
        }
    }

    public function showBoxTop(string $title = '', string $class = ''): void
    {
        static::show_box_top($title, $class);
    }

    public function showBoxBottom(): void
    {
        static::show_box_bottom();
    }

    /**
     * show_box_top
     *
     * This shows the top of the box.
     *
     * @deprecated Use non-static version
     */
    public static function show_box_top(string $title = '', string $class = ''): void
    {
        require self::find_template('show_box_top.inc.php');
    }

    /**
     * show_box_bottom
     *
     * This shows the bottom of the box
     *
     * @deprecated Use non-static version
     */
    public static function show_box_bottom(): void
    {
        require self::find_template('show_box_bottom.inc.php');
    }

    /**
     * This shows the query stats
     */
    public function showQueryStats(): void
    {
        require self::find_template('show_query_stats.inc.php');
    }

    public static function show_custom_style(): void
    {
        if (AmpConfig::get('custom_login_background', false)) {
            echo "<style> body { background-position: center; background-size: cover; background-image: url('" . AmpConfig::get('custom_login_background') . "') !important; }</style>";
        }

        if (AmpConfig::get('custom_login_logo', false)) {
            echo "<style>#loginPage #headerlogo, #registerPage #logo { background-image: url('" . AmpConfig::get('custom_login_logo') . "') !important; }</style>";
        }

        $favicon = AmpConfig::get('custom_favicon', false) ?: AmpConfig::get_web_path() . "/favicon.ico";
        echo "<link rel=\"icon\" href=\"" . $favicon . "\">\n";
    }

    /**
     * update_text
     *
     * Convenience function that, if the output is going to a browser,
     * blarfs JS to do a fancy update. Otherwise it just outputs the text.
     */
    public static function update_text(string $field, int|string $value): void
    {
        if (defined('API')) {
            return;
        }
        if (defined('CLI')) {
            echo $value . "\n";

            return;
        }

        static $update_id = 1;

        if (defined('SSE_OUTPUT')) {
            echo "id: " . $update_id . "\n";
            echo "data: displayNotification('" . json_encode($value) . "', 5000)\n\n";
        } elseif (!empty($field)) {
            echo "<script>updateText('" . $field . "', '" . json_encode($value) . "');</script>\n";
        } else {
            echo "<br />" . $value . "<br /><br />\n";
        }

        ob_flush();
        flush();
        $update_id++;
    }

    /**
     * get_logo_url
     *
     * Get the custom logo or logo relating to your theme color
     */
    public static function get_logo_url(?string $color = null): string
    {
        if (AmpConfig::get('custom_logo')) {
            return AmpConfig::get('custom_logo');
        }
        if ($color !== null) {
            return AmpConfig::get_web_path() . AmpConfig::get('theme_path', '/themes/reborn') . '/images/ampache-' . $color . '.png';
        }

        return AmpConfig::get_web_path() . AmpConfig::get('theme_path', '/themes/reborn') . '/images/ampache-' . AmpConfig::get('theme_color', 'dark') . '.png';
    }

    /**
     * is_grid_view
     */
    public static function is_grid_view(string $type): bool
    {
        $name = 'browse_' . $type . '_grid_view';
        if (isset($_COOKIE[$name])) {
            return ($_COOKIE[$name] == 'true');
        }

        return false;
    }

    /**
     * shows a confirmation of an action
     */
    public function showConfirmation(
        string $title,
        string $text,
        string $next_url,
        ?int $cancel = 0,
        ?string $form_name = 'confirmation',
        ?bool $visible = true
    ): void {
        $webPath = $this->configContainer->getWebPath();

        if (substr_count($next_url, $webPath)) {
            $path = $next_url;
        } else {
            $path = sprintf('%s/%s', $webPath, $next_url);
        }
        $this->show(
            'show_confirmation.inc.php',
            [
                'title' => $title,
                'text' => $text,
                'path' => $path,
                'form_name' => $form_name,
                'cancel' => $cancel
            ]
        );
    }

    /**
     * shows a simple continue button after an action
     */
    public function showContinue(
        string $title,
        string $text,
        string $next_url
    ): void {
        $webPath = $this->configContainer->getWebPath();

        if (substr_count($next_url, $webPath)) {
            $path = $next_url;
        } else {
            $path = sprintf('%s/%s', $webPath, $next_url);
        }

        $this->show(
            'show_continue.inc.php',
            [
                'title' => $title,
                'text' => $text,
                'path' => $path
            ]
        );
    }

    /**
     * This function is used to escape user data that is getting redisplayed
     * onto the page, it htmlentities the mojo
     * This is the inverse of the scrub_in function
     */
    public function scrubOut(?string $string): string
    {
        if ($string === null) {
            return '';
        }

        return htmlentities((string) $string, ENT_QUOTES, AmpConfig::get('site_charset', 'UTF-8'));
    }

    /**
     * takes the key and then creates the correct type of input for updating it
     */
    public function createPreferenceInput(
        string $name,
        $value
    ): void {
        if (!Preference::has_access($name)) {
            if ($value == '1') {
                echo T_("Enabled");
            } elseif ($value == '0') {
                echo T_("Disabled");
            } elseif (str_ends_with($name, '_pass') || str_ends_with($name, '_api_key')) {
                echo "******";
            } else {
                echo $value;
            }

            return;
        } // if we don't have access to it

        switch ($name) {
            case 'access_control':
            case 'access_list':
            case 'ajax_load':
            case 'album_group':
            case 'album_release_type':
            case 'allow_democratic_playback':
            case 'allow_localplay_playback':
            case 'allow_personal_info_agent':
            case 'allow_personal_info_now':
            case 'allow_personal_info_recent':
            case 'allow_personal_info_time':
            case 'allow_stream_playback':
            case 'allow_upload':
            case 'allow_video':
            case 'api_always_download':
            case 'api_enable_3':
            case 'api_enable_4':
            case 'api_enable_5':
            case 'api_enable_6':
            case 'api_hide_dupe_searches':
            case 'autoupdate_lastversion_new':
            case 'autoupdate':
            case 'bookmark_latest':
            case 'broadcast_by_default':
            case 'browse_album_disk_grid_view':
            case 'browse_album_grid_view':
            case 'browse_artist_grid_view':
            case 'browse_filter':
            case 'browse_live_stream_grid_view':
            case 'browse_playlist_grid_view':
            case 'browse_podcast_episode_grid_view':
            case 'browse_podcast_grid_view':
            case 'browse_song_grid_view':
            case 'browse_video_grid_view':
            case 'browser_notify':
            case 'catalog_check_duplicate':
            case 'catalogfav_gridview':
            case 'catalogfav_compact':
            case 'condPL':
            case 'cron_cache':
            case 'custom_logo_user':
            case 'daap_backend':
            case 'demo_clear_sessions':
            case 'demo_mode':
            case 'demo_use_search':
            case 'direct_link':
            case 'display_menu':
            case 'download':
            case 'extended_playlist_links':
            case 'external_links_bandcamp':
            case 'external_links_discogs':
            case 'external_links_duckduckgo':
            case 'external_links_google':
            case 'external_links_lastfm':
            case 'external_links_musicbrainz':
            case 'external_links_wikipedia':
            case 'force_http_play':
            case 'geolocation':
            case 'hide_genres':
            case 'hide_single_artist':
            case 'home_moment_albums':
            case 'home_moment_videos':
            case 'home_now_playing':
            case 'home_recently_played_all':
            case 'home_recently_played':
            case 'homedash_newest':
            case 'homedash_popular':
            case 'homedash_random':
            case 'homedash_recent':
            case 'homedash_trending':
            case 'index_dashboard_form':
            case 'libitem_contextmenu':
            case 'lock_songs':
            case 'mb_overwrite_name':
            case 'no_symlinks':
            case 'notify_email':
            case 'now_playing_per_user':
            case 'perpetual_api_session':
            case 'personalfav_display':
            case 'quarantine':
            case 'ratingmatch_flags':
            case 'ratingmatch_write_tags':
            case 'rio_global_stats':
            case 'rio_track_stats':
            case 'share_social':
            case 'share':
            case 'show_album_artist':
            case 'show_artist':
            case 'show_donate':
            case 'show_header_login':
            case 'show_license':
            case 'show_lyrics':
            case 'show_original_year':
            case 'show_played_times':
            case 'show_playlist_media_parent':
            case 'show_playlist_username':
            case 'show_skipped_times':
            case 'show_subtitle':
            case 'show_wrapped':
            case 'sidebar_hide_browse':
            case 'sidebar_hide_dashboard':
            case 'sidebar_hide_information':
            case 'sidebar_hide_playlist':
            case 'sidebar_hide_search':
            case 'sidebar_hide_switcher':
            case 'sidebar_hide_video':
            case 'sidebar_light':
            case 'song_page_title':
            case 'stream_beautiful_url':
            case 'subsonic_always_download':
            case 'subsonic_backend':
            case 'subsonic_legacy':
            case 'tadb_overwrite_name':
            case 'topmenu':
            case 'ui_fixed':
            case 'unique_playlist':
            case 'upload_allow_edit':
            case 'upload_allow_remove':
            case 'upload_catalog_pattern':
            case 'upload_subdir':
            case 'upload_user_artist':
            case 'upload':
            case 'upnp_backend':
            case 'use_auth':
            case 'use_original_year':
            case 'use_play2':
            case 'webdav_backend':
            case 'webplayer_aurora':
            case 'webplayer_confirmclose':
            case 'webplayer_flash':
            case 'webplayer_html5':
            case 'webplayer_pausetabs':
            case 'xml_rpc':
                $is_true  = '';
                $is_false = '';
                if ($value == '1') {
                    $is_true = "selected=\"selected\"";
                } else {
                    $is_false = "selected=\"selected\"";
                }
                echo "<select name=\"$name\">\n";
                echo "\t<option value=\"1\" $is_true>" . T_('On') . "</option>\n";
                echo "\t<option value=\"0\" $is_false>" . T_('Off') . "</option>\n";
                echo "</select>\n";
                break;
            case 'upload_catalog':
                show_catalog_select('upload_catalog', $value, '', true, 'music', 'local');
                break;
            case 'play_type':
                $is_stream     = '';
                $is_localplay  = '';
                $is_democratic = '';
                $is_web_player = '';
                switch ($value) {
                    case 'localplay':
                        $is_localplay = 'selected="selected"';
                        break;
                    case 'democratic':
                        $is_democratic = 'selected="selected"';
                        break;
                    case 'web_player':
                        $is_web_player = 'selected="selected"';
                        break;
                    default:
                        $is_stream = 'selected="selected"';
                }
                echo "<select name=\"$name\">\n";
                echo "\t<option value=\"\">" . T_('None') . "</option>\n";
                if (AmpConfig::get('allow_stream_playback')) {
                    echo "\t<option value=\"stream\" $is_stream>" . T_('Stream') . "</option>\n";
                }
                if (AmpConfig::get('allow_democratic_playback')) {
                    echo "\t<option value=\"democratic\" $is_democratic>" . T_('Democratic') . "</option>\n";
                }
                if (AmpConfig::get('allow_localplay_playback')) {
                    echo "\t<option value=\"localplay\" $is_localplay>" . T_('Localplay') . "</option>\n";
                }
                echo "\t<option value=\"web_player\" $is_web_player>" . T_('Web Player') . "</option>\n";
                echo "</select>\n";
                break;
            case 'playlist_type':
                $is_m3u        = '';
                $is_simple_m3u = '';
                $is_pls        = '';
                $is_asx        = '';
                $is_ram        = '';
                $is_xspf       = '';
                switch ($value) {
                    case 'simple_m3u':
                        $is_simple_m3u = 'selected="selected"';
                        break;
                    case 'pls':
                        $is_pls = 'selected="selected"';
                        break;
                    case 'asx':
                        $is_asx = 'selected="selected"';
                        break;
                    case 'ram':
                        $is_ram = 'selected="selected"';
                        break;
                    case 'xspf':
                        $is_xspf = 'selected="selected"';
                        break;
                    case 'm3u':
                    default:
                        $is_m3u = 'selected="selected"';
                }
                echo "<select name=\"$name\">\n";
                echo "\t<option value=\"m3u\" $is_m3u>" . T_('M3U') . "</option>\n";
                echo "\t<option value=\"simple_m3u\" $is_simple_m3u>" . T_('Simple M3U') . "</option>\n";
                echo "\t<option value=\"pls\" $is_pls>" . T_('PLS') . "</option>\n";
                echo "\t<option value=\"asx\" $is_asx>" . T_('Asx') . "</option>\n";
                echo "\t<option value=\"ram\" $is_ram>" . T_('RAM') . "</option>\n";
                echo "\t<option value=\"xspf\" $is_xspf>" . T_('XSPF') . "</option>\n";
                echo "</select>\n";
                break;
            case 'lang':
                $languages = get_languages();
                echo '<select name="' . $name . '">' . "\n";
                foreach ($languages as $lang => $tongue) {
                    $selected = ($lang == $value) ? 'selected="selected"' : '';
                    echo "\t<option value=\"$lang\" " . $selected . ">$tongue</option>\n";
                } // end foreach
                echo "</select>\n";
                break;
            case 'localplay_controller':
                $controllers = array_keys(LocalPlayTypeEnum::TYPE_MAPPING);
                echo "<select name=\"$name\">\n";
                echo "\t<option value=\"\">" . T_('None') . "</option>\n";
                foreach ($controllers as $controller) {
                    if (!LocalPlay::is_enabled($controller)) {
                        continue;
                    }
                    $is_selected = '';
                    if ($value == $controller) {
                        $is_selected = 'selected="selected"';
                    }
                    echo "\t<option value=\"" . $controller . "\" $is_selected>" . ucfirst($controller) . "</option>\n";
                } // end foreach
                echo "</select>\n";
                break;
            case 'api_force_version':
                $is_0 = '';
                $is_3 = '';
                $is_4 = '';
                $is_5 = '';
                $is_6 = '';
                if (!in_array($value, Api::API_VERSIONS)) {
                    $is_0 = 'selected="selected"';
                } elseif ($value == 3) {
                    $is_3 = 'selected="selected"';
                } elseif ($value == 4) {
                    $is_4 = 'selected="selected"';
                } elseif ($value == 5) {
                    $is_5 = 'selected="selected"';
                } elseif ($value == 6) {
                    $is_6 = 'selected="selected"';
                }
                echo "<select name=\"$name\">\n";
                echo "<option value=\"0\" $is_0>" . T_('Off') . "</option>\n";
                echo "<option value=\"3\" $is_3>" . T_('Allow API3 Only') . "</option>\n";
                echo "<option value=\"4\" $is_4>" . T_('Allow API4 Only') . "</option>\n";
                echo "<option value=\"5\" $is_5>" . T_('Allow API5 Only') . "</option>\n";
                echo "<option value=\"6\" $is_6>" . T_('Allow API6 Only') . "</option>\n";
                echo "</select>\n";
                break;
            case 'jp_volume':
                $is_0  = '';
                $is_1  = '';
                $is_2  = '';
                $is_3  = '';
                $is_4  = '';
                $is_5  = '';
                $is_6  = '';
                $is_7  = '';
                $is_8  = '';
                $is_9  = '';
                $is_10 = '';
                if ($value == 0.0) {
                    $is_0 = 'selected="selected"';
                } elseif ($value == 0.1) {
                    $is_1 = 'selected="selected"';
                } elseif ($value == 0.2) {
                    $is_2 = 'selected="selected"';
                } elseif ($value == 0.3) {
                    $is_3 = 'selected="selected"';
                } elseif ($value == 0.4) {
                    $is_4 = 'selected="selected"';
                } elseif ($value == 0.5) {
                    $is_5 = 'selected="selected"';
                } elseif ($value == 0.6) {
                    $is_6 = 'selected="selected"';
                } elseif ($value == 0.7) {
                    $is_7 = 'selected="selected"';
                } elseif ($value == 0.8) {
                    $is_8 = 'selected="selected"';
                } elseif ($value == 0.9) {
                    $is_9 = 'selected="selected"';
                } elseif ($value == 1.0) {
                    $is_10 = 'selected="selected"';
                }
                echo "<select name=\"$name\">\n";
                echo "<option value=0.00 $is_0>0%</option>\n";
                echo "<option value=0.10 $is_1>10%</option>\n";
                echo "<option value=0.20 $is_2>20%</option>\n";
                echo "<option value=0.30 $is_3>30%</option>\n";
                echo "<option value=0.40 $is_4>40%</option>\n";
                echo "<option value=0.50 $is_5>50%</option>\n";
                echo "<option value=0.60 $is_6>60%</option>\n";
                echo "<option value=0.70 $is_7>70%</option>\n";
                echo "<option value=0.80 $is_8>80%</option>\n";
                echo "<option value=0.90 $is_9>90%</option>\n";
                echo "<option value=1.00 $is_10>100%</option>\n";
                echo "</select>\n";
                break;
            case 'ratingmatch_stars':
                $is_0 = '';
                $is_1 = '';
                $is_2 = '';
                $is_3 = '';
                $is_4 = '';
                $is_5 = '';
                if ($value == 0) {
                    $is_0 = 'selected="selected"';
                } elseif ($value == 1) {
                    $is_1 = 'selected="selected"';
                } elseif ($value == 2) {
                    $is_2 = 'selected="selected"';
                } elseif ($value == 3) {
                    $is_3 = 'selected="selected"';
                } elseif ($value == 4) {
                    $is_4 = 'selected="selected"';
                } elseif ($value == 5) {
                    $is_5 = 'selected="selected"';
                }
                echo "<select name=\"$name\">\n";
                echo "<option value=\"0\" $is_0>" . T_('Disabled') . "</option>\n";
                echo "<option value=\"1\" $is_1>" . T_('1 Star') . "</option>\n";
                echo "<option value=\"2\" $is_2>" . T_('2 Stars') . "</option>\n";
                echo "<option value=\"3\" $is_3>" . T_('3 Stars') . "</option>\n";
                echo "<option value=\"4\" $is_4>" . T_('4 Stars') . "</option>\n";
                echo "<option value=\"5\" $is_5>" . T_('5 Stars') . "</option>\n";
                echo "</select>\n";
                break;
            case 'localplay_level':
            case 'upload_access_level':
                $is_user            = '';
                $is_content_manager = '';
                $is_catalog_manager = '';
                $is_admin           = '';
                if ($value == '25') {
                    $is_user = 'selected="selected"';
                } elseif ($value == '50') {
                    $is_content_manager = 'selected="selected"';
                } elseif ($value == '75') {
                    $is_catalog_manager = 'selected="selected"';
                } elseif ($value == '100') {
                    $is_admin = 'selected="selected"';
                }
                echo "<select name=\"$name\">\n";
                echo "<option value=\"0\">" . T_('Disabled') . "</option>\n";
                echo "<option value=\"25\" $is_user>" . T_('User') . "</option>\n";
                echo "<option value=\"50\" $is_content_manager>" . T_('Content Manager') . "</option>\n";
                echo "<option value=\"75\" $is_catalog_manager>" . T_('Catalog Manager') . "</option>\n";
                echo "<option value=\"100\" $is_admin>" . T_('Admin') . "</option>\n";
                echo "</select>\n";
                break;
            case 'webplayer_removeplayed':
                $is_one   = '';
                $is_two   = '';
                $is_three = '';
                $is_five  = '';
                $is_ten   = '';
                $is_all   = '';
                if ($value == '1') {
                    $is_one = 'selected="selected"';
                } elseif ($value == '2') {
                    $is_two = 'selected="selected"';
                } elseif ($value == '3') {
                    $is_three = 'selected="selected"';
                } elseif ($value == '5') {
                    $is_five = 'selected="selected"';
                } elseif ($value == '10') {
                    $is_ten = 'selected="selected"';
                } elseif ($value == '999') {
                    $is_all = 'selected="selected"';
                }
                echo "<select name=\"$name\">\n";
                echo "<option value=\"0\">" . T_('Disabled') . "</option>\n";
                echo "<option value=\"1\" $is_one>" . T_('Keep last played track') . "</option>\n";
                /* HINT: Keep (2|3|4|5|10) previous tracks */
                echo "<option value=\"2\" $is_two>" . sprintf(T_('Keep %s previous tracks'), '2') . "</option>\n";
                echo "<option value=\"3\" $is_three>" . sprintf(T_('Keep %s previous tracks'), '3') . "</option>\n";
                echo "<option value=\"5\" $is_five>" . sprintf(T_('Keep %s previous tracks'), '5') . "</option>\n";
                echo "<option value=\"10\" $is_ten>" . sprintf(T_('Keep %s previous tracks'), '10') . "</option>\n";
                echo "<option value=\"999\" $is_all>" . T_('Remove all previous tracks') . "</option>\n";
                echo "</select>\n";
                break;
            case 'theme_name':
                $themes = get_themes();
                echo "<select name=\"$name\">\n";
                foreach ($themes as $theme) {
                    $is_selected = "";
                    if ($value == $theme['path']) {
                        $is_selected = "selected=\"selected\"";
                    }
                    echo "\t<option value=\"" . $theme['path'] . "\" $is_selected>" . $theme['name'] . "</option>\n";
                } // foreach themes
                echo "</select>\n";
                break;
            case 'theme_color':
                // This include a two-step configuration (first change theme and save, then change theme color and save)
                $theme_cfg = get_theme(AmpConfig::get('theme_name', 'reborn'));
                if ($theme_cfg !== null) {
                    echo "<select name=\"$name\">\n";
                    foreach ($theme_cfg['colors'] as $color) {
                        $is_selected = "";
                        if ($value == strtolower((string) $color)) {
                            $is_selected = "selected=\"selected\"";
                        }
                        echo "\t<option value=\"" . strtolower((string) $color) . "\" $is_selected>" . $color . "</option>\n";
                    } // foreach themes
                    echo "</select>\n";
                }
                break;
            case 'playlist_method':
                $is_send       = '';
                $is_send_clear = '';
                $is_clear      = '';
                $is_default    = '';
                if ($value == 'send') {
                    $is_send = 'selected="selected"';
                } elseif ($value == 'send_clear') {
                    $is_send_clear = 'selected="selected"';
                } elseif ($value == 'clear') {
                    $is_clear = 'selected="selected"';
                } elseif ($value == 'default') {
                    $is_default = 'selected="selected"';
                }
                echo "<select name=\"$name\">\n";
                echo "\t<option value=\"send\"$is_send>" . T_('Send on Add') . "</option>\n";
                echo "\t<option value=\"send_clear\"$is_send_clear>" . T_('Send and Clear on Add') . "</option>\n";
                echo "\t<option value=\"clear\"$is_clear>" . T_('Clear on Send') . "</option>\n";
                echo "\t<option value=\"default\"$is_default>" . T_('Default') . "</option>\n";
                echo "</select>\n";
                break;
            case 'transcode':
                $is_never   = '';
                $is_default = '';
                $is_always  = '';
                if ($value == 'never') {
                    $is_never = 'selected="selected"';
                } elseif ($value == 'default') {
                    $is_default = 'selected="selected"';
                } elseif ($value == 'always') {
                    $is_always = 'selected="selected"';
                }
                echo "<select name=\"$name\">\n";
                echo "\t<option value=\"never\"$is_never>" . T_('Never') . "</option>\n";
                echo "\t<option value=\"default\"$is_default>" . T_('Default') . "</option>\n";
                echo "\t<option value=\"always\"$is_always>" . T_('Always') . "</option>\n";
                echo "</select>\n";
                break;
            case 'album_sort':
                $is_sort_year_asc  = '';
                $is_sort_year_desc = '';
                $is_sort_name_asc  = '';
                $is_sort_name_desc = '';
                $is_sort_default   = '';
                if ($value == 'year_asc') {
                    $is_sort_year_asc = 'selected="selected"';
                } elseif ($value == 'year_desc') {
                    $is_sort_year_desc = 'selected="selected"';
                } elseif ($value == 'name_asc') {
                    $is_sort_name_asc = 'selected="selected"';
                } elseif ($value == 'name_desc') {
                    $is_sort_name_desc = 'selected="selected"';
                } else {
                    $is_sort_default = 'selected="selected"';
                }

                echo "<select name=\"$name\">\n";
                echo "\t<option value=\"default\" $is_sort_default>" . T_('Default') . "</option>\n";
                echo "\t<option value=\"year_asc\" $is_sort_year_asc>" . T_('Year ascending') . "</option>\n";
                echo "\t<option value=\"year_desc\" $is_sort_year_desc>" . T_('Year descending') . "</option>\n";
                echo "\t<option value=\"name_asc\" $is_sort_name_asc>" . T_('Name ascending') . "</option>\n";
                echo "\t<option value=\"name_desc\" $is_sort_name_desc>" . T_('Name descending') . "</option>\n";
                echo "</select>\n";
                break;
            case 'disabled_custom_metadata_fields':
                $ids     = explode(',', $value);
                $options = [];
                foreach ($this->getMetadataFieldRepository()->getPropertyList() as $propertyId => $propertyName) {
                    $selected  = (in_array($propertyId, $ids)) ? ' selected="selected"' : '';
                    $options[] = '<option value="' . $propertyId . '"' . $selected . '>' . scrub_out($propertyName) . '</option>';
                }
                echo '<select multiple size="5" name="' . $name . '[]">' . implode("\n", $options) . '</select>';
                break;
            case 'personalfav_playlist':
            case 'personalfav_smartlist':
                $ids       = explode(',', $value);
                $options   = [];
                $playlists = ($name == 'personalfav_smartlist')
                    ? Search::get_search_array()
                    : Playlist::get_playlist_array();
                if (!empty($playlists)) {
                    foreach ($playlists as $list_id => $list_name) {
                        $selected  = (in_array($list_id, $ids)) ? ' selected="selected"' : '';
                        $options[] = '<option value="' . $list_id . '"' . $selected . '>' . scrub_out($list_name) . '</option>';
                    }
                    echo '<select multiple size="5" name="' . $name . '[]">' . implode("\n", $options) . '</select>';
                }
                break;
            case 'lastfm_grant_link':
            case 'librefm_grant_link':
                // construct links for granting access Ampache application to Last.fm and Libre.fm
                $plugin_name = ucfirst(str_replace('_grant_link', '', $name));
                $plugin      = new Plugin($plugin_name);
                $url         = $plugin->_plugin->url;
                $api_key     = rawurlencode(AmpConfig::get('lastfm_api_key'));
                $callback    = rawurlencode(AmpConfig::get_web_path() . '/preferences.php?tab=plugins&action=grant&plugin=' . $plugin_name);
                /* HINT: Plugin Name */
                echo "<a href=\"$url/api/auth/?api_key=$api_key&cb=$callback\" target=\"_blank\">" . self::get_material_symbol('extension', sprintf(T_("Click to grant %s access to Ampache"), $plugin_name)) . '</a>';
                break;
            case 'bandwidth':
            case 'features':
            case 'share_expire':
            case 'slideshow_time':
            case 'concerts_limit_future':
            case 'concerts_limit_past':
            case 'direct_play_limit':
            case 'browser_notify_timeout':
            case 'podcast_keep':
            case 'podcast_new_download':
            case 'of_the_moment':
            case 'amazon_max_results_pages':
            case 'catalogfav_max_items':
            case 'catalogfav_order':
            case 'ftl_max_items':
            case 'ftl_order':
            case 'homedash_max_items':
            case 'homedash_order':
            case 'personalfav_order':
            case 'rssview_max_items':
            case 'rssview_order':
            case 'shouthome_max_items':
            case 'shouthome_order':
            case 'sidebar_order_browse':
            case 'sidebar_order_dashboard':
            case 'sidebar_order_information':
            case 'sidebar_order_playlist':
            case 'sidebar_order_search':
            case 'sidebar_order_video':
            case 'stream_control_bandwidth_max':
            case 'stream_control_bandwidth_days':
            case 'stream_control_hits_max':
            case 'stream_control_hits_days':
            case 'stream_control_time_max':
            case 'stream_control_time_days':
                echo '<input type="number" name="' . $name . '" value="' . (int)$value . '" />';
                break;
            default:
                if (str_ends_with($name, '_pass')) {
                    echo '<input type="password" name="' . $name . '" value="******" />';
                } else {
                    echo '<input type="text" name="' . $name . '" value="' . strip_tags((string)$value) . '" />';
                }
                break;
        }
    }

    /**
     * This shows the preference box for the preferences pages.
     *
     * @param array<string, mixed> $preferences
     */
    public function showPreferenceBox(array $preferences): void
    {
        $this->show(
            'show_preference_box.inc.php',
            [
                'preferences' => $preferences,
                'ui' => $this
            ]
        );
    }

    /**
     * This function takes a boolean value and then prints out a friendly text
     * message.
     */
    public static function printBool(?bool $value = false): string
    {
        if ($value) {
            $string = '<span class="item_on">' . T_('On') . '</span>';
        } else {
            $string = '<span class="item_off">' . T_('Off') . '</span>';
        }

        return $string;
    }

    /**
     * @todo inject dependency
     */
    private function getMetadataFieldRepository(): MetadataFieldRepositoryInterface
    {
        global $dic;

        return $dic->get(MetadataFieldRepositoryInterface::class);
    }
}
