// OpenFang Visual Workflow Builder — Drag-and-drop workflow designer
'use strict';

function workflowBuilder() {
  return {
    // -- Canvas state --
    nodes: [],
    connections: [],
    selectedNode: null,
    selectedConnection: null,
    dragging: null,
    dragOffset: { x: 0, y: 0 },
    connecting: null, // { fromId, fromPort }
    connectPreview: null, // { x, y } mouse position during connect drag
    canvasOffset: { x: 0, y: 0 },
    canvasDragging: false,
    canvasDragStart: { x: 0, y: 0 },
    zoom: 1,
    nextId: 1,
    workflowName: '',
    workflowDescription: '',
    showSaveModal: false,
    showNodeEditor: false,
    showTomlPreview: false,
    tomlOutput: '',
    agents: [],
    _canvasEl: null,

    // Node types with their configs
    nodeTypes: [
      { type: 'agent', label: 'Agent Step', color: '#6366f1', icon: 'A', ports: { in: 1, out: 1 } },
      { type: 'parallel', label: 'Parallel Fan-out', color: '#f59e0b', icon: 'P', ports: { in: 1, out: 3 } },
      { type: 'condition', label: 'Condition', color: '#10b981', icon: '?', ports: { in: 1, out: 2 } },
      { type: 'loop', label: 'Loop', color: '#ef4444', icon: 'L', ports: { in: 1, out: 1 } },
      { type: 'collect', label: 'Collect', color: '#8b5cf6', icon: 'C', ports: { in: 3, out: 1 } },
      { type: 'start', label: 'Start', color: '#22c55e', icon: 'S', ports: { in: 0, out: 1 } },
      { type: 'end', label: 'End', color: '#ef4444', icon: 'E', ports: { in: 1, out: 0 } }
    ],

    async init() {
      var self = this;
      // Load agents for the agent step dropdown
      try {
        var list = await OpenFangAPI.get('/api/agents');
        self.agents = Array.isArray(list) ? list : [];
      } catch(_) {
        self.agents = [];
      }
      // Add default start node
      self.addNode('start', 60, 200);
    },

    // ── Node Management ──────────────────────────────────

    addNode: function(type, x, y) {
      var def = null;
      for (var i = 0; i < this.nodeTypes.length; i++) {
        if (this.nodeTypes[i].type === type) { def = this.nodeTypes[i]; break; }
      }
      if (!def) return;
      var node = {
        id: 'node-' + this.nextId++,
        type: type,
        label: def.label,
        color: def.color,
        icon: def.icon,
        x: x || 200,
        y: y || 200,
        width: 180,
        height: 70,
        ports: { in: def.ports.in, out: def.ports.out },
        config: {}
      };
      if (type === 'agent') {
        node.config = { agent_name: '', prompt: '{{input}}', model: '' };
      } else if (type === 'condition') {
        node.config = { expression: '', true_label: 'Yes', false_label: 'No' };
      } else if (type === 'loop') {
        node.config = { max_iterations: 5, until: '' };
      } else if (type === 'parallel') {
        node.config = { fan_count: 3 };
      } else if (type === 'collect') {
        node.config = { strategy: 'all' };
      }
      this.nodes.push(node);
      return node;
    },

    deleteNode: function(nodeId) {
      this.connections = this.connections.filter(function(c) {
        return c.from !== nodeId && c.to !== nodeId;
      });
      this.nodes = this.nodes.filter(function(n) { return n.id !== nodeId; });
      if (this.selectedNode && this.selectedNode.id === nodeId) {
        this.selectedNode = null;
        this.showNodeEditor = false;
      }
    },

    duplicateNode: function(node) {
      var newNode = this.addNode(node.type, node.x + 30, node.y + 30);
      if (newNode) {
        newNode.config = JSON.parse(JSON.stringify(node.config));
        newNode.label = node.label + ' copy';
      }
    },

    getNode: function(id) {
      for (var i = 0; i < this.nodes.length; i++) {
        if (this.nodes[i].id === id) return this.nodes[i];
      }
      return null;
    },

    // ── Port Positions ───────────────────────────────────

    getInputPortPos: function(node, portIndex) {
      var total = node.ports.in;
      var spacing = node.width / (total + 1);
      return { x: node.x + spacing * (portIndex + 1), y: node.y };
    },

    getOutputPortPos: function(node, portIndex) {
      var total = node.ports.out;
      var spacing = node.width / (total + 1);
      return { x: node.x + spacing * (portIndex + 1), y: node.y + node.height };
    },

    // ── Connection Management ────────────────────────────

    startConnect: function(nodeId, portIndex, e) {
      e.stopPropagation();
      this.connecting = { fromId: nodeId, fromPort: portIndex };
      var node = this.getNode(nodeId);
      var pos = this.getOutputPortPos(node, portIndex);
      this.connectPreview = { x: pos.x, y: pos.y };
    },

    endConnect: function(nodeId, portIndex, e) {
      e.stopPropagation();
      if (!this.connecting) return;
      if (this.connecting.fromId === nodeId) {
        this.connecting = null;
        this.connectPreview = null;
        return;
      }
      // Check for duplicate
      var fromId = this.connecting.fromId;
      var fromPort = this.connecting.fromPort;
      var dup = false;
      for (var i = 0; i < this.connections.length; i++) {
        var c = this.connections[i];
        if (c.from === fromId && c.fromPort === fromPort && c.to === nodeId && c.toPort === portIndex) {
          dup = true;
          break;
        }
      }
      if (!dup) {
        this.connections.push({
          id: 'conn-' + this.nextId++,
          from: fromId,
          fromPort: fromPort,
          to: nodeId,
          toPort: portIndex
        });
      }
      this.connecting = null;
      this.connectPreview = null;
    },

    deleteConnection: function(connId) {
      this.connections = this.connections.filter(function(c) { return c.id !== connId; });
      this.selectedConnection = null;
    },

    // ── Drag Handling ────────────────────────────────────

    onNodeMouseDown: function(node, e) {
      e.stopPropagation();
      this.selectedNode = node;
      this.selectedConnection = null;
      this.dragging = node.id;
      var rect = this._getCanvasRect();
      this.dragOffset = {
        x: (e.clientX - rect.left) / this.zoom - this.canvasOffset.x - node.x,
        y: (e.clientY - rect.top) / this.zoom - this.canvasOffset.y - node.y
      };
    },

    onCanvasMouseDown: function(e) {
      if (e.target.closest('.wf-node') || e.target.closest('.wf-port')) return;
      this.selectedNode = null;
      this.selectedConnection = null;
      this.showNodeEditor = false;
      // Start canvas pan
      this.canvasDragging = true;
      this.canvasDragStart = { x: e.clientX - this.canvasOffset.x * this.zoom, y: e.clientY - this.canvasOffset.y * this.zoom };
    },

    onCanvasMouseMove: function(e) {
      var rect = this._getCanvasRect();
      if (this.dragging) {
        var node = this.getNode(this.dragging);
        if (node) {
          node.x = Math.max(0, (e.clientX - rect.left) / this.zoom - this.canvasOffset.x - this.dragOffset.x);
          node.y = Math.max(0, (e.clientY - rect.top) / this.zoom - this.canvasOffset.y - this.dragOffset.y);
        }
      } else if (this.connecting) {
        this.connectPreview = {
          x: (e.clientX - rect.left) / this.zoom - this.canvasOffset.x,
          y: (e.clientY - rect.top) / this.zoom - this.canvasOffset.y
        };
      } else if (this.canvasDragging) {
        this.canvasOffset = {
          x: (e.clientX - this.canvasDragStart.x) / this.zoom,
          y: (e.clientY - this.canvasDragStart.y) / this.zoom
        };
      }
    },

    onCanvasMouseUp: function() {
      this.dragging = null;
      this.connecting = null;
      this.connectPreview = null;
      this.canvasDragging = false;
    },

    onCanvasWheel: function(e) {
      e.preventDefault();
      var delta = e.deltaY > 0 ? -0.05 : 0.05;
      this.zoom = Math.max(0.3, Math.min(2, this.zoom + delta));
    },

    _getCanvasRect: function() {
      if (!this._canvasEl) {
        this._canvasEl = document.getElementById('wf-canvas');
      }
      return this._canvasEl ? this._canvasEl.getBoundingClientRect() : { left: 0, top: 0 };
    },

    // ── Connection Path ──────────────────────────────────

    getConnectionPath: function(conn) {
      var fromNode = this.getNode(conn.from);
      var toNode = this.getNode(conn.to);
      if (!fromNode || !toNode) return '';
      var from = this.getOutputPortPos(fromNode, conn.fromPort);
      var to = this.getInputPortPos(toNode, conn.toPort);
      var dy = Math.abs(to.y - from.y);
      var cp = Math.max(40, dy * 0.5);
      return 'M ' + from.x + ' ' + from.y + ' C ' + from.x + ' ' + (from.y + cp) + ' ' + to.x + ' ' + (to.y - cp) + ' ' + to.x + ' ' + to.y;
    },

    getPreviewPath: function() {
      if (!this.connecting || !this.connectPreview) return '';
      var fromNode = this.getNode(this.connecting.fromId);
      if (!fromNode) return '';
      var from = this.getOutputPortPos(fromNode, this.connecting.fromPort);
      var to = this.connectPreview;
      var dy = Math.abs(to.y - from.y);
      var cp = Math.max(40, dy * 0.5);
      return 'M ' + from.x + ' ' + from.y + ' C ' + from.x + ' ' + (from.y + cp) + ' ' + to.x + ' ' + (to.y - cp) + ' ' + to.x + ' ' + to.y;
    },

    // ── Node editor ──────────────────────────────────────

    editNode: function(node) {
      this.selectedNode = node;
      this.showNodeEditor = true;
    },

    // ── TOML Generation ──────────────────────────────────

    generateToml: function() {
      var self = this;
      var lines = [];
      lines.push('[workflow]');
      lines.push('name = "' + (this.workflowName || 'untitled') + '"');
      lines.push('description = "' + (this.workflowDescription || '') + '"');
      lines.push('');

      // Topological sort the nodes (skip start/end for step generation)
      var stepNodes = this.nodes.filter(function(n) {
        return n.type !== 'start' && n.type !== 'end';
      });

      for (var i = 0; i < stepNodes.length; i++) {
        var node = stepNodes[i];
        lines.push('[[workflow.steps]]');
        lines.push('name = "' + (node.label || 'step-' + (i + 1)) + '"');

        if (node.type === 'agent') {
          lines.push('type = "agent"');
          if (node.config.agent_name) lines.push('agent_name = "' + node.config.agent_name + '"');
          lines.push('prompt = "' + (node.config.prompt || '{{input}}') + '"');
          if (node.config.model) lines.push('model = "' + node.config.model + '"');
        } else if (node.type === 'parallel') {
          lines.push('type = "fan_out"');
          lines.push('fan_count = ' + (node.config.fan_count || 3));
        } else if (node.type === 'condition') {
          lines.push('type = "conditional"');
          lines.push('expression = "' + (node.config.expression || '') + '"');
        } else if (node.type === 'loop') {
          lines.push('type = "loop"');
          lines.push('max_iterations = ' + (node.config.max_iterations || 5));
          if (node.config.until) lines.push('until = "' + node.config.until + '"');
        } else if (node.type === 'collect') {
          lines.push('type = "collect"');
          lines.push('strategy = "' + (node.config.strategy || 'all') + '"');
        }

        // Find what this node connects to
        var outConns = self.connections.filter(function(c) { return c.from === node.id; });
        if (outConns.length === 1) {
          var target = self.getNode(outConns[0].to);
          if (target && target.type !== 'end') {
            lines.push('next = "' + target.label + '"');
          }
        } else if (outConns.length > 1 && node.type === 'condition') {
          for (var j = 0; j < outConns.length; j++) {
            var t2 = self.getNode(outConns[j].to);
            if (t2 && t2.type !== 'end') {
              var branchLabel = j === 0 ? 'true' : 'false';
              lines.push('next_' + branchLabel + ' = "' + t2.label + '"');
            }
          }
        } else if (outConns.length > 1 && node.type === 'parallel') {
          var targets = [];
          for (var k = 0; k < outConns.length; k++) {
            var t3 = self.getNode(outConns[k].to);
            if (t3 && t3.type !== 'end') targets.push('"' + t3.label + '"');
          }
          if (targets.length) lines.push('fan_targets = [' + targets.join(', ') + ']');
        }

        lines.push('');
      }

      this.tomlOutput = lines.join('\n');
      this.showTomlPreview = true;
    },

    // ── Save Workflow ────────────────────────────────────

    async saveWorkflow() {
      var steps = [];
      var stepNodes = this.nodes.filter(function(n) {
        return n.type !== 'start' && n.type !== 'end';
      });
      for (var i = 0; i < stepNodes.length; i++) {
        var node = stepNodes[i];
        var step = {
          name: node.label || 'step-' + (i + 1),
          mode: node.type === 'parallel' ? 'fan_out' : node.type === 'loop' ? 'loop' : 'sequential'
        };
        if (node.type === 'agent') {
          step.agent_name = node.config.agent_name || '';
          step.prompt = node.config.prompt || '{{input}}';
        }
        steps.push(step);
      }
      try {
        await OpenFangAPI.post('/api/workflows', {
          name: this.workflowName || 'untitled',
          description: this.workflowDescription || '',
          steps: steps
        });
        OpenFangToast.success('Workflow saved!');
        this.showSaveModal = false;
      } catch(e) {
        OpenFangToast.error('Failed to save: ' + e.message);
      }
    },

    // ── Palette drop ─────────────────────────────────────

    onPaletteDragStart: function(type, e) {
      e.dataTransfer.setData('text/plain', type);
      e.dataTransfer.effectAllowed = 'copy';
    },

    onCanvasDrop: function(e) {
      e.preventDefault();
      var type = e.dataTransfer.getData('text/plain');
      if (!type) return;
      var rect = this._getCanvasRect();
      var x = (e.clientX - rect.left) / this.zoom - this.canvasOffset.x;
      var y = (e.clientY - rect.top) / this.zoom - this.canvasOffset.y;
      this.addNode(type, x - 90, y - 35);
    },

    onCanvasDragOver: function(e) {
      e.preventDefault();
      e.dataTransfer.dropEffect = 'copy';
    },

    // ── Auto Layout ──────────────────────────────────────

    autoLayout: function() {
      // Simple top-to-bottom layout
      var y = 40;
      var x = 200;
      for (var i = 0; i < this.nodes.length; i++) {
        this.nodes[i].x = x;
        this.nodes[i].y = y;
        y += 120;
      }
    },

    // ── Clear ────────────────────────────────────────────

    clearCanvas: function() {
      this.nodes = [];
      this.connections = [];
      this.selectedNode = null;
      this.nextId = 1;
      this.addNode('start', 60, 200);
    },

    // ── Zoom controls ────────────────────────────────────

    zoomIn: function() {
      this.zoom = Math.min(2, this.zoom + 0.1);
    },

    zoomOut: function() {
      this.zoom = Math.max(0.3, this.zoom - 0.1);
    },

    zoomReset: function() {
      this.zoom = 1;
      this.canvasOffset = { x: 0, y: 0 };
    }
  };
}
