/**
 * The JavaScript target-specific code generator for translating Kipper code into JavaScript.
 * @since 0.10.0
 */
import {
	ComparativeExpressionSemantics,
	LogicalExpressionSemantics,
	TranslatedCodeLine,
	TranslatedExpression,
	AdditiveExpression,
	AssignmentExpression,
	BoolPrimaryExpression,
	CastOrConvertExpression,
	ComparativeExpression,
	ConditionalExpression,
	EqualityExpression,
	Expression,
	ExpressionStatement,
	FStringPrimaryExpression,
	FunctionCallExpression,
	FunctionDeclaration,
	GenericTypeSpecifierExpression,
	IdentifierPrimaryExpression,
	IdentifierTypeSpecifierExpression,
	IncrementOrDecrementPostfixExpression,
	IncrementOrDecrementUnaryExpression,
	JumpStatement,
	KipperProgramContext,
	ArrayLiteralPrimaryExpression,
	LogicalAndExpression,
	LogicalExpression,
	LogicalOrExpression,
	MultiplicativeExpression,
	NumberPrimaryExpression,
	OperatorModifiedUnaryExpression,
	ParameterDeclaration,
	RelationalExpression,
	ReturnStatement,
	StringPrimaryExpression,
	SwitchStatement,
	TangledPrimaryExpression,
	TypeofTypeSpecifierExpression,
	VariableDeclaration,
} from "@kipper/core";
import {
	CompoundStatement,
	DoWhileLoopStatement,
	ForLoopStatement,
	getConversionFunctionIdentifier,
	IfStatement,
	KipperTargetCodeGenerator,
	MemberAccessExpression,
	ScopeDeclaration,
	ScopeFunctionDeclaration,
	VoidOrNullOrUndefinedPrimaryExpression,
	WhileLoopStatement,
} from "@kipper/core";
import { createJSFunctionSignature, getJSFunctionSignature, indentLines, removeBraces } from "./tools";
import { TargetJS, version } from "./index";

function removeBrackets(lines: Array<TranslatedCodeLine>) {
	return lines.slice(1, lines.length - 1);
}

/**
 * The JavaScript target-specific code generator for translating Kipper code into JavaScript.
 * @since 0.10.0
 */
export class JavaScriptTargetCodeGenerator extends KipperTargetCodeGenerator {
	/**
	 * Code generation function, which is called at the start of a translation and generates
	 * the dependencies for a file in the target language.
	 *
	 * This should be only used to set up a Kipper file in the target language and not as a
	 * replacement to {@link KipperTargetBuiltInGenerator}.
	 * @since 0.10.0
	 */
	setUp = async (programCtx: KipperProgramContext): Promise<Array<TranslatedCodeLine>> => {
		return [
			[`/* Generated from '${programCtx.fileName}' by the Kipper Compiler v${version} */`],
			// Always enable strict mode when using Kipper
			['"use strict"', ";"],
			// Determine the global scope in the JS execution environment
			["// @ts-ignore"],
			[
				'var __kipperGlobalScope = typeof __kipperGlobalScope !== "undefined" ? __kipperGlobalScope : typeof' +
					' globalThis !== "undefined" ?' +
					" globalThis : typeof" +
					' window !== "undefined" ?' +
					' window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {}',
				";",
			],
			// Create global kipper object - Always prefer the global '__kipper' instance
			["// @ts-ignore"],
			["var __kipper = __kipperGlobalScope.__kipper = __kipperGlobalScope.__kipper || __kipper || {}", ";"],
			// The following error classes are simply used for errors thrown in internal Kipper functions and should be used
			// when the user code uses a Kipper-specific feature, syntax or function incorrectly.
			[
				'__kipper.TypeError = __kipper.TypeError ||  (class KipperTypeError extends TypeError { constructor(msg) { super(msg); this.name="TypeError"; }})',
				";",
			],
			[
				'__kipper.IndexError = __kipper.IndexError ||  (class KipperIndexError extends Error { constructor(msg) { super(msg); this.name="IndexError"; }})',
				";",
			],
		];
	};

	/**
	 * Code generation function, which is called at the end of a translation and should wrap
	 * up a program in the target language.
	 *
	 * This should be only used to add additional items to finish a Kipper file in the target
	 * language and not as a replacement to {@link KipperTargetBuiltInGenerator}.
	 * @since 0.10.0
	 */
	wrapUp = async (programCtx: KipperProgramContext): Promise<Array<TranslatedCodeLine>> => {
		return [];
	};

