'use strict';

/**
 * Logic Builder Node
 *
 * Visual logic building using Google Blockly.
 * Allows users to create complex logic without writing JavaScript.
 *
 * The node:
 * - Stores a Blockly workspace as JSON
 * - Auto-detects inputs/outputs from blocks
 * - Generates and executes JavaScript from blocks
 * - Provides full Noodl API access (Variables, Objects, Arrays)
 */

const LogicBuilderNode = {
  name: 'Logic Builder',
  docs: 'https://docs.noodl.net/nodes/logic/logic-builder',
  displayNodeName: 'Logic Builder',
  shortDesc: 'Build logic visually with blocks',
  category: 'CustomCode',
  color: 'javascript',
  nodeDoubleClickAction: {
    focusPort: 'workspace'
  },
  searchTags: ['blockly', 'visual', 'logic', 'blocks', 'nocode'],

  initialize: function () {
    const internal = this._internal;

    internal.workspace = ''; // Blockly workspace JSON
    internal.compiledFunction = null;
    internal.executionError = null;
    internal.inputValues = {};
    internal.outputValues = {};
  },

  methods: {
    registerInputIfNeeded: function (name) {
      if (this.hasInput(name)) {
        return;
      }

      const internal = this._internal;

      this.registerInput(name, {
        set: function (value) {
          internal.inputValues[name] = value;
          // Don't auto-execute - wait for signal inputs
        }
      });
    },

    registerOutputIfNeeded: function (name, type) {
      if (this.hasOutput(name)) {
        return;
      }

      this.registerOutput(name, {
        type: type || '*',
        getter: function () {
          return this._internal.outputValues[name];
        }
      });
    },

    registerSignalInputIfNeeded: function (name) {
      if (this.hasInput(name)) {
        return;
      }

      this.registerInput(name, {
        type: 'signal',
        valueChangedToTrue: function () {
          this._executeLogic(name);
        }
      });
    },

    registerSignalOutputIfNeeded: function (name) {
      if (this.hasOutput(name)) {
        return;
      }

      this.registerOutput(name, {
        type: 'signal'
      });
    },

    _executeLogic: function (triggerSignal) {
      const internal = this._internal;

      // Compile function if needed
      if (!internal.compiledFunction && internal.workspace) {
        internal.compiledFunction = this._compileFunction();
      }

      if (!internal.compiledFunction) {
        console.warn('[Logic Builder] No logic to execute');
        return;
      }

      try {
        // Create execution context
        const context = this._createExecutionContext(triggerSignal);

        // Execute generated code
        internal.compiledFunction.call(context);

        // Update outputs
        for (const outputName in context.Outputs) {
          internal.outputValues[outputName] = context.Outputs[outputName];
          this.flagOutputDirty(outputName);
        }

        internal.executionError = null;
      } catch (error) {
        console.error('[Logic Builder] Execution error:', error);
        internal.executionError = error.message;
        this.flagOutputDirty('error');
      }
    },

    _createExecutionContext: function (triggerSignal) {
      const internal = this._internal;
      // eslint-disable-next-line @typescript-eslint/no-this-alias
      const self = this;

      // Create context with Noodl APIs
      // eslint-disable-next-line @typescript-eslint/no-var-requires
      const JavascriptNodeParser = require('../../javascriptnodeparser');
      const noodlAPI = JavascriptNodeParser.createNoodlAPI(this.context && this.context.modelScope);

      return {
        // Inputs object
        Inputs: internal.inputValues,

        // Outputs object (writable)
        Outputs: {},

        // Noodl global APIs
        Noodl: noodlAPI,
        Variables: noodlAPI.Variables,
        Objects: noodlAPI.Objects,
        Arrays: noodlAPI.Arrays,

        // Signal sending
        sendSignalOnOutput: function (name) {
          self.sendSignalOnOutput(name);
        },

        // Convenience alias
        this: {
          sendSignalOnOutput: function (name) {
            self.sendSignalOnOutput(name);
          }
        },

        // Trigger signal name (for conditional logic)
        __triggerSignal__: triggerSignal
      };
    },

    _compileFunction: function () {
      const internal = this._internal;

      if (!internal.workspace) {
        return null;
      }

      try {
        // Generate JavaScript from Blockly workspace
        // This will be done in the editor via code generation
        // For now, we expect the 'generatedCode' parameter to be set
        const code = internal.generatedCode || '';

        if (!code) {
          console.warn('[Logic Builder] No generated code available');
          return null;
        }

        // Wrap code in a function
        // Code will have access to: Inputs, Outputs, Noodl, Variables, Objects, Arrays, sendSignalOnOutput
        const fn = new Function(code);
        return fn;
      } catch (error) {
        console.error('[Logic Builder] Failed to compile function:', error);
        return null;
      }
    }
  },

  getInspectInfo() {
    const internal = this._internal;
    if (internal.executionError) {
      return `Error: ${internal.executionError}`;
    }
    return 'Logic Builder';
  },

  inputs: {
    workspace: {
      group: 'General',
      type: {
        name: 'string',
        allowEditOnly: true,
        editorType: 'logic-builder-workspace'
      },
      displayName: 'Workspace',
      set: function (value) {
        const internal = this._internal;
        internal.workspace = value;
        internal.compiledFunction = null; // Reset compiled function
      }
    },
    generatedCode: {
      group: 'General',
      type: 'string',
      displayName: 'Generated Code',
      set: function (value) {
        const internal = this._internal;
        internal.generatedCode = value;
        internal.compiledFunction = null; // Reset compiled function
      }
    }
  },

  outputs: {
    error: {
      group: 'Status',
      type: 'string',
      displayName: 'Error',
      getter: function () {
        return this._internal.executionError || '';
      }
    }
  }
};

