interface LetterMapping {
  [key: string]: string
}

const krulermorna = (text: string): string => {
  text = text.replace(/\./g, '')
  text = text.replace(/^/, '.')
  text = text.replace(/u([aeiouy])/g, 'w$1')
  text = text.replace(/i([aeiouy])/g, 'ɩ$1')
  text = text.replace(/au/g, 'ḁ')
  text = text.replace(/ai/g, 'ą')
  text = text.replace(/ei/g, 'ę')
  text = text.replace(/oi/g, 'ǫ')
  text = text.replace(/\./g, '')
  return text
}

const krulermornaize = (words: string[]): string[] => {
  return words.map(krulermorna)
}

const ipaVits: LetterMapping = {
  a$: 'aː',
  a: 'aː',
  // "e(?=v)": 'ɛːʔ',
  // "e$": 'ɛːʔ',
  e: 'ɛː',
  i: 'iː',
  o: 'oː',
  u: 'ʊu',
  // "u": 'ʊː',
  y: 'əː',
  ą: 'aɪ',
  ę: 'ɛɪ',
  // "ę(?=\b)(?!')": 'ɛɪʔ',
  ǫ: 'ɔɪ',
  ḁ: 'aʊ',
  ɩa: 'jaː',
  ɩe: 'jɛː',
  ɩi: 'jiː',
  ɩo: 'jɔː',
  ɩu: 'juː',
  ɩy: 'jəː',
  ɩ: 'j',
  wa: 'waː',
  we: 'wɛː',
  wi: 'wiː',
  wo: 'wɔː',
  wu: 'wuː',
  wy: 'wəː',
  w: 'w',
  c: 'ʃ',
  // "bj": 'bʒ',
  j: 'ʒ',
  s: 's',
  z: 'z',
  f: 'f',
  v: 'v',
  x: 'hhh',
  "'": 'h',
  // "dj":'dʒ',
  // "tc":'tʃ',
  // "dz":'ʣ',
  // "ts":'ʦ',
  r: 'ɹ',
  'r(?![ˈaeiouyḁąęǫ])': 'ɹɹ',
  // 'r(?=[ˈaeiouyḁąęǫ])': 'ɹ',
  nˈu: 'nˈʊuː',
  nu: 'nʊuː',
  ng: 'n.g',
  n: 'n',
  m: 'm',
  l: 'l',
  b: 'b',
  d: 'd',
  g: 'ɡ',
  k: 'k',
  p: 'p',
  t: 't',
  h: 'h',
}

const rukMapping: LetterMapping = {
  a: 'а',
  e: 'э',
  i: 'и',
  o: 'о',
  u: 'у',
  y: 'ы',
  f: 'ф',
  v: 'в',
  x: 'х',
  s: 'с',
  z: 'з',
  ci: 'щи',
  c: 'ш',
  j: 'ж',
  p: 'п',
  b: 'б',
  t: 'т',
  d: 'д',
  k: 'к',
  g: 'г',
  l: 'л',
  r: 'р',
  m: 'м',
  n: 'н',
  "'": 'h',
  ą: 'ай',
  ę: 'эй',
  ǫ: 'ой',
  ḁ: 'аw',
  ɩ: 'йː',
  w: 'w',
}

const vowelPattern = new RegExp('[aeiouyąęǫḁ]')
const vowelComingPattern = new RegExp('(?=[aeiouyąęǫḁ])')
const diphthongComingPattern = new RegExp('(?=[ąęǫḁ])')

const questionWords = krulermornaize(['ma', 'mo', 'xu'])
const starterWords = krulermornaize(['le', 'lo', 'lei', 'loi'])
const terminatorWords = krulermornaize(['kei', "ku'o", 'vau', "li'u"])

export const lojban2ipa = (text: string): string => {
  text = krulermorna(text.trim())
  const words = text.split(' ')
  const rebuiltWords: string[] = []
  for (let index = 0; index < words.length; index++) {
    let modifiedWord = words[index]
    let prefix = ''
    let postfix = ''

    if (questionWords.includes(modifiedWord)) {
      postfix = '?'
      prefix = ' ' + prefix
    }

    if (starterWords.includes(modifiedWord)) {
      prefix = ' ' + prefix
    }

    if (terminatorWords.includes(modifiedWord)) {
      postfix = ', '
    }
    if (index === 0 || modifiedWord === "ni'o" || modifiedWord === 'i') {
      prefix = ', ' + prefix
    }

    const splitWord = modifiedWord.split(vowelComingPattern)
    const tailWord = splitWord.slice(-2)
    // add stress to {klama}, {ni'o}
    if (
      tailWord.length === 2 &&
      tailWord[0].length > 0 &&
      vowelPattern.test(tailWord[0][0]) &&
      vowelPattern.test(tailWord[1][0])
    ) {
      const headWord = splitWord.slice(0, -2)
      modifiedWord = headWord.join('') + 'ˈ' + tailWord.join('')
      postfix = postfix + ' '
    }
    // add stress to {lau}, {coi}
    else if (
      tailWord.length === 2 &&
      tailWord[0].length > 0 &&
      diphthongComingPattern.test(tailWord[1][0])
    ) {
      const headWord = splitWord.slice(0, -2)
      modifiedWord = headWord.join('') + tailWord[0] + 'ˈ' + tailWord[1]
      postfix = postfix + ' '
    }

    // add a pause even after a cmavo
    if (!(index - 1 >= 0 && starterWords.includes(words[index - 1]))) {
      prefix = ' ' + prefix
    }

    let rebuiltWord = ''
    for (let idx = 0; idx < modifiedWord.length; idx++) {
      const tail = modifiedWord.slice(idx)
      let matched = false
      let consumed = 1
      for (const [attr, val] of Object.entries(ipaVits).sort(
        (a, b) => b[0].length - a[0].length
      )) {
        const pattern = new RegExp('^' + attr)
        const matches = pattern.exec(tail)
        if (matches) {
          const match = matches[0]
          consumed = match.length
          rebuiltWord += val
          matched = true
          break
        }
      }
      if (!matched) {
        rebuiltWord += modifiedWord[idx]
      }
      idx += consumed - 1
    }
    rebuiltWords.push(prefix + rebuiltWord + postfix)
  }
  return rebuiltWords.join(' ')
}

