/* eslint-disable no-inline-comments */
import { isMac } from './Types/Platforms'
import { Store, StoreKeys } from './Store'
import { isDev } from './Utils/Utils'

export enum Language {
  AF = 'af',
  ID = 'id',
  CA = 'ca',
  CS = 'cs',
  CY = 'cy',
  DA = 'da',
  DE = 'de',
  SH = 'sh',
  ET = 'et',
  EN_AU = 'en-AU',
  EN_CA = 'en-CA',
  EN_GB = 'en-GB',
  EN_US = 'en-US',
  ES = 'es',
  ES_419 = 'es-419',
  ES_ES = 'es-ES',
  ES_US = 'es-US',
  ES_MX = 'es-MX',
  ES_AR = 'es-AR',
  FO = 'fo',
  FR = 'fr',
  HR = 'hr',
  IT = 'it',
  PL = 'pl',
  LV = 'lv',
  LT = 'lt',
  HU = 'hu',
  NL = 'nl',
  NB = 'nb',
  PT_BR = 'pt-BR',
  PT_PT = 'pt-PT',
  RO = 'ro',
  SQ = 'sq',
  SK = 'sk',
  SL = 'sl',
  SV = 'sv',
  VI = 'vi',
  TR = 'tr',
  EL = 'el',
  BG = 'bg',
  RU = 'ru',
  SR = 'sr',
  TG = 'tg',
  UK = 'uk',
  HY = 'hy',
  HE = 'he',
  FA = 'fa',
  HI = 'hi',
  TA = 'ta',
  KO = 'ko',
}

function isLanguage(language: any): language is Language {
  return Object.values(Language).includes(language)
}

function log(...message: any) {
  console.log('spellcheckerMaager:', ...message)
}

export interface SpellcheckerManager {
  languages(): Array<{
    code: string
    name: string
    enabled: boolean
  }>
  addLanguage(code: string): void
  removeLanguage(code: string): void
}

