local SHOP_OPCODE = 201
local shopWindow
local shopButton
local confirmWindow
local selectedCategory = 'RUNES'
local selectedOffer
local status = {points = 0}
local searchText = ''
local quantityUpdating = false

-- Precos e produtos carregados EXATAMENTE da tabela z_shop_offer.sql enviada pela Bianca.
-- points = custo em Premium Coins por 1 oferta. count1 = quantidade entregue por 1 oferta.
local OFFERS = {
  RUNES = {
    {offerId=2, item=2287, clientItem=3174, count=5, price=5, name='5 LMM', description='5 Light Magic Missile Runes', stackable=true, max=100},
    {offerId=3, item=2311, clientItem=3198, count=5, price=8, name='5 HMM', description='5 Heavy Magic Missile Runes', stackable=true, max=100},
    {offerId=4, item=2304, clientItem=3191, count=3, price=8, name='3 GFB', description='3 Great Fireball Runes', stackable=true, max=100},
    {offerId=5, item=2313, clientItem=3200, count=3, price=10, name='3 Explosion', description='3 Explosion Runes', stackable=true, max=100},
    {offerId=6, item=2268, clientItem=3155, count=3, price=15, name='3 SD', description='3 Sudden Death Runes', stackable=true, max=100},
    {offerId=7, item=2273, clientItem=3160, count=3, price=10, name='3 UH', description='3 Ultimate Healing Runes', stackable=true, max=100},
    {offerId=8, item=2278, clientItem=3165, count=3, price=8, name='3 Paralyze', description='3 Paralyze Runes', stackable=true, max=100},
  },
  PACKS = {
    {offerId=9, item=8406, clientItem=8224, count=1, price=10, name='UH Backpack', description='Ultimate Healing Backpack', stackable=false, max=1},
    {offerId=10, item=8407, clientItem=8225, count=1, price=12, name='SD Backpack', description='Sudden Death Backpack', stackable=false, max=1},
    {offerId=11, item=8408, clientItem=8226, count=1, price=10, name='Energy Missile Backpack', description='Energy Missile Backpack', stackable=false, max=1},
    {offerId=12, item=8409, clientItem=8227, count=1, price=10, name='Mana Backpack', description='Mana Fluid Backpack', stackable=false, max=1},
    {offerId=13, item=8410, clientItem=8228, count=1, price=10, name='Magic Wall Backpack', description='Magic Wall Backpack', stackable=false, max=1},
    {offerId=14, item=8411, clientItem=8229, count=1, price=10, name='GFB Backpack', description='Greatfire Backpack', stackable=false, max=1},
    {offerId=15, item=8412, clientItem=8230, count=1, price=12, name='Destroy Backpack', description='Destroy Backpack', stackable=false, max=1},
  },
  COINS = {
    {offerId=16, item=8367, clientItem=8188, count=10, price=20, name='10 Roulette Coin', description='Coin used in the roulette', stackable=true, max=100},
    {offerId=17, item=8426, clientItem=8243, count=10, price=5, name='10 Slotmachine Coin', description='Coin used in the slot machine', stackable=true, max=100},
  },
  ADDONS = {
    {offerId=18, item=8357, clientItem=8178, count=1, price=20, name='Citizen Outfit', description='Doll of Citizen Outfit', stackable=false, max=1},
    {offerId=19, item=8361, clientItem=8182, count=1, price=20, name='Hunter Outfit', description='Doll of Hunter Outfit', stackable=false, max=1},
    {offerId=20, item=8404, clientItem=8222, count=1, price=20, name='Mage Outfit', description='Doll of Mage Outfit', stackable=false, max=1},
    {offerId=21, item=8356, clientItem=8177, count=1, price=20, name='Knight Outfit', description='Doll of Knight Outfit', stackable=false, max=1},
    {offerId=22, item=8428, clientItem=8245, count=1, price=20, name='Noble Outfit', description='Doll of Noble Outfit', stackable=false, max=1},
    {offerId=23, item=8349, clientItem=8170, count=1, price=20, name='Summoner Outfit', description='Doll of Summoner Outfit', stackable=false, max=1},
    {offerId=24, item=8427, clientItem=8244, count=1, price=20, name='Warrior Outfit', description='Doll of Warrior Outfit', stackable=false, max=1},
    {offerId=25, item=8362, clientItem=8183, count=1, price=20, name='Druid Outfit', description='Doll of Druid Outfit', stackable=false, max=1},
    {offerId=26, item=8360, clientItem=8181, count=1, price=25, name='Oriental Outfit', description='Doll of Oriental Outfit', stackable=false, max=1},
    {offerId=27, item=8358, clientItem=8179, count=1, price=25, name='Assassin Outfit', description='Doll of Assassin Outfit', stackable=false, max=1},
    {offerId=28, item=8431, clientItem=8248, count=1, price=25, name='Guardian Outfit', description='Doll of Guardian Outfit', stackable=false, max=1},
    {offerId=29, item=8432, clientItem=8249, count=1, price=25, name='Spike Elite Outfit', description='Doll of Spike Elite Outfit', stackable=false, max=1},
    {offerId=30, item=8433, clientItem=8250, count=1, price=25, name='Mysticelite Outfit', description='Doll of Mysticelite Outfit', stackable=false, max=1},
    {offerId=31, item=8430, clientItem=8247, count=1, price=30, name='Yalahari Outfit', description='Doll of Yalahari Outfit', stackable=false, max=1},
    {offerId=32, item=8355, clientItem=8176, count=1, price=25, name='Brotherhood Outfit', description='Doll of Brotherhood Outfit', stackable=false, max=1},
    {offerId=33, item=8348, clientItem=8169, count=1, price=30, name='Golden Outfit', description='Doll of Golden Outfit', stackable=false, max=1},
    {offerId=34, item=8429, clientItem=8246, count=1, price=25, name='Crown Outfit', description='Doll of Crown Outfit', stackable=false, max=1},
    {offerId=35, item=8417, clientItem=8235, count=1, price=30, name='Dragon Slayer Outfit', description='Doll of Dragon Slayer Outfit', stackable=false, max=1},
    {offerId=36, item=8353, clientItem=8174, count=1, price=25, name='Conjurer Outfit', description='Doll of Conjurer Outfit', stackable=false, max=1},
    {offerId=37, item=8363, clientItem=8184, count=1, price=30, name='Rascoohan Outfit', description='Doll of Rascoohan Outfit', stackable=false, max=1},
    {offerId=38, item=8434, clientItem=8247, count=1, price=30, name='Poltergeist Outfit', description='Scroll of Poltergeist Outfit', stackable=false, max=1},
    {offerId=39, item=8422, clientItem=8240, count=1, price=30, name='Angel Outfit', description='Doll of Angel Outfit', stackable=false, max=1},
    {offerId=40, item=8418, clientItem=8236, count=1, price=30, name='Demon Outfit', description='Doll of Demon Outfit', stackable=false, max=1},
    {offerId=41, item=8419, clientItem=8237, count=1, price=30, name='Centurion Outfit', description='Doll of Centurion Outfit', stackable=false, max=1},
    {offerId=42, item=8354, clientItem=8175, count=1, price=35, name='Tomb Assassin Outfit', description='Doll of Tomb Assassin Outfit', stackable=false, max=1},
  },
  KNIGHT = {
    {offerId=55, item=8113, clientItem=7934, count=1, price=90, name='Imperial Helmet', description='Armor: 12 | Axe +2 | Sword +2 | Club +2', stackable=false, max=1},
    {offerId=56, item=8111, clientItem=7932, count=1, price=110, name='Imperial Armor', description='Armor: 18 | Axe +2 | Sword +2 | Club +2', stackable=false, max=1},
    {offerId=57, item=8114, clientItem=7935, count=1, price=90, name='Imperial Legs', description='Armor: 10 | Axe +2 | Sword +2 | Club +2', stackable=false, max=1},
    {offerId=58, item=8112, clientItem=7933, count=1, price=70, name='Imperial Boots', description='Armor: 3 | Axe +2 | Sword +2 | Club +2', stackable=false, max=1},
    {offerId=59, item=8115, clientItem=7936, count=1, price=100, name='Imperial Shield', description='Defense: 39 | Axe +2 | Sword +2 | Club +2', stackable=false, max=1},
  },
  PALADIN = {
    {offerId=51, item=5813, clientItem=5641, count=1, price=80, name='Renegade Helmet', description='Armor: 11 | Distance +2 |   Absorb All: +1%', stackable=false, max=1},
    {offerId=52, item=5814, clientItem=5642, count=1, price=100, name='Renegade Armor', description='Armor: 17 | Magic Level: +1 | Distance +2 |   Absorb All: +1%', stackable=false, max=1},
    {offerId=53, item=5815, clientItem=5643, count=1, price=80, name='Renegade Legs', description='Armor: 9 | Distance +2 |   Absorb All: +1%', stackable=false, max=1},
    {offerId=54, item=8095, clientItem=7916, count=1, price=60, name='Renegade Boots', description='Armor: 2 | Distance +2 | Mana Gain: +2', stackable=false, max=1},
  },
  SORC = {
    {offerId=47, item=8126, clientItem=7947, count=1, price=70, name='Burning Rage Hat', description='Armor: 8 | Magic Level: +2 | Absorb All: +1%', stackable=false, max=1},
    {offerId=48, item=8127, clientItem=7948, count=1, price=70, name='Burning Rage Pants', description='Armor: 8 | Magic Level: +2 | Absorb All: +1%', stackable=false, max=1},
    {offerId=49, item=8125, clientItem=7946, count=1, price=90, name='Burning Rage Cloak', description='Armor: 16 | Magic Level: +2 | Absorb All: +1%', stackable=false, max=1},
    {offerId=50, item=8128, clientItem=7949, count=1, price=60, name='Burning Rage Boots', description='Armor: 2 | Magic Level: +2 | Mana Gain: +2', stackable=false, max=1},
  },
  DRUID = {
    {offerId=43, item=8129, clientItem=7950, count=1, price=60, name='Icecold Hat', description='Armor: 8 | Magic Level: +2 | Absorb All: +1%', stackable=false, max=1},
    {offerId=44, item=8130, clientItem=7951, count=1, price=90, name='Icecold Robe', description='Armor: 16 | Magic Level: +2 | Absorb All: +1%', stackable=false, max=1},
    {offerId=45, item=8131, clientItem=7952, count=1, price=60, name='Icecold Pants', description='Armor: 8 | Magic Level: +2 |  Absorb All: +1%', stackable=false, max=1},
    {offerId=46, item=8132, clientItem=7953, count=1, price=50, name='Icecold Boots', description='Armor: 2 | Magic Level: +2 |  Absorb All: +1%', stackable=false, max=1},
  },
}

