/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/

var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJS = (cb, mod) => function __require() {
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// node_modules/python-shell/index.js
var require_python_shell = __commonJS({
  "node_modules/python-shell/index.js"(exports) {
    "use strict";
    var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
      function adopt(value) {
        return value instanceof P ? value : new P(function(resolve) {
          resolve(value);
        });
      }
      return new (P || (P = Promise))(function(resolve, reject) {
        function fulfilled(value) {
          try {
            step(generator.next(value));
          } catch (e) {
            reject(e);
          }
        }
        function rejected(value) {
          try {
            step(generator["throw"](value));
          } catch (e) {
            reject(e);
          }
        }
        function step(result) {
          result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
        }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
      });
    };
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.PythonShell = exports.NewlineTransformer = exports.PythonShellErrorWithLogs = exports.PythonShellError = void 0;
    var events_1 = require("events");
    var child_process_1 = require("child_process");
    var os_1 = require("os");
    var path_1 = require("path");
    var stream_1 = require("stream");
    var fs_1 = require("fs");
    var util_1 = require("util");
    function toArray(source) {
      if (typeof source === "undefined" || source === null) {
        return [];
      } else if (!Array.isArray(source)) {
        return [source];
      }
      return source;
    }
    function extend(obj, ...args) {
      Array.prototype.slice.call(arguments, 1).forEach(function(source) {
        if (source) {
          for (let key in source) {
            obj[key] = source[key];
          }
        }
      });
      return obj;
    }
    function getRandomInt() {
      return Math.floor(Math.random() * 1e10);
    }
    var execPromise = (0, util_1.promisify)(child_process_1.exec);
    var PythonShellError = class extends Error {
    };
    exports.PythonShellError = PythonShellError;
    var PythonShellErrorWithLogs = class extends PythonShellError {
    };
    exports.PythonShellErrorWithLogs = PythonShellErrorWithLogs;
    var NewlineTransformer = class extends stream_1.Transform {
      _transform(chunk, encoding, callback) {
        let data = chunk.toString();
        if (this._lastLineData)
          data = this._lastLineData + data;
        const lines = data.split(os_1.EOL);
        this._lastLineData = lines.pop();
        lines.forEach(this.push.bind(this));
        callback();
      }
      _flush(done) {
        if (this._lastLineData)
          this.push(this._lastLineData);
        this._lastLineData = null;
        done();
      }
    };
    exports.NewlineTransformer = NewlineTransformer;
    var PythonShell2 = class extends events_1.EventEmitter {
      /**
       * spawns a python process
       * @param scriptPath path to script. Relative to current directory or options.scriptFolder if specified
       * @param options
       * @param stdoutSplitter Optional. Splits stdout into chunks, defaulting to splitting into newline-seperated lines
       * @param stderrSplitter Optional. splits stderr into chunks, defaulting to splitting into newline-seperated lines
       */
      constructor(scriptPath, options, stdoutSplitter = null, stderrSplitter = null) {
        super();
        function resolve(type, val) {
          if (typeof val === "string") {
            return PythonShell2[type][val];
          } else if (typeof val === "function") {
            return val;
          }
        }
        if (scriptPath.trim().length == 0)
          throw Error("scriptPath cannot be empty! You must give a script for python to run");
        let self = this;
        let errorData = "";
        events_1.EventEmitter.call(this);
        options = extend({}, PythonShell2.defaultOptions, options);
        let pythonPath;
        if (!options.pythonPath) {
          pythonPath = PythonShell2.defaultPythonPath;
        } else
          pythonPath = options.pythonPath;
        let pythonOptions = toArray(options.pythonOptions);
        let scriptArgs = toArray(options.args);
        this.scriptPath = (0, path_1.join)(options.scriptPath || "", scriptPath);
        this.command = pythonOptions.concat(this.scriptPath, scriptArgs);
        this.mode = options.mode || "text";
        this.formatter = resolve("format", options.formatter || this.mode);
        this.parser = resolve("parse", options.parser || this.mode);
        this.stderrParser = resolve("parse", options.stderrParser || "text");
        this.terminated = false;
        this.childProcess = (0, child_process_1.spawn)(pythonPath, this.command, options);
        ["stdout", "stdin", "stderr"].forEach(function(name) {
          self[name] = self.childProcess[name];
          self.parser && self[name] && self[name].setEncoding(options.encoding || "utf8");
        });
        if (this.parser && this.stdout) {
          if (!stdoutSplitter)
            stdoutSplitter = new NewlineTransformer();
          stdoutSplitter.setEncoding(options.encoding || "utf8");
          this.stdout.pipe(stdoutSplitter).on("data", (chunk) => {
            this.emit("message", self.parser(chunk));
          });
        }
        if (this.stderrParser && this.stderr) {
          if (!stderrSplitter)
            stderrSplitter = new NewlineTransformer();
          stderrSplitter.setEncoding(options.encoding || "utf8");
          this.stderr.pipe(stderrSplitter).on("data", (chunk) => {
            this.emit("stderr", self.stderrParser(chunk));
          });
        }
        if (this.stderr) {
          this.stderr.on("data", function(data) {
            errorData += "" + data;
          });
          this.stderr.on("end", function() {
            self.stderrHasEnded = true;
            terminateIfNeeded();
          });
        } else {
          self.stderrHasEnded = true;
        }
        if (this.stdout) {
          this.stdout.on("end", function() {
            self.stdoutHasEnded = true;
            terminateIfNeeded();
          });
        } else {
          self.stdoutHasEnded = true;
        }
        this.childProcess.on("error", function(err) {
          self.emit("error", err);
        });
        this.childProcess.on("exit", function(code, signal) {
          self.exitCode = code;
          self.exitSignal = signal;
          terminateIfNeeded();
        });
        function terminateIfNeeded() {
          if (!self.stderrHasEnded || !self.stdoutHasEnded || self.exitCode == null && self.exitSignal == null)
            return;
          let err;
          if (self.exitCode && self.exitCode !== 0) {
            if (errorData) {
              err = self.parseError(errorData);
            } else {
              err = new PythonShellError("process exited with code " + self.exitCode);
            }
            err = extend(err, {
              executable: pythonPath,
              options: pythonOptions.length ? pythonOptions : null,
              script: self.scriptPath,
              args: scriptArgs.length ? scriptArgs : null,
              exitCode: self.exitCode
            });
            if (self.listeners("pythonError").length || !self._endCallback) {
              self.emit("pythonError", err);
            }
          }
          self.terminated = true;
          self.emit("close");
          self._endCallback && self._endCallback(err, self.exitCode, self.exitSignal);
        }
        ;
      }
      /**
       * checks syntax without executing code
       * @returns rejects promise w/ string error output if syntax failure
       */
      static checkSyntax(code) {
        return __awaiter(this, void 0, void 0, function* () {
          const randomInt = getRandomInt();
          const filePath = (0, os_1.tmpdir)() + path_1.sep + `pythonShellSyntaxCheck${randomInt}.py`;
          const writeFilePromise = (0, util_1.promisify)(fs_1.writeFile);
          return writeFilePromise(filePath, code).then(() => {
            return this.checkSyntaxFile(filePath);
          });
        });
      }
      static getPythonPath() {
        return this.defaultOptions.pythonPath ? this.defaultOptions.pythonPath : this.defaultPythonPath;
      }
      /**
       * checks syntax without executing code
       * @returns {Promise} rejects w/ stderr if syntax failure
       */
      static checkSyntaxFile(filePath) {
        return __awaiter(this, void 0, void 0, function* () {
          const pythonPath = this.getPythonPath();
          let compileCommand = `${pythonPath} -m py_compile ${filePath}`;
          return execPromise(compileCommand);
        });
      }
      /**
       * Runs a Python script and returns collected messages as a promise.
       * If the promise is rejected, the err will probably be of type PythonShellErrorWithLogs
       * @param scriptPath   The path to the script to execute
       * @param options  The execution options
       */
      static run(scriptPath, options) {
        return new Promise((resolve, reject) => {
          let pyshell = new PythonShell2(scriptPath, options);
          let output = [];
          pyshell.on("message", function(message) {
            output.push(message);
          }).end(function(err) {
            if (err) {
              err.logs = output;
              reject(err);
            } else
              resolve(output);
          });
        });
      }
      /**
       * Runs the inputted string of python code and returns collected messages as a promise. DO NOT ALLOW UNTRUSTED USER INPUT HERE!
       * @param code   The python code to execute
       * @param options  The execution options
       * @return a promise with the output from the python script
       */
      static runString(code, options) {
        const randomInt = getRandomInt();
        const filePath = os_1.tmpdir + path_1.sep + `pythonShellFile${randomInt}.py`;
        (0, fs_1.writeFileSync)(filePath, code);
        return PythonShell2.run(filePath, options);
      }
      static getVersion(pythonPath) {
        if (!pythonPath)
          pythonPath = this.getPythonPath();
        return execPromise(pythonPath + " --version");
      }
      static getVersionSync(pythonPath) {
        if (!pythonPath)
          pythonPath = this.getPythonPath();
        return (0, child_process_1.execSync)(pythonPath + " --version").toString();
      }
      /**
       * Parses an error thrown from the Python process through stderr
       * @param  {string|Buffer} data The stderr contents to parse
       * @return {Error} The parsed error with extended stack trace when traceback is available
       */
      parseError(data) {
        let text = "" + data;
        let error;
        if (/^Traceback/.test(text)) {
          let lines = text.trim().split(os_1.EOL);
          let exception = lines.pop();
          error = new PythonShellError(exception);
          error.traceback = data;
          error.stack += os_1.EOL + "    ----- Python Traceback -----" + os_1.EOL + "  ";
          error.stack += lines.slice(1).join(os_1.EOL + "  ");
        } else {
          error = new PythonShellError(text);
        }
        return error;
      }
      /**
       * Sends a message to the Python shell through stdin
       * Override this method to format data to be sent to the Python process
       * @returns {PythonShell} The same instance for chaining calls
       */
      send(message) {
        if (!this.stdin)
          throw new Error("stdin not open for writing");
        let data = this.formatter ? this.formatter(message) : message;
        if (this.mode !== "binary")
          data += os_1.EOL;
        this.stdin.write(data);
        return this;
      }
      /**
       * Closes the stdin stream. Unless python is listening for stdin in a loop
       * this should cause the process to finish its work and close.
       * @returns {PythonShell} The same instance for chaining calls
       */
      end(callback) {
        if (this.childProcess.stdin) {
          this.childProcess.stdin.end();
        }
        this._endCallback = callback;
        return this;
      }
      /**
       * Sends a kill signal to the process
       * @returns {PythonShell} The same instance for chaining calls
       */
      kill(signal) {
        this.terminated = this.childProcess.kill(signal);
        return this;
      }
      /**
       * Alias for kill.
       * @deprecated
       */
      terminate(signal) {
        return this.kill(signal);
      }
    };
    exports.PythonShell = PythonShell2;
    PythonShell2.defaultPythonPath = process.platform != "win32" ? "python3" : "python";
    PythonShell2.defaultOptions = {};
    PythonShell2.format = {
      text: function toText(data) {
        if (!data)
          return "";
        else if (typeof data !== "string")
          return data.toString();
        return data;
      },
      json: function toJson(data) {
        return JSON.stringify(data);
      }
    };
    PythonShell2.parse = {
      text: function asText(data) {
        return data;
      },
      json: function asJson(data) {
        return JSON.parse(data);
      }
    };
  }
});

