-- SPDX-License-Identifier: GPL-3.0-or-later

local playCardEmotionAndSound = function(room, player, card)
  if card.type ~= Card.TypeEquip then
    local anim_path = "./packages/" .. card.package.extensionName .. "/image/anim/" .. card.name
    if not FileIO.exists(anim_path) then
      for _, dir in ipairs(FileIO.ls("./packages/")) do
        anim_path = "./packages/" .. dir .. "/image/anim/" .. card.name
        if FileIO.exists(anim_path) then break end
      end
    end
    if FileIO.exists(anim_path) then room:setEmotion(player, anim_path) end
  end

  local soundName
  if card.type == Card.TypeEquip then
    local subTypeStr
    if card.sub_type == Card.SubtypeDefensiveRide or card.sub_type == Card.SubtypeOffensiveRide then
      subTypeStr = "horse"
    elseif card.sub_type == Card.SubtypeWeapon then
      subTypeStr = "weapon"
    else
      subTypeStr = "armor"
    end

    soundName = "./audio/card/common/" .. subTypeStr
  else
    soundName = "./packages/" .. card.package.extensionName .. "/audio/card/"
      .. (player.gender == General.Male and "male/" or "female/") .. card.name
    if not FileIO.exists(soundName .. ".mp3") then
      for _, dir in ipairs(FileIO.ls("./packages/")) do
        soundName = "./packages/" .. dir .. "/audio/card/"
          .. (player.gender == General.Male and "male/" or "female/") .. card.name
        if FileIO.exists(soundName .. ".mp3") then break end
      end
    end
  end
  room:broadcastPlaySound(soundName)
end

---@param room Room
---@param cardUseEvent CardUseStruct
local sendCardEmotionAndLog = function(room, cardUseEvent)
  local from = cardUseEvent.from
  local _card = cardUseEvent.card

  -- when this function is called, card is already in PlaceTable and no filter skill is applied.
  -- So filter this card manually here to get 'real' use.card
  local card = _card
  ---[[
  if not _card:isVirtual() then
    local temp = { card = _card }
    Fk:filterCard(_card.id, room:getPlayerById(from), temp)
    card = temp.card
  end
  cardUseEvent.card = card
  --]]

  playCardEmotionAndSound(room, room:getPlayerById(from), card)
  room:doAnimate("Indicate", {
    from = from,
    to = cardUseEvent.tos or Util.DummyTable,
  })

  local useCardIds = card:isVirtual() and card.subcards or { card.id }
  if cardUseEvent.tos and #cardUseEvent.tos > 0 then
    local to = {}
    for _, t in ipairs(cardUseEvent.tos) do
      table.insert(to, t[1])
    end

    if card:isVirtual() or (card ~= _card) then
      if #useCardIds == 0 then
        room:sendLog{
          type = "#UseV0CardToTargets",
          from = from,
          to = to,
          arg = card:toLogString(),
        }
      else
        room:sendLog{
          type = "#UseVCardToTargets",
          from = from,
          to = to,
          card = useCardIds,
          arg = card:toLogString(),
        }
      end
    else
      room:sendLog{
        type = "#UseCardToTargets",
        from = from,
        to = to,
        card = useCardIds
      }
    end

    for _, t in ipairs(cardUseEvent.tos) do
      if t[2] then
        local temp = {table.unpack(t)}
        table.remove(temp, 1)
        room:sendLog{
          type = "#CardUseCollaborator",
          from = t[1],
          to = temp,
          arg = card.name,
        }
      end
    end
  elseif cardUseEvent.toCard then
    if card:isVirtual() or (card ~= _card) then
      if #useCardIds == 0 then
        room:sendLog{
          type = "#UseV0CardToCard",
          from = from,
          arg = cardUseEvent.toCard.name,
          arg2 = card:toLogString(),
        }
      else
        room:sendLog{
          type = "#UseVCardToCard",
          from = from,
          card = useCardIds,
          arg = cardUseEvent.toCard.name,
          arg2 = card:toLogString(),
        }
      end
    else
      room:sendLog{
        type = "#UseCardToCard",
        from = from,
        card = useCardIds,
        arg = cardUseEvent.toCard.name,
      }
    end
  else
    if card:isVirtual() or (card ~= _card) then
      if #useCardIds == 0 then
        room:sendLog{
          type = "#UseV0Card",
          from = from,
          arg = card:toLogString(),
        }
      else
        room:sendLog{
          type = "#UseVCard",
          from = from,
          card = useCardIds,
          arg = card:toLogString(),
        }
      end
    else
      room:sendLog{
        type = "#UseCard",
        from = from,
        card = useCardIds,
      }
    end
  end

  if #useCardIds == 0 then return end
  if cardUseEvent.tos and #cardUseEvent.tos > 0 and #cardUseEvent.tos <= 2 then
    local tos = table.map(cardUseEvent.tos, function(e) return e[1] end)
    room:sendFootnote(useCardIds, {
      type = "##UseCardTo",
      from = from,
      to = tos,
    })
    if card:isVirtual() then
      room:sendCardVirtName(useCardIds, card.name)
    end
  else
    room:sendFootnote(useCardIds, {
      type = "##UseCard",
      from = from,
    })
    if card:isVirtual() then
      room:sendCardVirtName(useCardIds, card.name)
    end
  end
end

