'use strict';

/**
 * units.js — ET-005: централизованный модуль единиц измерения расстояний.
 *
 * Единственный источник истины по выбору единицы (км/мили), её
 * персистентности и форматированию ОТОБРАЖАЕМЫХ расстояний.
 *
 * Подключается в index.html как классический скрипт СТРОГО перед app.js
 * (ADR-0001 п.2, риск R7 в 10-tech-risks.md) и публикует глобальный
 * неймспейс `window.Units`. Для unit-тестов модуль дополнительно
 * экспортируется через `module.exports` (среда Node).
 *
 * Каноническая единица — МЕТРЫ: `formatDistance()` всегда принимает
 * расстояние в метрах, конвертация в мили выполняется только в момент
 * форматирования строки (ADR-0001 п.4, 08-data-requirements.md §3).
 * Хранить или пересчитывать значения в милях во внутреннем состоянии
 * запрещено — это исключает накопление ошибок округления при
 * многократном переключении (ФТ-3 ТЗ, AC-2, тест TP-04).
 *
 * См. docs/work-items/ET-005/06-adr/adr-0001-unit-toggle-client-side.md
 */
(function (global) {
  'use strict';

  /** Ключ localStorage для сохранённой единицы (02-trz.md, 08-data-requirements.md §4). */
  var STORAGE_KEY = 'distance_unit';
  /** Коэффициент перевода: 1 км = 0.621371 мили (AC-2). Единственное место объявления. */
  var KM_TO_MI = 0.621371;
  /** Единица по умолчанию при отсутствии/некорректности сохранённого значения (AC-3). */
  var DEFAULT_UNIT = 'km';
  /** Допустимые значения единицы измерения. */
  var VALID_UNITS = ['km', 'mi'];
  /** Разделитель дробной части — запятая (R4: единый разделитель, ru-локаль интерфейса). */
  var DECIMAL_SEP = ',';
  /** Подписи единиц в UI. */
  var UNIT_LABEL = { km: 'км', mi: 'ми' };
  /** Подпись суб-километровых значений в режиме «км». */
  var SUBKM_LABEL = 'м';

  /** Рантайм-кэш текущей единицы; null — ещё не прочитана из localStorage. */
  var current = null;

  /**
   * Безопасное чтение localStorage: возвращает null, если хранилище
   * недоступно (private mode, отключённые cookies и т.п.).
   * @returns {?string}
   */
  function readStored() {
    try {
      return window.localStorage.getItem(STORAGE_KEY);
    } catch (e) {
      return null;
    }
  }

  /**
   * Безопасная запись в localStorage. Недоступность хранилища не
   * считается ошибкой — выбор просто не персистится между сессиями.
   * @param {string} value
   */
  function writeStored(value) {
    try {
      window.localStorage.setItem(STORAGE_KEY, value);
    } catch (e) {
      /* localStorage недоступен — намеренно проглатываем */
    }
  }

  /**
   * Проверяет, что переданное значение — допустимая единица измерения.
   * @param {*} unit
   * @returns {boolean}
   */
  function isValidUnit(unit) {
    return VALID_UNITS.indexOf(unit) !== -1;
  }

  /**
   * Возвращает текущую выбранную единицу измерения.
   *
   * При первом обращении значение читается из localStorage; отсутствующее
   * или некорректное значение даёт дефолт `'km'` (AC-3, TP-01).
   * @returns {('km'|'mi')}
   */
  function getUnit() {
    if (current === null) {
      var stored = readStored();
      current = isValidUnit(stored) ? stored : DEFAULT_UNIT;
    }
    return current;
  }

  /**
   * Устанавливает единицу измерения.
   *
   * Валидирует значение, сохраняет его в localStorage и диспатчит
   * событие `unitchange` на `document` (ADR-0001 п.3, п.6). Некорректное
   * значение игнорируется. Если единица фактически не изменилась — ни
   * записи, ни события не происходит.
   * @param {('km'|'mi')} unit
   * @returns {('km'|'mi')} актуальная единица после вызова
   */
  function setUnit(unit) {
    if (!isValidUnit(unit)) return getUnit();
    if (getUnit() === unit) return current;
    current = unit;
    writeStored(unit);
    try {
      document.dispatchEvent(new CustomEvent('unitchange', { detail: { unit: unit } }));
    } catch (e) {
      /* document/CustomEvent недоступны (headless-окружение) */
    }
    return current;
  }

  /**
   * Переключает единицу измерения km ⇄ mi.
   * @returns {('km'|'mi')} единица после переключения
   */
  function toggleUnit() {
    return setUnit(getUnit() === 'km' ? 'mi' : 'km');
  }

  /**
   * Форматирует число с фиксированной точностью и единым разделителем
   * дробной части (R4).
   * @param {number} value
   * @param {number} precision - знаков после запятой
   * @returns {string}
   */
  function formatNumber(value, precision) {
    var s = value.toFixed(precision);
    return DECIMAL_SEP === '.' ? s : s.replace('.', DECIMAL_SEP);
  }

  /**
   * Форматирует расстояние, заданное в МЕТРАХ, строкой в текущей единице.
   *
   * Политика суб-километровых расстояний (R2 в 10-tech-risks.md):
   *  - режим «km»: значения < 1000 м показываются в метрах ('850 м');
   *  - режим «mi»: всё показывается в милях; для значений < 1000 м
   *    точность повышается минимум до 2 знаков, чтобы короткие отрезки
   *    не схлопывались в «0 ми». Футы намеренно не вводятся.
   *
   * @param {number} meters - расстояние в метрах (каноническая единица).
   * @param {object} [opts]
   * @param {number} [opts.precision=1] - знаков после запятой для значения
   *        в км/милях. По умолчанию 1 (AC-2). Карточки маршрутов
   *        запрашивают `precision: 0` ради целочисленного вида (R5).
   * @returns {string} например '12,3 км', '7,6 ми', '850 м', '0,53 ми'.
   */
  function formatDistance(meters, opts) {
    if (meters === null || meters === undefined || isNaN(meters)) return '-';

    var precision = (opts && typeof opts.precision === 'number') ? opts.precision : 1;

    if (getUnit() === 'mi') {
      var miles = (meters / 1000) * KM_TO_MI;
      // R2: суб-километровые расстояния в милях — с повышенной точностью.
      var miPrecision = (meters < 1000) ? Math.max(precision, 2) : precision;
      return formatNumber(miles, miPrecision) + ' ' + UNIT_LABEL.mi;
    }

    // Режим «км».
    if (meters < 1000) {
      return Math.round(meters) + ' ' + SUBKM_LABEL;
    }
    return formatNumber(meters / 1000, precision) + ' ' + UNIT_LABEL.km;
  }

  /** Публичный контракт модуля (ADR-0001 п.3). */
  var Units = {
    STORAGE_KEY: STORAGE_KEY,
    KM_TO_MI: KM_TO_MI,
    getUnit: getUnit,
    setUnit: setUnit,
    toggleUnit: toggleUnit,
    formatDistance: formatDistance,
  };

  // Браузер — глобальный неймспейс window.Units (соответствует стилю app.js).
  if (global) {
    global.Units = Units;
  }
  // Node — экспорт для изолированных unit-тестов.
  if (typeof module === 'object' && module.exports) {
    module.exports = Units;
  }
})(typeof window !== 'undefined' ? window : (typeof globalThis !== 'undefined' ? globalThis : null));