// src/main.ts
var main_exports = {};
__export(main_exports, {
  boundingBoxFromNode: () => boundingBoxFromNode,
  canvasClosestNodeToPosition: () => canvasClosestNodeToPosition,
  canvasClosestNodeToPositionInBounds: () => canvasClosestNodeToPositionInBounds,
  canvasNodesInBounds: () => canvasNodesInBounds,
  checkContainsLanguage: () => checkContainsLanguage,
  checkContainsScript: () => checkContainsScript,
  checkTextContainsLanguage: () => checkTextContainsLanguage,
  checkTextContainsScript: () => checkTextContainsScript,
  default: () => CanvasBlocksPlugin4,
  extractLanguageText: () => extractLanguageText,
  getNodeText: () => getNodeText
});
module.exports = __toCommonJS(main_exports);
var import_obsidian5 = require("obsidian");

// src/settings.ts
var import_obsidian = require("obsidian");
var DEFAULT_SETTINGS = {
  dataFolder: "",
  workflowScriptFolder: "",
  pythonPath: "",
  variables: []
};
var CanvasBlocksPluginSettingTab = class extends import_obsidian.PluginSettingTab {
  constructor(app2, plugin) {
    super(app2, plugin);
    this.plugin = plugin;
  }
  display() {
    const { containerEl } = this;
    containerEl.empty();
    new import_obsidian.Setting(containerEl).setName("Plugin data location").setDesc("Where the plugin will save data").addText(
      (text) => text.setPlaceholder("/").setValue(this.plugin.settings.dataFolder).onChange(async (value) => {
        this.plugin.settings.dataFolder = value;
        await this.plugin.saveSettings();
      })
    );
    new import_obsidian.Setting(containerEl).setName("Workflow script folder").setDesc("Set the location for workflow scripts to be searched for").addText(
      (text) => text.setPlaceholder("/").setValue(this.plugin.settings.workflowScriptFolder).onChange(async (value) => {
        this.plugin.settings.workflowScriptFolder = value;
        await this.plugin.saveSettings();
      })
    );
    new import_obsidian.Setting(containerEl).setName("Python path").setDesc("Override default python install").addText(
      (text) => text.setPlaceholder("python").setValue(this.plugin.settings.pythonPath).onChange(async (value) => {
        this.plugin.settings.pythonPath = value;
        await this.plugin.saveSettings();
      })
    );
    new import_obsidian.Setting(containerEl).setName("Add variable").addButton((button) => {
      button.setButtonText("Add variable").onClick(async () => {
        this.plugin.settings.variables.push(["", ""]);
        await this.plugin.saveSettings();
        this.display();
      });
    });
    this.plugin.settings.variables.sort((a, b) => {
      if (a[0] === "")
        return 1;
      if (b[0] === "")
        return -1;
      return a[0].localeCompare(b[0]);
    });
    this.plugin.settings.variables.forEach((variable, index) => {
      const key = variable[0];
      const value = variable[1];
      new import_obsidian.Setting(containerEl).addText((variableKeyText) => {
        variableKeyText.setPlaceholder("Variable name").setValue(key).onChange(async (value2) => {
          this.plugin.settings.variables[index][0] = value2;
          await this.plugin.saveSettings();
        });
      }).addText((variableValueText) => {
        variableValueText.setPlaceholder("Value").setValue(value).onChange(async (value2) => {
          this.plugin.settings.variables[index][1] = value2;
          await this.plugin.saveSettings();
        });
      }).addExtraButton((removeVariableButton) => {
        removeVariableButton.setIcon("cross").onClick(async () => {
          this.plugin.settings.variables.splice(index, 1);
          await this.plugin.saveSettings();
          this.display();
        });
      });
    });
  }
};

// src/workflow.ts
var import_obsidian3 = require("obsidian");

// src/cache.ts
var TimedCache = class {
  constructor(defaultTTL = 6e4) {
    this.cache = {};
    this.defaultTTL = defaultTTL;
  }
  set(key, value, ttl = this.defaultTTL) {
    const stringKey = JSON.stringify(key);
    const expiry = Date.now() + ttl;
    this.cache[stringKey] = { value, expiry };
    setTimeout(() => {
      if (this.cache[stringKey] && this.cache[stringKey].expiry <= Date.now()) {
        delete this.cache[stringKey];
      }
    }, ttl);
  }
  get(key) {
    const stringKey = JSON.stringify(key);
    if (this.cache[stringKey] && this.cache[stringKey].expiry > Date.now()) {
      return this.cache[stringKey].value;
    } else {
      delete this.cache[stringKey];
      return void 0;
    }
  }
  has(key) {
    const stringKey = JSON.stringify(key);
    return !!this.cache[stringKey] && this.cache[stringKey].expiry > Date.now();
  }
  delete(key) {
    const stringKey = JSON.stringify(key);
    delete this.cache[stringKey];
  }
  clear() {
    this.cache = {};
  }
};

// src/constants.ts
var workflowNodesDimensions = {
  padding: 20,
  scriptWidth: 400,
  scriptHeight: 60,
  connectionPointWidth: 180,
  connectionPointHeight: 50,
  groupLabel: "\u200E"
};
var workflowTrailingCode = "\n\n#Generated by CanvasBlocks plugin\n_return_output_data()";
var supportedLanguagesNamePrefixes = ["py", "js", "lua"];
var scriptCodeBlockLanguageSuffix = "canvasblock";
var canvasBlockSettingsLanguageName = scriptCodeBlockLanguageSuffix + "settings";
var canvasBlockConnectionPointLanguageName = scriptCodeBlockLanguageSuffix + "connectionpoint";
var languageToLanguageNameMap = {
  python: "py",
  javascript: "js",
  lua: "lua"
};

// src/scriptexecution.ts
var import_obsidian2 = require("obsidian");

// src/pythonexecution.ts
var fs = __toESM(require("fs"));
var import_os = require("os");
var import_path = require("path");
var import_python_shell = __toESM(require_python_shell());

