local OPCODE=203
local window
local topButton
local state={recipes={},tokens=0,atForge=false,removeCost=5}

local function captureKeyboard()
  if not window then return end
  window:grabKeyboard()
  window.onKeyDown = function(_, keyCode, keyboardModifiers)
    if keyboardModifiers == KeyboardNoModifier and keyCode == KeyEscape then hide() end
    return true
  end
end

local function releaseKeyboard()
  if window then window:ungrabKeyboard() end
end

-- g_resources.fileExists may report a virtual path as present even when the
-- corresponding PNG is absent from the installed client. Keep the artwork
-- list explicit so recipes without dedicated art use the server-provided
-- item sprite instead of generating repeated texture-load errors.
local recipeImages={
  tempered_steel=true,arcane_thread=true,vital_essence=true,rift_alloy=true,
  armor_basic=true,armor_refined=true,armor_rift=true,
  shield_basic=true,shield_refined=true,shield_rift=true,
  amulet_vital_basic=true,amulet_vital_refined=true,amulet_vital_rift=true,
  amulet_arcane_basic=true,amulet_arcane_refined=true,amulet_arcane_rift=true,
  ring_healing_basic=true,ring_healing_refined=true,ring_healing_rift=true,
  vanguard_helmet=true,vanguard_armor=true,vanguard_legs=true,vanguard_boots=true,
  riftstalker_hood=true,riftstalker_coat=true,riftstalker_boots=true,
  helmet_basic=true,helmet_refined=true,helmet_rift=true,
  legs_basic=true,legs_refined=true,legs_rift=true,
  boots_basic=true,boots_refined=true,boots_rift=true,
  weapon_basic=true,weapon_refined=true,weapon_rift=true
}

local function send(action,data)
  local protocol=g_game.getProtocolGame()
  if protocol then protocol:sendExtendedOpcode(OPCODE,json.encode({action=action,data=data or {}})) end
end

local function message(text)
  if window then window.status:setText(text or '') end
end

local function materialText(recipe)
  local parts={}
  for _,m in ipairs(recipe.mats or {}) do
    local color=m.have>=m.need and '#72d68a' or '#e2766f'
    parts[#parts+1]=string.format('%s: %d/%d',m.name,m.have,m.need)
  end
  parts[#parts+1]=string.format('Repera Tokens: %d',recipe.token or 0)
  return 'MATERIALS NEEDED: '..table.concat(parts,'  |  ')
end

local function resultText(recipe)
  if recipe.result and recipe.result:len()>0 then return 'RESULT: '..recipe.result end
  if recipe.kind=='component' then return 'RESULT: [MATERIAL] Crafting material used in other Riftforge recipes.' end
  if recipe.kind=='upgrade' then return 'RESULT: [UPGRADE] Permanent equipment enhancement.' end
  return 'RESULT: [EQUIPMENT] Crafted equipment item.'
end

local function requestCraft(recipe)
  message('Crafting request: '..recipe.name..'...')
  send('craft',{id=recipe.id})
end

local function requestRemove()
  local option=window and window.removal.slot:getCurrentOption()
  if not option then return end
  message('Removing the enhancement from equipped '..option.text:lower()..'...')
  send('remove',{slot=option.data})
end

local function rebuild()
  if not window then return end
  local list=window.recipeList
  list:destroyChildren()
  window.status:setColor(state.atForge and '#78d98b' or '#e2b66f')
  window.status:setText(state.atForge and 'RIFTFORGE ACTIVE - crafting enabled.' or 'RECIPE BOOK - use a Riftforge Anvil to craft.')
  window:recursiveGetChildById('tokenValue'):setText(tostring(state.tokens or 0))
  window.removal:setVisible(state.atForge==true)
  for _,recipe in ipairs(state.recipes or {}) do
    if recipe.learned then
      local row=g_ui.createWidget('CraftRecipeRow',list)
      row.title:setText(recipe.name)
      row.result:setText(resultText(recipe))
      row.requirements:setText(materialText(recipe))
      row.learnedCount:setVisible(recipe.kind ~= 'component')
      local imagePath='/images/crafting/recipes/'..recipe.id
      if recipeImages[recipe.id] then
        row.icon:setImageSource(imagePath)
        row.icon:setVisible(true)
        row.spriteIcon:setVisible(false)
      else
        row.icon:setVisible(false)
        row.spriteIcon:setItemId(tonumber(recipe.icon) or 0)
        row.spriteIcon:setVisible(true)
      end
      row.craft:setVisible(state.atForge)
      row.craft:setEnabled(state.atForge)
      row.craft.onClick=function() requestCraft(recipe) end
    end
  end
end

local function onOpcode(protocol,opcode,buffer)
  local ok,msg=pcall(json.decode,buffer)
  if not ok or type(msg)~='table' then return end
  if msg.action=='sync' then state=msg.data or state; rebuild()
  elseif msg.action=='syncBegin' then
    state=msg.data or state
    state.recipes={}
  elseif msg.action=='syncRecipe' then
    local recipe=msg.data
    if recipe then
      recipe.mats={}
      state.recipes[#state.recipes+1]=recipe
    end
  elseif msg.action=='syncMaterial' then
    local data=msg.data
    local recipe=data and state.recipes[data.recipe]
    if recipe then recipe.mats[#recipe.mats+1]=data.material end
  elseif msg.action=='syncEnd' then rebuild()
  elseif msg.action=='message' then message(msg.data and msg.data.text or '')
  elseif msg.action=='open' then
    if window then window:show();window:raise();window:focus();captureKeyboard() end
  end
end

function init()
  ProtocolGame.registerExtendedOpcode(OPCODE,onOpcode)
  connect(g_game,{onGameStart=onGameStart,onGameEnd=onGameEnd})
  window=g_ui.displayUI('crafting'); window:hide()
  window.removal:hide()
  local slot=window.removal.slot
  slot:addOption('Helmet',1)
  slot:addOption('Amulet',2)
  slot:addOption('Armor',4)
  slot:addOption('Right hand',5)
  slot:addOption('Left hand',6)
  slot:addOption('Legs',7)
  slot:addOption('Boots',8)
  slot:addOption('Ring',9)
  window.removal.remove.onClick=requestRemove
  window:recursiveGetChildById('topClose').onClick=hide
  if g_game.isOnline() then onGameStart() end
end

function terminate()
  disconnect(g_game,{onGameStart=onGameStart,onGameEnd=onGameEnd})
  ProtocolGame.unregisterExtendedOpcode(OPCODE)
  if topButton then topButton:destroy(); topButton=nil end
  releaseKeyboard()
  if window then window:destroy(); window=nil end
end

function onGameStart() scheduleEvent(function()send('init')end,500) end
function onGameEnd() hide() end
function show() if window then window:show();window:raise();window:focus();captureKeyboard();send('init') end end
function hide() releaseKeyboard();if window then window:hide() end end
function toggle() if not window then return end;if window:isVisible() then hide() else show() end end
