import { app } from "../../scripts/app.js";

/* ── Category schema (fetched once from Python backend) ─────────── */

let _schemaPromise = null;

function fetchCategorySchema() {
  if (!_schemaPromise) {
    _schemaPromise = fetch("/booru-gacha/categories")
      .then((r) => r.json())
      .catch((e) => {
        console.error("[BooruGacha] Failed to load categories:", e);
        return { top_groups: [], subgroups: {} };
      });
  }
  return _schemaPromise;
}

/* ── Helpers ────────────────────────────────────────────────────── */

function fmt(key) {
  return key.replace(/_/g, " ");
}

function moveWidget(arr, item, newIndex) {
  const old = arr.indexOf(item);
  if (old < 0) return;
  arr.splice(old, 1);
  arr.splice(newIndex, 0, item);
}

/* ── Separator widget (thin horizontal line) ─────────────────────── */

function createSeparator() {
  return {
    type: "custom",
    name: "bg_sep",
    value: null,
    options: { serialize: false },
    computeSize(width) {
      return [width, 12];
    },
    draw(ctx, _node, width, posY, H) {
      ctx.strokeStyle = "#444";
      ctx.lineWidth = 1;
      ctx.beginPath();
      ctx.moveTo(10, posY + 4);
      ctx.lineTo(width - 10, posY + 4);
      ctx.stroke();
    },
    mouse() {
      return false;
    },
  };
}

/* ── Single-row custom widget for one category slot ────────────── */
/*
 *  Layout (22px tall, compact):
 *  [● toggle] [category name ..flex..] [◀ N ▶] [weight] [✖]
 *
 *  Click zones:
 *    toggle zone  → enable / disable
 *    ◀            → decrement count
 *    ▶            → increment count
 *    weight       → left half -0.1, right half +0.1
 *    ✖            → remove slot
 *    name area    → no action (add new + remove to change)
 */

const ROW_H = 22;
const PAD = 6;

function fmtWeight(v) {
  if (Math.abs(v - Math.round(v * 10) / 10) < 0.001) return v.toFixed(1);
  return v.toFixed(2);
}

