import { app } from "scripts/app.js";
import { RgthreeBaseVirtualNodeConstructor } from "typings/rgthree.js";
import { RgthreeBaseVirtualNode } from "./base_node.js";
import { NodeTypesString } from "./constants.js";
import type {
  LGraphCanvas as TLGraphCanvas,
  LGraphNode,
  AdjustedMouseEvent,
  Vector2,
} from "typings/litegraph.js";
import { rgthree } from "./rgthree.js";

/**
 * A label node that allows you to put floating text anywhere on the graph. The text is the `Title`
 * and the font size, family, color, alignment as well as a background color, padding, and
 * background border radius can all be adjusted in the properties. Multiline text can be added from
 * the properties panel (because ComfyUI let's you shift + enter there, only).
 */
export class Label extends RgthreeBaseVirtualNode {
  static override type = NodeTypesString.LABEL;
  static override title = NodeTypesString.LABEL;
  override comfyClass = NodeTypesString.LABEL;

  static readonly title_mode = LiteGraph.NO_TITLE;
  static collapsable = false;

  static "@fontSize" = { type: "number" };
  static "@fontFamily" = { type: "string" };
  static "@fontColor" = { type: "string" };
  static "@textAlign" = { type: "combo", values: ["left", "center", "right"] };
  static "@backgroundColor" = { type: "string" };
  static "@padding" = { type: "number" };
  static "@borderRadius" = { type: "number" };

  override resizable = false;

  constructor(title = Label.title) {
    super(title);
    this.properties["fontSize"] = 12;
    this.properties["fontFamily"] = "Arial";
    this.properties["fontColor"] = "#ffffff";
    this.properties["textAlign"] = "left";
    this.properties["backgroundColor"] = "transparent";
    this.properties["padding"] = 0;
    this.properties["borderRadius"] = 0;
    this.color = "#fff0";
    this.bgcolor = "#fff0";

    this.onConstructed();
  }

  draw(ctx: CanvasRenderingContext2D) {
    this.flags = this.flags || {};
    this.flags.allow_interaction = !this.flags.pinned;
    ctx.save();
    this.color = "#fff0";
    this.bgcolor = "#fff0";
    const fontColor = this.properties["fontColor"] || "#ffffff";
    const backgroundColor = this.properties["backgroundColor"] || "";
    ctx.font = `${Math.max(this.properties["fontSize"] || 0, 1)}px ${
      this.properties["fontFamily"] ?? "Arial"
    }`;
    const padding = Number(this.properties["padding"]) ?? 0;

    const lines = this.title.replace(/\n*$/, "").split("\n");
    const maxWidth = Math.max(...lines.map((s) => ctx.measureText(s).width));
    this.size[0] = maxWidth + padding * 2;
    this.size[1] = this.properties["fontSize"] * lines.length + padding * 2;
    if (backgroundColor) {
      ctx.beginPath();
      const borderRadius = Number(this.properties["borderRadius"]) || 0;
      ctx.roundRect(0, 0, this.size[0], this.size[1], [borderRadius]);
      ctx.fillStyle = backgroundColor;
      ctx.fill();
    }
    ctx.textAlign = "left";
    let textX = padding;
    if (this.properties["textAlign"] === "center") {
      ctx.textAlign = "center";
      textX = this.size[0] / 2;
    } else if (this.properties["textAlign"] === "right") {
      ctx.textAlign = "right";
      textX = this.size[0] - padding;
    }
    ctx.textBaseline = "top";
    ctx.fillStyle = fontColor;
    let currentY = padding;
    for (let i = 0; i < lines.length; i++) {
      ctx.fillText(lines[i] || " ", textX, currentY);
      currentY += this.properties["fontSize"];
    }
    ctx.restore();
  }

  override onDblClick(event: AdjustedMouseEvent, pos: Vector2, canvas: TLGraphCanvas) {
    // Since everything we can do here is in the properties, let's pop open the properties panel.
    LGraphCanvas.active_canvas.showShowNodePanel(this);
  }

