<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import Help from './screens/help.vue'
import Inventory from './screens/inventory.vue'

import { BLOCK_SIZE, STAGE_WIDTH, STAGE_HEIGHT, type Block } from './level/def'
import { getItem, getItemClass } from './level/items'
import createLevel from './level'

import useTime from './util/useTime'
import useInput from './util/useInput'
import usePlayer, { type InventoryItem } from './util/usePlayer'
import useLightMap from './util/useLightMap'

const { updateTime, time, timeOfDay, clock } = useTime()
const { player, direction, dx, dy, pocket, unpocket } = usePlayer()
const { inputX, inputY, running, paused, help, inventory } = useInput()
const level = createLevel(STAGE_WIDTH + 2, STAGE_HEIGHT + 2)

const lightMapEl = ref<HTMLCanvasElement | undefined>(undefined)
let updateLightMap: ReturnType<typeof useLightMap>

pocket({ name: 'Shovel', type: 'tool', icon: 'shovel', quality: 'wood' })
pocket({ name: 'Sword', type: 'weapon', icon: 'sword', quality: 'wood' })
pocket({ name: 'Pick Axe', type: 'tool', icon: 'pick', quality: 'wood' })

let animationFrame = 0
let lastTick = 0

const x = ref(0)
const y = ref(0)
const floorX = computed(() => Math.floor(x.value))
const floorY = computed(() => Math.floor(y.value))
const tx = computed(() => (x.value - floorX.value) * -BLOCK_SIZE)
const ty = computed(() => (y.value - floorY.value) * -BLOCK_SIZE)
const rows = computed(() => level.grid(floorX.value, floorY.value))
const lightBarrier = computed(() => level.sunLight(floorX.value))

const arriving = ref(true)
const walking = ref(false)
const inventorySelection = ref<InventoryItem>(player.inventory[0])

type Surroundings = {
  at: Block,
  left: Block,
  right: Block,
  up: Block,
  down: Block,
}
const surroundings = computed<Surroundings>(() => {
  const px = player.x
  const py = player.y
  const row = rows.value

  return {
    at: row[py][px],
    left: row[py][px - 1],
    right: row[py][px + 1],
    up: row[py - 1][px],
    down: row[py + 1][px],
  }
})
const blocked = computed(() => {
  const { left, right, up, down } = surroundings.value
  return {
    left: !left.walkable,
    right: !right.walkable,
    up: !up.walkable,
    down: !down.walkable,
  }
})

// TODO
const damagedBlocks = ref([])

function dig(blockX: number, blockY: number, block: Block) {
  // can only dig in player proximity
  if (Math.abs(player.x - blockX) > 2 || Math.abs(player.y - blockY) > 2) return
  // finally dig that block
  // TODO: damage blocks first
  level.change({
    type: 'exchange',
    x: floorX.value + blockX,
    y: floorY.value + blockY,
    newType: 'air'
  })

  // anything to pick up?
  if (block.drops) {
    const dropItem = getItem(block.drops)
    if (dropItem) pocket(dropItem)
  }
}

function build(blockX: number, blockY: number, block: InventoryItem) {
  const blockToBuild = block.builds
  // the block doesn't do anything
  if (!blockToBuild) return

  level.change({
    type: 'exchange',
    x: floorX.value + blockX,
    y: floorY.value + blockY,
    newType: blockToBuild
  })

  const newAmount = unpocket(block)
  if (newAmount < 1) inventorySelection.value = player.inventory[0]
}

function interactWith(blockX: number, blockY: number, block: Block) {
  // § 4 ArbZG
  if (paused.value) return

  const blockInHand = inventorySelection.value.type === 'block'
  const toolInHand = inventorySelection.value.type === 'tool'
  const emptyBlock = block.type === 'air' || block.type === 'cave'

  // put the selected block
  if (blockInHand && emptyBlock) {
    build(blockX, blockY, inventorySelection.value)
  // dig a block with shovel or pick axe
  } else if (toolInHand && !emptyBlock) {
    dig(blockX, blockY, block)
  }

  // This feels like cheating, but it makes Vue recalculate floorX
  // which then recalculates the blocks, so that the changes are
  // applied. Otherwise, they wouldn't be visible before moving
  x.value = x.value + 0.01
  x.value = x.value - 0.01
}

