local OPCODE, window = 202, nil
local offers, mode, category = {}, 'browse', 'all'
local catalog, allCatalog, catalogById, selectedClientId = {}, {}, {}, 0
local filteredCatalog, catalogPage, CATALOG_PAGE_SIZE = {}, 1, 50
local categoryByName, searchEvent, catalogRequestId, catalogLoading = {}, nil, 0, false
local render, renderCatalog, renderSelectedOffers
local marketCategories = {
  {'all', 'All'},
  {1, 'Armors'}, {2, 'Amulets'}, {3, 'Boots'}, {4, 'Containers'},
  {5, 'Decoration'}, {6, 'Food'}, {7, 'Helmets and Hats'}, {8, 'Legs'},
  {10, 'Potions'}, {11, 'Rings'}, {12, 'Runes'}, {13, 'Shields'},
  {14, 'Tools'}, {15, 'Valuables'}, {16, 'Ammunition'}, {17, 'Axes'},
  {18, 'Clubs'}, {19, 'Distance Weapons'}, {20, 'Swords'}, {21, 'Wands and Rods'},
  {22, 'Premium Scrolls'}, {24, 'Creature Products'}, {30, 'Gold'}
}

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

local function itemCategory(entry)
  if entry.category and entry.category >= 1 and entry.category <= 30 then
    if entry.category == 23 or entry.category >= 25 and entry.category <= 29 then return 9 end
    return entry.category
  end
  local name=(entry.name or ''):lower()
  if name:find('helmet',1,true) or name:find('hat',1,true) then return 7 end
  if name:find('armor',1,true) or name:find('robe',1,true) then return 1 end
  if name:find('legs',1,true) then return 8 end
  if name:find('boots',1,true) then return 3 end
  if name:find('shield',1,true) then return 13 end
  if name:find('sword',1,true) or name:find('blade',1,true) then return 20 end
  if name:find('axe',1,true) then return 17 end
  if name:find('club',1,true) or name:find('mace',1,true) or name:find('hammer',1,true) then return 18 end
  if name:find('wand',1,true) or name:find('rod',1,true) then return 21 end
  if name:find('bow',1,true) or name:find('crossbow',1,true) or name:find('spear',1,true) then return 19 end
  if name:find('arrow',1,true) or name:find('bolt',1,true) then return 16 end
  if name:find('rune',1,true) then return 12 end
  if name:find('potion',1,true) then return 10 end
  if name:find('ring',1,true) then return 11 end
  if name:find('amulet',1,true) or name:find('necklace',1,true) then return 2 end
  if name:find('backpack',1,true) or name:find('bag',1,true) then return 4 end
  return 9
end

local function loadCatalog(force)
  if not force and #catalog > 0 then return true end
  catalog, catalogById = {}, {}
  local types=g_things.findThingTypeByAttr(ThingAttrMarket,0) or {}
  local used={}
  for _,thingType in ipairs(types) do
    local data=thingType:getMarketData()
    if data and data.tradeAs and data.tradeAs>0 and not used[data.tradeAs] then
      local entry={clientId=data.tradeAs,showAs=data.showAs or data.tradeAs,name=data.name or ('Item '..data.tradeAs),category=data.category}
      table.insert(catalog,entry); catalogById[data.tradeAs]=entry
      used[data.tradeAs]=true
    end
  end
  table.sort(catalog,function(a,b) return a.name:lower()<b.name:lower() end)
  return #catalog > 0
end

function init()
  ProtocolGame.registerExtendedJSONOpcode(OPCODE, onOpcode)
  connect(g_game,{onGameEnd=hide})
  window=g_ui.displayUI('nexodusmarket'); window:hide()
  window.categories.categorySelect:clearOptions()
  for _,entry in ipairs(marketCategories) do
    categoryByName[entry[2]]=entry[1]
    window.categories.categorySelect:addOption(entry[2], entry[1])
  end
  window.categories.categorySelect:setCurrentOption('All')
  window.main.offerType:clearOptions()
  window.main.offerType:addOption('Sell',0)
  window.main.offerType:addOption('Buy',1)
  window.main.offerType:setCurrentOption('Sell')
  if g_game.isOnline() then loadCatalog() end
end
function terminate()
  disconnect(g_game,{onGameEnd=hide}); ProtocolGame.unregisterExtendedJSONOpcode(OPCODE)
  if window then window:destroy() end