// resources/canvasblocks-python-lib.py
var canvasblocks_python_lib_default = `# Required to support union typings on Python versions below 3.10.0
# Versions >= 3.10.0 are also supported
from __future__ import annotations

import base64
import io
import json
import importlib
import subprocess
import os
import sys
import sysconfig
from typing import IO

# Replace the default python print function with one which will call console.log in the obisidan console
send_command = print
print = None
def print(*args):
    """Outputs the arguments to the console"""
    send_command(json.dumps({"command": "PRINT", "text": ' '.join(map(str, args))}))

def notice(*args):
    """Sends a notice pop up in Obsidian
    """
    send_command(json.dumps({"command": "NOTICE", "text": ' '.join(map(str, args))}))

def install_dependency(module: str, import_name: str = None):
    """Installs a module required by a script

    Args:
        module (str): The name of the module used to install with "pip install"
        import_name (str, optional): The name of the module used when importing. Leave as None if it is the same as "module" Defaults to None.
    """

    if import_name == None:
        import_name = module
    try:
        importlib.import_module(import_name)
    except ImportError:
        pip_path = sysconfig.get_path('scripts') + '/pip'
        output = subprocess.check_output([pip_path, 'install', module], stderr=subprocess.STDOUT)
        print(output)
        

# Read data dictionary from stdin
data_from_node = sys.stdin.buffer.readline().decode('utf-8').strip()
data_dict = json.loads(data_from_node)

# Create variables from the data dictionary
for variable_name, variable_value in data_dict.items():
    locals()[variable_name] = variable_value
del data_dict


if execution_type == "workflow": # type: ignore
    install_dependency("pillow", "PIL")
    from PIL import Image

    for ioName, value in in_data.items(): # type: ignore
        ioType = script_settings["ioConnections"][ioName]["type"] # type: ignore
        newValue = value

        if ioType == "image":
            decoded_data = base64.b64decode(value)
            image_buffer = io.BytesIO(decoded_data)
            newValue = Image.open(image_buffer)

        if ioType == "integer":
            newValue = int(value)

        if ioType == "float":
            newValue = float(value)

        if ioType == "file":
            newValue = json.loads(value)

        in_data[ioName] = newValue # type: ignore


def get_text_from_node(node_data: str) -> str|None:
    """If node_data is a text node, the text will be returned
    If node_data is a file node and the extension of the file is .md or .txt, the file will be read and the text returned
    If node_data is a file node and the extention is invalid, None will be returned
    If node_data is a link node, the url will be returned

    Args:
        node_data (str): The node object in JSON Canvas format

    Returns:
        str|None: The text returned or None if it cannot be found
    """

    if node_data["type"] == "text":
        return node_data["text"]
    
    if node_data["type"] == "link":
        return node_data["url"]
    
    if node_data["type"] == "file":
        _, file_extension = os.path.splitext(node_data["file"])
        if not file_extension[1:] in ["md","txt"]:
            return None
        # Warning suppressed as vault_path will be injected by the plugin
        path = os.path.join(vault_path, node_data["file"]) # type: ignore
        with open(path, "r") as file:
            return file.read()


    return None

def create_text_node(text: str, x: int, y: int, width: int = 250, height: int = 60):
    """Creates a text node in the canvas

    Args:
        text (str): The the text of the node
        x (int): The x coordinate of the node in the canvas
        y (int): The y coordinate of the node in the canvas
        width (int, optional): The width of the node. Defaults to 250.
        height (int, optional): The height of the node. Defaults to 60.
    """

    send_command(json.dumps({"command": "CREATE_TEXT_NODE", "text": text, "x": x, "y": y, "width": width, "height": height}))

def create_file_node(file: str, x: int, y: int, width: int = 400, height: int = 225):
    """Creates a file node in the canvas

    Args:
        file (str): The path to the file from the root directory of the Obsidian vault
        x (int): The x coordinate of the node in the canvas
        y (int): The y coordinate of the node in the canvas
        width (int, optional): The width of the node. Defaults to 400.
        height (int, optional): The height of the node. Defaults to 225.
    """

    send_command(json.dumps({"command": "CREATE_FILE_NODE", "file": file, "x": x, "y": y, "width": width, "height": height}))

def set_text_node_text(node_id: str, text: str):
    """Replaces the text of a text node

    Args:
        node_id (str): The ID of the node to replace
        text (str): New text
    """
    send_command(json.dumps({"command": "MODIFY_TEXT_NODE", "id": node_id, "text": text}))

def rebuild_canvas():
    """Causes the canvas to reload. Only required if set_text_node_text is called"""

    send_command(json.dumps({"command": "REBUILD_CANVAS"}))



# execution_type will be injected into the script
if execution_type == "simple": # type: ignore
    def get_parameter_file_path() -> str:
        """Gets the path of the file of a parameter node if the node type is file. Does not check type

        Returns:
            str: The absolute path of the file
        """

        # Warnings are suppressed as vault_path and parameter_data will be injected by the plugin
        return os.path.join(vault_path, parameter_data["file"])  # type: ignore

    def get_parameter_file(mode: str = "r") -> IO:
        """Helper function to get a file handler for the parameter file

        Args:
            read_type (str, optional): The mode which the file will be opened in. Defaults to "r".

        Returns:
            IO: File handler for the parameter file
        """
        return open(get_parameter_file_path(), mode)


# executionType will be injected into the script
elif execution_type == "workflow": # type: ignore

    def _return_output_data():
        """This function is for internal use only and should not be called by scripts.

        Returns the output data of a script in the workflow
        """

        # Alter output data to be serialisable
        for ioName, value in out_data.items(): # type: ignore
            ioType = script_settings["ioConnections"][ioName]["type"] # type: ignore
            newValue = value

            if ioType == "image":
                buffered = io.BytesIO()
                value.save(buffered, format="PNG")
                newValue = base64.b64encode(buffered.getvalue()).decode()

            if ioType == "integer" or ioType == "float":
                newValue = str(value)

            out_data[ioName] = newValue # type: ignore

        send_command(json.dumps({"command": "RETURN_OUTPUT", "data": out_data})) # type: ignore`;

// src/pythonexecution.ts
async function executePythonString(plugin, canvas, scriptCode, injectionData, messageCallback = defaultMessageHandler, errorCallback = defaultErrorHandler) {
  return new Promise((resolve) => {
    const pythonScript = `
${canvasblocks_python_lib_default}
${scriptCode.replace(/[^\x20-\x7E\t\n]/g, "")}
        `;
    let pythonPath;
    if (plugin.settings.pythonPath.trim() !== "")
      pythonPath = plugin.settings.pythonPath;
    const randomInt = Math.floor(Math.random() * 1e10);
    const tempFile = (0, import_os.tmpdir)() + import_path.sep + `pythonShellFile${randomInt}.py`;
    fs.writeFileSync(tempFile, pythonScript);
    const pyshell = new import_python_shell.PythonShell(tempFile, { mode: "json", encoding: "utf-8", pythonPath });
    pyshell.send(injectionData);
    let errorMessage = "";
    pyshell.on("error", (error) => {
      errorMessage += error + "\n";
    });
    pyshell.on("stderr", (data) => {
      errorMessage += data.toString() + "\n";
    });
    const messageQueue = [];
    pyshell.on("message", (message) => {
      messageQueue.push(message);
    });
    pyshell.on("close", async () => {
      if (errorMessage.length > 0) {
        await errorCallback(canvas, errorMessage);
        resolve(false);
      }
      for (const message of messageQueue) {
        await messageCallback(canvas, message);
      }
      resolve(true);
    });
  });
}

