import { generateRandomString } from '@sim/utils/random'
import {
  createAgentBlock,
  createBlock,
  createFunctionBlock,
  createStarterBlock,
} from '../factories/block.factory'
import type { Position } from '../types'

/* eslint-disable @typescript-eslint/no-explicit-any */

/**
 * Fluent builder for creating complex workflow states.
 *
 * Use this when you need fine-grained control over workflow construction,
 * especially for testing edge cases or complex scenarios.
 *
 * @example
 * ```ts
 * // Simple linear workflow
 * const workflow = new WorkflowBuilder()
 *   .addStarter('start')
 *   .addAgent('agent', { x: 200, y: 0 })
 *   .addFunction('end', { x: 400, y: 0 })
 *   .connect('start', 'agent')
 *   .connect('agent', 'end')
 *   .build()
 *
 * // Using static presets
 * const workflow = WorkflowBuilder.linear(5).build()
 * const workflow = WorkflowBuilder.branching().build()
 * ```
 */
export class WorkflowBuilder {
  private blocks: Record<string, any> = {}
  private edges: any[] = []
  private loops: Record<string, any> = {}
  private parallels: Record<string, any> = {}
  private variables: any[] = []
  private isDeployed = false

  /**
   * Adds a generic block to the workflow.
   */
  addBlock(id: string, type: string, position?: Position, name?: string): this {
    this.blocks[id] = createBlock({
      id,
      type,
      name: name ?? id,
      position: position ?? { x: 0, y: 0 },
    })
    return this
  }

  /**
   * Adds a starter block (workflow entry point).
   */
  addStarter(id = 'start', position?: Position): this {
    this.blocks[id] = createStarterBlock({
      id,
      position: position ?? { x: 0, y: 0 },
    })
    return this
  }

  /**
   * Adds a function block.
   */
  addFunction(id: string, position?: Position, name?: string): this {
    this.blocks[id] = createFunctionBlock({
      id,
      name: name ?? id,
      position: position ?? { x: 0, y: 0 },
    })
    return this
  }

  /**
   * Adds an agent block.
   */
  addAgent(id: string, position?: Position, name?: string): this {
    this.blocks[id] = createAgentBlock({
      id,
      name: name ?? id,
      position: position ?? { x: 0, y: 0 },
    })
    return this
  }

  /**
   * Adds a condition block.
   */
  addCondition(id: string, position?: Position, name?: string): this {
    this.blocks[id] = createBlock({
      id,
      type: 'condition',
      name: name ?? id,
      position: position ?? { x: 0, y: 0 },
    })
    return this
  }

  /**
   * Adds a loop container block.
   */
  addLoop(
    id: string,
    position?: Position,
    config?: {
      iterations?: number
      loopType?: 'for' | 'forEach' | 'while' | 'doWhile'
    }
  ): this {
    this.blocks[id] = createBlock({
      id,
      type: 'loop',
      name: 'Loop',
      position: position ?? { x: 0, y: 0 },
      data: {
        loopType: config?.loopType ?? 'for',
        count: config?.iterations ?? 3,
        type: 'loop',
      },
    })
    this.loops[id] = {
      id,
      nodes: [],
      iterations: config?.iterations ?? 3,
      loopType: config?.loopType ?? 'for',
    }
    return this
  }

  /**
   * Adds a block as a child of a loop container.
   */
  addLoopChild(loopId: string, childId: string, type = 'function', position?: Position): this {
    if (!this.loops[loopId]) {
      throw new Error(`Loop ${loopId} does not exist. Call addLoop first.`)
    }

    this.blocks[childId] = createBlock({
      id: childId,
      type,
      name: childId,
      position: position ?? { x: 50, y: 50 },
      parentId: loopId,
    })

    this.loops[loopId].nodes.push(childId)
    return this
  }

  /**
   * Adds a parallel container block.
   */
  addParallel(
    id: string,
    position?: Position,
    config?: {
      count?: number
      parallelType?: 'count' | 'collection'
    }
  ): this {
    this.blocks[id] = createBlock({
      id,
      type: 'parallel',
      name: 'Parallel',
      position: position ?? { x: 0, y: 0 },
      data: {
        parallelType: config?.parallelType ?? 'count',
        count: config?.count ?? 2,
        type: 'parallel',
      },
    })
    this.parallels[id] = {
      id,
      nodes: [],
      count: config?.count ?? 2,
      parallelType: config?.parallelType ?? 'count',
    }
    return this
  }

