import {
  Application,
  Container,
  Graphics,
  Mesh,
  MeshGeometry,
  Shader,
  UniformGroup,
} from "pixi.js";

const vertex = /* language=GLSL */ `
#version 300 es

in vec2 aPosition;
in vec2 aUV;
out vec2 vPixelPos;

uniform mat3 uProjectionMatrix;
uniform mat3 uTransformMatrix;

void main() {
  vPixelPos = aUV;
  vec3 pos = uProjectionMatrix * uTransformMatrix * vec3(aPosition, 1.0);
  gl_Position = vec4(pos.xy, 0.0, 1.0);
}
`;

const fragment = /* language=GLSL */ `
#version 300 es

in vec2 vPixelPos;
out vec4 fragColor;

uniform float uTime;
uniform float uCenterX;
uniform float uCenterY;
uniform float uOmega;
uniform float uKr;
uniform float uAmp;

void main() {
  float dx = vPixelPos.x - uCenterX;
  float dy = vPixelPos.y - uCenterY;
  float r = sqrt(dx * dx + dy * dy);
  // concentric animated rings
  float v = uAmp + uAmp * sin(r * uKr - uTime * uOmega);
  fragColor = vec4(v, v, v, 1.0);
}
`;

const softCircleFragment = /* language=GLSL */ `
#version 300 es

in vec2 vPixelPos;
out vec4 fragColor;

uniform float uCenterX;
uniform float uCenterY;
uniform float uDiameter;
uniform float uEdgeWidth;

void main() {
  const float PI = 3.14159265;
  float r = length(vPixelPos - vec2(uCenterX, uCenterY));
  float t = clamp((r - (uDiameter * 0.5 - uEdgeWidth * 0.5)) / uEdgeWidth, 0.0, 1.0);
  float v = 0.5 * (1.0 + cos(PI * t));
  fragColor = vec4(v, v, v, 1.0);
}
`;

type SoftCircleUniforms = {
  uCenterX: { value: number; type: "f32" };
  uCenterY: { value: number; type: "f32" };
  uDiameter: { value: number; type: "f32" };
  uEdgeWidth: { value: number; type: "f32" };
};

type RippleUniforms = {
  uTime: { value: number; type: "f32" };
  uCenterX: { value: number; type: "f32" };
  uCenterY: { value: number; type: "f32" };
  uOmega: { value: number; type: "f32" };
  uKr: { value: number; type: "f32" };
  uAmp: { value: number; type: "f32" };
};

const SQRT2_INV = 1 / Math.sqrt(2);

function quadVerts(w: number, h: number) {
  return new Float32Array([0, 0, w, 0, w, h, 0, h]);
}

function makeShaderWidget(
  initialW: number,
  initialH: number,
): {
  container: Container;
  uniforms: UniformGroup<RippleUniforms>;
  resize: (w: number, h: number) => void;
  destroy: () => void;
} {
  const geometry = new MeshGeometry({
    positions: quadVerts(initialW, initialH),
    uvs: quadVerts(initialW, initialH),
    indices: new Uint32Array([0, 1, 2, 0, 2, 3]),
  });

  const uniforms = new UniformGroup<RippleUniforms>({
    uTime: { value: 0.0, type: "f32" },
    uCenterX: { value: initialW / 2, type: "f32" },
    uCenterY: { value: initialH / 2, type: "f32" },
    uOmega: { value: 2.4, type: "f32" },
    uKr: { value: 0.05, type: "f32" },
    uAmp: { value: 0.5, type: "f32" },
  });

  const shader = Shader.from({
    gl: { vertex, fragment },
    resources: { shaderUniforms: uniforms },
  });

  const container = new Container();
  container.addChild(new Mesh({ geometry, shader }));

  function resize(w: number, h: number) {
    geometry.positions = quadVerts(w, h);
    geometry.uvs = quadVerts(w, h);
    uniforms.uniforms.uCenterX = w / 2;
    uniforms.uniforms.uCenterY = h / 2;
  }

  function destroy() {
    geometry.destroy();
    shader.destroy();
    container.destroy({ children: true });
  }

  return { container, uniforms, resize, destroy };
}