// src/scriptexecution.ts
function defaultErrorHandler(canvas, error) {
  new import_obsidian2.Notice("An error has occured while running this script. Check the console for more detail.");
  console.error("Error parsing Python script result:", error);
}
async function defaultMessageHandler(canvas, message) {
  const commandType = message.command;
  switch (commandType) {
    case "CREATE_TEXT_NODE":
      {
        canvas.createTextNode({
          text: message.text,
          pos: {
            x: message.x,
            y: message.y
          },
          size: {
            width: message.width,
            height: message.height
          },
          save: false,
          focus: false
        });
      }
      break;
    case "CREATE_FILE_NODE":
      {
        const nodeFile = this.app.vault.getAbstractFileByPath((0, import_obsidian2.normalizePath)(message.file));
        canvas.createFileNode({
          file: nodeFile,
          pos: {
            x: message.x,
            y: message.y
          },
          size: {
            width: message.width,
            height: message.height
          },
          save: false,
          focus: false
        });
      }
      break;
    case "MODIFY_TEXT_NODE":
      {
        canvas.nodes.get(message.id).setText(message.text);
        canvas.data.nodes.filter((node) => node.id === message.id)[0].text = message.text;
        canvas.requestSave();
      }
      break;
    case "REBUILD_CANVAS":
      {
        canvas.view.leaf.rebuildView();
      }
      break;
    case "PRINT":
      {
        console.log(message.text);
      }
      break;
    case "NOTICE":
      {
        new import_obsidian2.Notice(message.text);
      }
      break;
    default:
      return message;
  }
  return null;
}
async function executeScript(plugin, canvas, scriptData, injectionData, scriptType, messageCallback = defaultMessageHandler, errorCallback = defaultErrorHandler) {
  const allowedLanguages = ["python", "javascript"];
  const nodeText = await getNodeText(plugin.app, scriptData);
  if (nodeText === null)
    return;
  let chosenLanguage = null;
  for (const language of allowedLanguages) {
    const containsLanguage = checkTextContainsLanguage(nodeText, languageToLanguageNameMap[language]);
    if (containsLanguage) {
      chosenLanguage = language;
      break;
    }
  }
  if (chosenLanguage === null) {
    new import_obsidian2.Notice(`This script doesn't contain any of the enabled languages`);
    return;
  }
  let scriptSettingsText = await extractLanguageText(plugin.app, nodeText, canvasBlockSettingsLanguageName);
  if (scriptSettingsText === null)
    return;
  let scriptSettings = JSON.parse(scriptSettingsText);
  let filteredVariables = {};
  if (scriptSettings.allowedVariables !== void 0) {
    for (const variable of scriptSettings.allowedVariables) {
      let found = false;
      for (const [key, value] of plugin.settings.variables) {
        if (key === variable) {
          filteredVariables[variable] = value;
          found = true;
          break;
        }
      }
      if (!found) {
        new import_obsidian2.Notice(`Requested variable "${variable}" cannot be found from plugin settings`);
        return;
      }
    }
  }
  const adapter = plugin.app.vault.adapter;
  const fullInjectionData = {
    ...injectionData,
    execution_type: scriptType,
    vault_path: adapter.basePath,
    canvas_path: canvas.view.file.path,
    plugin_folder: plugin.getDataFolder(),
    injected_variables: filteredVariables
  };
  let scriptText = await extractLanguageText(plugin.app, nodeText, languageToLanguageNameMap[chosenLanguage] + scriptCodeBlockLanguageSuffix);
  if (scriptText === null)
    return;
  if (scriptType === "workflow")
    scriptText += workflowTrailingCode;
  const executionFunctions = {
    python: executePythonString
    //javascript: executeJavascriptString
  };
  const executeFunction = executionFunctions[chosenLanguage];
  if (executeFunction) {
    return await executeFunction(plugin, canvas, scriptText, fullInjectionData, messageCallback, errorCallback);
  }
}

