import { StructRowProxy } from 'apache-arrow';
import * as d3 from 'd3';
import { easeBackIn, easeBackOut } from 'd3-ease';

import { Scatterplot } from './deepscatter/src/scatterplot';
import { Qid } from './deepscatter/src/tixrixqid';

type Circle = {
  clipId: string;
  row: StructRowProxy | { x: number; y: number };
  element: SVGElement;
  qid: Qid | null;
  hoverTransient: boolean;
  clickTransient: boolean;
  animating: boolean;
};

export class Highlights {
  private circles: Circle[] = [];

  constructor(
    private svg: SVGElement,
    private scatterplot: Scatterplot
  ) {
    // Clean up any existing highlights
    d3.select(svg).select('#mousepoints').selectAll('.highlight').remove();
  }

  updatePoints() {
    this.circles.forEach((circle) => {
      const { x, y } = this.scatterplot.zoom.world2screen(
        circle.row.x,
        circle.row.y
      );
      d3.select(circle.element).attr('transform', `translate(${x}, ${y})`);
    });
  }

  destroy() {
    this.removeAllCircles();
  }

  addCircle(
    row:
      | StructRowProxy
      | {
          x: number;
          y: number;
          image_url?: string;
          image_s3_id?: string; // Choose one of the two
        },
    clipId: string,
    qid: Qid | null,
    hoverTransient: boolean,
    clickTransient: boolean,
    style: { radius: number; strokeWidth: number; opacity: number }
  ) {
    const { radius, strokeWidth, opacity } = style;
    const { x, y } = this.scatterplot.zoom.world2screen(row.x, row.y);

    // Create positioned group
    const group = d3
      .select(this.svg)
      .select('#mousepoints')
      .append('g')
      .attr('class', 'highlight')
      .attr('transform', `translate(${x}, ${y})`)
      .attr('opacity', opacity)
      .attr('id', clipId)
      .attr('cursor', 'pointer');

    // Create scaling group with content
    const scaleGroup = group.append('g').attr('transform', 'scale(0)');

    // Add clipped image
    const clipPathId = `clip-${clipId}`;
    const imageRadius = radius - strokeWidth;

    scaleGroup
      .append('defs')
      .append('clipPath')
      .attr('id', clipPathId)
      .append('circle')
      .attr('cx', 0)
      .attr('cy', 0)
      .attr('r', imageRadius);

    const imageUrl =
      row.image_url || `https://cdn2.suno.ai/${row.image_s3_id}.jpeg`;
    scaleGroup
      .append('image')
      .attr('x', -imageRadius)
      .attr('y', -imageRadius)
      .attr('width', imageRadius * 2)
      .attr('height', imageRadius * 2)
      .attr('href', imageUrl)
      .attr('clip-path', `url(#${clipPathId})`);

    // Add stroke circle
    scaleGroup
      .append('circle')
      .attr('cx', 0)
      .attr('cy', 0)
      .attr('r', radius)
      .attr('fill', 'none')
      .attr('stroke', 'rgba(var(--rgb-border-primary) / 50)')
      .attr('stroke-width', strokeWidth);

    // Animate scale-in
    scaleGroup
      .transition()
      .duration(150)
      .ease(easeBackOut)
      .attr('transform', 'scale(1)');

    this.circles.push({
      clipId,
      row,
      element: group.node() as SVGElement,
      qid,
      hoverTransient,
      clickTransient,
      animating: false,
    });
  }

  async removeTransientCircles(isClick: boolean) {
    const shouldRemove = (c: Circle) =>
      c.hoverTransient || (c.clickTransient && isClick);
    const toRemove = this.circles.filter(shouldRemove);
    this.circles = this.circles.filter((c) => !shouldRemove(c));

    await Promise.all(
      toRemove.map((c) => !c.animating && this.removeCircle(c)).filter(Boolean)
    );
  }

  removeCircle(circle: Circle) {
    if (!this.svg) return;

    return new Promise<void>((resolve) => {
      circle.animating = true;

      d3.select(circle.element)
        .select('g')
        .transition()
        .duration(150)
        .ease(easeBackIn)
        .attr('transform', 'scale(0)')
        .on('end', () => {
          d3.select(circle.element).remove();
          circle.animating = false;
          resolve();
        });
    });
  }

  removeAllCircles() {
    this.circles.forEach((circle) => this.removeCircle(circle));
    this.circles = [];
  }
}