let lastTimeUpdate = 0

const move = (thisTick: number): void => {
  animationFrame = requestAnimationFrame(move)

  // do nothing when paused
  if (paused.value) {
    lastTick = thisTick // reset tick, to avoid huge tickDelta
    return
  }

  const tickDelta = thisTick - lastTick
  lastTimeUpdate += tickDelta
  // update in-game time every 60ms by 0.1
  // then a day needs 10000 updates, and it takes about 10 minutes
  if (lastTimeUpdate > 60) {
    updateTime()
    lastTimeUpdate = 0
  }

  player.vx = inputX.value
  player.vy = inputY.value

  if (inputX.value) player.lastDir = inputX.value

  let dx_ = dx.value
  let dy_ = dy.value

  if (running.value) dx_ *= 2

  if (dx_ > 0 && blocked.value.right) dx_ = 0
  else if (dx_ < 0 && blocked.value.left) dx_ = 0

  if (dy_ > 0 && blocked.value.down) dy_ = 0
  else if (dy_ < 0 && blocked.value.up) dy_ = 0

  const optimal = 16 // 16ms per tick => 60 FPS
  const movementMultiplier = (tickDelta / optimal) * 2
  const fallMultiplier = movementMultiplier * 2 // TODO: accelerated fall?

  if (arriving.value && dy_ === 0) {
    arriving.value = false
  }

  walking.value = !!dx_

  if (dy_ <= 0) x.value += dx_ * movementMultiplier

  if (dy_ < 0 || arriving.value) {
    y.value += dy_ * movementMultiplier
  } else {
    y.value += dy_ * fallMultiplier
  }

  updateLightMap()
  lastTick = thisTick
}

function calcBrightness(level: number, row: number) {
  const barrier = lightBarrier.value[row]
  const barrierLeft = lightBarrier.value[row - 1]
  const barrierRight = lightBarrier.value[row + 1]

  let delta = barrier - level - (floorY.value - 3)
  const deltaL = Math.min(3, barrierLeft - level - (floorY.value - 3))
  const deltaR = Math.min(3, barrierRight - level - (floorY.value - 3))

  if (delta > 3) delta = 3
  else if (delta < 0) delta = 0

  if (deltaR > delta || deltaL > delta) delta = Math.max(deltaL, deltaR) - 1

  return `sun-${delta}`
}

function selectTool(item: InventoryItem) {
  // only tools and weapons can be selected
  // if (item.type !== 'tool' && item.type !== 'weapon') return
  inventorySelection.value = item
}

onMounted(() => {
  const canvas = lightMapEl.value!
  canvas.height = (BLOCK_SIZE + 2) * STAGE_HEIGHT
  canvas.width = (BLOCK_SIZE + 2) * STAGE_WIDTH
  const ctx = canvas.getContext('2d')!

  updateLightMap = useLightMap(ctx, floorX, floorY, tx, ty, time, lightBarrier)
  lastTick = performance.now()
  move(lastTick)
})
</script>

<template>
  <div id="field" :class="timeOfDay">

    <div id="blocks" :style="{transform: `translate(${tx}px, ${ty}px)`}">
      <template v-for="(row, y) in rows">
        <div v-for="(block, x) in row"
          :class="['block', block.type]"
          @click="interactWith(x, y, block)"
        />
      </template>
    </div>

    <div id="player" :class="[direction, { walking }]" @click="inventory = !inventory">
      <div class="head"></div>
      <div class="body"></div>
      <div class="legs">
        <div class="left"></div>
        <div class="right"></div>
      </div>
      <div class="arms">
        <div v-if="inventorySelection"
          :class="['item', getItemClass(inventorySelection)]"
        ></div>
      </div>
    </div>

    <canvas id="light-mask" ref="lightMapEl" :style="{transform: `translate(${tx}px, ${ty}px)`}" />
    <div id="beam" v-if="arriving"></div>
    <div id="level-indicator">
      x:{{ floorX }}, y:{{ floorY }}
      <template v-if="paused">(PAUSED)</template>
      <template v-else>({{ clock }})</template>
    </div>

    <Inventory :shown="inventory"
      :items="player.inventory"
      @selection="selectTool($event)"
    />
    <Help v-show="help" />
  </div>
</template>