/**
 * Update dynamic ports based on workspace
 * This function is injected by the editor's setup code
 */
let updatePortsImpl = null;

function updatePorts(nodeId, workspace, editorConnection) {
  if (!workspace) {
    editorConnection.sendDynamicPorts(nodeId, []);
    return;
  }

  if (updatePortsImpl) {
    updatePortsImpl(nodeId, workspace, editorConnection);
  } else {
    console.warn('[Logic Builder] updatePortsImpl not initialized - running in runtime mode?');
  }
}

module.exports = {
  node: LogicBuilderNode,
  setup: function (context, graphModel) {
    if (!context.editorConnection || !context.editorConnection.isRunningLocally()) {
      return;
    }

    // Inject the real updatePorts implementation
    // This is set by the editor's initialization code
    updatePortsImpl = function (nodeId, workspace, editorConnection) {
      try {
        // The IODetector should be available in the editor context
        // We'll access it through the global window object (editor environment)
        if (typeof window !== 'undefined' && window.NoodlEditor && window.NoodlEditor.detectIO) {
          const detected = window.NoodlEditor.detectIO(workspace);

          const ports = [];

          // Add detected inputs
          detected.inputs.forEach((input) => {
            ports.push({
              name: input.name,
              type: input.type,
              plug: 'input',
              group: 'Inputs',
              displayName: input.name
            });
          });

          // Add detected outputs
          detected.outputs.forEach((output) => {
            ports.push({
              name: output.name,
              type: output.type,
              plug: 'output',
              group: 'Outputs',
              displayName: output.name
            });
          });

          // Add detected signal inputs
          detected.signalInputs.forEach((signalName) => {
            ports.push({
              name: signalName,
              type: 'signal',
              plug: 'input',
              group: 'Signal Inputs',
              displayName: signalName
            });
          });

          // Add detected signal outputs
          detected.signalOutputs.forEach((signalName) => {
            ports.push({
              name: signalName,
              type: 'signal',
              plug: 'output',
              group: 'Signal Outputs',
              displayName: signalName
            });
          });

          editorConnection.sendDynamicPorts(nodeId, ports);
        } else {
          console.warn('[Logic Builder] IODetector not available in editor context');
        }
      } catch (error) {
        console.error('[Logic Builder] Failed to update ports:', error);
      }
    };

    graphModel.on('nodeAdded.Logic Builder', function (node) {
      if (node.parameters.workspace) {
        updatePorts(node.id, node.parameters.workspace, context.editorConnection);
      }

      node.on('parameterUpdated', function (event) {
        if (event.name === 'workspace') {
          updatePorts(node.id, node.parameters.workspace, context.editorConnection);
        }
      });
    });
  },

  // Export for editor to set the implementation
  setUpdatePortsImpl: function (impl) {
    updatePortsImpl = impl;
  }
};
