-- RusFixer.lua — WoW addon module
local RusFixer = {}

local _, ns = ...

if type(ns) ~= "table" then
    ns = {}   -- создаём таблицу для тестов
end

ns.RusFixer = RusFixer


-- ========================= Enums =========================
RusFixer.Gender = { Male = "Male", Female = "Female", Neuter = "Neuter", Plural = "Plural" }
RusFixer.Tense  = { Past = "Past", Present = "Present", Future = "Future" }

-- ========================= Data =========================

-- Лицо по подлежащему
local PersonIndex = {
  ["я"] = 0, ["ты"] = 1, ["он"] = 2, ["она"] = 2, ["оно"] = 2,
  ["мы"] = 3, ["вы"] = 4, ["они"] = 5,
}

-- Нерегулярные прошедшие формы (инфинитив → m/f/n/pl)
local PastIrregular = {
  ["найти"]  = { m = "нашел",  f = "нашла",  n = "нашло",  pl = "нашли" },
  ["вести"]  = { m = "вел",    f = "вела",   n = "вело",   pl = "вели" },
  ["прийти"] = { m = "пришел", f = "пришла", n = "пришло", pl = "пришли" },
  ["выйти"]  = { m = "вышел",  f = "вышла",  n = "вышло",  pl = "вышли" },
  ["хотеть"] = { m = "хотел",  f = "хотела", n = "хотело", pl = "хотели" },
}

-- Нерегулярные настоящие формы (6 лиц: я, ты, он, мы, вы, они)
local PresentIrregular = {
  ["искать"] = { "ищу","ищешь","ищет","ищем","ищете","ищут" },
  ["спать"]  = { "сплю","спишь","спит","спим","спите","спят" },
  ["дать"]   = { "дам","дашь","даст","дадим","дадите","дадут" },
  ["хотеть"] = { "хочу","хочешь","хочет","хотим","хотите","хотят" },
}

-- Формы "хотеть" (во всех временах), после которых следующий инфинитив сохраняется
local VolitionHead = {
  -- прошедшее
  ["хотел"]=true, ["хотела"]=true, ["хотело"]=true, ["хотели"]=true,
  -- настоящее
  ["хочу"]=true, ["хочешь"]=true, ["хочет"]=true,
  ["хотим"]=true, ["хотите"]=true, ["хотят"]=true,
  -- будущее аналитическое (как один токен: т.к. ConjugateFuture возвращает строку с пробелом и она остаётся единым элементом в tokens)
  ["буду хотеть"]=true, ["будешь хотеть"]=true, ["будет хотеть"]=true,
  ["будем хотеть"]=true, ["будете хотеть"]=true, ["будут хотеть"]=true,
}

-- вспомогательные формы будущего, которые могут предшествовать "хотеть"
local VolitionAux = {
  ["буду"]=true, ["будешь"]=true, ["будет"]=true, ["будем"]=true, ["будете"]=true, ["будут"]=true
}

-- Притяжательные местоимения
local Possessives = {
  ["мой"] = { m = "мой", f = "моя", n = "моё", pl = "мои" },
  ["твой"] = { m = "твой", f = "твоя", n = "твоё", pl = "твои" },
  ["наш"] = { m = "наш", f = "наша", n = "наше", pl = "наши" },
  ["ваш"] = { m = "ваш", f = "ваша", n = "ваше", pl = "ваши" },
}

-- Дательный для модала «нужно/надо»
local DativePronoun = {
  ["я"] = "мне", ["ты"] = "тебе", ["он"] = "ему", ["она"] = "ей",
  ["оно"] = "ему", ["мы"] = "нам", ["вы"] = "вам", ["они"] = "им",
}

