-- Lógica pura do jogo: sem DOM, sem timer, sem I/O. `avancar` representa UM
-- tick do jogo em tempo real — quem chama o timer é a camada de UI
-- (web/ui.lua); aqui é só a regra, testável chamando avancar() na mão.

Cobra = {}

local DELTAS = {
  cima = { dx = 0, dy = -1 },
  baixo = { dx = 0, dy = 1 },
  esquerda = { dx = -1, dy = 0 },
  direita = { dx = 1, dy = 0 },
}

local OPOSTA = {
  cima = "baixo",
  baixo = "cima",
  esquerda = "direita",
  direita = "esquerda",
}

-- Gerador determinístico (mulberry32) — mesma semente sempre gera a mesma
-- sequência, o que torna a posição da comida testável.
local function proximoAleatorio(semente)
  local a = (semente + 0x6d2b79f5) & 0xffffffff
  local t = a
  t = ((t ~ (t >> 15)) * (t | 1)) & 0xffffffff
  local interno = ((t ~ (t >> 7)) * (t | 61)) & 0xffffffff
  local soma = (t + interno) & 0xffffffff
  t = (t ~ soma) & 0xffffffff
  local resultado = (t ~ (t >> 14)) & 0xffffffff
  return resultado / 4294967296, a
end

local function chave(x, y)
  return x .. "," .. y
end

local function conjuntoDaCobra(cobra)
  local ocupadas = {}
  for _, seg in ipairs(cobra) do
    ocupadas[chave(seg.x, seg.y)] = true
  end
  return ocupadas
end

--- Escolhe uma célula livre pra próxima comida. Tenta até achar uma célula
--- fora da cobra, ou desiste depois de largura*altura tentativas (tabuleiro
--- cheio — não deveria acontecer em jogo normal).
local function gerarComida(largura, altura, ocupadas, semente)
  for _ = 1, largura * altura do
    local r1, s1 = proximoAleatorio(semente)
    local r2, s2 = proximoAleatorio(s1)
    semente = s2
    local x = math.floor(r1 * largura)
    local y = math.floor(r2 * altura)
    if not ocupadas[chave(x, y)] then
      return { x = x, y = y }, semente
    end
  end
  return nil, semente -- tabuleiro cheio, sem espaço pra comida
end

function Cobra.novoJogo(largura, altura, semente)
  local meioX, meioY = math.floor(largura / 2), math.floor(altura / 2)
  local cobra = {
    { x = meioX, y = meioY },
    { x = meioX - 1, y = meioY },
    { x = meioX - 2, y = meioY },
  }

  local comida, novaSemente = gerarComida(largura, altura, conjuntoDaCobra(cobra), semente)

  return {
    largura = largura,
    altura = altura,
    cobra = cobra,
    direcaoAtual = "direita",
    direcaoFila = nil,
    comida = comida,
    pontuacao = 0,
    fase = "jogando",
    semente = novaSemente,
  }
end

--- Enfileira a próxima direção. Ignora se for a reversão de 180° da direção
--- atual (não dá pra cobra virar direto pra trás em cima do próprio corpo).
function Cobra.definirDirecao(estado, direcao)
  if not DELTAS[direcao] then return estado end
  if OPOSTA[direcao] == estado.direcaoAtual then return estado end
  return {
    largura = estado.largura, altura = estado.altura, cobra = estado.cobra,
    direcaoAtual = estado.direcaoAtual, direcaoFila = direcao, comida = estado.comida,
    pontuacao = estado.pontuacao, fase = estado.fase, semente = estado.semente,
  }
end

local function dentroDaGrade(x, y, largura, altura)
  return x >= 0 and x < largura and y >= 0 and y < altura
end

--- Avança um tick do jogo: move a cabeça na direção atual (ou na
--- enfileirada, se houver), checa colisão com parede e com o próprio corpo,
--- come a comida se for o caso (cresce e pontua), ou anda normalmente.
function Cobra.avancar(estado)
  if estado.fase ~= "jogando" then
    return estado
  end

  local direcao = estado.direcaoFila or estado.direcaoAtual
  local d = DELTAS[direcao]
  local cabecaAtual = estado.cobra[1]
  local novaCabeca = { x = cabecaAtual.x + d.dx, y = cabecaAtual.y + d.dy }

  if not dentroDaGrade(novaCabeca.x, novaCabeca.y, estado.largura, estado.altura) then
    return { largura = estado.largura, altura = estado.altura, cobra = estado.cobra,
      direcaoAtual = direcao, direcaoFila = nil, comida = estado.comida,
      pontuacao = estado.pontuacao, fase = "perdeu", semente = estado.semente }
  end

  local comeu = novaCabeca.x == estado.comida.x and novaCabeca.y == estado.comida.y

  -- Se não comeu, a cauda anda embora nesse tick — a célula dela não conta
  -- como colisão. Se comeu, a cauda fica, então conta.
  local limiteColisao = comeu and #estado.cobra or (#estado.cobra - 1)
  for i = 1, limiteColisao do
    local seg = estado.cobra[i]
    if seg.x == novaCabeca.x and seg.y == novaCabeca.y then
      return { largura = estado.largura, altura = estado.altura, cobra = estado.cobra,
        direcaoAtual = direcao, direcaoFila = nil, comida = estado.comida,
        pontuacao = estado.pontuacao, fase = "perdeu", semente = estado.semente }
    end
  end

  local novoCorpo = { novaCabeca }
  local limiteCopia = comeu and #estado.cobra or (#estado.cobra - 1)
  for i = 1, limiteCopia do
    table.insert(novoCorpo, estado.cobra[i])
  end

  local novaComida, novaSemente, novaPontuacao = estado.comida, estado.semente, estado.pontuacao
  if comeu then
    novaPontuacao = estado.pontuacao + 1
    novaComida, novaSemente = gerarComida(estado.largura, estado.altura, conjuntoDaCobra(novoCorpo), estado.semente)
  end

  local fase = "jogando"
  if not novaComida then
    fase = "vitoria" -- tabuleiro cheio, não cabe mais comida: jogador venceu
  end

  return {
    largura = estado.largura, altura = estado.altura, cobra = novoCorpo,
    direcaoAtual = direcao, direcaoFila = nil, comida = novaComida,
    pontuacao = novaPontuacao, fase = fase, semente = novaSemente,
  }
end
