import { createBasicBlock, addEdge, pushBlock, popBlock, pushBlockForModification, recordInBasicBlock } from './ir_cfg.js';
import { getOrCreateNode } from './ir_dag.js';
import { BlockType, NodeType } from './ir_types.js';
import { c as createStrandsNode } from '../ir_builders-w12-GSxu.js';
import { createPhiNode } from './strands_phi_utils.js';
import './strands_FES.js';
import './strands_builtins.js';

class StrandsConditional {
  constructor(strandsContext, condition, branchCallback) {
    // Condition must be a node...
    this.branches = [{
      condition,
      branchCallback,
      blockType: BlockType.IF_BODY
    }];
    this.ctx = strandsContext;
  }
  ElseIf(condition, branchCallback) {
    this.branches.push({
      condition,
      branchCallback,
      blockType: BlockType.IF_BODY
    });
    return this;
  }
  Else(branchCallback = () => ({})) {
    this.branches.push({
      condition: null,
      branchCallback,
      blockType: BlockType.IF_BODY
    });
    const phiNodes = buildConditional(this.ctx, this);
    const assignments = {};
    for (const [varName, phiNode] of Object.entries(phiNodes)) {
      assignments[varName] = createStrandsNode(phiNode.id, phiNode.dimension, this.ctx);
    }
    return assignments;
  }
}
function buildConditional(strandsContext, conditional) {
  const cfg = strandsContext.cfg;
  const branches = conditional.branches;
  const mergeBlock = createBasicBlock(cfg, BlockType.MERGE);
  const results = [];
  const branchEndBlocks = [];
  const mergedAssignments = {};
  const phiBlockDependencies = {};
  // Create a BRANCH block to handle phi node declarations
  const branchBlock = createBasicBlock(cfg, BlockType.BRANCH);
  addEdge(cfg, cfg.currentBlock, branchBlock);
  addEdge(cfg, branchBlock, mergeBlock);
  let previousBlock = branchBlock;
  for (let i = 0; i < branches.length; i++) {
    const { condition, branchCallback, blockType } = branches[i];
    if (condition !== null) {
      const conditionBlock = createBasicBlock(cfg, BlockType.IF_COND);
      addEdge(cfg, previousBlock, conditionBlock);
      pushBlock(cfg, conditionBlock);
      cfg.blockConditions[conditionBlock] = condition.id;
      previousBlock = conditionBlock;
      popBlock(cfg);
    } else {
      const elseCondBlock = createBasicBlock(cfg, BlockType.ELSE_COND);
      addEdge(cfg, previousBlock, elseCondBlock);
      previousBlock = elseCondBlock;
    }
    const scopeStartBlock = createBasicBlock(cfg, BlockType.SCOPE_START);
    addEdge(cfg, previousBlock, scopeStartBlock);
    const branchContentBlock = createBasicBlock(cfg, blockType);
    addEdge(cfg, scopeStartBlock, branchContentBlock);
    pushBlock(cfg, branchContentBlock);
    const branchResults = branchCallback();
    for (const key in branchResults) {
      branchResults[key] = strandsContext.p5.strandsNode(branchResults[key]);
    }
    for (const key in branchResults) {
      if (!phiBlockDependencies[key]) {
        phiBlockDependencies[key] = [{ value: branchResults[key], blockId: branchContentBlock }];
      } else {
        phiBlockDependencies[key].push({ value: branchResults[key], blockId: branchContentBlock });
      }
    }
    results.push(branchResults);

    // Create BRANCH_END block for phi assignments
    const branchEndBlock = createBasicBlock(cfg, BlockType.DEFAULT);
    addEdge(cfg, cfg.currentBlock, branchEndBlock);
    branchEndBlocks.push(branchEndBlock);
    popBlock(cfg);

    const scopeEndBlock = createBasicBlock(cfg, BlockType.SCOPE_END);
    addEdge(cfg, branchEndBlock, scopeEndBlock);
    addEdge(cfg, scopeEndBlock, mergeBlock);
    previousBlock = scopeStartBlock;
  }
  // Push the branch block for modification to avoid changing the ordering
  pushBlockForModification(cfg, branchBlock);
  for (const key in phiBlockDependencies) {
    mergedAssignments[key] = createPhiNode(strandsContext, phiBlockDependencies[key], key);
  }
  popBlock(cfg);
  for (let i = 0; i < results.length; i++) {
    const branchResult = results[i];
    const branchEndBlockID = branchEndBlocks[i];
    pushBlockForModification(cfg, branchEndBlockID);
    for (const key in branchResult) {
      if (mergedAssignments[key]) {
        // Create an assignment statement: phiNode = branchResult[key]
        const phiNodeID = mergedAssignments[key].id;
        const sourceNodeID = branchResult[key].id;
        // Create an assignment operation node
        // Use dependsOn[0] for phiNodeID and dependsOn[1] for sourceNodeID
        // This represents: dependsOn[0] = dependsOn[1] (phiNode = sourceNode)
        const assignmentNode = {
          nodeType: NodeType.ASSIGNMENT,
          dependsOn: [phiNodeID, sourceNodeID],
          phiBlocks: []
        };
        const assignmentID = getOrCreateNode(strandsContext.dag, assignmentNode);
        recordInBasicBlock(cfg, branchEndBlockID, assignmentID);
      }
    }
    popBlock(cfg);
  }
  pushBlock(cfg, mergeBlock);
  return mergedAssignments;
}

export { StrandsConditional };
