<?php

/**
 * Grid.js localization map used across list pages.
 */
function gridLang(): array
{
    return [
        'search' => [
            'placeholder' => t('Search...'),
        ],
        'sort' => [
            'sortAsc' => t('Sort column ascending'),
            'sortDesc' => t('Sort column descending'),
        ],
        'pagination' => [
            'previous' => t('Previous'),
            'next' => t('Next'),
            'navigate' => t('Page %d of %d'),
            'page' => t('Page %d'),
            'rowsPerPage' => t('Rows per page'),
            'rowsPerPageShort' => t('Per page'),
            'showing' => t('Showing'),
            'of' => t('of'),
            'to' => t('to'),
            'results' => t('results'),
        ],
        'actions' => [
            'column' => t('Actions'),
            'open' => t('Open'),
            'edit' => t('Edit'),
            'rowEnterHint' => t('Press Enter to open row'),
        ],
        'errorRetry' => t('Retry'),
        'loading' => t('Loading...'),
        'noRecordsFound' => t('No records found'),
        'error' => t('An error happened while fetching the data'),
    ];
}

/**
 * Render a multi-select filter field with hidden input sync.
 *
 * @param string $id         Base ID for the hidden input (select gets "-ui" suffix)
 * @param string $label      Field label (will be translated)
 * @param string $placeholder Placeholder text (will be translated)
 * @param array  $items      Array of items with 'id' and 'description' keys
 * @param array  $selected   Array of selected item IDs
 */
function multiSelectFilter(
    string $id,
    string $label,
    string $placeholder,
    array $items,
    array $selected
): void {
    // The hidden input holds a comma-separated value that server-side code already expects.
    ?>
    <label class="app-field">
        <span><?php e(t($label)); ?></span>
        <input type="hidden" id="<?php e($id); ?>" value="<?php e(implode(',', $selected)); ?>">
        <select
            id="<?php e($id); ?>-ui"
            multiple
            data-multi-select
            data-sync-target="#<?php e($id); ?>"
            data-select-all="true"
            data-list-all="false"
            data-search="true"
            data-placeholder="<?php e(t($placeholder)); ?>"
            data-search-placeholder="<?php e(t('Search...')); ?>"
            data-select-all-label="<?php e(t('Select all')); ?>"
            data-selected-label="<?php e(t('%d selected')); ?>"
        >
            <?php foreach ($items as $item): ?>
                <?php $itemId = (string) ($item['id'] ?? ''); ?>
                <?php if ($itemId !== ''): ?>
                    <option value="<?php e($itemId); ?>"<?php if (in_array($itemId, $selected, true)) { ?> selected<?php } ?>>
                        <?php e($item['description'] ?? ''); ?>
                    </option>
                <?php endif; ?>
            <?php endforeach; ?>
        </select>
    </label>
    <?php
}

/**
 * Render a standard select filter field.
 *
 * @param array<int, array{id:mixed,description:mixed,translate?:bool,attributes?:array<string,mixed>}> $items
 * @param array<string,mixed> $labelAttributes
 * @param array<string,mixed> $selectAttributes
 */
function selectFilter(
    string $id,
    string $label,
    array $items,
    string $selected = '',
    array $labelAttributes = [],
    array $selectAttributes = []
): void {
    $labelClass = trim((string) ($labelAttributes['class'] ?? ''));
    if ($labelClass === '') {
        $labelClass = 'app-field';
    } elseif (!str_contains($labelClass, 'app-field')) {
        $labelClass = 'app-field ' . $labelClass;
    }
    $labelAttributes['class'] = $labelClass;

    $selectAttributes['id'] = $id;

    ?>
    <label<?php uiRenderAttributes($labelAttributes); ?>>
        <span><?php e(t($label)); ?></span>
        <select<?php uiRenderAttributes($selectAttributes); ?>>
            <?php foreach ($items as $item): ?>
                <?php $itemId = (string) ($item['id'] ?? ''); ?>
                <?php $itemLabel = (string) ($item['description'] ?? ''); ?>
                <?php $translateLabel = !array_key_exists('translate', $item) || (bool) $item['translate']; ?>
                <?php $optionAttributes = is_array($item['attributes'] ?? null) ? $item['attributes'] : []; ?>
                <?php $optionAttributes['value'] = $itemId; ?>
                <?php if ($itemId === $selected): ?>
                    <?php $optionAttributes['selected'] = true; ?>
                <?php endif; ?>
                <option<?php uiRenderAttributes($optionAttributes); ?>>
                    <?php if ($translateLabel): ?>
                        <?php e(t($itemLabel)); ?>
                    <?php else: ?>
                        <?php e($itemLabel); ?>
                    <?php endif; ?>
                </option>
            <?php endforeach; ?>
        </select>
    </label>
    <?php
}