-- Модальные слова (одиночные токены)
local ModalWords = {
  -- Необходимость
  ["нужно"]=true,
  ["надо"]=true,
  ["необходимо"]=true,
  ["следует"]=true,
  ["обязан"]=true,
  ["должен"]=true,
  ["требуется"]=true,
  ["полагается"]=true,
  ["предстоит"]=true,
  ["надлежит"]=true,
  ["приходится"]=true,
  -- Возможность
  ["можно"]=true,
  ["возможно"]=true,
  ["допустимо"]=true,
  ["позволительно"]=true,
  ["разрешается"]=true,
  ["способен"]=true,
  -- Вероятность
  ["вероятно"]=true,
  ["наверное"]=true,
  ["кажется"]=true,
  ["видимо"]=true,
  ["пожалуй"]=true,
  -- Уверенность
  ["конечно"]=true,
  ["безусловно"]=true,
  ["действительно"]=true,
  ["несомненно"]=true,
  ["определённо"]=true,
  ["разумеется"]=true,
  ["естественно"]=true,
  ["непременно"]=true,
  ["точно"]=true,
  -- Сомнение
  ["вряд"]=true,
  ["сомнительно"]=true,
  ["неизвестно"]=true,
  -- Желание
  ["хочу"]=true,
  ["стоит"]=true,
  ["хотел"]=true,
  ["хочешь"]=true,
}

-- Модальные фразы (двух- и трёхсловные, проверяем последовательности токенов)
local ModalPhrases = {
  { "есть", "возможность" },
  { "имеется", "возможность" },
  { "по", "всей", "видимости" },
  { "должно", "быть" },
  { "скорее", "всего" },
  { "по", "правде", "говоря" },
  { "по", "совести", "говоря" },
  { "по", "всей", "вероятности" },
  { "по", "обыкновению" },
  { "по", "привычке" },
  { "не", "факт" },
  { "едва", "ли" },
  { "вряд", "ли" },
  { "хотел", "бы" },
  { "желал", "бы" },
  { "следовало", "бы" },
  { "надлежало", "бы" },
  { "не", "мешало", "бы" },
  { "неплохо", "бы" },
  { "лучше", "бы" },
}

-- Детерминаторы (указательные/притяжательные)
local Determiners = {
  ["этот"]=true,["эта"]=true,["это"]=true,["эти"]=true,["тот"]=true,["та"]=true,["то"]=true,["те"]=true,
  ["мой"]=true,["моя"]=true,["моё"]=true,["мои"]=true,["твой"]=true,["твоя"]=true,["твоё"]=true,["твои"]=true,
  ["наш"]=true,["наша"]=true,["наше"]=true,["наши"]=true,["ваш"]=true,["ваша"]=true,["ваше"]=true,["ваши"]=true,
}

-- ========================= Helpers =========================


-- Промежуточные служебные токены, которые пропускаем между модалом и глаголом
local Skippable = {
  ["меня"]=true, ["мне"]=true, ["тебя"]=true, ["тебе"]=true,
  ["его"]=true, ["ему"]=true, ["её"]=true, ["ее"]=true, ["ей"]=true,
  ["нас"]=true, ["нам"]=true, ["вас"]=true, ["вам"]=true, ["их"]=true, ["им"]=true,
  -- частицы/служебные
  ["же"]=true, ["ведь"]=true, ["вот"]=true, ["ли"]=true, ["то"]=true,
  ["не"]=true, ["ни"]=true,
  -- предлоги
  ["у"]=true, ["в"]=true, ["во"]=true, ["на"]=true, ["к"]=true, ["по"]=true,
  ["от"]=true, ["с"]=true, ["со"]=true, ["для"]=true, ["без"]=true, ["из"]=true,
}

local function isPunctuation(token)
  return token and token:match("^[%p%s]+$") ~= nil
end

local function normalizeToken(s)
  if not s then return s end
  s = s:match("^%s*(.-)%s*$")
  if utf8_utils and utf8_utils.lower then
    return utf8_utils.lower(s)
  else
    return s:lower()
  end
end

