import { CompileTimeConstants, CompileTimeHeap } from '../program';
import { Dict, Option } from '../core';
import { SingleBuilderOperand } from './operands';
import { NamedBlocks, HandleResult } from '../template';
import { Op, MachineOp } from '../vm-opcodes';
import * as WireFormat from './wire-format';
import { InternalComponentCapabilities } from '../managers/internal/component';
import { CompileTimeComponent } from '../..';

// These values are used in the same space as standard opcodes, so we need to
// start them at a higher value to prevent collisions
export const enum HighLevelBuilderOpcode {
  Label = 1000,
  StartLabels = 1001,
  StopLabels = 1002,

  Start = Label,
  End = StopLabels,
}

export const enum HighLevelResolutionOpcode {
  ResolveModifier = 1003,

  ResolveComponent = 1004,

  ResolveHelper = 1005,
  ResolveOptionalHelper = 1006,

  ResolveComponentOrHelper = 1007,
  ResolveOptionalComponentOrHelper = 1008,

  ResolveFree = 1009,
  ResolveLocal = 1010,
  ResolveTemplateLocal = 1011,

  Start = ResolveModifier,
  End = ResolveTemplateLocal,
}

export interface SimpleArgsOptions {
  positional: Option<WireFormat.Core.Params>;
  named: Option<WireFormat.Core.Hash>;
  atNames: boolean;
}

export interface ArgsOptions extends SimpleArgsOptions {
  named: WireFormat.Core.Hash;
  blocks: NamedBlocks;
}

export type StartLabelsOp = [op: HighLevelBuilderOpcode.StartLabels];

export type StopLabelsOp = [op: HighLevelBuilderOpcode.StopLabels];

export type LabelOp = [op: HighLevelBuilderOpcode.Label, op1: string];

export type HighLevelBuilderOp = StartLabelsOp | StopLabelsOp | LabelOp;

export type ResolveModifierOp = [
  op: HighLevelResolutionOpcode.ResolveModifier,
  op1: WireFormat.Expressions.Expression,
  op2: (handle: number) => void
];

export type ResolveComponentOp = [
  op: HighLevelResolutionOpcode.ResolveComponent,
  op1: WireFormat.Expressions.Expression,
  op2: (component: CompileTimeComponent) => void
];

export type ResolveComponentOrHelperOp = [
  op: HighLevelResolutionOpcode.ResolveComponentOrHelper,
  op1: WireFormat.Expressions.Expression,
  op2: {
    ifComponent: (component: CompileTimeComponent) => void;
    ifHelper: (handle: number) => void;
  }
];

export type ResolveHelperOp = [
  op: HighLevelResolutionOpcode.ResolveHelper,
  op1: WireFormat.Expressions.Expression,
  op2: (handle: number) => void
];

export type ResolveOptionalHelperOp = [
  op: HighLevelResolutionOpcode.ResolveOptionalHelper,
  op1: WireFormat.Expressions.Expression,
  op2: {
    ifHelper: (handle: number, name: string, moduleName: string) => void;
    ifFallback: (name: string, moduleName: string) => void;
  }
];

export type ResolveOptionalComponentOrHelperOp = [
  op: HighLevelResolutionOpcode.ResolveOptionalComponentOrHelper,
  op1: WireFormat.Expressions.Expression,
  op2: {
    ifComponent: (component: CompileTimeComponent) => void;
    ifHelper: (handle: number) => void;
    ifValue: (handle: number) => void;
    ifFallback: (name: string) => void;
  }
];

export type ResolveFreeOp = [
  op: HighLevelResolutionOpcode.ResolveFree,
  op1: number,
  op2: (handle: number) => void
];

export type ResolveTemplateLocalOp = [
  op: HighLevelResolutionOpcode.ResolveTemplateLocal,
  op1: number,
  op2: (handle: number) => void
];

export type ResolveLocalOp = [
  op: HighLevelResolutionOpcode.ResolveLocal,
  op1: number,
  op2: (name: string, moduleName: string) => void
];

export type HighLevelResolutionOp =
  | ResolveModifierOp
  | ResolveComponentOp
  | ResolveComponentOrHelperOp
  | ResolveHelperOp
  | ResolveOptionalHelperOp
  | ResolveOptionalComponentOrHelperOp
  | ResolveFreeOp
  | ResolveTemplateLocalOp
  | ResolveLocalOp;

export type HighLevelOp = HighLevelBuilderOp | HighLevelResolutionOp;

export type BuilderOpcode = Op | MachineOp;

export type BuilderOp = [
  op: BuilderOpcode,
  op1?: SingleBuilderOperand,
  op1?: SingleBuilderOperand,
  op1?: SingleBuilderOperand
];

export interface EncoderError {
  problem: string;
  span: {
    start: number;
    end: number;
  };
}

/**
 * The Encoder receives a stream of opcodes from the syntax compiler and turns
 * them into a binary program.
 */
export interface Encoder {
  /**
   * Finalize the current compilation unit, add a `(Return)`, and push the opcodes from
   * the buffer into the program. At this point, some of the opcodes might still be
   * placeholders, such as in the case of recursively compiled templates.
   *
   * @param compiler
   * @param size
   */
  commit(size: number): HandleResult;

  /**
   * Push a syscall into the program with up to three optional
   * operands.
   *
   * @param opcode
   * @param args up to three operands, formatted as
   *   { type: "type", value: value }
   */
  push(
    constants: CompileTimeConstants,
    opcode: BuilderOpcode,
    ...args: SingleBuilderOperand[]
  ): void;

  /**
   * Start a new labels block. A labels block is a scope for labels that
   * can be referred to before they are declared. For example, when compiling
   * an `if`, the `JumpUnless` opcode occurs before the target label. To
   * accommodate this use-case ergonomically, the `Encoder` allows a syntax
   * to create a labels block and then refer to labels that have not yet
   * been declared. Once the block is complete, a second pass replaces the
   * label names with offsets.
   *
   * The pattern is:
   *
   * ```ts
   * encoder.reserve(Op.JumpUnless);
   * encoder.target(encoder.pos, 'ELSE');
   * ```
   *
   * The `reserve` method creates a placeholder opcode with space for a target
   * in the future, and the `target` method registers the blank operand position
   * to be replaced with an offset to `ELSE`, once it's known.
   */
  startLabels(): void;

  /**
   * Finish the current labels block and replace label names with offsets,
   * now that all of the offsets are known.
   */
  stopLabels(): void;

  /**
   * Mark the current position with a label name. This label name
   * can be used by any other opcode in this label block.
   * @param name
   * @param index
   */
  label(name: string): void;

  error(error: EncoderError): void;
}