function createSlotWidget(name, category, node, onRemove, onSync) {
  const w = {
    type: "custom",
    name,
    value: { category, count: 1, enabled: true, weight: 1.0 },
    options: { serialize: false },
    _lastWidth: 300,

    computeSize(width) {
      return [width, ROW_H];
    },

    draw(ctx, _node, width, posY, H) {
      w._lastWidth = width;
      const on = w.value.enabled !== false;
      const midY = posY + H / 2;

      /* ── background ── */
      ctx.fillStyle = on ? "#2a2a2e" : "#222224";
      ctx.beginPath();
      ctx.roundRect(PAD, posY + 1, width - PAD * 2, H - 2, 4);
      ctx.fill();

      /* ── toggle dot ── */
      const tX = PAD + 10;
      ctx.beginPath();
      ctx.arc(tX, midY, 5, 0, Math.PI * 2);
      ctx.fillStyle = on ? "#6a6" : "#555";
      ctx.fill();

      /* ── layout zones (right to left) ── */
      const removeX = width - PAD - 20;
      const weightX = removeX - 36;
      const arrowRX = weightX - 12;
      const countNumX = arrowRX - 16;
      const arrowLX = countNumX - 12;
      const textX = PAD + 22;

      /* ── category name ── */
      ctx.fillStyle = on ? "#ccc" : "#666";
      ctx.font = "11px sans-serif";
      ctx.textAlign = "left";
      ctx.textBaseline = "middle";
      ctx.save();
      ctx.beginPath();
      ctx.rect(textX, posY, arrowLX - textX - 4, H);
      ctx.clip();
      ctx.fillText(fmt(w.value.category), textX, midY);
      ctx.restore();

      /* ── count: ◀ N ▶ ── */
      ctx.textBaseline = "middle";

      ctx.fillStyle = on ? "#888" : "#555";
      ctx.font = "8px sans-serif";
      ctx.textAlign = "center";
      ctx.fillText("\u25C0", arrowLX + 5, midY);

      ctx.fillStyle = on ? "#ddd" : "#777";
      ctx.font = "11px sans-serif";
      ctx.fillText(String(w.value.count), countNumX + 8, midY);

      ctx.fillStyle = on ? "#888" : "#555";
      ctx.font = "8px sans-serif";
      ctx.fillText("\u25B6", arrowRX + 5, midY);

      /* ── weight ── */
      const isDefault = Math.abs(w.value.weight - 1.0) < 0.001;
      ctx.fillStyle = on
        ? isDefault
          ? "#666"
          : "#b9a45a"
        : "#555";
      ctx.font = "10px sans-serif";
      ctx.textAlign = "center";
      ctx.fillText(fmtWeight(w.value.weight), weightX + 18, midY);

      /* ── remove ✖ ── */
      ctx.fillStyle = on ? "#955" : "#544";
      ctx.font = "10px sans-serif";
      ctx.textAlign = "center";
      ctx.fillText("\u2715", removeX + 10, midY);

      /* reset */
      ctx.textAlign = "left";
      ctx.textBaseline = "alphabetic";
    },

    mouse(event, pos) {
      if (event.type !== "pointerdown") return false;

      const x = pos[0];
      const width = w._lastWidth;
      const removeX = width - PAD - 20;
      const weightX = removeX - 36;
      const arrowRX = weightX - 12;
      const countNumX = arrowRX - 16;
      const arrowLX = countNumX - 12;
      const textX = PAD + 22;

      /* toggle */
      if (x < textX) {
        w.value.enabled = !w.value.enabled;
        onSync();
        return true;
      }

      /* remove ✖ */
      if (x >= removeX) {
        onRemove();
        return true;
      }

      /* weight — left half -0.1, right half +0.1 */
      if (x >= weightX && x < removeX) {
        const mid = weightX + 18;
        if (x < mid) {
          w.value.weight = Math.round(Math.max(0.0, w.value.weight - 0.1) * 10) / 10;
        } else {
          w.value.weight = Math.round(Math.min(2.0, w.value.weight + 0.1) * 10) / 10;
        }
        onSync();
        return true;
      }

      /* ▶ increment count */
      if (x >= arrowRX && x < weightX) {
        w.value.count = Math.min(20, w.value.count + 1);
        onSync();
        return true;
      }

      /* ◀ decrement count */
      if (x >= arrowLX && x < countNumX) {
        w.value.count = Math.max(1, w.value.count - 1);
        onSync();
        return true;
      }

      /* count number — increment */
      if (x >= countNumX && x < arrowRX) {
        w.value.count = Math.min(20, w.value.count + 1);
        onSync();
        return true;
      }

      return false;
    },
  };

  return w;
}

/* ── Hierarchical category menu ──────────────────────────────────── */

function buildMenuItems(schema, onSelect) {
  const items = [];

  for (const [subgroup, cats] of Object.entries(schema.subgroups)) {
    items.push({
      content: fmt(subgroup),
      has_submenu: true,
      callback: (_v, _opts, e, parentMenu) => {
        const sub = cats.map((cat) => ({
          content: fmt(cat),
          callback: () => onSelect(cat),
        }));
        new LiteGraph.ContextMenu(sub, {
          event: e,
          parentMenu,
          title: fmt(subgroup),
        });
        return false;
      },
    });
  }

  items.push(null);

  for (const group of schema.top_groups) {
    items.push({
      content: fmt(group),
      callback: () => onSelect(group),
    });
  }

  return items;
}

/* ── Extension ──────────────────────────────────────────────────── */