function makeSoftCircleWidget(
  initialW: number,
  initialH: number,
): {
  container: Container;
  uniforms: UniformGroup<SoftCircleUniforms>;
  resize: (w: number, h: number) => void;
  destroy: () => void;
} {
  const geometry = new MeshGeometry({
    positions: quadVerts(initialW, initialH),
    uvs: quadVerts(initialW, initialH),
    indices: new Uint32Array([0, 1, 2, 0, 2, 3]),
  });

  const uniforms = new UniformGroup<SoftCircleUniforms>({
    uCenterX: { value: initialW / 2, type: "f32" },
    uCenterY: { value: initialH / 2, type: "f32" },
    uDiameter: { value: Math.min(initialW, initialH) * 0.7, type: "f32" },
    uEdgeWidth: { value: 30.0, type: "f32" },
  });

  const shader = Shader.from({
    gl: { vertex, fragment: softCircleFragment },
    resources: { shaderUniforms: uniforms },
  });

  const container = new Container();
  container.addChild(new Mesh({ geometry, shader }));

  function resize(w: number, h: number) {
    geometry.positions = quadVerts(w, h);
    geometry.uvs = quadVerts(w, h);
    uniforms.uniforms.uCenterX = w / 2;
    uniforms.uniforms.uCenterY = h / 2;
  }

  function destroy() {
    geometry.destroy();
    shader.destroy();
    container.destroy({ children: true });
  }

  return { container, uniforms, resize, destroy };
}