end
function show()
  if not window or not g_game.isOnline() then return end
  local loaded=loadCatalog(#catalog == 0)
  window:show(); window:raise(); window:focus()
  applyFilters()
  if not loaded then
    scheduleEvent(function()
      if window and window:isVisible() and loadCatalog(true) then applyFilters() end
    end, 750)
  end
  refresh()
end
function hide() if window then window:hide() end end
function refresh() send(mode=='history' and 'history' or 'refresh') end
function setCategory(value) category=value; selectedClientId=0; catalogPage=1; applyFilters() end
function selectCategory(widget)
  local current=widget and widget:getCurrentOption()
  local label=(current and current.text) or (widget and widget:getText()) or 'All'
  setCategory(label)
end
function setMode(value)
  mode=value
  if value=='history' then send('history')
  elseif value=='mine' then renderSelectedOffers(true)
  else applyFilters(); renderSelectedOffers(false) end
end
function filterChanged()
  catalogPage=1
  if searchEvent then removeEvent(searchEvent) end
  searchEvent=scheduleEvent(function() searchEvent=nil; applyFilters() end,300)
end
function previousPage()
  if catalogPage>1 then catalogPage=catalogPage-1; applyFilters() end
end
function nextPage()
  catalogPage=catalogPage+1; applyFilters()
end
function sell()
  send('sell',{clientId=selectedClientId,
    amount=tonumber(window.main.amount:getText()) or 0,price=tonumber(window.main.price:getText()) or 0})
end
function createOffer()
  if selectedClientId==0 then return displayInfoBox('Market','Select an item first.') end
  local current=window.main.offerType:getCurrentOption()
  local offerType=current and tonumber(current.data) or 0
  send('create_offer',{offerType=offerType or 0,clientId=selectedClientId,
    amount=tonumber(window.main.amount:getText()) or 0,price=tonumber(window.main.price:getText()) or 0})
end

local function addOfferRow(panel,o,history)
  local row=g_ui.createWidget('Panel',panel); row:setHeight(42); row:setBorderWidth(1); row:setBorderColor('#555555')
  local label=g_ui.createWidget('Label',row); label:addAnchor(AnchorLeft,'parent',AnchorLeft); label:addAnchor(AnchorVerticalCenter,'parent',AnchorVerticalCenter); label:setMarginLeft(7)
  local piece=math.floor((o.price or 0)/math.max(1,o.amount or 1))
  label:setText((o.seller or o.info or '')..'  |  '..(o.amount or 0)..'x  |  '..(o.price or 0)..' gold  ('..piece..' each)')
  if not history then
    local b=g_ui.createWidget('Button',row); b:addAnchor(AnchorRight,'parent',AnchorRight); b:addAnchor(AnchorVerticalCenter,'parent',AnchorVerticalCenter); b:setWidth(70)
    if o.mine then b:setText('Cancel'); b.onClick=function() send('cancel',{id=o.id}) end
    else b:setText(o.offerType==1 and 'Sell' or 'Buy'); b.onClick=function() send('accept_offer',{id=o.id}) end end
  end
end

renderSelectedOffers=function(onlyMine)
  if not window then return end
  local sellPanel,buyPanel=window.main.sellOffers,window.main.buyOffers
  sellPanel:destroyChildren(); buyPanel:destroyChildren()
  for _,o in ipairs(offers) do
    if (not onlyMine or o.mine) and (onlyMine or selectedClientId>0 and o.clientId==selectedClientId) then
      addOfferRow(o.offerType==1 and buyPanel or sellPanel,o,false)
    end
  end
  window.main.sellTitle:setText(onlyMine and 'My Sell Offers' or 'Sell Offers - players selling this item')
  window.main.buyTitle:setText(onlyMine and 'My Buy Offers' or 'Buy Offers - players buying this item')
end

local function selectCatalogEntry(entry)
  selectedClientId=entry.clientId
  window.main.selectedIcon:setItem(Item.create(entry.showAs))
  window.main.selectedName:setText(entry.name)
  window.main.selectedDetails:setText('Loading item parameters...')
  window.main.selectedIcon:setTooltip(entry.name)
  mode='browse'
  renderSelectedOffers(false)
  send('details',{clientId=entry.clientId})
end

local function drawCatalogPage(list,total)
  catalog=list
  local first=total==0 and 0 or ((catalogPage-1)*CATALOG_PAGE_SIZE+1)
  local last=math.min(catalogPage*CATALOG_PAGE_SIZE,total)
  local pager=window.categories.catalogPager
  pager.pageLabel:setText(total==0 and '0 items' or (first..'-'..last..' / '..total))
  pager.previousPage:setEnabled(catalogPage>1)
  pager.nextPage:setEnabled(last<total)
  local panel=window.categories.catalogList; panel:destroyChildren()
  window.categories.catalogScroll:setValue(window.categories.catalogScroll:getMinimum())
  for index,o in ipairs(list) do
    local row=g_ui.createWidget('Panel',panel); row:setHeight(40); row:setBorderWidth(1); row:setBorderColor('#555555')
    local icon=g_ui.createWidget('Item',row); icon:setId('serverCatalogIcon'..index); icon:setSize({width=34,height=34})
    icon:addAnchor(AnchorLeft,'parent',AnchorLeft); icon:addAnchor(AnchorVerticalCenter,'parent',AnchorVerticalCenter); icon:setMarginLeft(4); icon:setItem(Item.create(o.showAs))
    local label=g_ui.createWidget('Label',row); label:addAnchor(AnchorLeft,icon:getId(),AnchorRight); label:addAnchor(AnchorVerticalCenter,'parent',AnchorVerticalCenter); label:setMarginLeft(8); label:setText(o.name)
    local button=g_ui.createWidget('Button',row); button:addAnchor(AnchorRight,'parent',AnchorRight); button:addAnchor(AnchorVerticalCenter,'parent',AnchorVerticalCenter); button:setWidth(65); button:setText('Select')
    button.onClick=function() selectCatalogEntry(o) end
    row.onDoubleClick=function() selectCatalogEntry(o) end
  end
end

function applyFilters()
  if not window or mode=='history' then return end
  local text=window.categories.catalogSearch:getText():lower(); local list={}
  if mode=='browse' and selectedClientId==0 then
    catalogRequestId=catalogRequestId+1
    send('catalog',{categoryName=category,page=catalogPage,search=text,requestId=catalogRequestId})
    return
  end
  for _,o in ipairs(offers) do
    local catalogEntry=catalogById[o.clientId]
    if (mode=='browse' or o.mine) and (category=='all' or itemCategory(catalogEntry or o)==category)
      and (text=='' or o.name:lower():find(text,1,true)) then table.insert(list,o) end
  end
  renderSelectedOffers(mode=='mine')
end

function onOpcode(protocol,opcode,p)
  if p.action=='offers' then
    offers=p.data or {}; window.categories.balance:setText('Bank: '..(p.status.balance or 0)..' gold')
    if mode=='mine' then renderSelectedOffers(true) elseif selectedClientId>0 then renderSelectedOffers(false) else applyFilters() end
  elseif p.action=='catalog_all' then
    catalogLoading=false; allCatalog=p.data or {}; catalogById={}
    for _,entry in ipairs(allCatalog) do catalogById[entry.clientId]=entry end
    catalogPage=1; selectedClientId=0; applyFilters()
  elseif p.action=='catalog' then
    if p.status and p.status.requestId and p.status.requestId~=catalogRequestId then return end
    catalog=p.data or {}; catalogById={}
    for _,entry in ipairs(catalog) do catalogById[entry.clientId]=entry end
    filteredCatalog=catalog
    local total=(p.status and p.status.total) or #catalog
    local page=(p.status and p.status.page) or catalogPage
    local first=total==0 and 0 or ((page-1)*CATALOG_PAGE_SIZE+1)
    local last=math.min(page*CATALOG_PAGE_SIZE,total)
    local loaded=(p.status and p.status.catalogSize) or 0
    local pager=window.categories.catalogPager
    pager.pageLabel:setText(loaded==0 and 'Catalog error: 0 loaded' or (total==0 and '0 items' or (first..'-'..last..' / '..total)))
    pager.previousPage:setEnabled(page>1)
    pager.nextPage:setEnabled(last<total)
    local panel=window.categories.catalogList; panel:destroyChildren()
    window.categories.catalogScroll:setValue(window.categories.catalogScroll:getMinimum())
    for index,o in ipairs(catalog) do
      local row=g_ui.createWidget('Panel',panel); row:setHeight(40); row:setBorderWidth(1); row:setBorderColor('#555555')
      local icon=g_ui.createWidget('Item',row); icon:setId('serverCatalogIcon'..index); icon:setSize({width=34,height=34})
      icon:addAnchor(AnchorLeft,'parent',AnchorLeft); icon:addAnchor(AnchorVerticalCenter,'parent',AnchorVerticalCenter); icon:setMarginLeft(4); icon:setItem(Item.create(o.showAs))
      local label=g_ui.createWidget('Label',row); label:addAnchor(AnchorLeft,icon:getId(),AnchorRight); label:addAnchor(AnchorVerticalCenter,'parent',AnchorVerticalCenter); label:setMarginLeft(8); label:setText(o.name)
      local button=g_ui.createWidget('Button',row); button:addAnchor(AnchorRight,'parent',AnchorRight); button:addAnchor(AnchorVerticalCenter,'parent',AnchorVerticalCenter); button:setWidth(65); button:setText('Select')
      button.onClick=function() selectCatalogEntry(o) end
      row.onDoubleClick=function() selectCatalogEntry(o) end
    end
  elseif p.action=='details' then
    local data=p.data or {}
    if tonumber(data.clientId)==selectedClientId then
      local details=data.text or 'Item details unavailable.'
      window.main.selectedDetails:setText(details)
      window.main.selectedIcon:setTooltip(details)
    end
  elseif p.action=='history' then
    window.main.sellOffers:destroyChildren(); window.main.buyOffers:destroyChildren()
    window.main.sellTitle:setText('Transaction History'); window.main.buyTitle:setText('')
    for _,entry in ipairs(p.data or {}) do addOfferRow(window.main.sellOffers,entry,true) end
  elseif p.action=='message' then displayInfoBox(p.data.title or 'Market',p.data.msg or '') end
end