local function isInfinitive_utf8(word)
  word = normalizeToken(word)
  if not word or word == "" then return false end
  if word:match("ться$") then return true end
  if word:match("ть$") then return true end
  if word:match("ти$") then return true end
  return false
end

local function isPastVerbCandidate(word)
  return word and (
    word:match("л$") or word:match("ла$") or word:match("ли$") or word:match("ло$")
    or word:match("лся$") or word:match("лась$") or word:match("лись$") or word:match("лось$")
  )
end

local function isVerbCandidate(word)
  if not word or word == "" then return false end
  if isInfinitive_utf8(word) then return true end
  if isPastVerbCandidate(word) then return true end
  -- при желании: добавить настоящ./будущ. окончания (-ешь/-ит/-им/-ят и др.)
  return false
end

-- Генератор инфинитива из прошедшего (вкл. возвратные формы)
local function GuessInfinitive(word)
  if not word or word == "" then return nil end
  if isInfinitive_utf8(word) then return word end

  -- возвратные прошедшие
  if word:match("лся$") then return word:gsub("лся$", "ться") end
  if word:match("лась$") then return word:gsub("лась$", "ться") end
  if word:match("лись$") then return word:gsub("лись$", "ться") end
  if word:match("лось$") then return word:gsub("лось$", "ться") end

  -- невозвратные прошедшие
  if word:match("ла$") then return word:gsub("ла$", "ть") end
  if word:match("ли$") then return word:gsub("ли$", "ть") end
  if word:match("ло$") then return word:gsub("ло$", "ть") end
  if word:match("л$")  then return word:gsub("л$",  "ть") end

  return nil
end

-- Преобразование: для каждого модала ищем первый глагол дальше, пропуская «вставки», и переводим в инфинитив
local function applyInfinitiveAfterModal(tokens, modal)
  if not modal or not tokens or #tokens == 0 then return end

  local window = 6  -- сколько токенов вперёд смотрим от модала (регулируемо)
  local i = 1
  while i <= #tokens do
    local t = tokens[i]
    -- ModalWords у тебя уже есть в коде; иначе используй hasModal и проверяй t по своему словарю
    if ModalWords and ModalWords[t] then
      local j = i + 1
      while j <= #tokens and j <= i + window do
        local tj = tokens[j]
        if isPunctuation(tj) or Skippable[tj] then
          j = j + 1
        elseif isVerbCandidate(tj) then
          local inf = GuessInfinitive(tj)
          if inf then
            tokens[j] = inf
          end
          break
        else
          -- «содержательное» слово, но не глагол — двигаемся дальше, вдруг глагол будет дальше
          j = j + 1
        end
      end
    end
    i = i + 1
  end
end

