// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import { join } from 'node:path';
import { assert } from 'chai';
import { stub } from 'sinon';
import * as LocaleMatcher from '@formatjs/intl-localematcher';
import { load, _getAvailableLocales } from '../../../app/locale.node.ts';
import { FAKE_DEFAULT_LOCALE } from '../../../app/spell_check.main.ts';
import { HourCyclePreference } from '../../types/I18N.std.ts';

const rootDir = join(__dirname, '..', '..', '..');

describe('locale', async () => {
  describe('load', () => {
    it('resolves expected locales correctly', async () => {
      const logger = {
        fatal: stub().throwsArg(0),
        error: stub().throwsArg(0),
        warn: stub().throwsArg(0),
        info: stub(),
        debug: stub(),
        trace: stub(),
        child: () => logger,
      };

      async function testCase(
        preferredSystemLocales: Array<string>,
        expectedLocale: string
      ) {
        const actualLocale = load({
          rootDir,
          hourCyclePreference: HourCyclePreference.UnknownPreference,
          isPackaged: false,
          localeDirectionTestingOverride: null,
          localeOverride: null,
          logger,
          preferredSystemLocales,
        });
        assert.strictEqual(actualLocale.name, expectedLocale);
      }

      // Basic tests
      await testCase(['en'], 'en');
      await testCase(['es'], 'es');
      await testCase(['fr', 'hk'], 'fr');
      await testCase(['fr-FR', 'hk'], 'fr');
      await testCase(['fa-UK'], 'fa-IR');
      await testCase(['an', 'fr-FR'], 'fr'); // If we ever add support for Aragonese, this test will fail.

      // Specific cases we want to ensure work as expected
      await testCase(['zh-TW'], 'zh-Hant');
      await testCase(['zh-Hant-TW'], 'zh-Hant');
      await testCase(['zh-Hant-CA'], 'zh-Hant');
      await testCase(['zh-Hant-HK'], 'zh-HK');
      await testCase(['zh'], 'zh-CN');
      await testCase(['yue'], 'yue');
      await testCase(['ug'], 'ug');
      await testCase(['nn', 'nb'], 'nb');
      await testCase(['es-419'], 'es');
      await testCase(['sr-RO', 'sr'], 'sr');
      await testCase(['sr-RS', 'sr'], 'sr');
    });
  });

  describe('Intl.LocaleMatcher', () => {
    it('should work for single locales outside of their region', () => {
      // Our supported locales where we only have a single region for a language
      const SINGLE_REGION_LOCALES = [
        'af-ZA',
        'az-AZ',
        'bg-BG',
        'bn-BD',
        'bs-BA',
        'et-EE',
        'fa-IR',
        'ga-IE',
        'gl-ES',
        'gu-IN',
        'hi-IN',
        'hr-HR',
        'ka-GE',
        'kk-KZ',
        'km-KH',
        'kn-IN',
        'ky-KG',
        'lt-LT',
        'lv-LV',
        'mk-MK',
        'ml-IN',
        'mr-IN',
        'pa-IN',
        'ro-RO',
        'sk-SK',
        'sl-SI',
        'sq-AL',
        'ta-IN',
        'te-IN',
        'tl-PH',
        'uk-UA',
      ];

      // Just a whole bunch of common regions
      const TEST_REGIONS = [
        'AE',
        'AR',
        'AT',
        'AU',
        'BE',
        'CA',
        'CH',
        'CL',
        'CN',
        'CO',
        'CR',
        'CZ',
        'DE',
        'DK',
        'DO',
        'EC',
        'EG',
        'ES',
        'FI',
        'FR',
        'GB',
        'GR',
        'GT',
        'HK',
        'IE',
        'IL',
        'IN',
        'IT',
        'JP',
        'KR',
        'MY',
        'NL',
        'NO',
        'PA',
        'PE',
        'PH',
        'PL',
        'PT',
        'RO',
        'RS',
        'RU',
        'SA',
        'SE',
        'SG',
        'SK',
        'TH',
        'TR',
        'UA',
        'US',
        'VE',
      ];

      const availableLocales = _getAvailableLocales(rootDir);

      for (const locale of SINGLE_REGION_LOCALES) {
        const { language } = new Intl.Locale(locale);
        for (const region of TEST_REGIONS) {
          const newLocale = new Intl.Locale(language, { region });

          const matched = LocaleMatcher.match(
            [newLocale.baseName],
            availableLocales,
            FAKE_DEFAULT_LOCALE,
            { algorithm: 'best fit' }
          );

          assert.strictEqual(
            matched,
            locale,
            `${locale} -> ${language} -> ${region} -> ${newLocale.baseName} -> ${matched}`
          );
        }
      }
    });
  });
});
