Source: utils/serialization.js

/**
 * Conversion between the editor's in-memory graph state and the persisted
 * workflow JSON format. `serializeWorkflow` writes editor state to workflow
 * JSON; `deserializeWorkflow` rebuilds editor state from workflow JSON.
 *
 * @module utils/serialization
 */

import { v4 as uuidv4 } from 'uuid'

const CONTROL_NODE_TYPES = ['start_point', 'end_point', 'filter_point']

/**
 * Serialize editor graph state into the persisted workflow JSON structure,
 * splitting nodes into start/end/filter control nodes and protocol components
 * and mapping connections to links.
 *
 * @param {Object} state - Editor state with `nodes` and `connections` arrays.
 * @returns {Object} Workflow JSON with `name`, `description`, `start`, `end`, `filters`, `components` and `links`.
 */
export function serializeWorkflow(state) {
  const startNode = state.nodes.find(n => n.type === 'start_point')
  const endNode = state.nodes.find(n => n.type === 'end_point')
  const filterNodes = state.nodes.filter(n => n.type === 'filter_point')
  const protocolNodes = state.nodes.filter(n => !CONTROL_NODE_TYPES.includes(n.type))

  // Debug logging
  console.log('DEBUG serializeWorkflow:')
  console.log('  All nodes:', state.nodes.map(n => ({ id: n.id, type: n.type, name: n.name })))
  console.log('  startNode:', startNode ? { id: startNode.id, type: startNode.type } : null)
  console.log('  endNode:', endNode ? { id: endNode.id, type: endNode.type } : null)
  console.log('  filterNodes:', filterNodes.map(n => ({ id: n.id, type: n.type })))

  return {
    name: 'AutoRA Workflow',
    description: 'Workflow created with AutoRA GUI',
    start: startNode ? {
      uuid: startNode.id,
      canvasLocation: {
        x: Math.round(startNode.x),
        y: Math.round(startNode.y)
      }
    } : null,
    end: endNode ? {
      uuid: endNode.id,
      canvasLocation: {
        x: Math.round(endNode.x),
        y: Math.round(endNode.y)
      }
    } : null,
    filters: filterNodes.map(node => ({
      uuid: node.id,
      maxCounter: node.filterParams?.maxCounter ?? 1,
      altTarget: node.filterParams?.altTarget ?? null,
      canvasLocation: {
        x: Math.round(node.x),
        y: Math.round(node.y)
      }
    })),
    components: protocolNodes.map(node => ({
      uuid: node.id,
      protocolUuid: node.protocolUuid,
      parameterSetting: Object.entries(node.parameters || {})
        .filter(([_, value]) => value !== null && value !== undefined)
        .map(([name, value]) => ({
          uuid: uuidv4(),
          name,
          value
        })),
      canvasLocation: {
        x: Math.round(node.x),
        y: Math.round(node.y)
      }
    })),
    links: state.connections.map(conn => ({
      source: conn.sourceId,
      target: conn.targetId,
      sourcePoint: conn.sourcePoint,
      targetPoint: conn.targetPoint
    }))
  }
}

/**
 * Rebuild editor graph state from workflow JSON, recreating control nodes and
 * protocol component nodes (with parameter defaults merged from the matching
 * protocol) and reconstructing connections for links whose endpoints exist.
 *
 * @param {Object} workflow - Workflow JSON with `start`, `end`, `filters`, `components` and `links`.
 * @param {Object} componentsMap - Map of protocol category to arrays of protocol definitions, keyed by `uuid`.
 * @returns {Object} Editor state `{ nodes, connections }`.
 */
export function deserializeWorkflow(workflow, componentsMap) {
  const allComponents = Object.values(componentsMap).flat()
  const nodes = []

  // Add start node if present
  if (workflow.start) {
    nodes.push({
      id: workflow.start.uuid,
      protocolUuid: 'start-node',
      type: 'start_point',
      name: 'Start',
      description: 'Starting point of the workflow',
      x: workflow.start.canvasLocation?.x ?? 100,
      y: workflow.start.canvasLocation?.y ?? 100,
      componentData: { uuid: 'start-node', protocolType: 'start_point', name: 'Start', isControlNode: true },
      parameters: {}
    })
  }

  // Add end node if present
  if (workflow.end) {
    nodes.push({
      id: workflow.end.uuid,
      protocolUuid: 'end-node',
      type: 'end_point',
      name: 'End',
      description: 'Ending point of the workflow',
      x: workflow.end.canvasLocation?.x ?? 100,
      y: workflow.end.canvasLocation?.y ?? 100,
      componentData: { uuid: 'end-node', protocolType: 'end_point', name: 'End', isControlNode: true },
      parameters: {}
    })
  }

  // Add filter nodes if present
  ;(workflow.filters || []).forEach(filter => {
    nodes.push({
      id: filter.uuid,
      protocolUuid: 'filter-node',
      type: 'filter_point',
      name: 'Filter',
      description: 'Filter/decision point in the workflow',
      x: filter.canvasLocation?.x ?? 100,
      y: filter.canvasLocation?.y ?? 100,
      componentData: { uuid: 'filter-node', protocolType: 'filter_point', name: 'Filter', isControlNode: true },
      parameters: {},
      filterParams: {
        maxCounter: filter.maxCounter ?? 1,
        altTarget: filter.altTarget ?? null
      }
    })
  })

  // Add protocol components
  ;(workflow.components || []).forEach(comp => {
    const protocol = allComponents.find(c => c.uuid === comp.protocolUuid)

    if (!protocol) {
      console.warn(`Protocol not found: ${comp.protocolUuid}`)
      return
    }

    const parameters = {}
    // parameters is now a dict with function names as keys and arrays of params as values
    const allParams = protocol.parameters
      ? Object.values(protocol.parameters).flat()
      : []
    allParams.forEach(param => {
      parameters[param.name] = param.default ?? null
    })
    ;(comp.parameterSetting || []).forEach(setting => {
      if (setting.name) {
        parameters[setting.name] = setting.value
      }
    })

    nodes.push({
      id: comp.uuid,
      protocolUuid: comp.protocolUuid,
      type: protocol.protocolType,
      name: protocol.name,
      description: protocol.description,
      x: comp.canvasLocation?.x ?? 100,
      y: comp.canvasLocation?.y ?? 100,
      componentData: protocol,
      parameters
    })
  })

  const nodeIds = new Set(nodes.map(n => n.id))

  const connections = (workflow.links || [])
    .filter(link => nodeIds.has(link.source) && nodeIds.has(link.target))
    .map(link => ({
      id: uuidv4(),
      sourceId: link.source,
      targetId: link.target,
      sourcePoint: link.sourcePoint,
      targetPoint: link.targetPoint
    }))

  return { nodes, connections }
}