	/**
	 * Translates a {@link CompoundStatement} into the JavaScript language.
	 */
	compoundStatement = async (node: CompoundStatement): Promise<Array<TranslatedCodeLine>> => {
		let blockItem: Array<TranslatedCodeLine> = [];
		for (let child of node.children) {
			const childCode = await child.translateCtxAndChildren();
			blockItem = blockItem.concat(childCode);
		}
		return [["{"], ...indentLines(blockItem), ["}"]];
	};

	/**
	 * Translates a {@link IfStatement} into the JavaScript language.
	 *
	 * Implementation notes:
	 * - This algorithm is indirectly recursive, as else-if statements are handling like else statements with an immediate
	 *   if statement in them.
	 * - The formatting algorithm tries to start at the top and slowly go down each area of the abstract syntax tree.
	 *   First the starting 'if' will be formatted, and afterwards the alternative branches are processed if they exists.
	 *   If they do, it is also formatted like with a regular starting 'if', unless there is another nested if-statement
	 *   in which case it will pass that job down to the child if-statement.
	 * @since 0.10.0
	 */
	ifStatement = async (node: IfStatement): Promise<Array<TranslatedCodeLine>> => {
		const semanticData = node.getSemanticData();

		// Core items, which will be always present
		let condition = await semanticData.condition.translateCtxAndChildren();
		let statement = await semanticData.ifBranch.translateCtxAndChildren();

		if (semanticData.ifBranch instanceof CompoundStatement) {
			statement = removeBrackets(statement); // remove brackets -> will be added later
		} else {
			statement = indentLines(statement); // Apply indent to the single statement
		}

		let baseCode = [
			["if", " ", "(", ...condition, ")", " ", "{"],
			...statement, // Statement, which is executed if the first condition is true
			["}", " "],
		];

		// If there is no alternative branch, return with this code
		if (!semanticData.elseBranch) {
			return baseCode;
		}

		let secondBranchIsCompoundStatement = semanticData.elseBranch instanceof CompoundStatement;
		let secondBranchIsElseIf = semanticData.elseBranch instanceof IfStatement;
		let secondBranchIsElse = !secondBranchIsElseIf;
		let secondCondition: Array<string> | null = null;
		let secondBranch: Array<TranslatedCodeLine> | null = null;

		// Evaluate the alternative branch if it exists
		if (semanticData.elseBranch) {
			secondBranch = await semanticData.elseBranch.translateCtxAndChildren();

			if (secondBranchIsElseIf) {
				// Else if statement
				// Move 'if' condition into the else line -> 'else if (condition)'
				secondCondition = ["else", " ", ...secondBranch[0]];
				secondBranch = secondBranch.slice(1, secondBranch.length);
			} else {
				// Else statement
				secondCondition = ["else"];
			}

			if (secondBranchIsCompoundStatement) {
				// Format code and remove brackets from compound statements if they exist
				secondBranch = removeBrackets(secondBranch);
			} else if (secondBranchIsElse) {
				// If the second branch is else, then the end of this branch of the AST was reached
				secondBranch = indentLines(secondBranch);
			}
		}

		// Return with the second branch added. (Since the function calls itself indirectly recursively, there can be as
		// many else-ifs as the user wants.)
		return [
			...baseCode.slice(0, baseCode.length - 1), // Add all lines except the last one that ends the if-statement
			["}", " ", ...(secondCondition ?? []), ...(secondBranchIsCompoundStatement ? [" ", "{"] : [])],
			...(secondBranch ?? []), // Else-if/else statement, which is executed if the second condition is true
			...(secondBranchIsCompoundStatement ? [["}", " "]] : []),
		];
	};

	/**
	 * Translates a {@link SwitchStatement} into the JavaScript language.
	 *
	 * @since 0.10.0
	 */
	switchStatement = async (node: SwitchStatement): Promise<Array<TranslatedCodeLine>> => {
		return [];
	};

	/**
	 * Translates a {@link ExpressionStatement} into the JavaScript language.
	 */
	expressionStatement = async (node: ExpressionStatement): Promise<Array<TranslatedCodeLine>> => {
		let exprCode: Array<TranslatedCodeLine> = [];
		for (let child of node.children) {
			// Expression lists (expression statements) will be evaluated per each expression, meaning every expression
			// can be considered a single line of code.
			const childCode = await child.translateCtxAndChildren();
			exprCode.push(childCode.concat(";"));
		}
		return exprCode;
	};

