import { api } from "../../scripts/api.js";
import {
  getConnectedTriggerToggleNodes,
  getLinkFromGraph,
  getNodeKey,
} from "./utils.js";

const TRIGGER_WORD_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
const triggerWordCache = new Map();

const LORA_NODE_CLASSES = new Set([
  "Lora Loader (LoraManager)",
  "Lora Stacker (LoraManager)",
  "WanVideo Lora Select (LoraManager)",
  "Create Hook LoRA (LoraManager)",
]);

function normalizeTriggerWordList(triggerWords) {
  if (!triggerWords) {
    return [];
  }

  if (triggerWords instanceof Set) {
    return Array.from(triggerWords)
      .map((word) => (word == null ? "" : String(word)).trim())
      .filter(Boolean);
  }

  if (!Array.isArray(triggerWords)) {
    return [(triggerWords == null ? "" : String(triggerWords)).trim()].filter(
      Boolean
    );
  }

  return triggerWords
    .map((word) => (word == null ? "" : String(word)).trim())
    .filter(Boolean);
}

export async function fetchTriggerWordsForLora(loraName) {
  if (!loraName) {
    return [];
  }

  const cached = triggerWordCache.get(loraName);
  if (cached && Date.now() - cached.timestamp < TRIGGER_WORD_CACHE_TTL) {
    return cached.words;
  }

  const response = await api.fetchApi(
    `/lm/loras/get-trigger-words?name=${encodeURIComponent(loraName)}`,
    { method: "GET" }
  );

  if (!response?.ok) {
    const errorText = response ? await response.text().catch(() => "") : "";
    throw new Error(errorText || `Failed to fetch trigger words for ${loraName}`);
  }

  const data = (await response.json().catch(() => ({}))) || {};
  const triggerWords = Array.isArray(data.trigger_words)
    ? data.trigger_words.filter((word) => typeof word === "string")
    : [];
  const normalized = triggerWords
    .map((word) => word.trim())
    .filter((word) => word.length > 0);

  triggerWordCache.set(loraName, {
    words: normalized,
    timestamp: Date.now(),
  });

  return normalized;
}

export function highlightTriggerWordsAlongChain(startNode, triggerWords) {
  const normalizedWords = normalizeTriggerWordList(triggerWords);
  highlightNodeRecursive(startNode, normalizedWords, new Set());
}

export async function applySelectionHighlight(node, selection) {
  if (!node) {
    return;
  }

  node.__lmSelectionHighlightToken =
    (node.__lmSelectionHighlightToken || 0) + 1;
  const requestId = node.__lmSelectionHighlightToken;

  const loraName = selection?.name;
  const isActive = !!selection?.active;

  if (!loraName || !isActive) {
    highlightTriggerWordsAlongChain(node, []);
    return;
  }

  try {
    const triggerWords = await fetchTriggerWordsForLora(loraName);
    if (node.__lmSelectionHighlightToken !== requestId) {
      return;
    }
    highlightTriggerWordsAlongChain(node, triggerWords);
  } catch (error) {
    console.error("Error fetching trigger words for highlight:", error);
    if (node.__lmSelectionHighlightToken === requestId) {
      highlightTriggerWordsAlongChain(node, []);
    }
  }
}

function highlightNodeRecursive(node, triggerWords, visited) {
  if (!node) {
    return;
  }

  const nodeKey = getNodeKey(node);
  if (!nodeKey || visited.has(nodeKey)) {
    return;
  }
  visited.add(nodeKey);

  highlightTriggerWordsOnNode(node, triggerWords);

  if (!node.outputs) {
    return;
  }

  for (const output of node.outputs) {
    if (!output?.links?.length) {
      continue;
    }

    for (const linkId of output.links) {
      const link = getLinkFromGraph(node.graph, linkId);
      if (!link) {
        continue;
      }

      const targetNode = node.graph?.getNodeById?.(link.target_id);
      if (!targetNode) {
        continue;
      }

      if (LORA_NODE_CLASSES.has(targetNode.comfyClass)) {
        highlightNodeRecursive(targetNode, triggerWords, visited);
      }
    }
  }
}

function highlightTriggerWordsOnNode(node, triggerWords) {
  const connectedToggles = getConnectedTriggerToggleNodes(node);
  if (!connectedToggles.length) {
    return;
  }

  connectedToggles.forEach((toggleNode) => {
    if (typeof toggleNode?.highlightTriggerWords === "function") {
      toggleNode.highlightTriggerWords(triggerWords);
    } else {
      toggleNode.__pendingHighlightWords = Array.isArray(triggerWords)
        ? [...triggerWords]
        : triggerWords;
    }
  });
}
