import type { SerializedGraph, BadLinksData } from "typings/index.js";
import { fixBadLinks } from "../common/link_fixer.js";
import { getPngMetadata } from "scripts/pnginfo.js";

function wait(ms = 16, value?: any) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(value);
    }, ms);
  });
}

const logger = {
  logTo: console as Console | HTMLElement,
  log: (...args: any[]) => {
    logger.logTo === console
      ? console.log(...args)
      : ((logger.logTo as HTMLElement).innerText += args.join(",") + "\n");
  },
};

const findBadLinksLogger = {
  log: async (...args: any[]) => {
    logger.log(...args);
    // await wait(48);
  },
};

export class LinkPage {
  private containerEl: HTMLDivElement;
  private figcaptionEl: HTMLElement;
  private btnFix: HTMLButtonElement;
  private outputeMessageEl: HTMLDivElement;
  private outputImageEl: HTMLImageElement;

  private file?: File | Blob;
  private graph?: SerializedGraph;
  private graphResults?: BadLinksData;
  private graphFinalResults?: BadLinksData;

  constructor() {
    // const consoleEl = document.getElementById("console")!;
    this.containerEl = document.querySelector(".box")!;
    this.figcaptionEl = document.querySelector("figcaption")!;
    this.outputeMessageEl = document.querySelector(".output")!;
    this.outputImageEl = document.querySelector(".output-image")!;
    this.btnFix = document.querySelector(".btn-fix")!;

    // Need to prevent on dragover to allow drop...
    document.addEventListener(
      "dragover",
      (e) => {
        e.preventDefault();
      },
      false,
    );
    document.addEventListener("drop", (e) => {
      this.onDrop(e);
    });
    this.btnFix.addEventListener("click", (e) => {
      this.onFixClick(e);
    });
  }

  private async onFixClick(e: MouseEvent) {
    if (!this.graphResults || !this.graph) {
      this.updateUi("⛔ Fix button click without results.");
      return;
    }
    // Fix
    let graphFinalResults = fixBadLinks(this.graph, true);
    // Confirm
    graphFinalResults = fixBadLinks(graphFinalResults.graph, true);
    // This should have happened, but try to run it through again if there's till an issue.
    if (graphFinalResults.patched || graphFinalResults.deleted) {
      graphFinalResults = fixBadLinks(graphFinalResults.graph, true);
    }
    this.graphFinalResults = graphFinalResults;

    await this.saveFixedWorkflow();

    if (graphFinalResults.hasBadLinks) {
      this.updateUi(
        "⛔ Hmm... Still detecting bad links. Can you file an issue at https://github.com/rgthree/rgthree-comfy/issues with your image/workflow.",
      );
    } else {
      this.updateUi(
        "✅ Workflow fixed.<br><br><small>Please load new saved workflow json and double check linking and execution.</small>",
      );
    }
  }

  private async onDrop(event: DragEvent) {
    if (!event.dataTransfer) {
      return;
    }
    this.reset();

    event.preventDefault();
    event.stopPropagation();

    // Dragging from Chrome->Firefox there is a file but its a bmp, so ignore that
    if (event.dataTransfer.files.length && event.dataTransfer.files?.[0]?.type !== "image/bmp") {
      await this.handleFile(event.dataTransfer.files[0]!);
      return;
    }

    // Try loading the first URI in the transfer list
    const validTypes = ["text/uri-list", "text/x-moz-url"];
    const match = [...event.dataTransfer.types].find((t) => validTypes.find((v) => t === v));
    if (match) {
      const uri = event.dataTransfer.getData(match)?.split("\n")?.[0];
      if (uri) {
        await this.handleFile(await (await fetch(uri)).blob());
      }
    }
  }

  reset() {
    this.file = undefined;
    this.graph = undefined;
    this.graphResults = undefined;
    this.graphFinalResults = undefined;
    this.updateUi();
  }

  private updateUi(msg?: string) {
    this.outputeMessageEl.innerHTML = "";
    if (this.file && !this.containerEl.classList.contains("-has-file")) {
      this.containerEl.classList.add("-has-file");
      this.figcaptionEl.innerHTML = (this.file as File).name || this.file.type;
      if (this.file.type === "application/json") {
        this.outputImageEl.src = "icon_file_json.png";
      } else {
        const reader = new FileReader();
        reader.onload = () => (this.outputImageEl.src = reader.result as string);
        reader.readAsDataURL(this.file);
      }
    } else if (!this.file && this.containerEl.classList.contains("-has-file")) {
      this.containerEl.classList.remove("-has-file");
      this.outputImageEl.src = "";
      this.outputImageEl.removeAttribute("src");
    }

    if (this.graphResults) {
      this.containerEl.classList.add("-has-results");
      if (!this.graphResults.patched && !this.graphResults.deleted) {
        this.outputeMessageEl.innerHTML = "✅ No bad links detected in the workflow.";
      } else {
        this.containerEl.classList.add("-has-fixable-results");
        this.outputeMessageEl.innerHTML = `⚠️ Found ${this.graphResults.patched} links to fix, and ${this.graphResults.deleted} to be removed.`;
      }
    } else {
      this.containerEl.classList.remove("-has-results");
      this.containerEl.classList.remove("-has-fixable-results");
    }

    if (msg) {
      this.outputeMessageEl.innerHTML = msg;
    }
  }

  private async handleFile(file: File | Blob) {
    this.file = file;
    this.updateUi();

    let workflow: string | undefined | null = null;
    if (file.type.startsWith("image/")) {
      const pngInfo = await getPngMetadata(file);
      workflow = pngInfo?.workflow;
    } else if (
      file.type === "application/json" ||
      (file instanceof File && file.name.endsWith(".json"))
    ) {
      workflow = await new Promise((resolve) => {
        const reader = new FileReader();
        reader.onload = () => {
          resolve(reader.result as string);
        };
        reader.readAsText(file);
      });
    }
    if (!workflow) {
      this.updateUi("⛔ No workflow found in dropped item.");
    } else {
      try {
        this.graph = JSON.parse(workflow);
      } catch (e) {
        this.graph = undefined;
      }
      if (!this.graph) {
        this.updateUi("⛔ Invalid workflow found in dropped item.");
      } else {
        this.loadGraphData(this.graph);
      }
    }
  }

  private async loadGraphData(graphData: SerializedGraph) {
    this.graphResults = await fixBadLinks(graphData);
    this.updateUi();
  }

  private async saveFixedWorkflow() {
    if (!this.graphFinalResults) {
      this.updateUi("⛔ Save w/o final graph patched.");
      return false;
    }

    let filename: string | null = (this.file as File).name || "workflow.json";
    let filenames = filename.split(".");
    filenames.pop();
    filename = filenames.join(".");
    filename += "_fixed.json";
    filename = prompt("Save workflow as:", filename);
    if (!filename) return false;
    if (!filename.toLowerCase().endsWith(".json")) {
      filename += ".json";
    }
    const json = JSON.stringify(this.graphFinalResults.graph, null, 2);
    const blob = new Blob([json], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const anchor = document.createElement("a");
    anchor.download = filename;
    anchor.href = url;
    anchor.style.display = "none";
    document.body.appendChild(anchor);
    await wait();
    anchor.click();
    await wait();
    anchor.remove();
    window.URL.revokeObjectURL(url);
    return true;
  }
}
