import { MAP_OFFSET, MAP_SIZE } from '@map/const/map';
import { MapChunked } from '@map/lib/map/map-chunked';
import type { Coordinate, CoordinateKey, Hash, Quadrangle, SegmentSize, Trapezoid } from '@map/types';
import { server } from '@game/network/server';
import { setsEqual } from '@core/lib/base/sets-equal';
import type { MapSegment } from '@map/lib/map/map-segment';
import { MapChunk } from '@game/struct/chunk';
import { action, makeObservable, observable } from 'mobx';

export class World extends MapChunked<MapChunk> {
  @observable visibleChunks: MapChunk[] = [];
  @observable visibleChunksCoords: Coordinate[] = [];
  @observable visibleArea: Quadrangle | undefined;

  public visibleChunksCoordsSet = new Set<CoordinateKey>();

  constructor() {
    super(MAP_SIZE, MAP_OFFSET);

    makeObservable(this);
  }

  mergeAll(childSegments: MapSegment[], actual = true) {
    const updated = super.mergeAll(childSegments, actual);

    if (updated > 0) {
      this.updateVisibleChunksList();
    }

    return updated;
  }

  validateAndUpdateChunks(chunksToValidate: Array<[Coordinate, Hash]>) {
    const chunksToUpdate = this.validateChunks(chunksToValidate);

    if (chunksToUpdate.length > 0) {
      server.updateOutdatedChunks(chunksToUpdate);
    }
  }

  @action
  updateVisibleArea(visibleArea: Trapezoid, force = false) {
    const visibleChunks = [...world.getChunksCoordsByQuadrangle(visibleArea).values()];
    const visibleChunksSet = new Set<CoordinateKey>(visibleChunks.map((chunk) => this.getChunkKey(chunk.x, chunk.y)));

    if (setsEqual(this.visibleChunksCoordsSet, visibleChunksSet) && !force) {
      return;
    }

    const chunksToSend = visibleChunks.filter((chunkCoords) => {
      return !world.hasChunkAt(chunkCoords.x, chunkCoords.y);
    });

    server?.updateVisibleChunks(visibleArea, chunksToSend);

    this.visibleArea = visibleArea;
    this.visibleChunksCoords = visibleChunks;
    this.visibleChunksCoordsSet = visibleChunksSet;

    this.updateVisibleChunksList();
  }

  protected createChunk(size: SegmentSize, offset: Coordinate, segment?: MapSegment) {
    const chunk = new MapChunk(size, offset, segment);

    return [chunk, chunk.squareValue] as const;
  }

  @action
  updateVisibleChunksList() {
    this.visibleChunks = this.visibleChunksCoords
      ?.map((chunk) => this.getChunkAt(chunk.x, chunk.y))
      .filter(Boolean) as MapChunk[];
  }
}

export const world = new World();