GameEvent.functions[GameEvent.UseCard] = function(self)
  local cardUseEvent = table.unpack(self.data)
  local room = self.room
  local logic = room.logic

  room:moveCardTo(cardUseEvent.card, Card.Processing, nil, fk.ReasonUse)

  if cardUseEvent.card.skill then
    cardUseEvent.card.skill:onUse(room, cardUseEvent)
  end

  if logic:trigger(fk.PreCardUse, room:getPlayerById(cardUseEvent.from), cardUseEvent) then
    logic:breakEvent()
  end

  sendCardEmotionAndLog(room, cardUseEvent)

  if not cardUseEvent.extraUse then
    room:getPlayerById(cardUseEvent.from):addCardUseHistory(cardUseEvent.card.trueName, 1)
  end

  if cardUseEvent.responseToEvent then
    cardUseEvent.responseToEvent.cardsResponded = cardUseEvent.responseToEvent.cardsResponded or {}
    table.insertIfNeed(cardUseEvent.responseToEvent.cardsResponded, cardUseEvent.card)
  end

  for _, event in ipairs({ fk.AfterCardUseDeclared, fk.AfterCardTargetDeclared, fk.CardUsing }) do
    if not cardUseEvent.toCard and #TargetGroup:getRealTargets(cardUseEvent.tos) == 0 then
      break
    end

    logic:trigger(event, room:getPlayerById(cardUseEvent.from), cardUseEvent)
    if event == fk.CardUsing then
      room:doCardUseEffect(cardUseEvent)
    end
  end
end

GameEvent.cleaners[GameEvent.UseCard] = function(self)
  local cardUseEvent = table.unpack(self.data)
  local room = self.room

  room.logic:trigger(fk.CardUseFinished, room:getPlayerById(cardUseEvent.from), cardUseEvent)

  local leftRealCardIds = room:getSubcardsByRule(cardUseEvent.card, { Card.Processing })
  if #leftRealCardIds > 0 then
    room:moveCards({
      ids = leftRealCardIds,
      toArea = Card.DiscardPile,
      moveReason = fk.ReasonUse,
    })
  end
end

GameEvent.functions[GameEvent.RespondCard] = function(self)
  local cardResponseEvent = table.unpack(self.data)
  local room = self.room
  local logic = room.logic
  local from = cardResponseEvent.customFrom or cardResponseEvent.from
  local card = cardResponseEvent.card
  local cardIds = room:getSubcardsByRule(card)

  if card:isVirtual() then
    if #cardIds == 0 then
      room:sendLog{
        type = "#ResponsePlayV0Card",
        from = from,
        arg = card:toLogString(),
      }
    else
      room:sendLog{
        type = "#ResponsePlayVCard",
        from = from,
        card = cardIds,
        arg = card:toLogString(),
      }
    end
  else
    room:sendLog{
      type = "#ResponsePlayCard",
      from = from,
      card = cardIds,
    }
  end
  room:moveCardTo(card, Card.Processing, nil, fk.ReasonResonpse)
  if #cardIds > 0 then
    room:sendFootnote(cardIds, {
      type = "##ResponsePlayCard",
      from = from,
    })
    if card:isVirtual() then
      room:sendCardVirtName(cardIds, card.name)
    end
  end

  if logic:trigger(fk.PreCardRespond, room:getPlayerById(cardResponseEvent.from), cardResponseEvent) then
    logic:breakEvent()
  end

  playCardEmotionAndSound(room, room:getPlayerById(from), card)

  logic:trigger(fk.CardResponding, room:getPlayerById(cardResponseEvent.from), cardResponseEvent)
end

GameEvent.cleaners[GameEvent.RespondCard] = function(self)
  local cardResponseEvent = table.unpack(self.data)
  local room = self.room

  room.logic:trigger(fk.CardRespondFinished, room:getPlayerById(cardResponseEvent.from), cardResponseEvent)

  local realCardIds = room:getSubcardsByRule(cardResponseEvent.card, { Card.Processing })
  if #realCardIds > 0 and not cardResponseEvent.skipDrop then
    room:moveCards({
      ids = realCardIds,
      toArea = Card.DiscardPile,
      moveReason = fk.ReasonResonpse,
    })
  end
end

GameEvent.functions[GameEvent.CardEffect] = function(self)
  local cardEffectEvent = table.unpack(self.data)
  local room = self.room
  local logic = room.logic

  for _, event in ipairs({ fk.PreCardEffect, fk.BeforeCardEffect, fk.CardEffecting, fk.CardEffectFinished }) do
    local user = cardEffectEvent.from and room:getPlayerById(cardEffectEvent.from) or nil
    if cardEffectEvent.isCancellOut then
      if logic:trigger(fk.CardEffectCancelledOut, user, cardEffectEvent) then
        cardEffectEvent.isCancellOut = false
      else
        logic:breakEvent()
      end
    end

    if
      not cardEffectEvent.toCard and
      (
        not (room:getPlayerById(cardEffectEvent.to):isAlive() and cardEffectEvent.to)
        or #room:deadPlayerFilter(TargetGroup:getRealTargets(cardEffectEvent.tos)) == 0
      )
    then
      logic:breakEvent()
    end

    if table.contains((cardEffectEvent.nullifiedTargets or Util.DummyTable), cardEffectEvent.to) then
      logic:breakEvent()
    end

    if event == fk.PreCardEffect then
      if cardEffectEvent.from and logic:trigger(event, room:getPlayerById(cardEffectEvent.from), cardEffectEvent) then
        logic:breakEvent()
      end
    elseif cardEffectEvent.to and logic:trigger(event, room:getPlayerById(cardEffectEvent.to), cardEffectEvent) then
      logic:breakEvent()
    end

    room:handleCardEffect(event, cardEffectEvent)
  end
end
