import * as Pixi from "pixi.js";
import { Vector2 } from "../lib/util/geometry/vector2";
import { GameState, WindowState } from "../data/GameState";
// eslint-disable-next-line
import { assertOnlyCalledOnce, Const } from "../lib/util/misc";
import { RootComponent } from "./components/RootComponent";
import { UpdaterGeneratorType2 } from "../lib/util/updaterGenerator";

type Props = {
  args: {
    fireBatch: () => void,
    isSecondConstructorCall: boolean
  },
  updaters: UpdaterGeneratorType2<GameState>, // aka updaters
  windowState: Const<WindowState>,
  gameState: Const<GameState>,
}

type State = {
  appSize: Vector2,
  originalAppSize: Vector2,
}

function appSizeFromWindowSize(window?: Const<Vector2>): Vector2 {
  return new Vector2({
    x: Math.min(1920, (window?.x || Infinity) - 24),
    y: Math.min(1080, (window?.y || Infinity) - 24),
  });
}

/**
 * TODO(bowei): move the resizing out of this function and into root component,
 * and only handle react/pixi state management in this class
 */
export class PixiReactBridge {
  public app!: Pixi.Application;

  state!: State;
  props!: Props;

  rootComponent: RootComponent | undefined;
  onTick!: (d: number) => void;

  /**
   * NOTE: for lifecycle convenience, we allow initializing with essentially empty props, and to finish the initialization
   * lazily at the first rerender() call
   */
  constructor(props?: Props, isSecondConstructorCall: boolean = false) {
    // verify that we are not loading this twice when we expect to load it only once -- bad for performance!!
    if (!(props?.args?.isSecondConstructorCall || isSecondConstructorCall)) {
      // assertOnlyCalledOnce("Pixi react bridge constructor"); // annoying with react hot reload, disable for now}
    }

    let appSize = new Vector2(800, 600);
    this.state = {
      appSize,
      originalAppSize: appSize
    }

    this.app = new Pixi.Application({
      width: this.state.appSize.x,
      height: this.state.appSize.y,
      antialias: true, // both about the same FPS, i get around 30 fps on 1600 x 900
      transparent: true, // true -> better fps?? https://github.com/pixijs/pixi.js/issues/5580
      resolution: window.devicePixelRatio || 1, // lower -> more FPS but uglier
      // resolution: 0.5,
      // resolution: 2,
      autoDensity: true,
      powerPreference: "low-power", // the only valid one for webgl
      backgroundColor: 0xffffff, // immaterial - we recommend setting color in backdrop graphics
    });

    // test
    // createBunnyExample({ parent: this.app.stage, ticker: this.app.ticker, x: this.app.screen.width / 2, y: this.app.screen.height / 2 });
  }

  public pause() {
    this.app.ticker.remove(this.onTick);
  }
  public destroy() {
    this.app.destroy(true, { children: true, texture: true, baseTexture: true });
  }

  public didMount() {
    this.onTick = (delta) => this.baseGameLoop(delta);
    this.onTick = this.onTick.bind(this);
    this.app.ticker.add(this.onTick);
  }

  /**
   * Please only call once!!
   * Usage: const container = useRef<HTMLDivElement>(null); useEffect(() => { application.register(container.current!); }, []);
   */
  public register(curr: HTMLDivElement) {
    curr.appendChild(this.app.view);
  }

  updateSelf(props: Props) {
    this.state.appSize = appSizeFromWindowSize(new Vector2(props.windowState.innerWidth, props.windowState.innerHeight));
  }

  // shim, called from react, possibly many times , possibly at any time, including during the baseGameLoop below
  // props should be a referentially distinct object from props the last time this was called
  rerender(props: Props) {
    console.log("base app rerender called", { playerUI: props.gameState.playerUI });
    this.props = props;
    if (!this.rootComponent) {
      // finish initialization
      this.rootComponent = new RootComponent({
        args: {
          renderer: this.app.renderer,
          markForceUpdate: () => { },
        },
        updaters: this.props.updaters,
        delta: 0,
        gameState: this.props.gameState,
        appSize: this.state.appSize,
      })
      this.app.stage.addChild(this.rootComponent.container);

      this.renderSelf(this.props);

      // test
      // createBunnyExample({ parent: this.app.stage, ticker: this.app.ticker, x: this.app.screen.width / 2, y: this.app.screen.height / 2 });
      this.didMount();
    }
  }

  renderSelf(props: Props) {
    this.app.renderer.resize(this.state.appSize.x, this.state.appSize.y);
  }

  baseGameLoop(delta: number) {
    // assume props is up to date
    this.updateSelf(this.props);
    // send props downwards
    this.rootComponent?.update({
      args: {
        renderer: this.app.renderer,
        markForceUpdate: () => { },
      },
      updaters: this.props.updaters,
      delta,
      gameState: this.props.gameState,
      appSize: this.state.appSize,
    });
    
    this.renderSelf(this.props);
    this.props.args.fireBatch(); // fire enqueued game state updates, which should come back from react in the rerender()
  }
}