local CATEGORIES = {
  {id='RUNES', label='Runas'},
  {id='PACKS', label='Packs'},
  {id='COINS', label='Coins'},
  {id='ADDONS', label='Addons'},
  {id='KNIGHT', label='Knight'},
  {id='PALADIN', label='Paladin'},
  {id='SORC', label='Sorcerer'},
  {id='DRUID', label='Druid'},
}

local function send(action, data)
  local pg = g_game.getProtocolGame()
  if pg then
    pg:sendExtendedJSONOpcode(SHOP_OPCODE, {action=action, data=data or {}})
  end
end

local function getOffers(category) return OFFERS[category] or {} end

local function matchesSearch(offer)
  if searchText == '' then return true end
  local q = searchText:lower()
  return tostring(offer.name):lower():find(q, 1, true) ~= nil or tostring(offer.description):lower():find(q, 1, true) ~= nil
end

local function updateBalance()
  if shopWindow and shopWindow.header and shopWindow.header.balance and shopWindow.header.balance.points then
    shopWindow.header.balance.points:setText(tostring(status.points or 0))
  end
end

local function updateQuantity(value, source)
  if not shopWindow or not selectedOffer or quantityUpdating then return end
  local d = shopWindow.body.details
  local max = tonumber(selectedOffer.max or 1) or 1
  value = math.floor(tonumber(value) or 1)
  value = math.max(1, math.min(max, value))

  quantityUpdating = true
  if source ~= d.quantity and d.quantity and d.quantity:getValue() ~= value then
    d.quantity:setValue(value)
  end
  if source ~= d.quantitySpin and d.quantitySpin and d.quantitySpin:getValue() ~= value then
    d.quantitySpin:setValue(value)
  end
  quantityUpdating = false

  local total = value * (tonumber(selectedOffer.price or 0) or 0)
  d.quantityLabel:setText(string.format('QUANTIDADE: %d', value))
  d.total:setText(string.format('TOTAL: %d', total))
  d.buy:setEnabled((status.points or 0) >= total)
