import * as Pixi from "pixi.js";
import { ChunkGen, ChunkRef, GameState, PointNodeRef, ZLevelGen } from "../../data/GameState";
import { ZLevelGenFactory } from "../../game/WorldGenStateFactory";
import { PixiPointFrom } from "../../lib/pixi/pixify";
import { HashSet, KeyedHashMap } from "../../lib/util/data_structures/hash";
import { Vector2 } from "../../lib/util/geometry/vector2";
import { Const } from "../../lib/util/misc";
import { UpdaterGeneratorType2 } from "../../lib/util/updaterGenerator";
import { RenderedChunkConstants, ChunkComponent, ChunkComponentProps } from "./ChunkComponent";
import { engageLifecycle, LifecycleHandlerBase } from "./LifecycleHandler";
import { RootComponentState } from "./RootComponent";

type Props = {
  delta: number,
  args: {
    pointNodeTexture: Pixi.Texture,
    markForceUpdate: (childInstance: any) => void,
  },
  z: number,
  updaters: UpdaterGeneratorType2<GameState>,
  tooltipUpdaters: UpdaterGeneratorType2<RootComponentState>['tooltip'],
  position: Vector2,
  zLevelGen: Const<ZLevelGen> | undefined,
  selectedPointNode: PointNodeRef | undefined,
  allocatedPointNodeSubset: Const<HashSet<PointNodeRef>>,
}

type State = {}

class ZLevelComponent2 extends LifecycleHandlerBase<Props, State> {
  public container: Pixi.Container;
  public state: State;

  public children: KeyedHashMap<ChunkRef, ChunkComponent> = new KeyedHashMap();

  constructor(props: Props) {
    super(props);
    this.state = {};
    this.container = new Pixi.Container();

    this.upsertChildren(props);
  }

  protected renderSelf(props: Props) {
    this.container.position = PixiPointFrom(props.position);
  }

  protected didMount() {
    const { updaters } = this._staleProps;
    // if we mounted but our data is not generated, please generate ourselves
    updaters.worldGen.zLevels.enqueueUpdate((prev, prevGameState) => {
      if (!prev[this._staleProps.z]) {
        return { [this._staleProps.z]: new ZLevelGenFactory({}).create({ seed: prevGameState.worldGen.seed, z: this._staleProps.z }) };
      }
      return prev;
    })
  }

  protected didForceUpdateChild(instance: LifecycleHandlerBase<any, any>) {
    // IMPORTANT! this is intended to raise the child that asked for a force update to the top so it isn't covered
    // by other sibling pixi containers. however this code doesnt work well during the update call, for some reason (not sure why)
    this.container.removeChild(instance.container);
    this.container.addChild(instance.container);
  }

  protected shouldUpdate(prevProps: Props, prevState: State, props: Props, state: State): boolean {
    for (let key of (Object.keys(prevProps) as (keyof Props)[])) {
      if (key === 'delta' || key === 'args' || key === 'updaters') { continue; }
      if (key === 'position') {
        if (!prevProps[key].equals(props[key])) {
          console.log(`zlevel shouldUpdate differed in ${key}, returning true`);
          return true;
        } else {
          continue;
        }
      }
      if (key === 'selectedPointNode') {
        if (prevProps[key]?.hash() !== props[key]?.hash()) {
          console.log(`zlevel shouldUpdate differed in ${key}, returning true`);
          return true;
        } else {
          continue;
        }
      }
      if (prevProps[key] !== props[key]) {
        console.log(`zlevel shouldUpdate differed in ${key}, returning true`);
        return true;
      }
    }
    return false;
  }

  protected updateChildren(props: Props) {
    this.upsertChildren(props);
  }

  private upsertChildren(props: Props) {
    let childrenToDelete = this.children.clone(); // track which children need to be destroyed according to new props
    console.log(`zlevel component have ${this.children.size()} children`)
    for (let [chunkCoord, chunkGen] of props.zLevelGen?.chunks?.entries() || []) {
      const { childKey, childPropsFactory } = this.doChild(props, chunkCoord, chunkGen);
      let childComponent = this.children.get(childKey);
      if (childComponent) {
        // childComponent.update(childPropsFactory(props, this.state));
        childrenToDelete.remove(childKey);
      } else {
        childComponent = new ChunkComponent(childPropsFactory(props, this.state));
        this.children.put(childKey, childComponent);
        // this.container.addChild(childComponent.container);
        this.addChild({
          childClass: ChunkComponent,
          instance: childComponent,
          propsFactory: childPropsFactory
        });
      }
    }
    console.log(`zlevel component have ${childrenToDelete.size()} children to delete`)
    for (let [childKey, childComponent] of childrenToDelete.entries()) {
      // childComponent.willUnmount();
      this.children.remove(childKey);
      // this.container.removeChild(childComponent.container);
      // this._children.splice(this._children.findIndex(it => it.instance === childComponent), 1);
      this.removeChild(childComponent);
    }
  }

  private doChild(props: Props, chunkCoord: Vector2, chunkGen: ChunkGen): { childKey: ChunkRef, childPropsFactory: (p: Props, s: State) => ChunkComponentProps } {
    const chunkRef = new ChunkRef({
      z: props.z,
      chunkCoord,
      chunkId: chunkGen.id,
    });

    let childPropsFactory = (props: Props, state: State) => {
      let allocatedPointNodeSubset = new HashSet(
        props.allocatedPointNodeSubset.values().filter((pointNodeRef) => pointNodeRef.chunkCoord.equals(chunkRef.chunkCoord))
      );

      return {
        delta: props.delta,
        args: {
          pointNodeTexture: props.args.pointNodeTexture,
          markForceUpdate: this.markForceUpdate
        },
        selfChunkRef: chunkRef,
        updaters: props.updaters,
        tooltipUpdaters: props.tooltipUpdaters,
        position: chunkRef.chunkCoord.multiply(RenderedChunkConstants.CHUNK_SPACING_PX),
        chunkGen: chunkGen,
        // NOTE(bowei): for optimization, we dont tell other chunks about selected nodes in other chunks
        selectedPointNode: (props.selectedPointNode?.chunkCoord.equals(chunkRef.chunkCoord) ? props.selectedPointNode : undefined),
        allocatedPointNodeSubset,
      };
    }
    return {
      childKey: chunkRef,
      childPropsFactory
    };
  }

}

const wrapped = engageLifecycle(ZLevelComponent2);
// eslint-disable-next-line
type wrapped = ZLevelComponent2;
export { wrapped as ZLevelComponent };
export type { Props as ZLevelComponentProps };