/**
 * Render a full toolbar from a filter schema.
 *
 * @param array<int,array<string,mixed>> $schema
 * @param array<string,mixed> $active
 * @param array<string,array<int,array<string,mixed>>> $optionSets
 */
function renderGridFilterToolbar(array $schema, array $active = [], array $optionSets = []): void
{
    foreach ($schema as $field) {
        if (!is_array($field)) {
            continue;
        }
        if (($field['render'] ?? true) === false) {
            continue;
        }

        $key = trim((string) ($field['key'] ?? ''));
        if ($key === '') {
            continue;
        }
        $type = strtolower(trim((string) ($field['type'] ?? 'text')));
        if (!in_array($type, ['text', 'date', 'number', 'hidden', 'select', 'multi_csv'], true)) {
            continue;
        }

        $inputId = trim((string) ($field['input_id'] ?? ''));
        if ($inputId === '') {
            $inputId = $key . '-filter';
        }

        $label = (string) ($field['label'] ?? $key);
        $placeholder = (string) ($field['placeholder'] ?? '');
        $defaultValue = $field['default'] ?? '';
        $activeValue = $active[$key] ?? $defaultValue;
        $labelAttributes = is_array($field['label_attributes'] ?? null) ? $field['label_attributes'] : [];
        $inputAttributes = is_array($field['input_attributes'] ?? null) ? $field['input_attributes'] : [];

        if ($type === 'hidden') {
            $inputAttributes['type'] = 'hidden';
            $inputAttributes['id'] = $inputId;
            $inputAttributes['value'] = is_scalar($activeValue) ? (string) $activeValue : '';
            echo '<input';
            uiRenderAttributes($inputAttributes);
            echo '>';
            continue;
        }

        if ($type === 'select') {
            $items = uiResolveFilterItems($field, $optionSets);
            $selected = is_scalar($activeValue) ? (string) $activeValue : (string) $defaultValue;
            selectFilter(
                $inputId,
                $label,
                $items,
                $selected,
                $labelAttributes,
                $inputAttributes
            );
            continue;
        }

        if ($type === 'multi_csv') {
            $items = uiResolveFilterItems($field, $optionSets);
            $selected = uiNormalizeFilterSelection($activeValue);
            multiSelectFilter(
                $inputId,
                $label,
                $placeholder !== '' ? $placeholder : 'Select options',
                $items,
                $selected
            );
            continue;
        }

        $labelClass = trim((string) ($labelAttributes['class'] ?? ''));
        if ($labelClass === '') {
            $labelClass = 'app-field';
        } elseif (!str_contains($labelClass, 'app-field')) {
            $labelClass = 'app-field ' . $labelClass;
        }
        $labelAttributes['class'] = $labelClass;

        $inputAttributes['id'] = $inputId;
        $inputAttributes['type'] = $type === 'number' ? 'number' : $type;
        if ($placeholder !== '' && !isset($inputAttributes['placeholder']) && $type === 'text') {
            $inputAttributes['placeholder'] = t($placeholder);
        }
        if (in_array($type, ['text', 'date', 'number'], true)) {
            $inputAttributes['value'] = is_scalar($activeValue) ? (string) $activeValue : '';
        }

        echo '<label';
        uiRenderAttributes($labelAttributes);
        echo '>';
        echo '<span>';
        e(t($label));
        echo '</span>';
        echo '<input';
        uiRenderAttributes($inputAttributes);
        echo '>';
        echo '</label>';
    }
}

/**
 * @param array<string,mixed> $field
 * @param array<string,array<int,array<string,mixed>>> $optionSets
 * @return array<int,array<string,mixed>>
 */
function uiResolveFilterItems(array $field, array $optionSets): array
{
    $allowed = $field['allowed'] ?? null;
    if (is_array($allowed)) {
        return array_values(array_filter(array_map(static function (mixed $item): array {
            if (is_array($item)) {
                return $item;
            }
            if (is_scalar($item)) {
                $value = (string) $item;
                return ['id' => $value, 'description' => $value, 'translate' => false];
            }
            return [];
        }, $allowed), static fn (array $item): bool => $item !== []));
    }

    $optionsKey = trim((string) ($field['options_key'] ?? ''));
    if ($optionsKey !== '' && isset($optionSets[$optionsKey])) {
        return array_values(array_filter($optionSets[$optionsKey], static fn (array $item): bool => $item !== []));
    }

    return [];
}

/**
 * @return string[]
 */
