import { app } from "scripts/app.js";
import type { LGraphCanvas as TLGraphCanvas, Vector2 } from "../typings/litegraph.js";

function binarySearch(max: number, getValue: (n: number) => number, match: number) {
  let min = 0;

  while (min <= max) {
    let guess = Math.floor((min + max) / 2);
    const compareVal = getValue(guess);

    if (compareVal === match) return guess;
    if (compareVal < match) min = guess + 1;
    else max = guess - 1;
  }

  return max;
}

/**
 * Fits a string against a max width for a ctx. Font should be defined on ctx beforehand.
 */
export function fitString(ctx: CanvasRenderingContext2D, str: string, maxWidth: number) {
  let width = ctx.measureText(str).width;
  const ellipsis = "…";
  const ellipsisWidth = measureText(ctx, ellipsis);
  if (width <= maxWidth || width <= ellipsisWidth) {
    return str;
  }

  const index = binarySearch(
    str.length,
    (guess) => measureText(ctx, str.substring(0, guess)),
    maxWidth - ellipsisWidth,
  );

  return str.substring(0, index) + ellipsis;
}

/** Measures the width of text for a canvas context. */
export function measureText(ctx: CanvasRenderingContext2D, str: string) {
  return ctx.measureText(str).width;
}

export type WidgetRenderingOptionsPart = {
  type?: "toggle" | "custom";
  margin?: number;
  fillStyle?: string;
  strokeStyle?: string;
  lowQuality?: boolean;
  draw?(ctx: CanvasRenderingContext2D, x: number, lowQuality: boolean): number;
};

type WidgetRenderingOptions = {
  width: number;
  height: number;
  posX?: number;
  posY: number;
  borderRadius?: number;
  colorStroke?: string;
  colorBackground?: string;
  // node: LGraphNode;
  // value?: any;
  // margin?: number;
  // direction?: "right" | "left";
  // fillStyle?: string;
  // strokeStyle?: string;
  // parts: WidgetRenderingOptionsPart[];
};

export function isLowQuality() {
  const canvas = app.canvas as TLGraphCanvas;
  return (canvas.ds?.scale || 1) <= 0.5;
}

export function drawNodeWidget(ctx: CanvasRenderingContext2D, options: WidgetRenderingOptions) {
  const lowQuality = isLowQuality();

  const data = {
    width: options.width,
    height: options.height,
    posY: options.posY,
    lowQuality,
    margin: 15,
    colorOutline: LiteGraph.WIDGET_OUTLINE_COLOR,
    colorBackground: LiteGraph.WIDGET_BGCOLOR,
    colorText: LiteGraph.WIDGET_TEXT_COLOR,
    colorTextSecondary: LiteGraph.WIDGET_SECONDARY_TEXT_COLOR,
  };

  // Draw background.
  ctx.strokeStyle = options.colorStroke || data.colorOutline;
  ctx.fillStyle = options.colorBackground || data.colorBackground;
  ctx.beginPath();
  ctx.roundRect(
    data.margin,
    data.posY,
    data.width - data.margin * 2,
    data.height,
    lowQuality ? [0] : options.borderRadius ? [options.borderRadius] : [options.height * 0.5],
  );
  ctx.fill();
  if (!lowQuality) {
    ctx.stroke();
  }

  return data;
}

/** Draws a rounded rectangle. */
export function drawRoundedRectangle(
  ctx: CanvasRenderingContext2D,
  options: WidgetRenderingOptions,
) {
  const lowQuality = isLowQuality();
  options = { ...options };
  ctx.strokeStyle = options.colorStroke || LiteGraph.WIDGET_OUTLINE_COLOR;
  ctx.fillStyle = options.colorBackground || LiteGraph.WIDGET_BGCOLOR;
  ctx.beginPath();
  ctx.roundRect(
    options.posX!,
    options.posY,
    options.width,
    options.height,
    lowQuality ? [0] : options.borderRadius ? [options.borderRadius] : [options.height * 0.5],
  );
  ctx.fill();
  !lowQuality && ctx.stroke();
}

type DrawNumberWidgetPartOptions = {
  posX: number;
  posY: number;
  height: number;
  value: number;
  direction?: 1 | -1;
  textColor?: string;
};

/**
 * Draws a number picker with arrows off to each side.
 *
 * This is for internal widgets that may have many hit areas (full-width, default number widgets put
 * the arrows on either side of the full-width row).
 */