// src/workflow.ts
function refreshNode(canvas, id) {
  const node = canvas.nodes.get(id);
  if (node === void 0)
    return;
  const text = node.text;
  if (text === void 0)
    return;
  if (node.child === void 0)
    return;
  node.child.previewMode.renderer.set("");
  function waitForRenderQueue() {
    setTimeout(() => {
      try {
        if (node.child.previewMode.renderer.queued != null) {
          waitForRenderQueue();
        } else {
          setTimeout(() => {
            node.child.previewMode.renderer.set(text);
          }, 0);
        }
      } catch (error) {
      }
    }, 0);
  }
  waitForRenderQueue();
}
async function getWorkflowNodes(plugin, canvas, selectedNodeID) {
  const selectedNode = canvas.nodes.get(selectedNodeID);
  if (selectedNode === void 0)
    return;
  const selectedNodeData = canvas.data.nodes.find((node) => node.id === selectedNode.id);
  if (selectedNodeData === void 0)
    return;
  let isSettingsNode = await checkContainsLanguage(plugin.app, selectedNodeData, canvasBlockSettingsLanguageName);
  if (isSettingsNode) {
    const settingsText = await extractLanguageText(plugin.app, selectedNodeData, canvasBlockSettingsLanguageName);
    if (settingsText === null)
      return;
    const settings = JSON.parse(settingsText);
    isSettingsNode = settings.type !== "simple";
  }
  let settingsNodeID = selectedNodeData.id;
  if (!isSettingsNode) {
    if (selectedNodeData.type === "text") {
      const languageText = await extractLanguageText(plugin.app, selectedNodeData, canvasBlockConnectionPointLanguageName);
      if (languageText === null)
        return;
      const connectionPointData = JSON.parse(languageText);
      settingsNodeID = connectionPointData.scriptID;
    } else if (selectedNodeData.type === "group") {
      if (selectedNodeData.label !== workflowNodesDimensions.groupLabel)
        return;
      const possibleMainNodeID = await canvasClosestNodeToPositionInBounds(canvas, boundingBoxFromNode(selectedNode), async (testNode) => {
        let hasCode = await checkContainsLanguage(plugin.app, testNode, canvasBlockSettingsLanguageName);
        if (hasCode) {
          const settingsText = await extractLanguageText(plugin.app, testNode, canvasBlockSettingsLanguageName);
          if (settingsText === null)
            return false;
          const settings = JSON.parse(settingsText);
          hasCode = settings.type !== "simple";
        }
        return !hasCode;
      });
      if (possibleMainNodeID === null)
        return;
      settingsNodeID = possibleMainNodeID;
    } else
      return;
  }
  if (canvas.data.nodes === void 0)
    return;
  const settingsNode = canvas.data.nodes.find((node) => node.id === settingsNodeID);
  if (settingsNode === void 0)
    return;
  const connectionNodes = [];
  for (const node of canvas.data.nodes) {
    const text = await extractLanguageText(plugin.app, node, canvasBlockConnectionPointLanguageName);
    if (text === null)
      continue;
    const connectionPointData = JSON.parse(text);
    if (connectionPointData.scriptID === settingsNodeID) {
      connectionNodes.push(node);
    }
  }
  let totalBoundingBox = void 0;
  for (const node of [...connectionNodes, settingsNode]) {
    const nodeBoundingBox = boundingBoxFromNode(node);
    if (totalBoundingBox === void 0)
      totalBoundingBox = nodeBoundingBox;
    else {
      totalBoundingBox = {
        minX: Math.min(nodeBoundingBox.minX, totalBoundingBox.minX),
        minY: Math.min(nodeBoundingBox.minY, totalBoundingBox.minY),
        maxX: Math.max(nodeBoundingBox.maxX, totalBoundingBox.maxX),
        maxY: Math.max(nodeBoundingBox.maxY, totalBoundingBox.maxY)
      };
    }
  }
  if (totalBoundingBox === void 0)
    return;
  const groups = [];
  for (const node of canvas.data.nodes) {
    if (node.type !== "group")
      continue;
    const nodeBoundingBox = boundingBoxFromNode(node);
    if (nodeBoundingBox.minX <= totalBoundingBox.minX && nodeBoundingBox.maxX >= totalBoundingBox.maxX && nodeBoundingBox.minY <= totalBoundingBox.minY && nodeBoundingBox.maxY >= totalBoundingBox.maxY) {
      groups.push(node);
    }
  }
  let minDistance = Infinity;
  let closestGroup = void 0;
  for (const group of groups) {
    const groupBoundingBox = boundingBoxFromNode(group);
    const minXDistance = Math.abs(groupBoundingBox.minX - totalBoundingBox.minX);
    const minYDistance = Math.abs(groupBoundingBox.minY - totalBoundingBox.minY);
    const maxXDistance = Math.abs(groupBoundingBox.maxX - totalBoundingBox.maxX);
    const maxYDistance = Math.abs(groupBoundingBox.maxY - totalBoundingBox.maxY);
    const sumDistance = minXDistance + minYDistance + maxXDistance + maxYDistance;
    if (sumDistance < minDistance) {
      minDistance = sumDistance;
      closestGroup = group;
    }
  }
  return { settingsNode, connectionNodes, groupNode: closestGroup };
}
var workflowNodesIDsCache = new TimedCache();
var workflowNodesCache = new TimedCache();
async function cachedGetWorkflowNodes(plugin, canvas, selectedNodeID) {
  if (workflowNodesIDsCache.has(selectedNodeID)) {
    const mainNodeID = workflowNodesIDsCache.get(selectedNodeID);
    if (mainNodeID === void 0)
      return void 0;
    const workflowNodes2 = workflowNodesCache.get(mainNodeID);
    if (workflowNodes2 !== void 0)
      return workflowNodes2;
  }
  const workflowNodes = await getWorkflowNodes(plugin, canvas, selectedNodeID);
  if (workflowNodes === void 0) {
    workflowNodesIDsCache.set(selectedNodeID, void 0);
    return void 0;
  }
  if (workflowNodes.groupNode === void 0) {
    return workflowNodes;
  }
  workflowNodesCache.set(workflowNodes.settingsNode.id, workflowNodes);
  workflowNodesIDsCache.set(workflowNodes.settingsNode.id, workflowNodes.settingsNode.id);
  if (workflowNodes.groupNode !== void 0)
    workflowNodesIDsCache.set(workflowNodes.groupNode.id, workflowNodes.settingsNode.id);
  for (const connectionNode of workflowNodes.connectionNodes)
    workflowNodesIDsCache.set(connectionNode.id, workflowNodes.settingsNode.id);
  return workflowNodes;
}
async function handleWorkflowFromGroup(plugin, canvas, selectedData) {
  const nodes = canvas.data.nodes;
  const closestID = await canvasClosestNodeToPositionInBounds(
    canvas,
    boundingBoxFromNode(selectedData),
    async (node) => {
      if (node.id === selectedData.id)
        return true;
      const hasCode = await checkContainsScript(plugin.app, node);
      if (!hasCode)
        return true;
      const hasSettings = await checkContainsLanguage(plugin.app, node, canvasBlockSettingsLanguageName);
      if (!hasSettings)
        return true;
      return false;
    }
  );
  if (closestID === null)
    return;
  const startScriptNode = nodes.find((node) => node.id === closestID);
  if (startScriptNode === void 0)
    return;
  executeWorkflow(plugin, canvas, startScriptNode);
}
async function executeWorkflow(plugin, canvas, startScriptNode) {
  const nodes = canvas.data.nodes;
  const edges = canvas.data.edges;
  const scriptConnectionPoints = {};
  const dataFlow = {};
  const executionData = {};
  for (const node of nodes) {
    const text = await extractLanguageText(plugin.app, node, canvasBlockConnectionPointLanguageName);
    if (text === null)
      continue;
    const connectionPointData = JSON.parse(text);
    if (!scriptConnectionPoints[connectionPointData.scriptID]) {
      scriptConnectionPoints[connectionPointData.scriptID] = [];
    }
    scriptConnectionPoints[connectionPointData.scriptID].push(node);
  }
  const executeStack = [];
  const searchQueue = [startScriptNode];
  while (searchQueue.length > 0) {
    const currentScript = searchQueue.shift();
    if (currentScript === void 0)
      continue;
    if (!executeStack.includes(currentScript)) {
      executeStack.push(currentScript);
    }
    const settingsLanguageBlock = await extractLanguageText(plugin.app, currentScript, canvasBlockSettingsLanguageName);
    if (settingsLanguageBlock === null)
      continue;
    const scriptSettings = JSON.parse(settingsLanguageBlock);
    const connectionPoints = scriptConnectionPoints[currentScript.id];
    for (const connectionPoint of connectionPoints) {
      const connectionPointLanguageBlock = await extractLanguageText(plugin.app, connectionPoint, canvasBlockConnectionPointLanguageName);
      if (connectionPointLanguageBlock === null)
        continue;
      const connectionPointData = JSON.parse(connectionPointLanguageBlock);
      const ioConnection = scriptSettings.ioConnections[connectionPointData.name];
      if (ioConnection === void 0)
        continue;
      if (ioConnection.direction === "output")
        continue;
      const connectionEdge = edges.find((edge) => edge.toNode === connectionPoint.id);
      if (connectionEdge === void 0)
        continue;
      const otherConnectionID = connectionEdge.fromNode;
      if (otherConnectionID === void 0)
        continue;
      const otherConnectionPoint = nodes.find((node) => node.id === otherConnectionID);
      if (otherConnectionPoint === void 0)
        continue;
      const otherConnectionPointLanguageBlock = await extractLanguageText(plugin.app, otherConnectionPoint, canvasBlockConnectionPointLanguageName);
      if (otherConnectionPointLanguageBlock === null) {
        const flowName = `${otherConnectionID}_NODE}`;
        dataFlow[`${currentScript.id}_${connectionPointData.name}`] = flowName;
        switch (ioConnection.type) {
          case "text":
          case "integer":
          case "float": {
            const text = await getNodeText(plugin.app, otherConnectionPoint);
            if (text === null) {
              new import_obsidian3.Notice("Failed to load node as text");
              return;
            }
            executionData[flowName] = text;
            break;
          }
          case "image": {
            if (otherConnectionPoint.type !== "file") {
              new import_obsidian3.Notice(`Attempted to load a non-image node (${otherConnectionPoint.file}) as an image`);
              return;
            }
            const file = plugin.app.vault.getAbstractFileByPath(otherConnectionPoint.file);
            if (file === null || !(file instanceof import_obsidian3.TFile)) {
              new import_obsidian3.Notice(`Attempt to load image ${otherConnectionPoint.file} failed`);
              return;
            }
            const image = await plugin.app.vault.readBinary(file);
            const base64Image = Buffer.from(image).toString("base64");
            executionData[flowName] = base64Image;
            break;
          }
          case "file":
          case "any":
          default:
            executionData[flowName] = JSON.stringify(otherConnectionPoint);
            break;
        }
        continue;
      }
      const otherConnectionPointData = JSON.parse(otherConnectionPointLanguageBlock);
      const otherScript = nodes.find((node) => node.id === otherConnectionPointData.scriptID);
      if (otherScript === void 0)
        continue;
      if (!searchQueue.includes(otherScript)) {
        searchQueue.push(otherScript);
      }
      dataFlow[`${currentScript.id}_${connectionPointData.name}`] = `${otherScript.id}_${otherConnectionPointData.name}`;
    }
  }
  while (executeStack.length > 0) {
    const currentScript = executeStack.pop();
    if (currentScript === void 0)
      continue;
    const sucess = await executeWorkflowScript(plugin, canvas, currentScript, dataFlow, executionData);
    if (!sucess) {
      new import_obsidian3.Notice("Workflow execution stopped because a script failed");
      break;
    }
  }
}
async function executeWorkflowScript(plugin, canvas, scriptNode, dataFlow, executionData) {
  const settingsLanguageBlock = await extractLanguageText(plugin.app, scriptNode, canvasBlockSettingsLanguageName);
  if (settingsLanguageBlock === null)
    return false;
  const scriptSettings = JSON.parse(settingsLanguageBlock);
  const processScriptText = await getNodeText(plugin.app, scriptNode);
  if (processScriptText === null)
    return false;
  if (scriptSettings.type !== "workflow") {
    scriptSettings.type = "workflow";
    const newSettingsText = JSON.stringify(scriptSettings, null, "	");
    const newText = processScriptText.replace(settingsLanguageBlock, newSettingsText);
    const file = plugin.app.vault.getFileByPath(scriptNode.file);
    if (file === null)
      return false;
    plugin.app.vault.modify(file, newText);
    new import_obsidian3.Notice("WARNING: Settings type not set to workflow. This has been automatically fixed (Workflow script)", 1e4);
  }
  const inputData = {};
  const outputData = {};
  for (const connectionName in scriptSettings.ioConnections) {
    const ioConnection = scriptSettings.ioConnections[connectionName];
    if (ioConnection.direction === "output") {
      outputData[connectionName] = null;
      continue;
    }
    const flow = dataFlow[`${scriptNode.id}_${connectionName}`];
    const data = executionData[flow];
    inputData[connectionName] = data;
  }
  const injectionData = {
    in_data: inputData,
    out_data: outputData,
    script_settings: scriptSettings,
    script_data: scriptNode
  };
  const messageHandler = async (canvas2, message) => {
    const response = await defaultMessageHandler(canvas2, message);
    if (response === null)
      return;
    const commandType = message.command;
    if (commandType === "RETURN_OUTPUT") {
      const outData = message.data;
      for (const key in outData) {
        if (outData.hasOwnProperty(key)) {
          executionData[`${scriptNode.id}_${key}`] = outData[key];
        }
      }
    }
  };
  const sucess = await executeScript(plugin, canvas, scriptNode, injectionData, "workflow", messageHandler);
  return sucess;
}
async function addWorkflowScript(plugin, scriptFile, tx, ty) {
  const view = plugin.app.workspace.getActiveViewOfType(import_obsidian3.ItemView);
  if (view === null)
    return;
  if (!view.hasOwnProperty("canvas")) {
    new import_obsidian3.Notice("A canvas must be open to run this command");
    return;
  }
  const canvas = view.canvas;
  const scriptText = await app.vault.read(scriptFile);
  const text = await extractLanguageText(plugin.app, scriptText, canvasBlockSettingsLanguageName);
  if (text === null)
    return;
  const scriptSettings = JSON.parse(text);
  const scriptNode = canvas.createFileNode({
    file: scriptFile,
    pos: {
      x: tx,
      y: ty
    },
    size: {
      width: workflowNodesDimensions.scriptWidth,
      height: workflowNodesDimensions.scriptHeight
    },
    save: false,
    focus: false
  });
  let numInput = 0;
  let numOutput = 0;
  const connectionNodes = [];
  for (const connectionName in scriptSettings.ioConnections) {
    const ioConnection = scriptSettings.ioConnections[connectionName];
    const numberOfConnectionsAbove = ioConnection.direction === "input" ? numInput : numOutput;
    const offset = ioConnection.direction === "input" ? 0 : workflowNodesDimensions.scriptWidth - workflowNodesDimensions.connectionPointWidth;
    const connectionNode = canvas.createTextNode({
      text: `\`\`\`${canvasBlockConnectionPointLanguageName}
{
    "name": "${connectionName}",
    "scriptID": "${scriptNode.id}"
}
\`\`\``,
      pos: {
        x: tx + offset,
        y: ty + workflowNodesDimensions.scriptHeight + workflowNodesDimensions.padding + workflowNodesDimensions.connectionPointHeight * numberOfConnectionsAbove
      },
      size: {
        width: workflowNodesDimensions.connectionPointWidth,
        height: workflowNodesDimensions.connectionPointHeight
      },
      save: false,
      focus: false
    });
    connectionNodes.push(connectionNode);
    if (ioConnection.direction === "input")
      numInput++;
    else
      numOutput++;
  }
  canvas.createGroupNode({
    label: workflowNodesDimensions.groupLabel,
    pos: {
      x: tx - workflowNodesDimensions.padding,
      y: ty - workflowNodesDimensions.padding
    },
    size: {
      width: workflowNodesDimensions.scriptWidth + 2 * workflowNodesDimensions.padding,
      height: workflowNodesDimensions.padding + workflowNodesDimensions.scriptHeight + workflowNodesDimensions.padding + workflowNodesDimensions.connectionPointHeight * Math.max(numInput, numOutput) + workflowNodesDimensions.padding
    },
    save: false,
    focus: false
  });
  canvas.requestSave();
}