function makeSoftCircleControls(
  stage: Container,
  widgetContainer: Container,
  uniforms: UniformGroup<SoftCircleUniforms>,
): { container: Container; redraw: () => void; destroy: () => void } {
  const handleLayer = new Graphics();
  const centerHandle = new Graphics();
  centerHandle.eventMode = "static";
  centerHandle.cursor = "move";
  const radiusHandle = new Graphics();
  radiusHandle.eventMode = "static";
  radiusHandle.cursor = "ew-resize";
  const edgeInnerHandle = new Graphics();
  edgeInnerHandle.eventMode = "static";
  edgeInnerHandle.cursor = "nesw-resize";
  const edgeOuterHandle = new Graphics();
  edgeOuterHandle.eventMode = "static";
  edgeOuterHandle.cursor = "nesw-resize";

  const container = new Container();
  container.addChild(
    handleLayer,
    centerHandle,
    radiusHandle,
    edgeInnerHandle,
    edgeOuterHandle,
  );

  function redraw() {
    const cx = uniforms.uniforms.uCenterX;
    const cy = uniforms.uniforms.uCenterY;
    const r = uniforms.uniforms.uDiameter / 2;
    const hw = uniforms.uniforms.uEdgeWidth / 2;
    const rInner = r - hw;
    const rOuter = r + hw;

    const guideStroke = { color: 0x44aaff, width: 1 };
    handleLayer
      .clear()
      .moveTo(cx, cy)
      .lineTo(cx + r, cy)
      .stroke(guideStroke)
      .moveTo(cx + rInner * SQRT2_INV, cy - rInner * SQRT2_INV)
      .lineTo(cx + rOuter * SQRT2_INV, cy - rOuter * SQRT2_INV)
      .stroke(guideStroke);
    const outline = { color: 0x000000, width: 1.5 };
    centerHandle
      .clear()
      .circle(cx, cy, 5)
      .fill({ color: 0xffdd00 })
      .stroke(outline);
    radiusHandle
      .clear()
      .circle(cx + r, cy, 5)
      .fill({ color: 0x44ff88 })
      .stroke(outline);
    edgeInnerHandle
      .clear()
      .circle(cx + rInner * SQRT2_INV, cy - rInner * SQRT2_INV, 5)
      .fill({ color: 0xff8844 })
      .stroke(outline);
    edgeOuterHandle
      .clear()
      .circle(cx + rOuter * SQRT2_INV, cy - rOuter * SQRT2_INV, 5)
      .fill({ color: 0xff8844 })
      .stroke(outline);
  }
  redraw();

  function localPos(e: { global: { x: number; y: number } }) {
    return widgetContainer.toLocal(e.global);
  }

  function projectedR(e: { global: { x: number; y: number } }) {
    const p = localPos(e);
    const px = p.x - uniforms.uniforms.uCenterX;
    const py = p.y - uniforms.uniforms.uCenterY;
    return (px - py) * SQRT2_INV;
  }

  // --- Center drag ---
  const centerOffset = { x: 0, y: 0 };

  function onCenterMove(e: { global: { x: number; y: number } }) {
    const p = localPos(e);
    uniforms.uniforms.uCenterX = p.x - centerOffset.x;
    uniforms.uniforms.uCenterY = p.y - centerOffset.y;
    redraw();
  }

  function onCenterEnd() {
    stage.off("pointermove", onCenterMove);
    window.removeEventListener("pointerup", onCenterEnd);
  }

  centerHandle.on("pointerdown", (e) => {
    onCenterEnd();
    const p = localPos(e);
    centerOffset.x = p.x - uniforms.uniforms.uCenterX;
    centerOffset.y = p.y - uniforms.uniforms.uCenterY;
    stage.on("pointermove", onCenterMove);
    window.addEventListener("pointerup", onCenterEnd);
  });

  // --- Radius drag ---
  let radiusOffsetX = 0;

  function onRadiusMove(e: { global: { x: number; y: number } }) {
    const p = localPos(e);
    const newR = Math.max(
      uniforms.uniforms.uEdgeWidth / 2 + 1,
      p.x - uniforms.uniforms.uCenterX - radiusOffsetX,
    );
    uniforms.uniforms.uDiameter = newR * 2;
    redraw();
  }

  function onRadiusEnd() {
    stage.off("pointermove", onRadiusMove);
    window.removeEventListener("pointerup", onRadiusEnd);
  }

  radiusHandle.on("pointerdown", (e) => {
    onRadiusEnd();
    const p = localPos(e);
    radiusOffsetX =
      p.x - uniforms.uniforms.uCenterX - uniforms.uniforms.uDiameter / 2;
    stage.on("pointermove", onRadiusMove);
    window.addEventListener("pointerup", onRadiusEnd);
  });

  // --- Edge width drag ---
  let edgeDragOffset = 0;

  function onInnerEdgeMove(e: { global: { x: number; y: number } }) {
    const newRInner = projectedR(e) - edgeDragOffset;
    uniforms.uniforms.uEdgeWidth = Math.max(
      1,
      2 * (uniforms.uniforms.uDiameter / 2 - newRInner),
    );
    redraw();
  }

  function onOuterEdgeMove(e: { global: { x: number; y: number } }) {
    const newROuter = projectedR(e) - edgeDragOffset;
    uniforms.uniforms.uEdgeWidth = Math.max(
      1,
      2 * (newROuter - uniforms.uniforms.uDiameter / 2),
    );
    redraw();
  }

  function onInnerEdgeEnd() {
    stage.off("pointermove", onInnerEdgeMove);
    window.removeEventListener("pointerup", onInnerEdgeEnd);
  }

  function onOuterEdgeEnd() {
    stage.off("pointermove", onOuterEdgeMove);
    window.removeEventListener("pointerup", onOuterEdgeEnd);
  }

  edgeInnerHandle.on("pointerdown", (e) => {
    onInnerEdgeEnd();
    edgeDragOffset =
      projectedR(e) -
      (uniforms.uniforms.uDiameter / 2 - uniforms.uniforms.uEdgeWidth / 2);
    stage.on("pointermove", onInnerEdgeMove);
    window.addEventListener("pointerup", onInnerEdgeEnd);
  });

  edgeOuterHandle.on("pointerdown", (e) => {
    onOuterEdgeEnd();
    edgeDragOffset =
      projectedR(e) -
      (uniforms.uniforms.uDiameter / 2 + uniforms.uniforms.uEdgeWidth / 2);
    stage.on("pointermove", onOuterEdgeMove);
    window.addEventListener("pointerup", onOuterEdgeEnd);
  });

  function destroy() {
    onCenterEnd();
    onRadiusEnd();
    onInnerEdgeEnd();
    onOuterEdgeEnd();
    container.destroy({ children: true });
  }

  return { container, redraw, destroy };
}