  /**
   * Adds a block as a child of a parallel container.
   */
  addParallelChild(
    parallelId: string,
    childId: string,
    type = 'function',
    position?: Position
  ): this {
    if (!this.parallels[parallelId]) {
      throw new Error(`Parallel ${parallelId} does not exist. Call addParallel first.`)
    }

    this.blocks[childId] = createBlock({
      id: childId,
      type,
      name: childId,
      position: position ?? { x: 50, y: 50 },
      parentId: parallelId,
    })

    this.parallels[parallelId].nodes.push(childId)
    return this
  }

  /**
   * Creates an edge connecting two blocks.
   */
  connect(sourceId: string, targetId: string, sourceHandle?: string, targetHandle?: string): this {
    this.edges.push({
      id: `${sourceId}-${targetId}`,
      source: sourceId,
      target: targetId,
      sourceHandle,
      targetHandle,
    })
    return this
  }

  /**
   * Adds a workflow variable.
   */
  addVariable(
    name: string,
    type: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'plain',
    value: any
  ): this {
    this.variables?.push({
      id: `var-${generateRandomString(6)}`,
      name,
      type,
      value,
    })
    return this
  }

  /**
   * Sets the workflow as deployed.
   */
  setDeployed(deployed = true): this {
    this.isDeployed = deployed
    return this
  }

  /**
   * Builds and returns the workflow state.
   * Returns `any` to be assignable to any app's workflow type.
   */
  build(): any {
    return {
      blocks: this.blocks,
      edges: this.edges,
      loops: this.loops,
      parallels: this.parallels,
      lastSaved: Date.now(),
      isDeployed: this.isDeployed,
      variables: this.variables?.length ? this.variables : undefined,
    }
  }

  /**
   * Creates a workflow with the specified blocks and connects them linearly.
   */
  static chain(...blockConfigs: Array<{ id: string; type: string }>): WorkflowBuilder {
    const builder = new WorkflowBuilder()
    let x = 0
    const spacing = 200

    blockConfigs.forEach((config, index) => {
      builder.addBlock(config.id, config.type, { x, y: 0 })
      x += spacing

      if (index > 0) {
        builder.connect(blockConfigs[index - 1].id, config.id)
      }
    })

    return builder
  }

  /**
   * Creates a linear workflow with N blocks.
   * First block is a starter, rest are function blocks.
   */
  static linear(blockCount: number): WorkflowBuilder {
    const builder = new WorkflowBuilder()
    const spacing = 200

    for (let i = 0; i < blockCount; i++) {
      const id = `block-${i}`
      const position = { x: i * spacing, y: 0 }

      if (i === 0) {
        builder.addStarter(id, position)
      } else {
        builder.addFunction(id, position, `Step ${i}`)
      }

      if (i > 0) {
        builder.connect(`block-${i - 1}`, id)
      }
    }

    return builder
  }

  /**
   * Creates a branching workflow with a condition.
   *
   * Structure:
   * ```
   *           ┌─→ true ─┐
   * start ─→ cond       ├─→ end
   *           └─→ false ┘
   * ```
   */
  static branching(): WorkflowBuilder {
    return new WorkflowBuilder()
      .addStarter('start', { x: 0, y: 0 })
      .addCondition('condition', { x: 200, y: 0 })
      .addFunction('true-branch', { x: 400, y: -100 }, 'If True')
      .addFunction('false-branch', { x: 400, y: 100 }, 'If False')
      .addFunction('end', { x: 600, y: 0 }, 'End')
      .connect('start', 'condition')
      .connect('condition', 'true-branch', 'condition-if')
      .connect('condition', 'false-branch', 'condition-else')
      .connect('true-branch', 'end')
      .connect('false-branch', 'end')
  }

  /**
   * Creates a workflow with a loop.
   */
  static withLoop(iterations = 3): WorkflowBuilder {
    return new WorkflowBuilder()
      .addStarter('start', { x: 0, y: 0 })
      .addLoop('loop', { x: 200, y: 0 }, { iterations })
      .addLoopChild('loop', 'loop-body', 'function', { x: 50, y: 50 })
      .addFunction('end', { x: 500, y: 0 })
      .connect('start', 'loop')
      .connect('loop', 'end')
  }

  /**
   * Creates a workflow with parallel execution.
   */
  static withParallel(count = 2): WorkflowBuilder {
    return new WorkflowBuilder()
      .addStarter('start', { x: 0, y: 0 })
      .addParallel('parallel', { x: 200, y: 0 }, { count })
      .addParallelChild('parallel', 'parallel-task', 'function', { x: 50, y: 50 })
      .addFunction('end', { x: 500, y: 0 })
      .connect('start', 'parallel')
      .connect('parallel', 'end')
  }
}
