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

---@class RandomAI: AI
local RandomAI = AI:subclass("RandomAI")

---@param self RandomAI
---@param skill ActiveSkill
---@param card Card | nil
local function useActiveSkill(self, skill, card)
  local room = self.room
  local player = self.player

  local filter_func = skill.cardFilter
  if card then
    filter_func = function() return false end
  end

  if self.command == "PlayCard" and (not skill:canUse(player, card) or player:prohibitUse(card)) then
    return ""
  end

  local max_try_times = 100
  local selected_targets = {}
  local selected_cards = {}
  local min = skill:getMinTargetNum()
  local max = skill:getMaxTargetNum(player, card)
  local min_card = skill:getMinCardNum()
  local max_card = skill:getMaxCardNum()
  for _ = 0, max_try_times do
    if skill:feasible(selected_targets, selected_cards, self.player, card) then break end
    local avail_targets = table.filter(self.room:getAlivePlayers(), function(p)
      local ret = skill:targetFilter(p.id, selected_targets, selected_cards, card or Fk:cloneCard'zixing')
      if ret and card then
        if player:prohibitUse(card) then
          ret = false
        end
      end
      return ret
    end)
    avail_targets = table.map(avail_targets, function(p) return p.id end)
    local avail_cards = table.filter(player:getCardIds{ Player.Hand, Player.Equip }, function(id)
      return filter_func(skill, id, selected_cards, selected_targets)
    end)

    if #avail_targets == 0 and #avail_cards == 0 then break end
    table.insertIfNeed(selected_targets, table.random(avail_targets))
    table.insertIfNeed(selected_cards, table.random(avail_cards))
  end
  if skill:feasible(selected_targets, selected_cards, self.player, card) then
    local ret = json.encode{
      card = card and card.id or json.encode{
        skill = skill.name,
        subcards = selected_cards,
      },
      targets = selected_targets,
    }
    -- print(ret)
    return ret
  end
  return ""
end

---@param self RandomAI
---@param skill ViewAsSkill
local function useVSSkill(self, skill, pattern, cancelable, extra_data)
  local player = self.player
  local room = self.room
  local precondition

  if self.command == "PlayCard" then
    precondition = skill:enabledAtPlay(player)
    if not precondition then return nil end
    local exp = Exppattern:Parse(skill.pattern)
    local cnames = {}
    for _, m in ipairs(exp.matchers) do
      if m.name then table.insertTable(cnames, m.name) end
    end
    for _, n in ipairs(cnames) do
      local c = Fk:cloneCard(n)
      precondition = c.skill:canUse(Self, c)
      if precondition then break end
    end
  else
    precondition = skill:enabledAtResponse(player)
    if not precondition then return nil end
    local exp = Exppattern:Parse(pattern)
    precondition = exp:matchExp(skill.pattern)
  end

  if (not precondition) or math.random() < 0.2 then return nil end

  local selected_cards = {}
  local max_try_time = 100

  for _ = 0, max_try_time do
    local avail_cards = table.filter(player:getCardIds{ Player.Hand, Player.Equip }, function(id)
      return skill:cardFilter(id, selected_cards)
    end)
    if #avail_cards == 0 then break end
    table.insert(selected_cards, table.random(avail_cards))
    if skill:viewAs(selected_cards) then
      return {
        skill = skill.name,
        subcards = selected_cards,
      }
    end
  end
  return nil
end

local random_cb = {}

random_cb.AskForUseActiveSkill = function(self, jsonData)
  local data = json.decode(jsonData)
  local skill = Fk.skills[data[1]]
  local cancelable = data[3]
  if cancelable and math.random() < 0.25 then return "" end
  local extra_data = json.decode(data[4])
  for k, v in pairs(extra_data) do
    skill[k] = v
  end
  return useActiveSkill(self, skill)
end

random_cb.AskForSkillInvoke = function(self, jsonData)
  return table.random{"1", ""}
end

random_cb.AskForUseCard = function(self, jsonData)
  local player = self.player
  local data = json.decode(jsonData)
  local card_name = data[1]
  local pattern = data[2] or card_name
  local cancelable = data[4] or true
  local exp = Exppattern:Parse(pattern)

  local avail_cards = table.filter(player:getCardIds("he"), function(id)
    return exp:match(Fk:getCardById(id)) and not player:prohibitUse(Fk:getCardById(id))
  end)
  if #avail_cards > 0 then
    if math.random() < 0.25 then return "" end
    for _, card in ipairs(avail_cards) do
      local skill = Fk:getCardById(card).skill
      local max_try_times = 100
      local selected_targets = {}
      local min = skill:getMinTargetNum()
      local max = skill:getMaxTargetNum(player, card)
      local min_card = skill:getMinCardNum()
      local max_card = skill:getMaxCardNum()
      for _ = 0, max_try_times do
        if skill:feasible(selected_targets, {card}, self.player, card) then break end
        local avail_targets = table.filter(self.room:getAlivePlayers(), function(p)
          local ret = skill:targetFilter(p.id, selected_targets, {card}, card or Fk:cloneCard'zixing')
          return ret
        end)
        avail_targets = table.map(avail_targets, function(p) return p.id end)

        if #avail_targets == 0 and #avail_cards == 0 then break end
        table.insertIfNeed(selected_targets, table.random(avail_targets))
        table.insertIfNeed({card}, table.random(avail_cards))
      end
      if skill:feasible(selected_targets, {card}, self.player, card) then
        return json.encode{
          card = table.random(avail_cards),
          targets = selected_targets,
        }
      end
    end
  end
  return ""
end

---@param self RandomAI
random_cb.AskForResponseCard = function(self, jsonData)
  local data = json.decode(jsonData)
  local pattern = data[2]
  local cancelable = true
  local exp = Exppattern:Parse(pattern)
  local avail_cards = table.filter(self.player:getCardIds{ Player.Hand, Player.Equip }, function(id)
    return exp:match(Fk:getCardById(id))
  end)
  if #avail_cards > 0 then return json.encode{
    card = table.random(avail_cards),
    targets = {},
  } end
  -- TODO: vs skill
  return ""
end

---@param self RandomAI
random_cb.PlayCard = function(self, jsonData)
  local cards = table.map(self.player:getCardIds(Player.Hand),
    function(id) return Fk:getCardById(id) end)
  local actives = table.filter(self.player:getAllSkills(), function(s)
    return s:isInstanceOf(ActiveSkill)
  end)
  local vss = table.filter(self.player:getAllSkills(), function(s)
    return s:isInstanceOf(ViewAsSkill)
  end)
  table.insertTable(cards, actives)
  table.insertTable(cards, vss)

  while #cards > 0 do
    local sth = table.random(cards)
    if sth:isInstanceOf(Card) then
      local card = sth
      local skill = card.skill ---@type ActiveSkill
      if math.random() > 0.15 then
        local ret = useActiveSkill(self, skill, card)
        if ret ~= "" then return ret end
        table.removeOne(cards, card)
      else
        table.removeOne(cards, card)
      end
    elseif sth:isInstanceOf(ActiveSkill) then
      local active = sth
      if math.random() > 0.30 then
        local ret = useActiveSkill(self, active, nil)
        if ret ~= "" then return ret end
      end
      table.removeOne(cards, active)
    else
      local vs = sth
      if math.random() > 0.20 then
        local ret = useVSSkill(self, vs)
        -- TODO: handle vs result
      end
      table.removeOne(cards, vs)
    end
  end

  return ""
end

function RandomAI:initialize(player)
  AI.initialize(self, player)
  self.cb_table = random_cb
end

return RandomAI