	/**
	 * Translates a {@link DoWhileLoopStatement} into the JavaScript language.
	 * @since 0.10.0
	 */
	doWhileLoopStatement = async (node: DoWhileLoopStatement): Promise<Array<TranslatedCodeLine>> => {
		return [];
	};

	/**
	 * Translates a {@link WhileLoopStatement} into the JavaScript language.
	 * @since 0.10.0
	 */
	whileLoopStatement = async (node: WhileLoopStatement): Promise<Array<TranslatedCodeLine>> => {
		const semanticData = node.getSemanticData();
		const condition = await semanticData.loopCondition.translateCtxAndChildren();
		const statement = await semanticData.loopBody.translateCtxAndChildren();

		// Check whether the loop body is a compound statement
		const isCompound = semanticData.loopBody instanceof CompoundStatement;

		return [
			["while", " ", "(", ...condition, ")", " ", isCompound ? "{" : ""],
			...(isCompound ? removeBraces(statement) : indentLines(statement)),
			[isCompound ? "}" : ""],
		];
	};

	/**
	 * Translates a {@link ForLoopStatement} into the JavaScript language.
	 * @since 0.10.0
	 */
	forLoopStatement = async (node: ForLoopStatement): Promise<Array<TranslatedCodeLine>> => {
		const semanticData = node.getSemanticData();

		// Translate the parts of the for loop statement - Everything except the loop body is optional
		let forDeclaration: TranslatedExpression | Array<TranslatedCodeLine> = semanticData.forDeclaration
			? await semanticData.forDeclaration.translateCtxAndChildren()
			: [];
		const condition: TranslatedExpression = semanticData.loopCondition
			? await semanticData.loopCondition.translateCtxAndChildren()
			: [];
		const forIterationExp: TranslatedExpression = semanticData.forIterationExp
			? await semanticData.forIterationExp.translateCtxAndChildren()
			: [];

		// Apply formatting for the loop body (compound statements are formatted differently)
		let isCompound = semanticData.loopBody instanceof CompoundStatement;
		let loopBody = await semanticData.loopBody.translateCtxAndChildren();
		if (isCompound) {
			loopBody = removeBrackets(loopBody); // remove brackets -> will be added later
		} else {
			loopBody = indentLines(loopBody); // Indent the loop body
		}

		// Ensure the variable declaration in the for declaration is properly included in the output code
		forDeclaration = <TranslatedExpression>(
			// Variable declarations are already translated as a single line of code and have a semicolon at the end ->
			// We need to ensure that the semicolon is not added twice
			(semanticData.forDeclaration instanceof VariableDeclaration ? forDeclaration[0] : [...forDeclaration, ";"])
		);

		return [
			[
				"for",
				" ",
				"(",
				...forDeclaration,
				" ",
				...condition,
				";",
				" ",
				...forIterationExp,
				")",
				" ",
				...(isCompound ? ["{"] : []),
			],
			...loopBody,
			isCompound ? ["}"] : [],
		];
	};

	/**
	 * Translates a {@link JumpStatement} into the JavaScript language.
	 */
	jumpStatement = async (node: JumpStatement): Promise<Array<TranslatedCodeLine>> => {
		const semanticData = node.getSemanticData();
		return [semanticData.jmpType === "break" ? ["break", ";"] : ["continue", ";"]];
	};

	/**
	 * Translates a {@link ReturnStatement} into the JavaScript language.
	 * @since 0.10.0
	 */
	returnStatement = async (node: ReturnStatement): Promise<Array<TranslatedCodeLine>> => {
		const semanticData = node.getSemanticData();
		const returnValue = await semanticData.returnValue?.translateCtxAndChildren();

		return [["return", ...(returnValue ? [" ", ...returnValue] : []), ";"]];
	};

	/**
	 * Translates a {@link ParameterDeclaration} into the JavaScript language.
	 */
	parameterDeclaration = async (node: ParameterDeclaration): Promise<Array<TranslatedCodeLine>> => {
		return [];
	};

	/**
	 * Translates a {@link FunctionDeclaration} into the JavaScript language.
	 */
	functionDeclaration = async (node: FunctionDeclaration): Promise<Array<TranslatedCodeLine>> => {
		const semanticData = node.getSemanticData();

		// Function signature and body
		const signature = getJSFunctionSignature(node);
		const functionBody = await semanticData.functionBody.translateCtxAndChildren();

		// Define the function signature and its body. We will simply use 'console.log(msg)' for printing out IO.
		return [[createJSFunctionSignature(signature)], ...functionBody];
	};