function makeResizableChrome(
  stage: Container,
  inner: Container,
  initialW: number,
  initialH: number,
  onResize: (w: number, h: number) => void,
): { container: Container; destroy: () => void } {
  let W = initialW;
  let H = initialH;

  const frame = new Graphics();

  const moveHandle = new Graphics();
  moveHandle.eventMode = "static";
  moveHandle.cursor = "grab";

  const resizeHandle = new Graphics();
  resizeHandle.eventMode = "static";
  resizeHandle.cursor = "nwse-resize";

  function redraw() {
    frame
      .clear()
      .rect(-1, -1, W + 2, H + 2)
      .stroke({ color: 0xffffff, width: 1 });
    moveHandle.clear().circle(-1, -1, 5).fill({ color: 0x4488ff });
    resizeHandle
      .clear()
      .circle(W + 1, H + 1, 5)
      .fill({ color: 0xffffff });
  }
  redraw();

  const container = new Container();
  container.addChild(inner, frame, moveHandle, resizeHandle);

  const dragOffset = { x: 0, y: 0 };

  function onResize_(e: { global: { x: number; y: number } }) {
    W = Math.max(50, e.global.x - container.x - dragOffset.x);
    H = Math.max(50, e.global.y - container.y - dragOffset.y);
    onResize(W, H);
    redraw();
  }

  function onMove(e: { global: { x: number; y: number } }) {
    container.x = e.global.x - dragOffset.x;
    container.y = e.global.y - dragOffset.y;
  }

  function onMoveEnd() {
    moveHandle.cursor = "grab";
    stage.off("pointermove", onMove);
    window.removeEventListener("pointerup", onMoveEnd);
  }

  function onResizeEnd() {
    stage.off("pointermove", onResize_);
    window.removeEventListener("pointerup", onResizeEnd);
  }

  moveHandle.on("pointerdown", (e) => {
    moveHandle.cursor = "grabbing";
    onMoveEnd();
    dragOffset.x = e.global.x - container.x;
    dragOffset.y = e.global.y - container.y;
    stage.on("pointermove", onMove);
    window.addEventListener("pointerup", onMoveEnd);
  });

  resizeHandle.on("pointerdown", (e) => {
    e.stopPropagation();
    onResizeEnd();
    dragOffset.x = e.global.x - container.x - W;
    dragOffset.y = e.global.y - container.y - H;
    stage.on("pointermove", onResize_);
    window.addEventListener("pointerup", onResizeEnd);
  });

  function destroy() {
    onMoveEnd();
    onResizeEnd();
    container.removeChild(inner);
    container.destroy({ children: true });
  }

  return { container, destroy };
}

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

  app.stage.eventMode = "static";
  app.stage.hitArea = app.screen;
  app.renderer.on("resize", () => {
    app.stage.hitArea = app.screen;
  });

  const {
    container: rippleDisplay,
    uniforms: rippleUniforms,
    resize: rippleResize,
  } = makeShaderWidget(400, 300);
  const { container: rippleChrome } = makeResizableChrome(
    app.stage,
    rippleDisplay,
    400,
    300,
    rippleResize,
  );
  rippleChrome.x = 50;
  rippleChrome.y = 100;
  app.stage.addChild(rippleChrome);

  const {
    container: circleDisplay,
    uniforms: circleUniforms,
    resize: circleResize,
  } = makeSoftCircleWidget(300, 300);
  const circleControls = makeSoftCircleControls(
    app.stage,
    circleDisplay,
    circleUniforms,
  );
  circleDisplay.addChild(circleControls.container);
  const { container: circleChrome } = makeResizableChrome(
    app.stage,
    circleDisplay,
    300,
    300,
    (w, h) => {
      circleResize(w, h);
      circleControls.redraw();
    },
  );
  circleChrome.x = 500;
  circleChrome.y = 100;
  app.stage.addChild(circleChrome);

  app.ticker.add((ticker) => {
    rippleUniforms.uniforms.uTime += ticker.deltaMS / 1000;
  });

  const wrap = document.createElement("div");
  wrap.style.cssText =
    "position:fixed;top:16px;right:16px;display:flex;flex-direction:column;gap:8px;";

  const linkStyle =
    "padding:4px 12px;color:#88aacc;font-family:monospace;font-size:13px;text-decoration:none;display:block;";

  const homeLink = document.createElement("a");
  homeLink.href = "/";
  homeLink.textContent = "← All demos";
  homeLink.style.cssText = linkStyle;
  wrap.appendChild(homeLink);

  const codeLink = document.createElement("a");
  codeLink.href = "scratchFragmentShaders_source.html";
  codeLink.textContent = "View source";
  codeLink.style.cssText = linkStyle;
  wrap.appendChild(codeLink);

  document.body.appendChild(wrap);
}