local function endswith(s, suf)
  return s:sub(-#suf) == suf
end

local function lower(s) return s:lower() end

-- ожидается внешний utf8_utils с remove_last/capitalize

local function isInfinitive(w)
  return endswith(w, "ть") or endswith(w, "ти")
end

local function isConjunction(w)
  return w == "и" or w == "а" or w == "но"
end

local function isPreposition(w)
  return w == "в" or w == "на" or w == "к" or w == "по" or w == "с" or w == "о" or w == "об" or w == "за" or w == "при"
end

local function looksLikeDeterminer(w)
  return Determiners[lower(w)] == true
end

local function looksLikeAdjective(w)
  w = lower(w)
  return endswith(w, "ый") or endswith(w, "ий") or endswith(w, "ой")
      or endswith(w, "ая") or endswith(w, "яя")
      or endswith(w, "ое") or endswith(w, "ее")
      or endswith(w, "ые") or endswith(w, "ие")
      or endswith(w, "кий")
end

local function looksLikeNoun(w)
  w = lower(w)
  if isInfinitive_utf8(w) or looksLikeAdjective(w) or looksLikeDeterminer(w) or isConjunction(w) or isPreposition(w) then
    return false
  end
  return true
end

local function hasModal(tokens)
  -- single-word modals
  for i = 1, #tokens do
    local w = tokens[i]
    if ModalWords[w] then return true end
  end
  -- multi-word phrases
  for _, phrase in ipairs(ModalPhrases) do
    local plen = #phrase
    if plen <= #tokens then
      for i = 1, #tokens - plen + 1 do
        local ok = true
        for j = 1, plen do
          if tokens[i + j - 1] ~= phrase[j] then ok = false; break end
        end
        if ok then return true end
      end
    end
  end
  return false
end

local function guessGenderNumber(noun)
  noun = lower(noun)
  if endswith(noun, "ы") or endswith(noun, "и") then return "x", true end
  if endswith(noun, "а") or endswith(noun, "я") then return "f", false end
  if endswith(noun, "о") or endswith(noun, "е") then return "n", false end
  if endswith(noun, "ь") then return "f", false end
  return "m", false
end

local function agreePossessive(poss, noun)
  if not Possessives[poss] then return poss end
  local gender, plural = guessGenderNumber(noun)
  local forms = Possessives[poss]
  if plural then return forms.pl end
  if gender == "f" then return forms.f
  elseif gender == "n" then return forms.n
  else return forms.m end
end

-- ========================= Morphology =========================

function RusFixer.ToPast(infinitive, gender)
  local inf = lower(infinitive)
  local irr = PastIrregular[inf]
  if irr then
    if gender == RusFixer.Gender.Female then return irr.f
    elseif gender == RusFixer.Gender.Neuter then return irr.n
    elseif gender == RusFixer.Gender.Plural then return irr.pl
    else return irr.m end
  end

  local basePast
  if endswith(inf, "ать") or endswith(inf, "ять") then
    basePast = utf8_utils.remove_last(inf, 3) .. "ал"
  elseif endswith(inf, "ить") then
    basePast = utf8_utils.remove_last(inf, 3) .. "ил"
  elseif endswith(inf, "еть") then
    basePast = utf8_utils.remove_last(inf, 3) .. "ел"
  elseif endswith(inf, "нуть") then
    basePast = utf8_utils.remove_last(inf, 4) .. "нул"
  elseif endswith(inf, "ти") then
    basePast = utf8_utils.remove_last(inf, 2) .. "л"
  elseif endswith(inf, "ть") then
    basePast = utf8_utils.remove_last(inf, 2) .. "л"
  else
    basePast = inf
  end

  if gender == RusFixer.Gender.Female then return basePast .. "а"
  elseif gender == RusFixer.Gender.Neuter then return basePast .. "о"
  elseif gender == RusFixer.Gender.Plural then return basePast .. "и"
  else return basePast end
end

function RusFixer.ConjugatePresent(infinitive, person)
  local inf = lower(infinitive)
  local forms = PresentIrregular[inf]
  if forms then return forms[person + 1] end -- Lua arrays start at 1

  if endswith(inf, "ать") or endswith(inf, "ять") then
    local stem = utf8_utils.remove_last(inf, 3)
    local formsReg = { stem.."аю", stem.."аешь", stem.."ает", stem.."аем", stem.."аете", stem.."ают" }
    return formsReg[person + 1]
  elseif endswith(inf, "ить") then
    local stem = utf8_utils.remove_last(inf, 3)
    local formsReg = { stem.."ю", stem.."ишь", stem.."ит", stem.."им", stem.."ите", stem.."ят" }
    return formsReg[person + 1]
  elseif endswith(inf, "еть") then
    local stem = utf8_utils.remove_last(inf, 3)
    local formsReg = { stem.."ю", stem.."ешь", stem.."ет", stem.."ем", stem.."ете", stem.."ют" }
    return formsReg[person + 1]
  end
  return inf
end

function RusFixer.ConjugateFuture(infinitive, person)
  local forms = { "буду","будешь","будет","будем","будете","будут" }
  return forms[person + 1] .. " " .. infinitive
end

function RusFixer.ToPrepositionalNoun(noun)
  noun = lower(noun)
  if endswith(noun, "а") or endswith(noun, "я") then
    return utf8_utils.remove_last(noun, 1) .. "е"
  elseif endswith(noun, "о") then
    return utf8_utils.remove_last(noun, 1) .. "е"
  elseif endswith(noun, "е") then
    return noun
  elseif endswith(noun, "ь") then
    return utf8_utils.remove_last(noun, 1) .. "е"
  else
    return noun .. "е"
  end
end

function RusFixer.ToPrepositionalAdj(adj, noun)
  adj = lower(adj)
  local gender, plural = guessGenderNumber(noun)

  if plural then
    if endswith(adj, "ые") then return utf8_utils.remove_last(adj, 2) .. "ых" end
    if endswith(adj, "ие") then return utf8_utils.remove_last(adj, 2) .. "их" end
    return adj
  end

  if gender == "m" then
    if endswith(adj, "ый") then return utf8_utils.remove_last(adj, 2) .. "ом" end
    if endswith(adj, "ий") then return utf8_utils.remove_last(adj, 2) .. "им" end
    if endswith(adj, "ой") then return utf8_utils.remove_last(adj, 2) .. "ом" end
    if endswith(adj, "кий") then return utf8_utils.remove_last(adj, 3) .. "ком" end
    return adj
  elseif gender == "f" then
    if endswith(adj, "ая") then return utf8_utils.remove_last(adj, 2) .. "ой" end
    if endswith(adj, "яя") then return utf8_utils.remove_last(adj, 2) .. "ей" end
    return adj
  elseif gender == "n" then
    if endswith(adj, "ое") then return utf8_utils.remove_last(adj, 2) .. "ом" end
    if endswith(adj, "ее") then return utf8_utils.remove_last(adj, 2) .. "ем" end
    return adj
  else
    return adj
  end
end

function RusFixer.ToAccusativeNoun(noun)
  noun = lower(noun)
  if endswith(noun, "а") then return utf8_utils.remove_last(noun, 1) .. "у" end
  if endswith(noun, "я") then return utf8_utils.remove_last(noun, 1) .. "ю" end
  return noun
end

function RusFixer.AgreeDeterminerAcc(det, noun)
  local gender, plural = guessGenderNumber(noun)
  det = lower(det)

  if plural then
    if det == "эти" or det == "те" or det == "мои" or det == "твои" or det == "наши" or det == "ваши" then
      return det
    else
      return det
    end
  end

  if gender == "f" then
    if det == "эта" then return "эту" end
    if det == "та" then return "ту" end
    if det == "моя" then return "мою" end
    if det == "твоя" then return "твою" end
    if det == "наша" then return "нашу" end
    if det == "ваша" then return "вашу" end
    return det
  elseif gender == "n" then
    if det == "это" or det == "то" or det == "моё" or det == "твоё" or det == "наше" or det == "ваше" then
      return det
    end
    return det
  else
    if det == "этот" or det == "тот" or det == "мой" or det == "твой" or det == "наш" or det == "ваш" then
      return det
    end
    return det
  end
end

local function agreeAdjectiveAcc(adj, noun)
  adj = lower(adj)
  local gender, plural = guessGenderNumber(noun)
  if plural then return adj end

  if gender == "f" then
    if endswith(adj, "ая") then return utf8_utils.remove_last(adj, 2) .. "ую" end
    if endswith(adj, "яя") then return utf8_utils.remove_last(adj, 2) .. "юю" end
    if endswith(adj, "ый") then return utf8_utils.remove_last(adj, 2) .. "ую" end -- упрощение
    if endswith(adj, "ий") then return utf8_utils.remove_last(adj, 2) .. "юю" end -- упрощение
  end
  return adj
end

-- ========================= Sentence splitting =========================

local function splitIntoSentencesWithDelimiters(text)
  local res = {}
  local current = {}

  for i = 1, #text do
    local ch = text:sub(i, i)
    if ch == "." or ch == "!" or ch == "?" then
      table.insert(res, { s = table.concat(current), endc = ch })
      current = {}
    else
      table.insert(current, ch)
    end
  end
  if #current > 0 then
    table.insert(res, { s = table.concat(current), endc = nil })
  end

  -- trim and filter empty
  local trimmed = {}
  for _, t in ipairs(res) do
    local s = t.s:gsub("^%s+", ""):gsub("%s+$", "")
    if #s > 0 then
      table.insert(trimmed, { s = s, endc = t.endc })
    end
  end
  return trimmed
end

local function tokenize(piece)
  local tokens = {}
  for w in piece:lower():gmatch("%S+") do
    table.insert(tokens, w)
  end
  return tokens
end

-- ========================= Main fix function =========================

function RusFixer:Fix(sentence, subjectGender, tense)
  subjectGender = subjectGender or RusFixer.Gender.Male
  tense = tense or RusFixer.Tense.Past

  local pieces = splitIntoSentencesWithDelimiters(sentence)
  local fixedPieces = {}

  for _, item in ipairs(pieces) do
    local piece, endc = item.s, item.endc
    local tokens = tokenize(piece)

    -- Определяем лицо для настоящего/будущего
    local person = 2
    for i = 1, math.min(3, #tokens) do  -- проверим первые 2-3 слова
      local t = tokens[i]
      if PersonIndex[t] ~= nil then
        person = PersonIndex[t]
        break
      end
    end


    local modal = hasModal(tokens)

    -- 1) Подлежащее → дательный при модале (если первое слово — местоимение)
    if modal then
      for i = 1, math.min(3, #tokens) do
        local t = tokens[i]
        if DativePronoun[t] then
          tokens[i] = DativePronoun[t]
          break
        end
      end
    end

    -- 2) "не" + инфинитив → прошедшее (только в Past)
    if tense == RusFixer.Tense.Past then
      local i = 1
      while i <= #tokens - 1 do
        if tokens[i] == "не" and isInfinitive_utf8(tokens[i + 1]) then
          tokens[i + 1] = RusFixer.ToPast(tokens[i + 1], subjectGender)
          i = i + 2
        else
          i = i + 1
        end
      end
    end

    -- 3) Глаголы: обработка по времени + спецкейсы модала и "хотеть"
    local i = 1
    while i <= #tokens do
      local w = tokens[i]

      -- 0) Если текущий токен — волевая форма (например, "хочу"/"хотел") и следующий — инфинитив, просто пропускаем пару
      if VolitionHead[w] and tokens[i+1] and isInfinitive_utf8(tokens[i+1]) then
      i = i + 2

      -- 0b) Если текущий токен — вспомогательная форма будущего ("буду" и т.п.),
      -- и следующий токен — "хотеть", а за ним инфинитив, пропускаем три токена
      elseif VolitionAux[w] and tokens[i+1] == "хотеть" and tokens[i+2] and isInfinitive_utf8(tokens[i+2]) then
      i = i + 3

      -- 1) Если текущий токен — инфинитив, но предыдущий — волевая голова, не трогаем его (страховка)
      elseif isInfinitive_utf8(w) and i > 1 and (VolitionHead[tokens[i - 1]] or (i > 2 and VolitionAux[tokens[i - 2]] and tokens[i - 1] == "хотеть")) then
      i = i + 1

      -- 2) После модала (однословного) → приводим текущую форму к инфинитиву
      elseif i > 1 and modal and ModalWords[tokens[i - 1]] then
        if not isInfinitive_utf8(w) then
          if endswith(w, "л") then
            tokens[i] = utf8_utils.remove_last(w, 1) .. "ть"
          elseif endswith(w, "ла") then
            tokens[i] = utf8_utils.remove_last(w, 2) .. "ть"
          elseif endswith(w, "ли") then
            tokens[i] = utf8_utils.remove_last(w, 2) .. "ть"
          end
        end
        i = i + 1

      -- 3) Обычная обработка инфинитива по времени
      elseif isInfinitive_utf8(w) then
        if tense == RusFixer.Tense.Past then
          if not (i > 1 and tokens[i - 1] == "не") then
            tokens[i] = RusFixer.ToPast(w, subjectGender)
          end
        elseif tense == RusFixer.Tense.Present then
          tokens[i] = RusFixer.ConjugatePresent(w, person)
        elseif tense == RusFixer.Tense.Future then
          tokens[i] = RusFixer.ConjugateFuture(w, person)
        end
        i = i + 1

      else
        i = i + 1
      end
    end

    -- 4) Предложный: "в + [adj]? + noun"
    local i = 1
    while i <= #tokens do
      if tokens[i] == "в" and i + 1 <= #tokens then
        if (i + 2 <= #tokens) and looksLikeAdjective(tokens[i + 1]) and looksLikeNoun(tokens[i + 2]) then
          tokens[i + 1] = RusFixer.ToPrepositionalAdj(tokens[i + 1], tokens[i + 2])
          tokens[i + 2] = RusFixer.ToPrepositionalNoun(tokens[i + 2])
          i = i + 3
        elseif looksLikeNoun(tokens[i + 1]) then
          tokens[i + 1] = RusFixer.ToPrepositionalNoun(tokens[i + 1])
          i = i + 2
        else
          i = i + 1
        end
      else
        i = i + 1
      end
    end

    -- 5) Винительный объект при модале: "[det?][adj?] noun" не после предлога
    if modal then
      local i = 1
      while i <= #tokens do
        if i > 1 and isPreposition(tokens[i - 1]) then
          i = i + 1
        else
          local start = i
          local det, adj, noun

          if i <= #tokens and looksLikeDeterminer(tokens[i]) then det = tokens[i]; i = i + 1 end
          if i <= #tokens and looksLikeAdjective(tokens[i]) then adj = tokens[i]; i = i + 1 end
          if i <= #tokens and looksLikeNoun(tokens[i]) then noun = tokens[i] end

          if noun then
            if det then tokens[start] = RusFixer.AgreeDeterminerAcc(det, noun) end
            if adj then tokens[start + (det and 1 or 0)] = agreeAdjectiveAcc(adj, noun) end
            tokens[start + (det and 1 or 0) + (adj and 1 or 0)] = RusFixer.ToAccusativeNoun(noun)
          end

          i = i + 1
        end
      end
    end

    -- 6) Согласование притяжательных местоимений
    local i = 1
    while i <= #tokens - 1 do
      local poss = tokens[i]
      if Possessives[poss] then
        if (i + 3 <= #tokens) and looksLikeNoun(tokens[i + 1]) and tokens[i + 2] == "и" and looksLikeNoun(tokens[i + 3]) then
          tokens[i] = Possessives[poss].pl
          i = i + 4
        elseif looksLikeNoun(tokens[i + 1]) then
          tokens[i] = agreePossessive(poss, tokens[i + 1])
          i = i + 2
        else
          i = i + 1
        end
      else
        i = i + 1
      end
    end

    -- >>> Новая строка: конвертируем первый глагол после модала в инфинитив, пропуская «вставки»
    applyInfinitiveAfterModal(tokens, true)

    -- 7) Капитализация + возвращаем исходный знак препинания
    local result = table.concat(tokens, " ")
    if #result > 0 then
      result = utf8_utils.capitalize(result)
    end
    if endc then result = result .. endc end

    table.insert(fixedPieces, result)
  end

  return table.concat(fixedPieces, " ")
end

_G.RusFixer = RusFixer