function uiNormalizeFilterSelection(mixed $value): array
{
    if (is_array($value)) {
        return array_values(array_filter(array_map(
            static fn (mixed $item): string => trim((string) $item),
            $value
        ), static fn (string $item): bool => $item !== ''));
    }
    if (!is_scalar($value)) {
        return [];
    }
    $text = trim((string) $value);
    if ($text === '') {
        return [];
    }
    return array_values(array_filter(array_map('trim', explode(',', $text)), static fn (string $item): bool => $item !== ''));
}

/**
 * Render a multi-select form field for data entry.
 *
 * @param string $id          Element ID
 * @param string $name        Form field name
 * @param string $label       Field label (will be translated)
 * @param string $placeholder Placeholder text (will be translated)
 * @param array  $items       Array of items with 'id' and 'description' keys
 * @param array  $selected    Array of selected item IDs
 * @param bool   $disabled    Whether the field is disabled/readonly
 * @param string|array $labelKeys Key(s) to use for option label (tries in order)
 * @param string|null $formId Form ID for selects outside form element
 */
function multiSelectForm(
    string $id,
    string $name,
    string $label,
    string $placeholder,
    array $items,
    array $selected,
    bool $disabled = false,
    string|array $labelKeys = 'description',
    ?string $formId = null
): void {
    // Allow a fallback chain for label keys, e.g. ['description', 'label', 'name'].
    $labelKeyList = is_array($labelKeys) ? $labelKeys : [$labelKeys];
    // Vendor MultiSelect appends [] to the field name internally for hidden inputs.
    // Strip a trailing [] here to avoid accidental [][] payloads.
    $normalizedName = preg_replace('/\[\]$/', '', $name) ?: $name;
    ?>
    <label>
        <span><?php e(t($label)); ?></span>
    </label>
    <select
        id="<?php e($id); ?>"
        name="<?php e($normalizedName); ?>"
        <?php if ($formId !== null): ?>form="<?php e($formId); ?>"<?php endif; ?>
        multiple
        data-multi-select="true"
        data-select-all="true"
        data-search="true"
        data-placeholder="<?php e(t($placeholder)); ?>"
        data-search-placeholder="<?php e(t('Search...')); ?>"
        data-select-all-label="<?php e(t('Select all')); ?>"
        <?php if ($disabled): ?>data-disabled="true" disabled<?php endif; ?>
    >
        <?php foreach ($items as $item): ?>
            <?php $itemId = (int) ($item['id'] ?? 0); ?>
            <?php if ($itemId > 0): ?>
                <?php
                $isSelected = in_array($itemId, $selected, true);
                $itemLabel = '';
                foreach ($labelKeyList as $key) {
                    if (isset($item[$key]) && (string) $item[$key] !== '') {
                        $itemLabel = (string) $item[$key];
                        break;
                    }
                }
                ?>
                <option value="<?php e((string) $itemId); ?>"<?php if ($isSelected) { ?> selected<?php } ?>>
                    <?php e($itemLabel); ?>
                </option>
            <?php endif; ?>
        <?php endforeach; ?>
    </select>
    <?php
}

/**
 * Render arbitrary HTML attributes from a key-value map.
 *
 * @param array<string,mixed> $attributes
 */
function uiRenderAttributes(array $attributes): void
{
    foreach ($attributes as $name => $value) {
        $attrName = trim((string) $name);
        if ($attrName === '') {
            continue;
        }
        if ($value === false || $value === null) {
            continue;
        }
        if ($value === true) {
            echo ' ' . $attrName;
            continue;
        }
        echo ' ' . $attrName . '="';
        e((string) $value);
        echo '"';
    }
}

/**
 * Resolve nav active state for one or many paths.
 *
 * @param string|array $path   A single path or a list of candidate paths.
 * @param bool         $prefix Prefix matching (path starts with candidate).
 * @return array{class:string,aria:string,isActive:bool}
 */
function navActive($path, bool $prefix = false): array
{
    $current = \MintyPHP\Http\Request::path();
    // Normalize to array to keep the matching loop simple.
    $paths = is_array($path) ? $path : [$path];
    $isActive = false;
    foreach ($paths as $p) {
        $p = (string) $p;
        $match = $prefix ? strpos($current, $p) === 0 : $current === $p;
        if (!$match && ($current === '' || $current === '/')) {
            $match = $p === '' || $p === '/';
        }
        if ($match) {
            $isActive = true;
            break;
        }
    }

    return [
        'class' => $isActive ? 'active' : '',
        'aria' => $isActive ? 'aria-current="page"' : '',
        'isActive' => $isActive,
    ];
}