end

local function selectOffer(offer)
  if not offer then return end
  selectedOffer = offer
  local d = shopWindow.body.details
  d.preview:setItemId(tonumber(offer.clientItem or offer.item) or 0)
  d.name:setText(offer.name or '')
  d.description:setText(offer.description or '')
  local max = tonumber(offer.max or 1)
  d.quantity:setMinimum(1); d.quantity:setMaximum(max); d.quantity:setValue(1)
  d.quantitySpin:setMinimum(1); d.quantitySpin:setMaximum(max); d.quantitySpin:setValue(1)
  updateQuantity(1)
end

local function cancelPurchaseQueue()
  -- Mantido por compatibilidade com versões anteriores do módulo.
end

local function closeConfirm()
  if confirmWindow then confirmWindow:destroy(); confirmWindow=nil end
end

-- Uma única requisição para a compra inteira.
-- O servidor deve usar 'quantity' para debitar o TOTAL uma única vez
-- e entregar count * quantity unidades.
local function sendPurchaseQuantity(offer, qty)
  local unitCount = tonumber(offer.count or 1) or 1
  local total = (tonumber(offer.price or 0) or 0) * qty
  send('buy', {
    id=offer.offerId,
    offerId=offer.offerId,
    item=offer.item,
    itemid=offer.item,
    quantity=qty,
    count=unitCount,
    delivered=unitCount * qty,
    price=offer.price,
    total=total
  })