export const lojban2ruk = (text: string): string => {
  text = krulermorna(text.trim().replace(/\n/g, ' \n'))
  const words = text.split(' ')
  const rebuiltWords: string[] = []
  for (let index = 0; index < words.length; index++) {
    let modifiedWord = words[index]
    let prefix = ''
    let postfix = ''

    if (questionWords.includes(modifiedWord)) {
      postfix = '?'
      prefix = ' ' + prefix
    }

    if (starterWords.includes(modifiedWord)) {
      prefix = ' ' + prefix
    }

    if (terminatorWords.includes(modifiedWord)) {
      postfix = ', '
    }

    const splitWord = modifiedWord.split(vowelComingPattern)
    const tailWord = splitWord.slice(-2)
    // add stress to {klama}, {ni'o}
    if (
      tailWord.length === 2 &&
      tailWord[0].length > 0 &&
      vowelPattern.test(tailWord[0][0]) &&
      vowelPattern.test(tailWord[1][0])
    ) {
      const headWord = splitWord.slice(0, -2)
      modifiedWord =
        headWord.join('') +
        tailWord[0][0] +
        '\u0301' +
        tailWord[0].split('').slice(1).join('') +
        tailWord.slice(1).join('')
      postfix = postfix + ' '
    }
    // add stress to {lau}, {coi}
    else if (
      tailWord.length === 2 &&
      tailWord[0].length > 0 &&
      diphthongComingPattern.test(tailWord[1][0])
    ) {
      const headWord = splitWord.slice(0, -2)

      modifiedWord = headWord.join('') + tailWord[0] + tailWord[1] + '\u0301'
      postfix = postfix + ' '
    }

    // add stress to {lo}
    else if (
      tailWord.length === 2 &&
      tailWord[0].length > 0 &&
      /o/.test(tailWord[1][0])
    ) {
      const headWord = splitWord.slice(0, -2)

      modifiedWord = headWord.join('') + tailWord[0] + tailWord[1] + '\u0301'
      postfix = postfix + ' '
    }

    // add a pause even after a cmavo
    if (!(index - 1 >= 0 && starterWords.includes(words[index - 1]))) {
      prefix = ' ' + prefix
    }

    let rebuiltWord = ''
    for (let idx = 0; idx < modifiedWord.length; idx++) {
      const tail = modifiedWord.slice(idx)
      let matched = false
      let consumed = 1
      for (const [attr, val] of Object.entries(rukMapping).sort(
        (a, b) => b[0].length - a[0].length
      )) {
        const pattern = new RegExp('^' + attr)
        const matches = pattern.exec(tail)
        if (matches) {
          const match = matches[0]
          consumed = match.length
          rebuiltWord += val
          matched = true
          break
        }
      }

      if (!matched) {
        rebuiltWord += modifiedWord[idx]
      }
      rebuiltWord = rebuiltWord.replace(/([wй])\u0301/g, '\u0301$1')
      idx += consumed - 1
    }
    rebuiltWords.push(prefix + rebuiltWord + postfix)
  }
  return rebuiltWords
    .join(' ')
    .replace(/ {2,}/g, ' ')
    .trim()
    .split('\n')
    .map((line) => {
      line = line.trim()
      return `${line.charAt(0).toUpperCase()}${line.slice(1)}.`
    })
    .join('\n')
    .replace(/\.{2,}/, '.')
}

console.log(
  lojban2ruk(
    `
	.i ba milxe bo carvi .i panci lo dertu
.i le cipni ba sutra bo vofli co certu
.i le banfi ba sanga .i melbi bo vanci
.i le flaume bo tricu ba blabi se manci
.i no cipni ba xanka fa no jdari tricu
fe le fanmo be mi'a noi no da ba skicu
.i la vensa ba cikna ke viska le cmana
.i te mintu le jalge .i vy. ri na zgana
`
  )
)