app.registerExtension({
  name: "BooruGacha.Roll",

  async beforeRegisterNodeDef(nodeType, nodeData) {
    if (nodeData.name !== "BooruGachaRoll") return;

    const schemaReady = fetchCategorySchema();

    /* ---- onNodeCreated ----------------------------------------- */
    const origCreated = nodeType.prototype.onNodeCreated;
    nodeType.prototype.onNodeCreated = function () {
      origCreated?.apply(this, arguments);

      this._bgSlots = [];
      this._bgCounter = 0;

      // Hide the "slots" text widget — keep in array for value
      // transport to Python, but prevent all rendering/interaction.
      const slotsW = this.widgets?.find((w) => w.name === "slots");
      if (slotsW) {
        slotsW.hidden = true;
        slotsW.computeSize = () => [0, -4];
        slotsW.draw = () => {};
        slotsW.mouse = () => false;
      }

      // Separator between standard inputs and our slot list
      this._separator = createSeparator();
      this.addCustomWidget(this._separator);

      // "Add Category" button
      this._addBtn = this.addWidget(
        "button",
        "bg_add",
        null,
        () => schemaReady.then((schema) => this._bgShowAddMenu(schema)),
      );
      this._addBtn.label = "\u2795 Add Category";
    };

    /* ---- "Add Category" menu ----------------------------------- */
    nodeType.prototype._bgShowAddMenu = function (schema) {
      const items = buildMenuItems(schema, (cat) =>
        this._bgAddSlot(cat, schema),
      );
      new LiteGraph.ContextMenu(items, {
        event: window.event,
        title: "Add Category",
      });
    };

    /* ---- add / remove slots ------------------------------------ */
    nodeType.prototype._bgAddSlot = function (category) {
      const idx = this._bgCounter++;

      const slotW = createSlotWidget(
        `bg_slot_${idx}`,
        category,
        this,
        () => {
          const i = this._bgSlots.indexOf(slotW);
          if (i >= 0) this._bgRemoveSlot(i);
        },
        () => this._bgSync(),
      );

      this.addCustomWidget(slotW);

      // Keep slot widgets above the Add button
      if (this._addBtn) {
        const btnIdx = this.widgets.indexOf(this._addBtn);
        if (btnIdx >= 0) moveWidget(this.widgets, slotW, btnIdx);
      }

      this._bgSlots.push(slotW);
      this._bgSync();
      this._bgResize();
    };

    nodeType.prototype._bgRemoveSlot = function (slotIdx) {
      const slotW = this._bgSlots[slotIdx];
      if (!slotW) return;
      const wi = this.widgets.indexOf(slotW);
      if (wi >= 0) this.widgets.splice(wi, 1);
      this._bgSlots.splice(slotIdx, 1);
      this._bgSync();
      this._bgResize();
    };

    nodeType.prototype._bgResize = function () {
      const sz = this.computeSize();
      this.size[0] = Math.max(this.size[0], sz[0]);
      this.size[1] = Math.max(this.size[1], sz[1]);
      app.canvas?.setDirty(true, true);
    };

    /* ---- sync to hidden widget + properties -------------------- */
    nodeType.prototype._bgSync = function () {
      const configs = this._bgSlots.map((s) => ({
        category: s.value.category,
        count: s.value.count,
        enabled: s.value.enabled,
        weight: s.value.weight,
      }));
      const slotsW = this.widgets?.find((w) => w.name === "slots");
      if (slotsW) slotsW.value = JSON.stringify(configs);
      if (!this.properties) this.properties = {};
      this.properties.bg_slots = configs;
    };

    /* ---- restore on workflow load ------------------------------ */
    const origConfigure = nodeType.prototype.onConfigure;
    nodeType.prototype.onConfigure = function (info) {
      origConfigure?.apply(this, arguments);

      const saved = this.properties?.bg_slots;
      if (!saved || !Array.isArray(saved) || saved.length === 0) return;

      schemaReady.then((schema) => {
        if (this._bgSlots.length > 0) return;
        for (const slot of saved) {
          this._bgAddSlot(slot.category);
          const last = this._bgSlots[this._bgSlots.length - 1];
          if (last) {
            last.value.count = slot.count || 1;
            last.value.enabled = slot.enabled !== false;
            last.value.weight = slot.weight ?? 1.0;
          }
        }
        this._bgSync();
      });
    };
  },
});