	/**
	 * Translates a {@link VariableDeclaration} into the JavaScript language.
	 */
	variableDeclaration = async (node: VariableDeclaration): Promise<Array<TranslatedCodeLine>> => {
		const semanticData = node.getSemanticData();
		const storage = semanticData.storageType === "const" ? "const" : "let";
		const assign = semanticData.value ? await semanticData.value.translateCtxAndChildren() : [];

		// Only add ' = EXP' if assignValue is defined
		return [[storage, " ", semanticData.identifier, ...(assign.length > 0 ? [" ", "=", " ", ...assign] : []), ";"]];
	};

	/**
	 * Translates a {@link NumberPrimaryExpression} into the JavaScript language.
	 */
	numberPrimaryExpression = async (node: NumberPrimaryExpression): Promise<TranslatedExpression> => {
		const semanticData = node.getSemanticData();

		return [
			semanticData.value, // Simply get the constant value
		];
	};

	/**
	 * Translates a {@link ArrayLiteralPrimaryExpression} into the JavaScript language.
	 */
	arrayLiteralExpression = async (node: ArrayLiteralPrimaryExpression): Promise<TranslatedExpression> => {
		return [];
	};

	/**
	 * Translates a {@link IdentifierPrimaryExpression} into the JavaScript language.
	 */
	identifierPrimaryExpression = async (node: IdentifierPrimaryExpression): Promise<TranslatedExpression> => {
		const semanticData = node.getSemanticData();
		let identifier: string = semanticData.identifier;

		// If the identifier is not declared by the user, assume it's a built-in function and format the identifier
		// accordingly.
		if (!(semanticData.ref.refTarget instanceof ScopeDeclaration)) {
			identifier = TargetJS.getBuiltInIdentifier(semanticData.ref.refTarget);
		}
		return [identifier];
	};

	/**
	 * Translates a {@link IdentifierPrimaryExpression} into the JavaScript language.
	 * @since 0.10.0
	 */
	memberAccessExpression = async (node: MemberAccessExpression): Promise<TranslatedExpression> => {
		const semanticData = node.getSemanticData();
		const object = await semanticData.objectLike.translateCtxAndChildren();

		switch (<"dot" | "bracket" | "slice">semanticData.accessType) {
			case "dot":
				return []; // TODO: Not implemented
			case "bracket": {
				// -> The member access is done via brackets, meaning the member name is an expression
				// In this case, only indexes are allowed, not keys, but in the future, this will change with the implementation
				// of objects.
				const keyOrIndex = await (<Expression>semanticData.propertyIndexOrKeyOrSlice).translateCtxAndChildren();

				// Return the member access expression in form of a function call to the internal 'index' function
				const sliceIdentifier = TargetJS.getBuiltInIdentifier("index");
				return [sliceIdentifier, "(", ...object, ", ", ...keyOrIndex, ")"];
			}
			case "slice": {
				// -> The member access is done via a slice, meaning the member name is a slice expression
				const slice = <{ start?: Expression; end?: Expression }>semanticData.propertyIndexOrKeyOrSlice;

				// Translate the start and end expression, if they exist.
				// If they don't, simply undefined will be passed onto the underlying function
				const start = slice.start ? await slice.start.translateCtxAndChildren() : "undefined";
				const end = slice.end ? await slice.end.translateCtxAndChildren() : "undefined";

				// Return the slice expression in form of a function call to the internal 'slice' function
				const sliceIdentifier = TargetJS.getBuiltInIdentifier("slice");
				return [sliceIdentifier, "(", ...object, ", ", ...start, ", ", ...end, ")"];
			}
		}
	};

	/**
	 * Translates a {@link IdentifierTypeSpecifierExpression} into the JavaScript language.
	 */
	identifierTypeSpecifierExpression = async (
		node: IdentifierTypeSpecifierExpression,
	): Promise<TranslatedExpression> => {
		return [];
	};

	/**
	 * Translates a {@link GenericTypeSpecifierExpression} into the JavaScript language.
	 */
	genericTypeSpecifierExpression = async (node: GenericTypeSpecifierExpression): Promise<TranslatedExpression> => {
		return [];
	};

	/**
	 * Translates a {@link TypeofTypeSpecifierExpression} into the JavaScript language.
	 */
	typeofTypeSpecifierExpression = async (node: TypeofTypeSpecifierExpression): Promise<TranslatedExpression> => {
		return [];
	};