export function createSpellcheckerManager(
  store: Store,
  webContents: Electron.WebContents,
  userLocale: string,
): SpellcheckerManager | undefined {
  /**
   * On MacOS the system spellchecker is used and every related Electron method
   * is a no-op. Return early to prevent unnecessary code execution/allocations
   */
  if (isMac()) {
    return
  }

  const session = webContents.session

  /**
   * Mapping of language codes predominantly based on
   * https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
   */
  const LanguageCodes: Readonly<Record<Language, string>> = {
    af: 'Afrikaans' /** Afrikaans */,
    id: 'Bahasa Indonesia' /** Indonesian */,
    ca: 'Català, Valencià' /** Catalan, Valencian */,
    cs: 'Čeština, Český Jazyk' /** Czech */,
    cy: 'Cymraeg' /** Welsh */,
    da: 'Dansk' /** Danish */,
    de: 'Deutsch' /** German */,
    sh: 'Deutsch, Schaffhausen' /** German, Canton of Schaffhausen */,
    et: 'Eesti, Eesti Keel' /** Estonian */,
    'en-AU': 'English, Australia',
    'en-CA': 'English, Canada',
    'en-GB': 'English, Great Britain',
    'en-US': 'English, United States',
    es: 'Español' /** Spanish, Castilian */,
    'es-419': 'Español, America Latina' /** Spanish, Latin American */,
    'es-ES': 'Español, España' /** Spanish, Spain */,
    'es-US': 'Español, Estados Unidos de América' /** Spanish, United States */,
    'es-MX': 'Español, Estados Unidos Mexicanos' /** Spanish, Mexico */,
    'es-AR': 'Español, República Argentina' /** Spanish, Argentine Republic */,
    fo: 'Føroyskt' /** Faroese */,
    fr: 'Français' /** French */,
    hr: 'Hrvatski Jezik' /** Croatian */,
    it: 'Italiano' /** Italian */,
    pl: 'Język Polski, Polszczyzna' /** Polish */,
    lv: 'Latviešu Valoda' /** Latvian */,
    lt: 'Lietuvių Kalba' /** Lithuanian */,
    hu: 'Magyar' /** Hungarian */,
    nl: 'Nederlands, Vlaams' /** Dutch, Flemish */,
    nb: 'Norsk Bokmål' /** Norwegian Bokmål */,
    'pt-BR': 'Português, Brasil' /** Portuguese, Brazil */,
    'pt-PT': 'Português, República Portuguesa' /** Portuguese, Portugal */,
    ro: 'Română' /** Romanian, Moldavian, Moldovan */,
    sq: 'Shqip' /** Albanian */,
    sk: 'Slovenčina, Slovenský Jazyk' /** Slovak */,
    sl: 'Slovenski Jezik, Slovenščina' /** Slovenian */,
    sv: 'Svenska' /** Swedish */,
    vi: 'Tiếng Việt' /** Vietnamese */,
    tr: 'Türkçe' /** Turkish */,
    el: 'ελληνικά' /** Greek */,
    bg: 'български език' /** Bulgarian */,
    ru: 'Русский' /** Russian */,
    sr: 'српски језик' /** Serbian */,
    tg: 'тоҷикӣ, toçikī, تاجیکی‎' /** Tajik */,
    uk: 'Українська' /** Ukrainian */,
    hy: 'Հայերեն' /** Armenian */,
    he: 'עברית' /** Hebrew */,
    fa: 'فارسی' /** Persian */,
    hi: 'हिन्दी, हिंदी' /** Hindi */,
    ta: 'தமிழ்' /** Tamil */,
    ko: '한국어' /** Korean */,
  }

  const availableSpellCheckerLanguages = Object.values(Language).filter((language) =>
    session.availableSpellCheckerLanguages.includes(language),
  )

  if (isDev() && availableSpellCheckerLanguages.length !== session.availableSpellCheckerLanguages.length) {
    /** This means that not every available language has been accounted for. */
    const firstOutlier = session.availableSpellCheckerLanguages.find(
      (language, index) => availableSpellCheckerLanguages[index] !== language,
    )
    throw new Error(`Found unsupported language code: ${firstOutlier}`)
  }

  setSpellcheckerLanguages()

  function setSpellcheckerLanguages() {
    const { session } = webContents
    let selectedCodes = store.get(StoreKeys.SelectedSpellCheckerLanguageCodes)

    if (selectedCodes === null) {
      /** First-time setup. Set a default language */
      selectedCodes = determineDefaultSpellcheckerLanguageCodes(session.availableSpellCheckerLanguages, userLocale)
      store.set(StoreKeys.SelectedSpellCheckerLanguageCodes, selectedCodes)
    }
    session.setSpellCheckerLanguages([...selectedCodes])
  }

  function determineDefaultSpellcheckerLanguageCodes(
    availableSpellCheckerLanguages: string[],
    userLocale: string,
  ): Set<Language> {
    const localeIsSupported = availableSpellCheckerLanguages.includes(userLocale)
    if (localeIsSupported && isLanguage(userLocale)) {
      return new Set([userLocale])
    } else {
      log(`Spellchecker doesn't support locale '${userLocale}'.`)
      return new Set()
    }
  }

  function selectedLanguageCodes(): Set<Language> {
    return store.get(StoreKeys.SelectedSpellCheckerLanguageCodes) || new Set()
  }

  return {
    languages() {
      const codes = selectedLanguageCodes()
      return availableSpellCheckerLanguages.map((code) => ({
        code,
        name: LanguageCodes[code],
        enabled: codes.has(code),
      }))
    },
    addLanguage(code: Language) {
      const selectedCodes = selectedLanguageCodes()
      selectedCodes.add(code)
      store.set(StoreKeys.SelectedSpellCheckerLanguageCodes, selectedCodes)
      session.setSpellCheckerLanguages(Array.from(selectedCodes))
    },
    removeLanguage(code: Language) {
      const selectedCodes = selectedLanguageCodes()
      selectedCodes.delete(code)
      store.set(StoreKeys.SelectedSpellCheckerLanguageCodes, selectedCodes)
      session.setSpellCheckerLanguages(Array.from(selectedCodes))
    },
  }
}