  override onShowCustomPanelInfo(panel: HTMLElement) {
    panel.querySelector('div.property[data-property="Mode"]')?.remove();
    panel.querySelector('div.property[data-property="Color"]')?.remove();
  }

  override inResizeCorner(x: number, y: number) {
    // A little ridiculous there's both a resizable property and this method separately to draw the
    // resize icon...
    return this.resizable;
  }

  override getHelp() {
    return `
      <p>
        The rgthree-comfy ${this.type!.replace("(rgthree)", "")} node allows you to add a floating
        label to your workflow.
      </p>
      <p>
        The text shown is the "Title" of the node and you can adjust the the font size, font family,
        font color, text alignment as well as a background color, padding, and background border
        radius from the node's properties. You can double-click the node to open the properties
        panel.
      <p>
      <ul>
        <li>
          <p>
            <strong>Pro tip #1:</strong> You can add multiline text from the properties panel
            <i>(because ComfyUI let's you shift + enter there, only)</i>.
          </p>
        </li>
        <li>
          <p>
            <strong>Pro tip #2:</strong> You can use ComfyUI's native "pin" option in the
            right-click menu to make the label stick to the workflow and clicks to "go through".
            You can right-click at any time to unpin.
          </p>
        </li>
        <li>
          <p>
            <strong>Pro tip #3:</strong> Color values are hexidecimal strings, like "#FFFFFF" for
            white, or "#660000" for dark red. You can supply a 7th & 8th value (or 5th if using
            shorthand) to create a transluscent color. For instance, "#FFFFFF88" is semi-transparent
            white.
          </p>
        </li>
      </ul>`;
  }
}

/**
 * We override the drawNode to see if we're drawing our label and, if so, hijack it so we can draw
 * it like we want. We also do call out to oldDrawNode, which takes care of very minimal things,
 * like a select box.
 */
const oldDrawNode = LGraphCanvas.prototype.drawNode;
LGraphCanvas.prototype.drawNode = function (node: LGraphNode, ctx: CanvasRenderingContext2D) {
  if (node.constructor === Label) {
    // These get set very aggressively; maybe an extension is doing it. We'll just clear them out
    // each time.
    (node as Label).bgcolor = "transparent";
    (node as Label).color = "transparent";
    const v = oldDrawNode.apply(this, arguments as any);
    (node as Label).draw(ctx);
    return v;
  }

  const v = oldDrawNode.apply(this, arguments as any);
  return v;
};

/**
 * We override LGraph getNodeOnPos to see if we're being called while also processing a mouse down
 * and, if so, filter out any label nodes on labels that are pinned. This makes the click go
 * "through" the label. We still allow right clicking (so you can unpin) and double click for the
 * properties panel, though that takes two double clicks (one to select, one to actually double
 * click).
 */
const oldGetNodeOnPos = LGraph.prototype.getNodeOnPos;
LGraph.prototype.getNodeOnPos = function <T extends LGraphNode>(
  x: number,
  y: number,
  nodes_list?: LGraphNode[],
  margin?: number,
) {
  if (
    // processMouseDown always passes in the nodes_list
    nodes_list &&
    rgthree.processingMouseDown &&
    rgthree.lastAdjustedMouseEvent?.type.includes("down") &&
    rgthree.lastAdjustedMouseEvent?.which === 1
  ) {
    // Using the same logic from LGraphCanvas processMouseDown, let's see if we consider this a
    // double click.
    let isDoubleClick = LiteGraph.getTime() - LGraphCanvas.active_canvas.last_mouseclick < 300;
    if (!isDoubleClick) {
      nodes_list = [...nodes_list].filter((n) => !(n instanceof Label) || !n.flags?.pinned);
    }
  }
  return oldGetNodeOnPos.apply(this, [x, y, nodes_list, margin]) as T | null;
};

// Register the extension.
app.registerExtension({
  name: "rgthree.Label",
  registerCustomNodes() {
    Label.setUp();
  },
});
