import {
  Application,
  Container,
  Graphics,
  Sprite,
  Text,
  TextStyle,
  Texture,
} from "pixi.js";

async function setup(app: Application) {
  await app.init({ background: "#111111", resizeTo: window, antialias: true });
  document.getElementById("pixi-container")!.appendChild(app.canvas);
}

function makeColorBlock(color: number): Container {
  const container = new Container();
  const rect = new Graphics().rect(0, 0, 400, 400).fill(color);
  container.addChild(rect);
  return container;
}

interface Unit {
  x: number;
  y: number;
  vx: number;
  vy: number;
}

function stepUnit(u: Unit, dt: number, bounds: number, radius: number) {
  u.x += u.vx * dt;
  u.y += u.vy * dt;
  if (u.x < radius && u.vx < 0) {
    u.x = radius;
    u.vx *= -1;
  } else if (u.x > bounds - radius && u.vx > 0) {
    u.x = bounds - radius;
    u.vx *= -1;
  }
  if (u.y < radius && u.vy < 0) {
    u.y = radius;
    u.vy *= -1;
  } else if (u.y > bounds - radius && u.vy > 0) {
    u.y = bounds - radius;
    u.vy *= -1;
  }
}

function makeCosSqMaskTexture(
  size: number,
  nominalRadius: number,
  edgeWidth: number,
): Texture {
  const canvas = document.createElement("canvas");
  canvas.width = size;
  canvas.height = size;
  const ctx = canvas.getContext("2d")!;
  const imageData = ctx.createImageData(size, size);
  const cx = size / 2;
  const cy = size / 2;
  const inner = nominalRadius - edgeWidth / 2;
  const outer = nominalRadius + edgeWidth / 2;
  for (let y = 0; y < size; y++) {
    for (let x = 0; x < size; x++) {
      const r = Math.hypot(x - cx, y - cy);
      let v: number;
      if (r <= inner) {
        v = 1;
      } else if (r >= outer) {
        v = 0;
      } else {
        const t = (r - inner) / edgeWidth;
        const c = Math.cos((Math.PI / 2) * t);
        v = c * c;
      }
      const i = (y * size + x) * 4;
      const byte = Math.round(v * 255);
      imageData.data[i] = byte;
      imageData.data[i + 1] = byte;
      imageData.data[i + 2] = byte;
      imageData.data[i + 3] = 255;
    }
  }
  ctx.putImageData(imageData, 0, 0);
  return Texture.from(canvas);
}

function addGrid(container: Container, size: number, interval: number) {
  const grid = new Graphics();
  grid.setStrokeStyle({ color: 0x808080, width: 1 });
  for (let x = interval; x < size; x += interval) {
    grid.moveTo(x, 0).lineTo(x, size);
  }
  for (let y = interval; y < size; y += interval) {
    grid.moveTo(0, y).lineTo(size, y);
  }
  grid.stroke();
  container.addChild(grid);
}

export async function main() {
  const app = new Application();
  await setup(app);

  const labelStyle = new TextStyle({
    fill: 0x666666,
    fontSize: 14,
    fontFamily: "monospace",
  });

  const red = makeColorBlock(0xff0000);
  const green = makeColorBlock(0x00ff00);
  const blue = makeColorBlock(0x0000ff);

  const layouts = (["Scissors", "Stencil", "Alpha"] as const).map(
    (label, i) => {
      const block = [red, green, blue][i];
      const layout = new Container();
      layout.addChild(new Text({ text: label, style: labelStyle }));
      block.position.set(0, 20);
      layout.addChild(block);
      app.stage.addChild(layout);
      return layout;
    },
  );

  const updateLayout = () => {
    const isLandscape = window.innerWidth > window.innerHeight;
    layouts.forEach((layout, i) => {
      layout.position.set(
        isLandscape ? 50 + i * 450 : 50,
        isLandscape ? 50 : 30 + i * 450,
      );
    });
  };
  updateLayout();
  app.renderer.on("resize", updateLayout);

  addGrid(red, 400, 50);
  addGrid(green, 400, 50);
  addGrid(blue, 400, 50);

  const scissorMask = new Graphics().rect(-100, -100, 200, 200).fill(0xffffff);
  red.addChild(scissorMask);
  red.mask = scissorMask;

  const greenSideContainer = new Container();
  green.addChild(greenSideContainer);
  green.mask = greenSideContainer;
  const blueMaskTexture = makeCosSqMaskTexture(400, 100, 50);

  const blueMaskContainer = new Container();
  const blueMaskSprite = new Sprite(blueMaskTexture);
  blueMaskSprite.position.set(-200, -200);
  blueMaskContainer.addChild(blueMaskSprite);
  blue.addChild(blueMaskContainer);
  blue.mask = blueMaskSprite;

  const unitRadius = 10;
  const blueUnit: Unit = { x: 200, y: 200, vx: -2.1, vy: 1.6 };
  const blueUnitGraphic = new Graphics()
    .circle(0, 0, unitRadius)
    .fill(0xffffff);
  blueUnitGraphic.position.set(blueUnit.x, blueUnit.y);
  blue.addChild(blueUnitGraphic);

  const redUnit: Unit = { x: 200, y: 200, vx: 1.7, vy: -2.3 };
  const redUnitGraphic = new Graphics().circle(0, 0, unitRadius).fill(0xffffff);
  redUnitGraphic.position.set(redUnit.x, redUnit.y);
  red.addChild(redUnitGraphic);

  const greenUnits: Unit[] = [
    { x: 190, y: 210, vx: -0.8, vy: -2.9 },
    { x: 210, y: 210, vx: 1.3, vy: -2.5 },
  ];
  const unitGraphics = greenUnits.map((u) => {
    const g = new Graphics().circle(0, 0, unitRadius).fill(0xffffff);
    g.position.set(u.x, u.y);
    green.addChild(g);
    return g;
  });

  const followerCircles = greenUnits.map((u) => {
    const g = new Graphics().circle(0, 0, 100).fill(0xffffff);
    g.position.set(u.x, u.y);
    greenSideContainer.addChild(g);
    return g;
  });

  app.ticker.add((ticker) => {
    const dt = ticker.deltaTime;

    stepUnit(blueUnit, dt, 400, unitRadius);
    blueUnitGraphic.position.set(blueUnit.x, blueUnit.y);
    blueMaskContainer.position.set(blueUnit.x, blueUnit.y);

    stepUnit(redUnit, dt, 400, unitRadius);
    redUnitGraphic.position.set(redUnit.x, redUnit.y);
    scissorMask.position.set(redUnit.x, redUnit.y);

    for (let i = 0; i < greenUnits.length; i++) {
      stepUnit(greenUnits[i], dt, 400, unitRadius);
      unitGraphics[i].position.set(greenUnits[i].x, greenUnits[i].y);
      followerCircles[i].position.set(greenUnits[i].x, greenUnits[i].y);
    }
  });
}