<?php
// =============================================================
// DASHBOARD/LIB/SETTINGS_SCHEMA — единый каталог настроек
// =============================================================
// ОДИН источник правды о том, как настройка называется по-человечески, в чём
// она измеряется, что делает и чем грозит. Из него рендерятся ВСЕ три места,
// где настройки редактируются:
//
//   • Настройки (глобальные дефолты)      — settings.php
//   • Карточка трейдера (overrides)       — trader.php
//   • Массовое изменение                  — bulk.php
//
// Раньше эти три места жили независимо: подписи расходились, единицы
// измерения не указывались, а половина полей была подписана именем ключа в
// JSON. Добавляя настройку, правь ТОЛЬКО этот файл — она появится везде.
//
// Формат поля:
//   key      — имя ключа в GLOBAL_SETTINGS_DEFAULTS (оно же имя поля формы)
//   label    — человеческое название БЕЗ имени ключа
//   unit     — единица измерения; показывается суффиксом в поле ввода
//   type     — number | text | select | symbols | leverage_map | password
//   what     — что параметр делает (одно предложение, без жаргона)
//   example  — конкретный пример «значение → что получится»
//   options  — [значение => подпись] для select
//   attrs    — доп. атрибуты input (step/min/max)
//   scope    — где поле доступно: 'both' | 'global' (только глобально)
//   bulk     — участвует ли в массовом изменении
// =============================================================

require_once __DIR__ . '/../../config.php';

/**
 * Каталог: группа => ['title', 'intro', 'fields' => [...]].
 * Порядок групп = порядок вкладок в Настройках.
 */