export function drawNumberWidgetPart(
  ctx: CanvasRenderingContext2D,
  options: DrawNumberWidgetPartOptions,
): [Vector2, Vector2, Vector2] {
  const arrowWidth = 9;
  const arrowHeight = 10;
  const innerMargin = 3;
  const numberWidth = 32;

  const xBoundsArrowLess: Vector2 = [0, 0];
  const xBoundsNumber: Vector2 = [0, 0];
  const xBoundsArrowMore: Vector2 = [0, 0];

  ctx.save();

  let posX = options.posX;
  const { posY, height, value, textColor } = options;
  const midY = posY + height / 2;

  // If we're drawing parts from right to left (usually when something in the middle will be
  // flexible), then we can simply move left the expected width of our widget and draw forwards.
  if (options.direction === -1) {
    posX = posX - arrowWidth - innerMargin - numberWidth - innerMargin - arrowWidth;
  }

  // Draw the strength left arrow.
  ctx.fill(
    new Path2D(
      `M ${posX} ${midY} l ${arrowWidth} ${
        arrowHeight / 2
      } l 0 -${arrowHeight} L ${posX} ${midY} z`,
    ),
  );

  xBoundsArrowLess[0] = posX;
  xBoundsArrowLess[1] = arrowWidth;
  posX += arrowWidth + innerMargin;

  // Draw the strength text.
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  const oldTextcolor = ctx.fillStyle;
  if (textColor) {
    ctx.fillStyle = textColor;
  }
  ctx.fillText(fitString(ctx, value.toFixed(2), numberWidth), posX + numberWidth / 2, midY);
  ctx.fillStyle = oldTextcolor;

  xBoundsNumber[0] = posX;
  xBoundsNumber[1] = numberWidth;
  posX += numberWidth + innerMargin;

  // Draw the strength right arrow.
  ctx.fill(
    new Path2D(
      `M ${posX} ${midY - arrowHeight / 2} l ${arrowWidth} ${arrowHeight / 2} l -${arrowWidth} ${
        arrowHeight / 2
      } v -${arrowHeight} z`,
    ),
  );

  xBoundsArrowMore[0] = posX;
  xBoundsArrowMore[1] = arrowWidth;

  ctx.restore();

  return [xBoundsArrowLess, xBoundsNumber, xBoundsArrowMore];
}
drawNumberWidgetPart.WIDTH_TOTAL = 9 + 3 + 32 + 3 + 9;

type DrawTogglePartOptions = {
  posX: number;
  posY: number;
  height: number;
  value: boolean | null;
};

/**
 * Draws a toggle for a widget. The toggle is a three-way switch with left being false, right being
 * true, and a middle state being null.
 */
export function drawTogglePart(
  ctx: CanvasRenderingContext2D,
  options: DrawTogglePartOptions,
): Vector2 {
  const lowQuality = isLowQuality();
  ctx.save();

  const { posX, posY, height, value } = options;

  const toggleRadius = height * 0.36; // This is the standard toggle height calc.
  const toggleBgWidth = height * 1.5; // We don't draw a separate bg, but this would be it.

  // Toggle Track
  if (!lowQuality) {
    ctx.beginPath();
    ctx.roundRect(posX + 4, posY + 4, toggleBgWidth - 8, height - 8, [height * 0.5]);
    ctx.globalAlpha = app.canvas.editor_alpha * 0.25;
    ctx.fillStyle = "rgba(255,255,255,0.45)";
    ctx.fill();
    ctx.globalAlpha = app.canvas.editor_alpha;
  }

  // Toggle itself
  ctx.fillStyle = value === true ? "#89B" : "#888";
  const toggleX =
    lowQuality || value === false
      ? posX + height * 0.5
      : value === true
      ? posX + height
      : posX + height * 0.75;
  ctx.beginPath();
  ctx.arc(toggleX, posY + height * 0.5, toggleRadius, 0, Math.PI * 2);
  ctx.fill();

  ctx.restore();

  return [posX, toggleBgWidth];
}

export function drawInfoIcon(
  ctx: CanvasRenderingContext2D,
  x: number,
  y: number,
  size: number = 12,
) {
  ctx.save();
  ctx.beginPath();
  ctx.roundRect(x, y, size, size, [size * 0.1]);
  ctx.fillStyle = "#2f82ec";
  ctx.strokeStyle = "#0f2a5e";
  ctx.fill();
  // ctx.stroke();
  ctx.strokeStyle = "#FFF";
  ctx.lineWidth = 2;
  // ctx.lineCap = 'round';
  const midX = x + size / 2;
  const serifSize = size * 0.175;
  ctx.stroke(
    new Path2D(`
    M ${midX} ${y + size * 0.15}
    v 2
    M ${midX - serifSize} ${y + size * 0.45}
    h ${serifSize}
    v ${size * 0.325}
    h ${serifSize}
    h -${serifSize * 2}
  `),
  );
  ctx.restore();
}