// src/simple.ts
var import_obsidian4 = require("obsidian");
async function handleSimpleScript(plugin, canvas, selectedID, selectedNode) {
  const otherID = await canvasClosestNodeToPositionInBounds(
    canvas,
    selectedNode.bbox,
    (node) => {
      return node.id === selectedID;
    }
  );
  const selectedData = plugin.getNodeByID(canvas, selectedID);
  if (selectedData === void 0)
    return;
  const selectedIsValidScript = await checkContainsScript(plugin.app, selectedData);
  if (!selectedIsValidScript && otherID === null) {
    new import_obsidian4.Notice("No valid scripts are selected");
    return;
  }
  let scriptID;
  let parameterID;
  if (selectedIsValidScript) {
    scriptID = selectedID;
    parameterID = otherID;
  } else {
    if (otherID === null)
      return;
    const otherData = plugin.getNodeByID(canvas, otherID);
    if (otherData === void 0)
      return;
    const otherIsValidScript = await checkContainsScript(plugin.app, otherData);
    if (!otherIsValidScript) {
      new import_obsidian4.Notice("No valid scripts are selected");
      return;
    }
    scriptID = otherID;
    parameterID = selectedID;
  }
  const processScriptData = plugin.getNodeByID(canvas, scriptID);
  if (processScriptData === void 0)
    return;
  const processScriptText = await getNodeText(plugin.app, processScriptData);
  if (processScriptText === null)
    return;
  if (!await checkTextContainsLanguage(processScriptText, canvasBlockSettingsLanguageName)) {
    const settingsData = {
      type: "simple"
    };
    const mergeData = "```" + canvasBlockSettingsLanguageName + "\n" + JSON.stringify(settingsData, null, "	") + "\n```";
    const position = processScriptText.indexOf("```");
    const newText = processScriptText.slice(0, position) + "\n" + mergeData + "\n" + processScriptText.slice(position);
    if (processScriptData.type === "text") {
      canvas.nodes.get(processScriptData.id).setText(newText);
      canvas.data.nodes.filter((node) => node.id === processScriptData.id)[0].text = newText;
      canvas.requestSave();
    } else if (processScriptData.type === "file") {
      const file = plugin.app.vault.getFileByPath(processScriptData.file);
      if (file === null)
        return;
      plugin.app.vault.modify(file, newText);
    }
    new import_obsidian4.Notice("WARNING: Settings not added to script. This has been automatically fixed (Simple script)", 1e4);
  }
  let parameterData = {};
  if (parameterID !== null)
    parameterData = plugin.getNodeByID(canvas, parameterID);
  const scriptData = plugin.getNodeByID(canvas, scriptID);
  if (scriptData === void 0 || parameterData === void 0)
    return;
  const arrowParamterEdges = canvas.data.edges.filter((edge) => edge.toNode === scriptID);
  const arrowParameterIDs = arrowParamterEdges.map((edge) => edge.fromNode);
  const arrowParameters = canvas.data.nodes.filter((node) => arrowParameterIDs.includes(node.id));
  const injectionData = {
    parameter_data: parameterData,
    script_data: scriptData,
    arrow_parameters: arrowParameters,
    has_parameter: parameterID !== null
  };
  executeScript(plugin, canvas, scriptData, injectionData, "simple");
}

// src/injectFunction.ts
function injectFunction(object, functionName, targetObject, replacementFunction) {
  const functionPath = functionName.split(".");
  const originalFunctionName = `original_${functionPath.join("_")}`;
  let context = object;
  for (let i = 0; i < functionPath.length - 1; i++) {
    context = context[functionPath[i]];
    if (!context) {
      throw new Error(`Property path ${functionName} does not exist on the object.`);
    }
  }
  const finalFunctionName = functionPath[functionPath.length - 1];
  if (targetObject[originalFunctionName]) {
    context[finalFunctionName] = targetObject[originalFunctionName];
  } else {
    targetObject[originalFunctionName] = context[finalFunctionName];
  }
  function toInject(...args) {
    if (!targetObject[originalFunctionName])
      return;
    replacementFunction(targetObject[originalFunctionName].bind(context), ...args);
  }
  context[finalFunctionName] = toInject;
}