	/**
	 * Translates a {@link StringPrimaryExpression} into the JavaScript language.
	 */
	stringPrimaryExpression = async (node: StringPrimaryExpression): Promise<TranslatedExpression> => {
		const semanticData = node.getSemanticData();

		return [`${semanticData.quotationMarks}${semanticData.value}${semanticData.quotationMarks}`];
	};

	/**
	 * Translates a {@link FStringPrimaryExpression} into the JavaScript language.
	 */
	fStringPrimaryExpression = async (node: FStringPrimaryExpression): Promise<TranslatedExpression> => {
		return [];
	};

	/**
	 * Translates a {@link BoolPrimaryExpression} into the JavaScript language.
	 */
	boolPrimaryExpression = async (node: BoolPrimaryExpression): Promise<TranslatedExpression> => {
		return [node.getSemanticData().value];
	};

	/**
	 * Translates a {@link TangledPrimaryExpression} into the JavaScript language.
	 */
	tangledPrimaryExpression = async (node: TangledPrimaryExpression): Promise<TranslatedExpression> => {
		// Tangled expressions always contain only a single child (Enforced by Parser)
		return ["(", ...(await node.children[0].translateCtxAndChildren()), ")"];
	};

	/**
	 * Translates a {@link IncrementOrDecrementPostfixExpression} into the JavaScript language.
	 */
	voidOrNullOrUndefinedPrimaryExpression = async (
		node: VoidOrNullOrUndefinedPrimaryExpression,
	): Promise<TranslatedExpression> => {
		const constantIdentifier = node.getSemanticData().constantIdentifier;

		return [constantIdentifier === "void" ? "void(0)" : constantIdentifier];
	};

	/**
	 * Translates a {@link IncrementOrDecrementPostfixExpression} into the JavaScript language.
	 */
	incrementOrDecrementPostfixExpression = async (
		node: IncrementOrDecrementPostfixExpression,
	): Promise<TranslatedExpression> => {
		const semanticData = node.getSemanticData();
		const operandCode = await semanticData.operand.translateCtxAndChildren();

		return [...operandCode, semanticData.operator];
	};

	/**
	 * Translates a {@link FunctionCallExpression} into the JavaScript language.
	 */
	functionCallExpression = async (node: FunctionCallExpression): Promise<TranslatedExpression> => {
		// Get the function and semantic data
		const semanticData = node.getSemanticData();
		const func = node.getTypeSemanticData().func;

		// Get the proper identifier for the function
		const identifier =
			func instanceof ScopeFunctionDeclaration ? func.identifier : TargetJS.getBuiltInIdentifier(func.identifier);

		// Generate the arguments
		let args: TranslatedExpression = [];
		for (const i of semanticData.args) {
			const arg = await i.translateCtxAndChildren();
			args = args.concat(arg.concat(", "));
		}
		args = args.slice(0, -1); // Removing last whitespace and comma before the closing parenthesis

		// Return the compiled function call
		return [identifier, "(", ...args, ")"];
	};

	/**
	 * Translates a {@link IncrementOrDecrementUnaryExpression} into the JavaScript language.
	 */
	incrementOrDecrementUnaryExpression = async (
		node: IncrementOrDecrementUnaryExpression,
	): Promise<TranslatedExpression> => {
		const semanticData = node.getSemanticData();
		const operandCode = await semanticData.operand.translateCtxAndChildren();

		return [semanticData.operator, ...operandCode];
	};

	/**
	 * Translates a {@link OperatorModifiedUnaryExpression} into the JavaScript language.
	 */
	operatorModifiedUnaryExpression = async (node: OperatorModifiedUnaryExpression): Promise<TranslatedExpression> => {
		// Get the semantic data
		const semanticData = node.getSemanticData();

		// Get the operator and the operand
		const operator: string = semanticData.operator;
		const operand: Expression = semanticData.operand;

		// Return the generated unary expression
		return [operator].concat(await operand.translateCtxAndChildren());
	};