end

local function firstConfirmation()
  if not selectedOffer then return end
  local qty = tonumber(shopWindow.body.details.quantity:getValue()) or 1
  local max = tonumber(selectedOffer.max or 1) or 1
  qty = math.max(1, math.min(max, qty))
  local unitPrice = tonumber(selectedOffer.price or 0) or 0
  local unitCount = tonumber(selectedOffer.count or 1) or 1
  local total = qty * unitPrice
  local delivered = qty * unitCount
  if (status.points or 0) < total then
    displayInfoBox('Gandora Shop', 'Premium Coins insuficientes.')
    return
  end
  closeConfirm()
  confirmWindow = displayGeneralBox('Revisar compra', string.format('%s\n\nOfertas: %d\nEntrega total: %d unidade(s)\nPreco por oferta: %d Premium Coins\nTOTAL: %d Premium Coins\n\nDeseja continuar?', selectedOffer.name, qty, delivered, unitPrice, total), {
    {text='CONTINUAR', callback=function()
      closeConfirm()
      confirmWindow = displayGeneralBox('Confirmar compra', string.format('CONFIRMACAO FINAL\n\n%s\nQuantidade de ofertas: %d\nEntrega total: %d\nTOTAL: %d Premium Coins\n\nConfirmar agora?', selectedOffer.name, qty, delivered, total), {
        {text='CONFIRMAR', callback=function()
          closeConfirm()
          sendPurchaseQuantity(selectedOffer, qty)
        end},
        {text='CANCELAR', callback=function() closeConfirm() end}
      })
      confirmWindow:show(); confirmWindow:raise(); confirmWindow:focus()
    end},
    {text='CANCELAR', callback=function() closeConfirm() end}
  })
  confirmWindow:show(); confirmWindow:raise(); confirmWindow:focus()
end

local function renderOffers()
  if not shopWindow then return end
  if not OFFERS[selectedCategory] then selectedCategory='RUNES' end
  local list = shopWindow.body.middle.offers
  list:destroyChildren()
  shopWindow.body.middle.section:setText((CATEGORIES[1] and selectedCategory) or selectedCategory)
  local shown=0
  for _, offer in ipairs(getOffers(selectedCategory)) do
    if matchesSearch(offer) then
      local card=g_ui.createWidget('ShopOfferCard', list)
      card.item:setItemId(tonumber(offer.clientItem or offer.item) or 0)
      card.name:setText(offer.name or '')
      card.price:setText(tostring(offer.price or 0))
      if offer.stackable then card.badge:setText('x'..tostring(offer.count)); card.badge:setVisible(true) end
      card.onClick=function() selectOffer(offer) end
      card.item.onClick=function() selectOffer(offer) end
      card.buy.onClick=function() selectOffer(offer); firstConfirmation() end
      card.buy:setEnabled((status.points or 0) >= tonumber(offer.price or 0))
      shown=shown+1
    end
  end
  if shown==0 then shopWindow.body.middle.section:setText(selectedCategory..' - NENHUM ITEM ENCONTRADO') end