// src/main.ts
function boundingBoxFromNode(node) {
  const bbox = { minX: node.x, minY: node.y, maxX: node.x + node.width, maxY: node.y + node.height };
  return bbox;
}
async function canvasClosestNodeToPosition(canvas, x, y, filterInvalidFunction = () => false) {
  let closest = null;
  const nodes = canvas.data.nodes;
  let closestDistanceSquared = 1e10;
  for (const node of nodes) {
    if (await filterInvalidFunction(node))
      continue;
    const distanceSquared = (node.x - x) ** 2 + (node.y - y) ** 2;
    if (distanceSquared < closestDistanceSquared) {
      closest = node.id;
      closestDistanceSquared = distanceSquared;
    }
  }
  return closest;
}
async function canvasClosestNodeToPositionInBounds(canvas, boundingBox, filterInvalidFunction = () => false) {
  let closest = null;
  const nodes = canvas.data.nodes;
  let closestDistanceSquared = 1e10;
  if (!nodes || !nodes.length)
    return null;
  for (const node of nodes) {
    const { minX: x1_min, minY: y1_min, maxX: x1_max, maxY: y1_max } = boundingBox;
    const { minX: x2_min, minY: y2_min, maxX: x2_max, maxY: y2_max } = boundingBoxFromNode(node);
    const bbox1_center_x = (x1_min + x1_max) / 2;
    const bbox1_center_y = (y1_min + y1_max) / 2;
    const bbox2_center_x = (x2_min + x2_max) / 2;
    const bbox2_center_y = (y2_min + y2_max) / 2;
    const distanceSquared = (bbox1_center_x - bbox2_center_x) ** 2 + (bbox1_center_y - bbox2_center_y) ** 2;
    if (x1_min <= x2_max && x1_max >= x2_min && (y1_min <= y2_max && y1_max >= y2_min)) {
      if (await filterInvalidFunction(node))
        continue;
      if (distanceSquared < closestDistanceSquared) {
        closest = node.id;
        closestDistanceSquared = distanceSquared;
      }
    }
  }
  return closest;
}
async function canvasNodesInBounds(canvas, boundingBox, filterInvalidFunction = () => false) {
  const nodesInBounds = [];
  const nodes = canvas.data.nodes;
  for (const node of nodes) {
    const { minX: x1_min, minY: y1_min, maxX: x1_max, maxY: y1_max } = boundingBox;
    const { minX: x2_min, minY: y2_min, maxX: x2_max, maxY: y2_max } = boundingBoxFromNode(node);
    if (x1_min <= x2_max && x1_max >= x2_min && (y1_min <= y2_max && y1_max >= y2_min)) {
      if (await filterInvalidFunction(node))
        continue;
      nodesInBounds.push(node.id);
    }
  }
  return nodesInBounds;
}
async function getNodeText(app2, node) {
  let text;
  if (node.hasOwnProperty("filePath")) {
    node.file = node.filePath;
    node.type = "file";
  }
  if (node.type === "text")
    text = node.text;
  else if (node.type === "link")
    text = node.url;
  else if (node.type === "group") {
    if (node.label !== void 0)
      text = node.label;
    else
      return null;
  } else if (node.type === "file" && node.file.endsWith("md")) {
    const filePath = node.file;
    const file = app2.vault.getAbstractFileByPath(filePath);
    if (file === null)
      return null;
    if (!(file instanceof import_obsidian5.TFile))
      return null;
    text = await app2.vault.read(file);
  } else {
    return node.file;
  }
  return text;
}
async function extractLanguageText(app2, source, language) {
  let text;
  if (typeof source === "string")
    text = source;
  else {
    const returnedText = await getNodeText(app2, source);
    if (returnedText === null)
      return null;
    text = returnedText;
  }
  const languageRegex = new RegExp("```\\s*" + language + "\\s*([\\s\\S]*?)```");
  const match = languageRegex.exec(text);
  return match ? match[1] : null;
}
function checkTextContainsLanguage(text, language) {
  const languageRegex = new RegExp("```\\s*" + language + "\\s*([\\s\\S]*?)```");
  return languageRegex.test(text);
}
async function checkContainsLanguage(app2, node, language) {
  const text = await getNodeText(app2, node);
  if (text === null)
    return false;
  return checkTextContainsLanguage(text, language);
}
async function checkContainsScript(app2, node) {
  const text = await getNodeText(app2, node);
  if (text === null)
    return false;
  return checkTextContainsScript(text);
}
function checkTextContainsScript(text) {
  const languageRegex = new RegExp("```\\s*(?:" + supportedLanguagesNamePrefixes.join("|") + ")" + scriptCodeBlockLanguageSuffix + "\\s*([\\s\\S]*?)```");
  return languageRegex.test(text);
}
var FuzzyScriptSuggester = class extends import_obsidian5.FuzzySuggestModal {
  constructor(plugin) {
    super(app);
    this.plugin = plugin;
    this.setPlaceholder("Enter the name of a script");
  }
  getItems() {
    const files = [];
    const folder = app.vault.getAbstractFileByPath((0, import_obsidian5.normalizePath)(this.plugin.settings.workflowScriptFolder));
    if (folder === null)
      return [];
    if (!(folder instanceof import_obsidian5.TFolder))
      return [];
    import_obsidian5.Vault.recurseChildren(folder, (file) => {
      if (file instanceof import_obsidian5.TFile) {
        files.push(file);
      }
    });
    return files;
  }
  getItemText(item) {
    return item.basename;
  }
  onChooseItem(item) {
    const view = this.plugin.app.workspace.getActiveViewOfType(import_obsidian5.ItemView);
    if (view === null)
      return;
    if (!view.hasOwnProperty("canvas")) {
      new import_obsidian5.Notice("A canvas must be open to run this command");
      return;
    }
    const canvas = view.canvas;
    const tx = canvas.tx;
    const ty = canvas.ty;
    addWorkflowScript(this.plugin, item, tx, ty);
  }
};
var CanvasBlocksPlugin4 = class extends import_obsidian5.Plugin {
  constructor() {
    super(...arguments);
    this.plugin = this;
  }
  async onload() {
    await this.loadSettings();
    await this.saveSettings();
    this.addSettingTab(new CanvasBlocksPluginSettingTab(this.app, this));
    this.addCommand({
      id: "execute-canvas-script",
      name: "Execute canvas script",
      callback: () => {
        this.handleRun();
      }
    });
    this.addCommand({
      id: "add-workflow-script",
      name: "Add workflow script",
      callback: () => {
        new FuzzyScriptSuggester(this).open();
      }
    });
    this.registerEvent(this.app.workspace.on("layout-change", () => {
      const that = this;
      const view = this.app.workspace.getActiveViewOfType(import_obsidian5.ItemView);
      if (view === null)
        return;
      if (!view.hasOwnProperty("canvas")) {
        return;
      }
      const viewFilePath = view.file.path;
      const canvas = view.canvas;
      injectFunction(canvas, "handlePaste", view, async (originalHandlePaste, event) => {
        var _a;
        const dataJSON = (_a = event.clipboardData) == null ? void 0 : _a.getData("obsidian/canvas");
        if (dataJSON === void 0)
          return;
        if (dataJSON === "") {
          originalHandlePaste(event);
          return;
        }
        const data = JSON.parse(dataJSON);
        const newNodes = [];
        const settingsNodes = [];
        for (const node of data.nodes) {
          if (node.type === "file") {
            if (!node.file.endsWith(".md")) {
              newNodes.push(node);
              continue;
            }
            const file = that.app.vault.getAbstractFileByPath((0, import_obsidian5.normalizePath)(node.file));
            if (!(file instanceof import_obsidian5.TFile)) {
              newNodes.push(node);
              continue;
            }
            const text = await that.app.vault.cachedRead(file);
            const containsSettingsLanguage = checkTextContainsLanguage(text, canvasBlockSettingsLanguageName);
            if (!containsSettingsLanguage) {
              newNodes.push(node);
              continue;
            }
            settingsNodes.push(node);
          } else if (node.type === "group") {
            if (node.label !== workflowNodesDimensions.groupLabel)
              newNodes.push(node);
          } else if (node.type === "text") {
            const containsConnectionPointLanguage = checkTextContainsLanguage(node.text, canvasBlockConnectionPointLanguageName);
            if (!containsConnectionPointLanguage) {
              newNodes.push(node);
              continue;
            }
          } else
            newNodes.push(node);
        }
        data.nodes = newNodes;
        const newEvent = new ClipboardEvent("paste", {
          clipboardData: new DataTransfer()
        });
        if (!newEvent.clipboardData)
          return;
        newEvent.clipboardData.setData("obsidian/canvas", JSON.stringify(data));
        originalHandlePaste(newEvent);
        for (const settingsNode of settingsNodes) {
          const file = that.app.vault.getAbstractFileByPath((0, import_obsidian5.normalizePath)(settingsNode.file));
          if (!(file instanceof import_obsidian5.TFile))
            continue;
          addWorkflowScript(that, file, settingsNode.x - data.center.x + canvas.tx, settingsNode.y - data.center.y + canvas.ty);
        }
      });
      injectFunction(canvas, "menu.render", view, async function(originalFunction, redraw) {
        originalFunction(redraw);
        if (!redraw)
          return;
        const menuEl = canvas.menu.menuEl;
        const button = menuEl.createEl("button", "clickable-icon");
        (0, import_obsidian5.setTooltip)(button, "Execute script");
        (0, import_obsidian5.setIcon)(button, "play");
        button.onclick = function() {
          that.handleRun();
        };
      });
      injectFunction(canvas, "addEdge", view, async (originalAddEdge, edge) => {
        originalAddEdge(edge);
        edge.lastTo = edge.to.node.id;
        canvas.requestSave();
        refreshNode(canvas, edge.from.node.id);
        injectFunction(edge, "update", edge, async (originalEdgeUpdate, ...args) => {
          originalEdgeUpdate(...args);
          if (edge.lastTo !== edge.to.node.id) {
            const lastTo = edge.lastTo;
            edge.lastTo = edge.to.node.id;
            canvas.requestSave();
            refreshNode(canvas, lastTo);
            refreshNode(canvas, edge.to.node.id);
          }
        });
      });
      injectFunction(canvas, "removeEdge", view, async (originalRemoveEdge, ...args) => {
        originalRemoveEdge.apply(this, args);
        canvas.requestSave();
        refreshNode(canvas, args[0].from.node.id);
        refreshNode(canvas, args[0].to.node.id);
      });
      injectFunction(canvas, "addNode", view, async (originalAddNode, node) => {
        if (!node.skipAddNode) {
          originalAddNode(node);
        }
        if (canvas.data.nodes === void 0)
          return;
        const newNodeData = canvas.data.nodes.find((searchNode) => searchNode.id === node.id);
        if (newNodeData === void 0) {
          if (node.hasOwnProperty("file") && node.width === canvas.config.defaultFileNodeDimensions.width && node.height === canvas.config.defaultFileNodeDimensions.height) {
            if (node.file.path.endsWith(".md")) {
              const file = that.app.vault.getFileByPath(node.file.path);
              if (file !== null) {
                const text = await that.app.vault.cachedRead(file);
                const containsLanguage = checkTextContainsLanguage(text, canvasBlockSettingsLanguageName);
                if (containsLanguage) {
                  addWorkflowScript(that, file, node.x, node.y);
                  node.destroy();
                  node.detach();
                  canvas.removeNode(node);
                  canvas.requestSave();
                }
              }
            }
          }
        }
        injectFunction(node, "moveAndResize", node, async (originalMoveAndResize, newPositionAndSize) => {
          canvas.requestSave();
          const isInSelected = function(canvas2, id) {
            for (const element of canvas2.selection) {
              if (element.id === id) {
                return true;
              }
            }
            return false;
          };
          const nodeData = canvas.data.nodes.find((searchNode) => searchNode.id === node.id);
          if (nodeData === void 0)
            return;
          if (node.preventRecursion) {
            originalMoveAndResize(newPositionAndSize);
            return;
          }
          if (!isInSelected(canvas, node.id))
            return;
          const oldPosition = { x: node.x, y: node.y };
          const newPosition = { x: newPositionAndSize.x, y: newPositionAndSize.y };
          const translationX = newPosition.x - oldPosition.x;
          const translationY = newPosition.y - oldPosition.y;
          const workflowNodes = await cachedGetWorkflowNodes(this, canvas, node.id);
          if (workflowNodes === void 0) {
            node.preventRecursion = true;
            node.moveTo(newPositionAndSize);
            node.preventRecursion = false;
            canvas.requestSave();
            return;
          }
          if (workflowNodes.groupNode === void 0)
            return;
          const settingsNode = canvas.nodes.get(workflowNodes.settingsNode.id);
          if (settingsNode === void 0)
            return;
          const settingsNodePosition = { x: settingsNode.x + translationX, y: settingsNode.y + translationY };
          const groupNode = canvas.nodes.get(workflowNodes.groupNode.id);
          if (groupNode === void 0)
            return;
          const moveQueue = [];
          moveQueue.push({ node: settingsNode, position: settingsNodePosition });
          const text = await extractLanguageText(this.app, workflowNodes.settingsNode, canvasBlockSettingsLanguageName);
          if (text === null)
            return;
          const scriptSettings = JSON.parse(text);
          let numInput = 0;
          let numOutput = 0;
          for (const connectionName in scriptSettings.ioConnections) {
            const ioConnection = scriptSettings.ioConnections[connectionName];
            const numberOfConnectionsAbove = ioConnection.direction === "input" ? numInput : numOutput;
            const offset = ioConnection.direction === "input" ? 0 : workflowNodesDimensions.scriptWidth - workflowNodesDimensions.connectionPointWidth;
            for (const connectionNodeData of workflowNodes.connectionNodes) {
              const connectionPointText = await extractLanguageText(this.app, connectionNodeData, canvasBlockConnectionPointLanguageName);
              if (connectionPointText === null)
                continue;
              const connectionPointData = JSON.parse(connectionPointText);
              if (connectionPointData.name === connectionName) {
                const connectionNode = canvas.nodes.get(connectionNodeData.id);
                if (connectionNode === void 0)
                  return;
                moveQueue.push({ node: connectionNode, position: {
                  x: settingsNodePosition.x + offset,
                  y: settingsNodePosition.y + workflowNodesDimensions.scriptHeight + workflowNodesDimensions.padding + workflowNodesDimensions.connectionPointHeight * numberOfConnectionsAbove
                } });
                break;
              }
            }
            if (ioConnection.direction === "input")
              numInput++;
            else
              numOutput++;
          }
          moveQueue.push({ node: groupNode, position: {
            x: settingsNodePosition.x - workflowNodesDimensions.padding,
            y: settingsNodePosition.y - workflowNodesDimensions.padding
          } });
          const moveQueueIds = [];
          for (const element of moveQueue) {
            moveQueueIds.push(element.node.id);
          }
          const relevantSelectedNodes = [];
          for (const element of canvas.selection) {
            if (moveQueueIds.includes(element.id)) {
              relevantSelectedNodes.push(element);
            }
          }
          if (relevantSelectedNodes.length === 0)
            return;
          if (relevantSelectedNodes[0].id !== node.id)
            return;
          for (const element of moveQueue) {
            element.node.preventRecursion = true;
            element.node.moveTo(element.position);
            element.node.preventRecursion = false;
          }
          canvas.requestSave();
        });
      });
      for (const node of canvas.nodes.values()) {
        setTimeout(() => {
          node.skipAddNode = true;
          canvas.addNode(node);
        }, 100);
      }
      injectFunction(canvas, "removeNode", view, async function(originalRemoveNode, deletedNode) {
        canvas.requestSave();
        const isViewLoaded = view._loaded;
        if (!isViewLoaded) {
          originalRemoveNode(deletedNode);
          return;
        }
        const newViewFilePath = view.file.path;
        if (viewFilePath != newViewFilePath) {
          originalRemoveNode(deletedNode);
          return;
        }
        if (canvas.data.nodes === void 0)
          return;
        const workflowNodes = await getWorkflowNodes(that, canvas, deletedNode.id);
        if (workflowNodes === void 0) {
          originalRemoveNode(deletedNode);
          canvas.requestSave();
          return;
        }
        const settingsNode = canvas.nodes.get(workflowNodes.settingsNode.id);
        if (settingsNode !== void 0)
          originalRemoveNode(settingsNode);
        if (workflowNodes.groupNode !== void 0) {
          const groupNode = canvas.nodes.get(workflowNodes.groupNode.id);
          if (groupNode !== void 0)
            originalRemoveNode(groupNode);
        }
        for (const node of workflowNodes.connectionNodes) {
          const deleteNode = canvas.nodes.get(node.id);
          if (deleteNode !== void 0)
            originalRemoveNode(deleteNode);
        }
        canvas.requestSave();
      });
    }));
    for (const language of supportedLanguagesNamePrefixes) {
      const codeBlockLanguageName = language + scriptCodeBlockLanguageSuffix;
      this.registerMarkdownCodeBlockProcessor(codeBlockLanguageName, () => {
      });
    }
    this.registerMarkdownCodeBlockProcessor(canvasBlockSettingsLanguageName, () => {
    });
    this.registerMarkdownCodeBlockProcessor(canvasBlockConnectionPointLanguageName, async (source, el) => {
      const connectionPointData = JSON.parse(source);
      const view = this.app.workspace.getActiveViewOfType(import_obsidian5.ItemView);
      if (view === null)
        return;
      if (!view.hasOwnProperty("canvas")) {
        return;
      }
      const canvas = view.canvas;
      const scriptNode = canvas.data.nodes.find((node) => node.id === connectionPointData.scriptID);
      if (scriptNode === void 0)
        return;
      const scriptData = await extractLanguageText(this.app, scriptNode, canvasBlockSettingsLanguageName);
      if (scriptData === null)
        return;
      const scriptSettings = JSON.parse(scriptData);
      const ioConnection = scriptSettings.ioConnections[connectionPointData.name];
      if (ioConnection === void 0)
        return;
      const connectionNode = canvas.data.nodes.find((node) => node.type === "text" && node.text.includes(source));
      if (connectionNode === void 0)
        return;
      const connectionEdge = canvas.data.edges.find((edge) => edge.toNode === connectionNode.id || edge.fromNode === connectionNode.id);
      let circle;
      if (connectionEdge === void 0)
        circle = "\u25EF";
      else
        circle = "\u2B24";
      let color;
      switch (ioConnection.type) {
        case "image":
          color = "#ffff00";
          break;
        case "text":
          color = "#AA5555";
          break;
        case "integer":
          color = "#06aacf";
          break;
        case "float":
          color = "#13d493";
          break;
        case "file":
          color = "#55FF55";
          break;
        case "any":
          color = "#0476c2";
          break;
        default:
          color = "#555555";
          break;
      }
      const circleSpan = createSpan({ text: circle });
      circleSpan.setCssStyles({ color });
      if (ioConnection.direction === "input") {
        const span = el.createSpan({ cls: "canvasblock-input-node" });
        span.appendChild(circleSpan);
        span.appendText(" " + connectionPointData.name);
      } else {
        const span = el.createSpan({ cls: "canvasblock-output-node" });
        span.appendText(connectionPointData.name + " ");
        span.appendChild(circleSpan);
      }
    });
  }
  onunload() {
  }
  async loadSettings() {
    this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
  }
  async saveSettings() {
    await this.saveData(this.settings);
  }
  // Returns folder name with leading and trailing '/'
  getDataFolder() {
    const folder = this.settings.dataFolder;
    if (folder === "/")
      return "";
    return folder;
  }
  async handleRun() {
    const view = this.app.workspace.getActiveViewOfType(import_obsidian5.ItemView);
    if (view === null)
      return;
    if (!view.hasOwnProperty("canvas")) {
      new import_obsidian5.Notice("This command requires a canvas file to be open");
      return;
    }
    const canvas = view.canvas;
    const selected = canvas.selection;
    if (selected.size === 0) {
      new import_obsidian5.Notice("This command requires a node to be selected");
      return;
    }
    const selectedNode = selected.values().next().value;
    const selectionID = selectedNode.id;
    const workflowNodes = await getWorkflowNodes(this, canvas, selectionID);
    if (workflowNodes) {
      if (workflowNodes.groupNode === void 0)
        return;
      handleWorkflowFromGroup(this, canvas, workflowNodes.groupNode);
      return;
    }
    handleSimpleScript(this, canvas, selectionID, selectedNode);
  }
  getNodeByID(canvas, id) {
    return canvas.data.nodes.find((node) => node.id === id);
  }
};

/* nosourcemap */