function settings_schema(): array
{
    return [

        // -----------------------------------------------------
        'size' => [
            'title' => 'Размер позиции',
            'icon'  => '◧',
            'intro' => 'Сколько денег уходит в одну сделку. Считается в ЧЕТЫРЕ шага, '
                     . 'каждый следующий перебивает предыдущий — см. калькулятор ниже.',
            'fields' => [
                [
                    'key'   => 'virtual_balance',
                    'label' => 'Стартовый баланс',
                    'unit'  => 'USDC',
                    'type'  => 'number',
                    'attrs' => 'step="any" min="0"',
                    'what'  => 'Депозит виртуального счёта. У каждого трейдера счёт свой, они не делят общий банк.',
                    'example' => '1000 → счёт открывается с $1000 и дальше живёт своей историей сделок.',
                    'warn'  => 'Применяется ТОЛЬКО при создании счёта. У работающего трейдера изменение не сработает, '
                             . 'пока не сбросить его state (карточка трейдера → Сброс).',
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'sizing_mode',
                    'label' => 'Как считать размер',
                    'type'  => 'select',
                    'options' => [
                        'fixed'           => 'Фиксированная сумма — всегда одинаково',
                        'percent_balance' => 'Процент от баланса — растёт вместе со счётом',
                        'proportional'    => 'Как у трейдера — зеркалим его долю капитала',
                    ],
                    'what'  => 'Шаг 1. Определяет, от чего считается МАРЖА сделки.',
                    'example' => 'Фикс. сумма — самый предсказуемый режим; процент — сложный процент; '
                               . '«как у трейдера» — повторяет его агрессивность.',
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'sizing_fixed_usd',
                    'label' => 'Сумма на сделку',
                    'unit'  => 'USD',
                    'type'  => 'number',
                    'attrs' => 'step="any" min="0"',
                    'what'  => 'Маржа одной сделки, если выбран режим «Фиксированная сумма».',
                    'example' => '100 → в каждую сделку уходит $100 маржи. При плече 5 объём ордера = $500.',
                    'depends' => ['sizing_mode' => 'fixed'],
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'sizing_percent',
                    'label' => 'Процент от баланса',
                    'unit'  => '%',
                    'type'  => 'number',
                    'attrs' => 'step="any" min="0" max="100"',
                    'what'  => 'Какая доля ТЕКУЩЕГО баланса уходит в маржу, если выбран режим «Процент от баланса».',
                    'example' => '10 при балансе $1000 → маржа $100. Баланс вырос до $2000 → маржа станет $200.',
                    'depends' => ['sizing_mode' => 'percent_balance'],
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'position_min_usd',
                    'label' => 'Минимальный объём ордера',
                    'unit'  => 'USD нотионала',
                    'type'  => 'number',
                    'attrs' => 'step="any" min="0"',
                    'what'  => 'Шаг 2, нижняя граница. Вышло меньше — поднимаем до этого значения. 0 = выключено.',
                    'example' => 'ВНИМАНИЕ: это НОТИОНАЛ (маржа × плечо), а не маржа. '
                               . '110 при плече 5 → маржа $22. Биржевой минимум Hyperliquid ~$10, ставь от 11.',
                    'warn'  => 'Если свободного баланса не хватает даже на минимум — сделка ПРОПУСКАЕТСЯ целиком, '
                             . 'а не открывается меньше.',
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'position_max_usd',
                    'label' => 'Максимальный объём ордера',
                    'unit'  => 'USD нотионала',
                    'type'  => 'number',
                    'attrs' => 'step="any" min="0"',
                    'what'  => 'Шаг 2, верхняя граница — жёсткий потолок риска. Вышло больше — срезаем. 0 = выключено.',
                    'example' => 'Тоже НОТИОНАЛ. Хочешь потолок $100 МАРЖИ при плече 5 — ставь 500.',
                    'warn'  => 'Приоритет над любым режимом размера. Если минимум больше максимума — побеждает максимум.',
                    'scope' => 'both', 'bulk' => true,
                ],
            ],
        ],

        // -----------------------------------------------------
        'entry' => [
            'title' => 'Вход и выход',
            'icon'  => '◱',
            'intro' => 'Какие сигналы трейдера мы вообще берём в работу и когда открываем позицию.',
            'fields' => [
                [
                    'key'   => 'allowed_symbols',
                    'label' => 'Разрешённые монеты',
                    'type'  => 'symbols',
                    'what'  => 'Белый список. Сигналы по остальным монетам игнорируются.',
                    'example' => 'BTC, ETH, SOL → копируем только эти три. Пусто = разрешены ВСЕ монеты.',
                    'warn'  => 'Глобальный список применяется и к трейдерам внутри пулов.',
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'cooldown_seconds',
                    'label' => 'Пауза между входами',
                    'unit'  => 'сек',
                    'type'  => 'number',
                    'attrs' => 'step="1" min="0"',
                    'what'  => 'Минимум секунд между двумя сигналами по одной монете в одну сторону.',
                    'example' => '30 → второй сигнал BTC LONG в пределах 30 секунд будет пропущен.',
                    'warn'  => 'Сигнал в паузе ПРОПУСКАЕТСЯ, а не откладывается.',
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'poll_interval_minutes',
                    'label' => 'Как часто опрашивать трейдера',
                    'unit'  => 'мин',
                    'type'  => 'number',
                    'attrs' => 'step="any" min="0"',
                    'what'  => 'Минимум минут между запросами сделок трейдера к Hyperliquid.',
                    'example' => '0 = каждый цикл (скальперы). 3–10 для дневных и свинг-трейдеров — экономит лимит API.',
                    'warn'  => 'Влияет только на скорость обнаружения НОВЫХ сделок. Риск уже открытых позиций '
                             . '(ликвидация, просадка) проверяется каждый цикл независимо.',
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'copy_after_drawdown_enabled',
                    'label' => 'Ждать просадку перед входом',
                    'type'  => 'select',
                    'options' => ['0' => 'Нет — входим сразу за трейдером', '1' => 'Да — ждём, пока он уйдёт в минус'],
                    'what'  => 'Вместо входа сразу ставим монету на наблюдение и заходим, когда трейдер уйдёт в убыток.',
                    'example' => 'Смысл — получить цену лучше, чем у трейдера. Риск — вход может не случиться никогда.',
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'copy_after_drawdown_pct',
                    'label' => 'Порог просадки трейдера',
                    'unit'  => '%',
                    'type'  => 'number',
                    'attrs' => 'step="any" min="0" max="100"',
                    'what'  => 'Насколько глубоко трейдер должен уйти в минус, чтобы мы вошли.',
                    'example' => '5 → входим, когда нереализованный убыток трейдера достигнет −5%.',
                    'depends' => ['copy_after_drawdown_enabled' => '1'],
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'copy_after_drawdown_ttl_hours',
                    'label' => 'Сколько ждать просадку',
                    'unit'  => 'часов',
                    'type'  => 'number',
                    'attrs' => 'step="any" min="0"',
                    'what'  => 'Через сколько часов снять наблюдение, если просадка так и не наступила.',
                    'example' => '24 → ждём сутки и забываем. 0 = ждать бесконечно, пока трейдер не закроется.',
                    'depends' => ['copy_after_drawdown_enabled' => '1'],
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'partial_close_mode',
                    'label' => 'Частичное закрытие',
                    'type'  => 'select',
                    'options' => [
                        'off' => 'Выключено — любое закрытие трейдера закрывает нашу позицию целиком',
                        'on'  => 'Включено — закрываем ту же долю, что и трейдер',
                    ],
                    'what'  => 'Что делать, когда трейдер закрыл только часть своей позиции.',
                    'example' => 'Включай, только если приёмник сигналов умеет уменьшать позицию: custom или katoshi.',
                    'warn'  => 'Altrady частичное закрытие НЕ умеет — оставь выключенным.',
                    'scope' => 'global', 'bulk' => false,
                ],
            ],
        ],

        // -----------------------------------------------------
        'leverage' => [
            'title' => 'Плечо и издержки',
            'icon'  => '◲',
            'intro' => 'Плечо превращает маржу в объём ордера. Издержки делают бумажный результат '
                     . 'похожим на реальный — без них симулятор систематически завышает прибыль.',
            'fields' => [
                [
                    'key'   => 'default_leverage',
                    'label' => 'Плечо по умолчанию',
                    'unit'  => '×',
                    'type'  => 'number',
                    'attrs' => 'step="1" min="1"',
                    'what'  => 'Во сколько раз объём ордера больше маржи.',
                    'example' => 'Маржа $100 при плече 5 → объём позиции $500. Убыток 20% по цене = минус весь депозит сделки.',
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'symbol_leverage',
                    'label' => 'Плечо для отдельных монет',
                    'type'  => 'leverage_map',
                    'what'  => 'Исключения из плеча по умолчанию для конкретных монет.',
                    'example' => 'BTC=5, ETH=2 → у BTC плечо 5, у ETH 2, у всех остальных — плечо по умолчанию.',
                    'warn'  => 'К пулам не применяется: там плечо задаётся на пуле или его трейдере.',
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'fee_pct_per_side',
                    'label' => 'Комиссия биржи',
                    'unit'  => '% за сторону',
                    'type'  => 'number',
                    'attrs' => 'step="any" min="0"',
                    'what'  => 'Берётся дважды: при входе и при выходе. Считается от ОБЪЁМА, не от маржи.',
                    'example' => '0.035 = тейкерская комиссия Hyperliquid. При объёме $500 это $0.175 на вход и столько же на выход.',
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'slippage_pct_per_side',
                    'label' => 'Проскальзывание',
                    'unit'  => '% за сторону',
                    'type'  => 'number',
                    'attrs' => 'step="any" min="0"',
                    'what'  => 'Насколько цена входа и выхода хуже середины спреда. Копи-ордер — всегда тейкер.',
                    'example' => '0.01–0.03 реалистично для BTC/ETH. 0 = считать по идеальной цене (завышает результат).',
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'funding_rate_hourly_pct',
                    'label' => 'Ставка финансирования',
                    'unit'  => '% в час',
                    'type'  => 'number',
                    'attrs' => 'step="any"',
                    'what'  => 'Плата за удержание позиции, начисляется на объём за каждый час.',
                    'example' => '0.00125 ≈ типичные 0.01% за 8 часов. При ставке больше нуля лонг платит, шорт получает. 0 = не учитывать.',
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'scale_factor',
                    'label' => 'Множитель размера для вебхука',
                    'unit'  => '×',
                    'type'  => 'number',
                    'attrs' => 'step="any" min="0" max="1"',
                    'what'  => 'Во сколько раз уменьшить размер В МОНЕТАХ, который уходит в вебхук внешнему боту.',
                    'example' => '0.1 → трейдер купил 10 ETH, боту уйдёт 1 ETH.',
                    'warn'  => 'На бумажный счёт и на прямую торговлю НЕ влияет — там размер считает блок '
                             . '«Размер позиции». Это поле нужно только приёмникам вебхуков.',
                    'scope' => 'both', 'bulk' => true,
                ],
            ],
        ],

        // -----------------------------------------------------
        'exec' => [
            'title' => 'Исполнение',
            'icon'  => '◳',
            'intro' => 'Куда уходит сигнал после того, как бумажный счёт его обработал.',
            'fields' => [
                [
                    'key'   => 'execution_mode',
                    'label' => 'Куда отправлять сделки',
                    'type'  => 'select',
                    'options' => [
                        'webhook' => 'На вебхуки и внешний исполнитель',
                        'direct'  => 'Напрямую на биржу Hyperliquid',
                    ],
                    'what'  => 'Главный переключатель. «Напрямую» = бот сам ставит реальные ордера.',
                    'example' => 'В режиме «напрямую» торговые вебхуки глушатся, чтобы бот-приёмник не исполнил сделку второй раз. '
                               . 'Telegram-уведомления при этом остаются.',
                    'warn'  => 'Режим «напрямую» требует ключей HL_* в config.php и стоп-лосса.',
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'executor_mode',
                    'label' => 'Внешний исполнитель',
                    'type'  => 'select',
                    'options' => [
                        'mock' => 'Только запись в лог — ничего не отправляется',
                        'live' => 'Отправлять POST на адрес ниже',
                    ],
                    'what'  => 'Подрежим для «На вебхуки». Если выбрано «Напрямую на биржу» — не действует.',
                    'example' => 'Не путать с переключателем выше: тот решает биржа/вебхуки, этот — слать ли на свой сервис.',
                    'depends' => ['execution_mode' => 'webhook'],
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'executor_url',
                    'label' => 'Адрес внешнего исполнителя',
                    'type'  => 'text',
                    'attrs' => 'placeholder="http://your-executor-service/order"',
                    'what'  => 'HTTP-адрес своего сервиса, который будет получать сигналы.',
                    'example' => 'Только внешние http/https адреса — локальные и приватные отклоняются.',
                    'depends' => ['execution_mode' => 'webhook'],
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'hl_margin_mode',
                    'label' => 'Режим маржи на бирже',
                    'type'  => 'select',
                    'options' => [
                        'isolated' => 'Изолированная — позиция рискует только своей маржой',
                        'cross'    => 'Кросс — позиции делят общую маржу счёта',
                    ],
                    'what'  => 'Как биржа считает ликвидацию для реальных ордеров.',
                    'example' => 'Изолированная совпадает с моделью симулятора — бумага и биржа согласованы.',
                    'depends' => ['execution_mode' => 'direct'],
                    'scope' => 'both', 'bulk' => true,
                ],
                [
                    'key'   => 'hl_subaccount',
                    'label' => 'Субаккаунт Hyperliquid',
                    'type'  => 'text',
                    'attrs' => 'placeholder="0x… (пусто = основной аккаунт)"',
                    'what'  => 'Отдельный счёт биржи для этого трейдера.',
                    'example' => 'На бирже действует правило «одна позиция на монету на счёт». Свой субаккаунт каждому '
                               . 'direct-трейдеру снимает конфликт за монету между ними.',
                    'depends' => ['execution_mode' => 'direct'],
                    'scope' => 'both', 'bulk' => true,
                ],
            ],
        ],

        // -----------------------------------------------------
        'notify' => [
            'title' => 'Уведомления',
            'icon'  => '◴',
            'intro' => 'Токен и чат задаются один раз глобально; включать удобнее точечно нужным трейдерам.',
            'fields' => [
                [
                    'key'   => 'telegram_bot_token',
                    'label' => 'Токен бота Telegram',
                    'type'  => 'password',
                    'attrs' => 'placeholder="123456:ABC-DEF…"',
                    'what'  => 'Создай бота у @BotFather и вставь выданный токен.',
                    'example' => 'Задаётся только глобально — трейдеры используют общий токен.',
                    'scope' => 'global', 'bulk' => false,
                ],
                [
                    'key'   => 'telegram_chat_id',
                    'label' => 'ID чата',
                    'type'  => 'text',
                    'attrs' => 'placeholder="-1001234567890"',
                    'what'  => 'Куда слать сообщения: свой user id или id канала.',
                    'example' => 'Свой id узнаёшь у @userinfobot. Боту нужно один раз написать /start.',
                    'scope' => 'global', 'bulk' => false,
                ],
                [
                    'key'   => 'telegram_notify',
                    'label' => 'Слать уведомления',
                    'type'  => 'select',
                    'options' => ['0' => 'Выключено', '1' => 'Включено'],
                    'what'  => 'Сообщение при открытии, закрытии, ликвидации и срабатывании отложенного входа.',
                    'example' => 'Обычно удобнее держать выключенным глобально и включать только важным трейдерам.',
                    'scope' => 'both', 'bulk' => true,
                ],
            ],
        ],
    ];
}

/**
 * Плоская карта key => поле (для массового редактирования и подписей в других
 * местах, где группировка не нужна).
 */
function settings_schema_flat(): array
{
    static $flat = null;
    if ($flat !== null) {
        return $flat;
    }
    $flat = [];
    foreach (settings_schema() as $group) {
        foreach ($group['fields'] as $field) {
            $flat[$field['key']] = $field;
        }
    }
    return $flat;
}

/**
 * Человеческая подпись поля с единицей измерения — «Комиссия биржи, % за сторону».
 * Используется там, где места на полную карточку нет (массовое редактирование).
 */
function settings_field_label(string $key): string
{
    $field = settings_schema_flat()[$key] ?? null;
    if ($field === null) {
        return $key;
    }
    return $field['label'] . (($field['unit'] ?? '') !== '' ? ', ' . $field['unit'] : '');
}