end

local function buildCategories()
  local list=shopWindow.body.categories.list
  list:destroyChildren()
  for _, category in ipairs(CATEGORIES) do
    local b=g_ui.createWidget('ShopCategoryButton', list)
    b:setId('cat_'..category.id); b:setText(category.label)
    b.onClick=function()
      selectedCategory=category.id
      for _, c in ipairs(CATEGORIES) do local other=list:getChildById('cat_'..c.id); if other then other:setChecked(c.id==selectedCategory) end end
      renderOffers(); selectOffer(getOffers(selectedCategory)[1])
    end
  end
  local first=list:getChildById('cat_RUNES'); if first then first:setChecked(true) end
end

function createShop()
  if shopWindow then return end
  shopWindow=g_ui.displayUI('shop'); shopWindow:hide()
  shopButton=modules.client_topmenu.addLeftGameButton('gandoraShopButton','Shop','/images/topbuttons/shop',toggle,false,8)
  g_keyboard.bindKeyDown('Alt+S',toggle)
  shopWindow.body.middle.searchBar.search.onTextChange=function(_,text) searchText=tostring(text or ''):lower(); renderOffers() end
  shopWindow.body.details.quantity.onValueChange=function(widget,value) updateQuantity(value, widget) end
  shopWindow.body.details.quantitySpin.onValueChange=function(widget,value) updateQuantity(value, widget) end
  shopWindow.body.details.buy.onClick=firstConfirmation
  buildCategories(); renderOffers(); selectOffer(getOffers(selectedCategory)[1]); updateBalance()
end

function onOpcode(_, code, json)
  if code~=SHOP_OPCODE then return end
  createShop()
  if json and json.status then status=json.status; updateBalance(); renderOffers(); if selectedOffer then selectOffer(selectedOffer) end end
  if json and json.action=='message' and json.data then
    local msg=tostring(json.data.msg or ''):lower()
    local title=tostring(json.data.title or ''):lower()
    -- Sucesso de compra: nao exibir popup.
    -- Cobre mensagens com e sem acento e os titulos usados pelo servidor.
    local isPurchaseSuccess =
      msg:find('compra realizada', 1, true) or
      msg:find('compra realizada com sucesso', 1, true) or
      msg:find('compra conclu', 1, true) or
      msg:find('voce comprou', 1, true) or
      msg:find('você comprou', 1, true) or
      msg:find('you bought', 1, true) or
      msg:find('purchase successful', 1, true) or
      title:find('compra realizada', 1, true) or
      title:find('compra conclu', 1, true)
    local isInventorySpace =
      msg:find('not have enough inventory space', 1, true) or
      msg:find('do not have enough inventory space', 1, true) or
      msg:find('nao tem espaco', 1, true) or
      msg:find('não tem espaço', 1, true) or
      msg:find('sem espaço', 1, true) or
      msg:find('espaco no inventario', 1, true) or
      msg:find('espaço no inventário', 1, true)

    if not isPurchaseSuccess and not isInventorySpace then
      displayInfoBox(json.data.title or 'Gandora Shop',json.data.msg or '')
    end
  end
end

function init()
  connect(g_game,{onGameStart=function() createShop(); send('init') end,onGameEnd=hide})
  ProtocolGame.registerExtendedJSONOpcode(SHOP_OPCODE,onOpcode)
  if g_game.isOnline() then createShop(); send('init') end
end

function terminate()
  ProtocolGame.unregisterExtendedJSONOpcode(SHOP_OPCODE,onOpcode)
  g_keyboard.unbindKeyDown('Alt+S')
  if shopButton then shopButton:destroy(); shopButton=nil end
  if shopWindow then shopWindow:destroy(); shopWindow=nil end
  if confirmWindow then confirmWindow:destroy(); confirmWindow=nil end
end

function show() if not shopWindow then createShop() end; shopWindow:show(); shopWindow:raise(); shopWindow:focus() end
function hide() if shopWindow then shopWindow:hide() end end
function toggle() if not shopWindow then createShop() end; if shopWindow:isVisible() then hide() else show() end end