import { MapSegment } from '@map/lib/map/map-segment';
import type { Coordinate, CoordinateKey, Hash, Region, SegmentSize, Updateable } from '@map/types';
import { MapRegion } from '@game/struct/region';
import { computed, makeObservable, observable } from 'mobx';

export class MapChunk extends MapSegment {
  @observable regionsMap = new Map<CoordinateKey, MapRegion>();

  constructor(size: SegmentSize, offset: Coordinate = { x: 0, y: 0 }, segment?: MapSegment) {
    super(size, offset);

    if (segment) {
      this.merge(segment, false);
    }

    this.createRegions();

    makeObservable(this);
  }

  update(worldX: number, worldY: number, update: Partial<Region> & Updateable): Hash {
    const curHash = this.getHash(worldX, worldY);
    const newHash = super.update(worldX, worldY, update);

    const changed = curHash !== newHash;

    if (changed) {
      this.sync(worldX, worldY, update);
    }

    return newHash;
  }

  syncAll() {
    for (const offset of this) {
      this.syncAt(offset);
    }
  }

  sync(worldX: number, worldY: number, update?: Partial<Region> & Updateable) {
    const item = update ?? this.get(worldX, worldY);

    const regionKey = `${worldX}_${worldY}` as CoordinateKey;
    const region = this.regionsMap.get(regionKey);

    region?.update(item);
  }

  syncAt(offset: number, update?: Partial<Region> & Updateable) {
    const { x, y } = this.computeCoords(offset);

    return this.sync(x, y, update);
  }

  protected mergeForce(childSegment: MapSegment) {
    const updated = super.mergeForce(childSegment);

    this.syncAll();

    return updated;
  }

  protected copyRegionAt(target: Uint32Array, source: Uint32Array, targetOffset: number, sourceOffset: number) {
    const newHash = super.copyRegionAt(target, source, targetOffset, sourceOffset);

    this.syncAt(targetOffset);

    return newHash;
  }

  private createRegions() {
    for (const item of this.items()) {
      const region = new MapRegion(item);

      this.regionsMap.set(region.key, region);
    }
  }

  @computed
  get regions() {
    return [...this.regionsMap.values()];
  }

  @computed
  get key() {
    return `${this.minWorldX}_${this.minWorldY}`;
  }
}
