import { PassThrough } from 'stream'
import { EventEmitter } from 'events'

import minecraft_data from 'minecraft-data'
import { aiter } from 'iterator-helper'

import Entities from '../data/entities.json' assert { type: 'json' }

import { last_event_value, Mob } from './events.js'
import { path_end, path_position } from './mobs/path.js'
import mobs_goto from './mobs/goto.js'
import mobs_damage from './mobs/damage.js'
import mobs_target from './mobs/target.js'
import behavior_tree from './mobs/behavior_tree.js'
import { VERSION } from './settings.js'

const { entitiesByName } = minecraft_data(VERSION)

function reduce_mob(state, action, context) {
  return [
    //
    mobs_goto.reduce_mob,
    mobs_damage.reduce_mob,
    behavior_tree.reduce_mob,
    mobs_target.reduce_mob,
  ].reduce(
    async (intermediate, fn) => fn(await intermediate, action, context),
    state
  )
}

function observe_mobs(mobs) {
  path_end(mobs)
}

const DEFAULT_SPEED = 100 /* instant speed, 1ms/block */

const MOVEMENT_SPEED_TO_BLOCKS_PER_SECOND = 10

/** @param {import('./context.js').InitialWorld} world */
export function register(world) {
  const mobs = world.mob_positions.map(({ position, type, level }, i) => {
    const { speed = DEFAULT_SPEED, health } = Entities[type]
    const initial_state = {
      path: [position],
      open: [],
      closed: [],
      start_time: 0,
      speed:
        (1 / (speed * MOVEMENT_SPEED_TO_BLOCKS_PER_SECOND)) *
        1000 /* ms/block */,
      health /* halfheart */,
      blackboard: {},
      attack_sequence_number: 0,
      wakeup_at: 0,
      sleep_id: null,
      look_at: { player: false, yaw: 0, pitch: 0 },
    }

    const actions = new PassThrough({ objectMode: true })
    const events = new EventEmitter()
    events.setMaxListeners(Infinity)

    const entity_id = world.next_entity_id + i

    aiter(actions).reduce(async (last_state, action) => {
      const state = await reduce_mob(last_state, action, {
        world: world.get(),
        type,
        entity_id,
      })
      events.emit(Mob.STATE, state)
      return state
    }, initial_state)

    setImmediate(() => events.emit(Mob.STATE, initial_state))

    const get_state = last_event_value(events, Mob.STATE)

    return {
      entity_id,
      type,
      level,
      events,
      get_state,
      constants: entitiesByName[Entities[type].minecraft_entity],
      position(time = Date.now()) {
        const { path, start_time, speed } = get_state()

        return path_position({ path, time, start_time, speed })
      },
      dispatch(action_type, payload, time = Date.now()) {
        actions.write({ type: action_type, payload, time })
      },
    }
  })

  observe_mobs(mobs)

  const { next_entity_id } = world

  return {
    ...world,
    next_entity_id: next_entity_id + mobs.length,
    mobs: {
      all: mobs,
      by_entity_id(id) {
        if (id >= next_entity_id && id <= next_entity_id + mobs.length)
          return mobs[id - next_entity_id]
        else return null
      },
    },
  }
}