<template>
  <Controls v-if="props.tileMapLayer" :layer="props.tileMapLayer" :depth="0" />
</template>

<script setup lang="ts">
import Controls from '@/components/utilities/Controls.vue'
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { createTileArray, getTile, placeTile, setLayerTiles } from '@/services/mapService'
import { useScene } from 'phavuer'
import { onMounted, ref, watch } from 'vue'

const emit = defineEmits(['tileMap:create'])
const mapEditor = useMapEditorComposable()

defineExpose({ handlePointer, finalizeCommand, undo, redo })

const props = defineProps<{
  tileMap: Phaser.Tilemaps.Tilemap
  tileMapLayer: Phaser.Tilemaps.TilemapLayer
}>()

class EditorCommand {
  public operation: 'draw' | 'erase' = 'draw'
  public tileName: string = 'blank_tile'
  public affectedTiles: number[][]

  constructor(operation: 'draw' | 'erase', tileName: string) {
    this.operation = operation
    this.tileName = tileName
    this.affectedTiles = []
  }
}

//Record of commands
let commandStack: EditorCommand[] = []
let currentCommand: EditorCommand | null = null
let commandIndex = ref(0)
let originTiles: string[][] = []

function pencil(pointer: Phaser.Input.Pointer) {
  let map = mapEditor.currentMap.value
  if (!map) return

  // Check if there is a selected tile
  if (!mapEditor.selectedTile.value) return

  // Check if there is a tile
  const tile = getTile(props.tileMapLayer, pointer.worldX, pointer.worldY)
  if (!tile) return

  // Place tile
  placeTile(props.tileMap, props.tileMapLayer, tile.x, tile.y, mapEditor.selectedTile.value)

  createCommandUpdate(tile.x, tile.y, mapEditor.selectedTile.value, 'draw')

  // Adjust mapEditorStore.map.tiles
  map.tiles[tile.y][tile.x] = mapEditor.selectedTile.value
}

function eraser(pointer: Phaser.Input.Pointer) {
  let map = mapEditor.currentMap.value
  if (!map) return

  // Check if there is a tile
  const tile = getTile(props.tileMapLayer, pointer.worldX, pointer.worldY)
  if (!tile) return

  // Place tile
  placeTile(props.tileMap, props.tileMapLayer, tile.x, tile.y, 'blank_tile')

  createCommandUpdate(tile.x, tile.y, 'blank_tile', 'erase')

  // Adjust mapEditorStore.map.tiles
  map.tiles[tile.y][tile.x] = 'blank_tile'
}

function paint(pointer: Phaser.Input.Pointer) {
  let map = mapEditor.currentMap.value
  if (!map) return

  // Set new tileArray with selected tile
  const tileArray = createTileArray(props.tileMap.width, props.tileMap.height, mapEditor.selectedTile.value)
  setLayerTiles(props.tileMap, props.tileMapLayer, tileArray)

  // Adjust mapEditorStore.map.tiles
  map.tiles = tileArray
}

// When alt is pressed, and the pointer is down, select the tile that the pointer is over
function tilePicker(pointer: Phaser.Input.Pointer) {
  let map = mapEditor.currentMap.value
  if (!map) return

  // Check if there is a tile
  const tile = getTile(props.tileMapLayer, pointer.worldX, pointer.worldY)
  if (!tile) return

  // Select the tile
  mapEditor.setSelectedTile(map.tiles[tile.y][tile.x])
}

function handlePointer(pointer: Phaser.Input.Pointer) {
  // Check if left mouse button is pressed
  if (!pointer.isDown && pointer.button === 0) return

  // Check if shift is not pressed, this means we are moving the camera
  if (pointer.event.shiftKey) return

  // Check if alt is pressed
  if (pointer.event.altKey) {
    tilePicker(pointer)
    return
  }

  // Check if draw mode is tile
  switch (mapEditor.tool.value) {
    case 'pencil':
      pencil(pointer)
      break
    case 'eraser':
      eraser(pointer)
      break
    case 'paint':
      paint(pointer)
      break
  }
}

function createCommandUpdate(x: number, y: number, tileName: string, operation: 'draw' | 'erase') {
  if (!currentCommand) {
    currentCommand = new EditorCommand(operation, tileName)
  }

  //If position is already in, do not proceed
  for (const vec of currentCommand.affectedTiles) {
    if (vec[0] === x && vec[1] === y) return
  }

  currentCommand.affectedTiles.push([x, y])
}

function finalizeCommand() {
  if (!currentCommand) return
  //Cut the stack so the current edit is the last
  commandStack = commandStack.slice(0, commandIndex.value)
  commandStack.push(currentCommand)
  if (commandStack.length >= 9) {
    originTiles = applyCommands(originTiles, commandStack.shift()!)
  }

  commandIndex.value = commandStack.length
  currentCommand = null
}

function undo() {
  if (commandIndex.value > 0) {
    commandIndex.value--
    updateMapTiles()
  }
}

function redo() {
  if (commandIndex.value <= 9 && commandIndex.value <= commandStack.length) {
    commandIndex.value++
    updateMapTiles()
  }
}

function applyCommands(tiles: string[][], ...commands: EditorCommand[]): string[][] {
  let tileVersion = cloneArray(tiles)
  for (let command of commands) {
    for (const position of command.affectedTiles) {
      tileVersion[position[1]][position[0]] = command.tileName
    }
  }
  return tileVersion
}

function updateMapTiles() {
  if (!mapEditor.currentMap.value) return

  let indexedCommands = commandStack.slice(0, commandIndex.value)
  let modifiedTiles = applyCommands(originTiles, ...indexedCommands)

  setLayerTiles(props.tileMap, props.tileMapLayer, modifiedTiles)
  mapEditor.currentMap.value.tiles = modifiedTiles
}

//Recursive Array Clone
function cloneArray(arr: any[]): any[] {
  return arr.map((item) => (item instanceof Array ? cloneArray(item) : item))
}

watch(
  () => mapEditor.shouldClearTiles.value,
  (shouldClear) => {
    if (shouldClear && mapEditor.currentMap.value) {
      const blankTiles = createTileArray(props.tileMapLayer.width, props.tileMapLayer.height, 'blank_tile')
      setLayerTiles(props.tileMap, props.tileMapLayer, blankTiles)
      mapEditor.currentMap.value.tiles = blankTiles
      mapEditor.resetClearTilesFlag()
    }
  }
)

onMounted(async () => {
  if (!mapEditor.currentMap.value) return
  const mapState = mapEditor.currentMap.value

  //Clone
  originTiles = cloneArray(mapState.tiles)

  setLayerTiles(props.tileMap, props.tileMapLayer, mapState.tiles)
})
</script>