/**
 * Resolve active state for public page links (static pages and page/* slugs).
 *
 * @param array $extraSlugs Additional public slugs that should be treated as active.
 * @return array{class:string,aria:string,isActive:bool}
 */
function navActivePublicPages(array $extraSlugs = []): array
{
    $current = \MintyPHP\Http\Request::path();
    if ($current === '') {
        return ['class' => '', 'aria' => '', 'isActive' => false];
    }

    // "page/<slug>" routes and known standalone slugs should highlight the same nav entry.
    $publicSlugs = array_merge(['imprint', 'privacy', 'terms'], $extraSlugs);
    $isActive = strpos($current, 'page/') === 0 || in_array($current, $publicSlugs, true);

    return [
        'class' => $isActive ? 'active' : '',
        'aria' => $isActive ? 'aria-current="page"' : '',
        'isActive' => $isActive,
    ];
}

/**
 * Load and cache the asset registry config from config/assets.php.
 */
function appAssetConfig(): array
{
    // Static local cache avoids repeated filesystem access during one request.
    static $config = null;
    if ($config !== null) {
        return $config;
    }

    $file = dirname(__DIR__, 3) . '/config/assets.php';
    if (!is_file($file)) {
        $config = [];
        return $config;
    }

    $loaded = include $file;
    $config = is_array($loaded) ? $loaded : [];
    return $config;
}

/**
 * Resolve all CSS paths for one or more asset groups.
 *
 * @param string[] $groupNames
 *
 * @return string[] List of unique web-relative asset paths.
 */
function appStylePathsForGroups(array $groupNames): array
{
    $config = appAssetConfig();
    $styles = $config['styles'] ?? [];
    if (!is_array($styles)) {
        return [];
    }

    $groups = $styles['groups'] ?? [];
    if (!is_array($groups)) {
        return [];
    }

    $resolved = [];
    // Used as a fast set to keep insertion order while removing duplicates.
    $seen = [];
    foreach ($groupNames as $groupName) {
        $groupKey = trim((string) $groupName);
        if ($groupKey === '' || !isset($groups[$groupKey]) || !is_array($groups[$groupKey])) {
            continue;
        }
        foreach ($groups[$groupKey] as $path) {
            $assetPath = ltrim(trim((string) $path), '/');
            if ($assetPath === '' || isset($seen[$assetPath])) {
                continue;
            }
            $seen[$assetPath] = true;
            $resolved[] = $assetPath;
        }
    }

    return $resolved;
}

/**
 * Resolve all CSS paths for a template based on grouped config entries.
 *
 * @return string[] List of unique web-relative asset paths.
 */
function appTemplateStylePaths(string $templateName): array
{
    $config = appAssetConfig();
    $styles = $config['styles'] ?? [];
    if (!is_array($styles)) {
        return [];
    }

    $templates = $styles['templates'] ?? [];
    if (!is_array($templates)) {
        return [];
    }

    $groupNames = $templates[$templateName] ?? [];
    if (!is_array($groupNames)) {
        return [];
    }

    return appStylePathsForGroups($groupNames);
}

/**
 * Read a raw Buffer value without echoing it to output.
 */
function appBufferValue(string $key): string
{
    ob_start();
    $hasValue = \MintyPHP\Buffer::get($key);
    $raw = (string) ob_get_clean();
    return $hasValue ? trim($raw) : '';
}

/**
 * Resolve page-specific CSS files declared via Buffer::set('style_groups', ...).
 *
 * Accepted formats:
 * - JSON array (e.g. ["address-book"])
 * - comma-separated list (e.g. "address-book,foo")
 *
 * @return string[] List of unique web-relative asset paths.
 */
function appPageStylePaths(): array
{
    $raw = appBufferValue('style_groups');
    if ($raw === '') {
        return [];
    }

    $groupNames = json_decode($raw, true);
    if (!is_array($groupNames)) {
        $groupNames = array_filter(array_map('trim', explode(',', $raw)));
    }
    return appStylePathsForGroups($groupNames);
}

/**
 * Render stylesheet link tags.
 *
 * @param string[] $stylePaths
 */
function renderStylePaths(array $stylePaths): void
{
    foreach ($stylePaths as $stylePath) {
        echo '<link href="';
        e(assetVersion($stylePath));
        echo "\" rel=\"stylesheet\">\n";
    }
}

/**
 * Render stylesheet link tags for the given template.
 */
function renderTemplateStyles(string $templateName): void
{
    renderStylePaths(appTemplateStylePaths($templateName));
}

/**
 * Render page-specific stylesheet links from Buffer::set('style_groups', ...).
 */
function renderPageStyles(): void
{
    renderStylePaths(appPageStylePaths());
}