	/**
	 * Translates a {@link CastOrConvertExpression} into the JavaScript language.
	 */
	castOrConvertExpression = async (node: CastOrConvertExpression): Promise<TranslatedExpression> => {
		const semanticData = node.getSemanticData();
		const typeData = node.getTypeSemanticData();

		const exp: TranslatedExpression = await semanticData.exp.translateCtxAndChildren();
		const originalType = semanticData.exp.getTypeSemanticData().evaluatedType.getCompilableType();
		const destType = typeData.castType.getCompilableType();

		if (originalType === destType) {
			// If both types are the same we will only return the translated expression to avoid useless conversions.
			return exp;
		} else {
			const func: string = TargetJS.getBuiltInIdentifier(getConversionFunctionIdentifier(originalType, destType));
			return [func, "(", ...exp, ")"];
		}
	};

	/**
	 * Translates a {@link MultiplicativeExpression} into the JavaScript language.
	 */
	multiplicativeExpression = async (node: MultiplicativeExpression): Promise<TranslatedExpression> => {
		// Get the semantic data
		const semanticData = node.getSemanticData();

		const exp1: TranslatedExpression = await semanticData.leftOp.translateCtxAndChildren();
		const exp2: TranslatedExpression = await semanticData.rightOp.translateCtxAndChildren();
		return [...exp1, " ", semanticData.operator, " ", ...exp2];
	};

	/**
	 * Translates a {@link AdditiveExpression} into the JavaScript language.
	 */
	additiveExpression = async (node: AdditiveExpression): Promise<TranslatedExpression> => {
		// Get the semantic data
		const semanticData = node.getSemanticData();

		const exp1: TranslatedExpression = await semanticData.leftOp.translateCtxAndChildren();
		const exp2: TranslatedExpression = await semanticData.rightOp.translateCtxAndChildren();
		return [...exp1, " ", semanticData.operator, " ", ...exp2];
	};

	/**
	 * Translates any form of operator-based expression with two operands into the JavaScript language.
	 * @param node The node to translate.
	 * @since 0.10.0
	 * @private
	 */
	protected translateOperatorExpressionWithOperands = async (
		node: ComparativeExpression | LogicalExpression,
	): Promise<TranslatedExpression> => {
		// Get the semantic data
		const semanticData: ComparativeExpressionSemantics | LogicalExpressionSemantics = node.getSemanticData();

		// Generate the code for the operands
		const exp1: TranslatedExpression = await semanticData.leftOp.translateCtxAndChildren();
		const exp2: TranslatedExpression = await semanticData.rightOp.translateCtxAndChildren();

		let operator: string = semanticData.operator;

		// Make sure equality checks are done with ===/!== and not ==/!= to ensure strict equality
		if (operator === "==" || operator === "!=") {
			operator = semanticData.operator + "=";
		}

		return [...exp1, " ", operator, " ", ...exp2];
	};

	/**
	 * Translates a {@link RelationalExpression} into the JavaScript language.
	 */
	relationalExpression = async (node: RelationalExpression): Promise<TranslatedExpression> => {
		return await this.translateOperatorExpressionWithOperands(node);
	};

	/**
	 * Translates a {@link EqualityExpression} into the JavaScript language.
	 */
	equalityExpression = async (node: EqualityExpression): Promise<TranslatedExpression> => {
		return await this.translateOperatorExpressionWithOperands(node);
	};

	/**
	 * Translates a {@link LogicalAndExpression} into the JavaScript language.
	 */
	logicalAndExpression = async (node: LogicalAndExpression): Promise<TranslatedExpression> => {
		return await this.translateOperatorExpressionWithOperands(node);
	};

	/**
	 * Translates a {@link LogicalOrExpression} into the JavaScript language.
	 */
	logicalOrExpression = async (node: LogicalOrExpression): Promise<TranslatedExpression> => {
		return await this.translateOperatorExpressionWithOperands(node);
	};

	/**
	 * Translates a {@link ConditionalExpression} into the JavaScript language.
	 */
	conditionalExpression = async (node: ConditionalExpression): Promise<TranslatedExpression> => {
		return [];
	};

	/**
	 * Translates a {@link AssignmentExpression} into the JavaScript language.
	 */
	assignmentExpression = async (node: AssignmentExpression): Promise<TranslatedExpression> => {
		const semanticData = node.getSemanticData();
		let identifier = semanticData.identifier;

		// If the identifier is not found in the global scope, assume it's a built-in function and format the identifier
		// accordingly.
		if (!(semanticData.assignTarget.refTarget instanceof ScopeDeclaration)) {
			identifier = TargetJS.getBuiltInIdentifier(identifier);
		}

		// The expression that is assigned to the reference
		const assignExp = await semanticData.value.translateCtxAndChildren();

		return [identifier, " ", semanticData.operator, " ", ...assignExp];
	};
}
