import { builders, utils } from "prettier/doc";
import { call, definedKeys, each, findBaseIndent, flatMap, hasAssignmentOperators, hasLeadingComments, hasNonAssignmentOperators, indentInParentheses, isNonTerminal, isTerminal, map, onlyDefinedKey, printDanglingComments, printList, printName, printSingle } from "./helpers.js";
const { align, breakParent, conditionalGroup, group, hardline, ifBreak, indent, indentIfBreak, join, line, lineSuffixBoundary, softline } = builders;
const { removeLines, willBreak } = utils;
export default {
    expression: printSingle,
    lambdaExpression(path, print, _, args = {}) {
        var _a;
        const hug = (_a = args.hug) !== null && _a !== void 0 ? _a : false;
        const parameters = call(path, print, "lambdaParameters");
        const expression = [hug ? removeLines(parameters) : parameters, " ->"];
        const lambdaExpression = path.node.children.lambdaBody[0].children.expression;
        const body = call(path, print, "lambdaBody");
        if (lambdaExpression) {
            const suffix = indent([line, body]);
            expression.push(group(hug ? [suffix, softline] : suffix));
        }
        else {
            expression.push(" ", body);
        }
        return expression;
    },
    lambdaParameters(path, print, options) {
        const parameters = printSingle(path, print);
        return !path.node.children.lambdaParametersWithBraces &&
            options.arrowParens === "always"
            ? ["(", parameters, ")"]
            : parameters;
    },
    lambdaParametersWithBraces(path, print, options) {
        var _a;
        const { lambdaParameterList } = path.node.children;
        if (!lambdaParameterList) {
            return "()";
        }
        const { conciseLambdaParameterList, normalLambdaParameterList } = lambdaParameterList[0].children;
        const parameterCount = ((_a = conciseLambdaParameterList === null || conciseLambdaParameterList === void 0 ? void 0 : conciseLambdaParameterList[0].children.conciseLambdaParameter) !== null && _a !== void 0 ? _a : normalLambdaParameterList === null || normalLambdaParameterList === void 0 ? void 0 : normalLambdaParameterList[0].children.normalLambdaParameter).length;
        const parameters = call(path, print, "lambdaParameterList");
        if (parameterCount > 1) {
            return indentInParentheses(parameters);
        }
        return conciseLambdaParameterList && options.arrowParens === "avoid"
            ? parameters
            : ["(", parameters, ")"];
    },
    lambdaParameterList: printSingle,
    conciseLambdaParameterList(path, print) {
        return printList(path, print, "conciseLambdaParameter");
    },
    normalLambdaParameterList(path, print) {
        return printList(path, print, "normalLambdaParameter");
    },
    normalLambdaParameter: printSingle,
    regularLambdaParameter(path, print) {
        return join(" ", [
            ...map(path, print, "variableModifier"),
            call(path, print, "lambdaParameterType"),
            call(path, print, "variableDeclaratorId")
        ]);
    },
    lambdaParameterType: printSingle,
    conciseLambdaParameter: printSingle,
    lambdaBody: printSingle,
    conditionalExpression(path, print, options) {
        var _a;
        const binaryExpression = call(path, print, "binaryExpression");
        const grandparentNodeName = (_a = path.getNode(4)) === null || _a === void 0 ? void 0 : _a.name;
        const isInParentheses = grandparentNodeName === "parenthesisExpression";
        if (!path.node.children.QuestionMark) {
            return isInParentheses ? binaryExpression : group(binaryExpression);
        }
        const isInReturn = grandparentNodeName === "returnStatement";
        const prefix = group(isInReturn ? indent(binaryExpression) : binaryExpression);
        const [consequent, alternate] = map(path, print, "expression");
        const suffix = [
            line,
            ["? ", options.useTabs ? indent(consequent) : align(2, consequent)],
            line,
            [": ", options.useTabs ? indent(alternate) : align(2, alternate)]
        ];
        const isNestedTernary = grandparentNodeName === "conditionalExpression";
        const alignedSuffix = !isNestedTernary || options.useTabs
            ? suffix
            : align(Math.max(0, options.tabWidth - 2), suffix);
        if (isNestedTernary) {
            return [prefix, alignedSuffix];
        }
        const parts = [prefix, indent(alignedSuffix)];
        return isInParentheses ? parts : group(parts);
    },
    binaryExpression(path, print, options) {
        var _a, _b, _c, _d;
        const { children } = path.node;
        const operands = flatMap(path, print, definedKeys(children, [
            "expression",
            "pattern",
            "referenceType",
            "unaryExpression"
        ]));
        const operators = flatMap(path, operatorPath => {
            const { node } = operatorPath;
            let image;
            if (isTerminal(node)) {
                image = node.image;
            }
            else if (node.children.Less) {
                image = "<<";
            }
            else {
                image = node.children.Greater.length === 2 ? ">>" : ">>>";
            }
            return { image, doc: print(operatorPath) };
        }, definedKeys(children, [
            "AssignmentOperator",
            "BinaryOperator",
            "Instanceof",
            "shiftOperator"
        ]));
        const isInList = ((_a = path.getNode(4)) === null || _a === void 0 ? void 0 : _a.name) === "elementValue" ||
            ((_b = path.getNode(6)) === null || _b === void 0 ? void 0 : _b.name) === "argumentList";
        const binaryExpression = (_d = (_c = children.expression) === null || _c === void 0 ? void 0 : _c[0].children.conditionalExpression) === null || _d === void 0 ? void 0 : _d[0].children.binaryExpression[0];
        return binary(operands, operators, {
            hasNonAssignmentOperators: (operators.length > 0 && !children.AssignmentOperator) ||
                (binaryExpression && hasNonAssignmentOperators(binaryExpression)),
            isInList,
            isRoot: true,
            operatorPosition: options.experimentalOperatorPosition
        });
    },
    unaryExpression(path, print) {
        return [
            ...map(path, print, "UnaryPrefixOperator"),
            call(path, print, "primary"),
            ...map(path, print, "UnarySuffixOperator")
        ];
    },
    unaryExpressionNotPlusMinus(path, print) {
        const { children } = path.node;
        const expression = [];
        if (children.UnaryPrefixOperatorNotPlusMinus) {
            expression.push(...map(path, print, "UnaryPrefixOperatorNotPlusMinus"));
        }
        expression.push(call(path, print, "primary"));
        if (children.UnarySuffixOperator) {
            expression.push(...map(path, print, "UnarySuffixOperator"));
        }
        return join(" ", expression);
    },
    primary(path, print) {
        var _a, _b;
        const { children } = path.node;
        if (!children.primarySuffix) {
            return call(path, print, "primaryPrefix");
        }
        const methodInvocations = children.primarySuffix
            .filter(({ children }) => children.methodInvocationSuffix)
            .map(({ children }) => children.methodInvocationSuffix[0].children);
        const hasLambdaMethodParameter = methodInvocations.some(({ argumentList }) => argumentList === null || argumentList === void 0 ? void 0 : argumentList[0].children.expression.some(({ children }) => children.lambdaExpression));
        const prefixIsCallExpression = children.primaryPrefix[0].children.newExpression;
        const callExpressionCount = methodInvocations.length +
            (prefixIsCallExpression ? 1 : 0) +
            children.primarySuffix.filter(({ children }) => children.unqualifiedClassInstanceCreationExpression).length;
        const fqnOrRefType = (_a = children.primaryPrefix[0].children.fqnOrRefType) === null || _a === void 0 ? void 0 : _a[0].children;
        const prefixIsMethodInvocation = (fqnOrRefType === null || fqnOrRefType === void 0 ? void 0 : fqnOrRefType.fqnOrRefTypePartRest) !== undefined &&
            ((_b = children.primarySuffix) === null || _b === void 0 ? void 0 : _b[0].children.methodInvocationSuffix) !== undefined;
        const prefixIsStaticMethodInvocation = prefixIsMethodInvocation && isCapitalizedIdentifier(fqnOrRefType);
        const prefixIsInstanceMethodInvocation = prefixIsMethodInvocation && !prefixIsStaticMethodInvocation;
        const mustBreakForCallExpressions = methodInvocations.length > 2 && hasLambdaMethodParameter;
        const separator = mustBreakForCallExpressions ? hardline : softline;
        const prefix = [
            call(path, prefixPath => print(prefixPath, {
                lastSeparator: prefixIsStaticMethodInvocation ||
                    (prefixIsInstanceMethodInvocation && callExpressionCount === 1)
                    ? ""
                    : separator
            }), "primaryPrefix")
        ];
        const canBreakForCallExpressions = callExpressionCount > 2 ||
            (callExpressionCount === 2 && prefixIsInstanceMethodInvocation) ||
            willBreak(prefix);
        const suffixes = [];
        each(path, suffixPath => {
            const { node, previous } = suffixPath;
            const suffix = print(suffixPath);
            if (node.children.Dot) {
                if ((canBreakForCallExpressions &&
                    ((!previous && prefixIsCallExpression) ||
                        (previous === null || previous === void 0 ? void 0 : previous.children.methodInvocationSuffix) ||
                        (previous === null || previous === void 0 ? void 0 : previous.children.unqualifiedClassInstanceCreationExpression))) ||
                    (!node.children.templateArgument && willBreak(suffix))) {
                    suffixes.push(separator);
                }
                suffixes.push(suffix);
            }
            else if (previous) {
                suffixes.push(suffix);
            }
            else {
                prefix.push(prefixIsInstanceMethodInvocation && callExpressionCount >= 2
                    ? indent(suffix)
                    : suffix);
            }
        }, "primarySuffix");
        const hasSuffixComments = children.primarySuffix.some(suffix => hasLeadingComments(suffix));
        return group(canBreakForCallExpressions || hasSuffixComments
            ? [prefix, indent(suffixes)]
            : [prefix, ...suffixes]);
    },
    primaryPrefix: printSingle,
    primarySuffix(path, print) {
        const { children } = path.node;
        if (!children.Dot) {
            return printSingle(path, print);
        }
        const suffix = ["."];
        if (children.This) {
            suffix.push("this");
        }
        else if (children.Identifier) {
            if (children.typeArguments) {
                suffix.push(call(path, print, "typeArguments"));
            }
            suffix.push(call(path, print, "Identifier"));
        }
        else {
            const suffixKey = onlyDefinedKey(children, [
                "templateArgument",
                "unqualifiedClassInstanceCreationExpression"
            ]);
            suffix.push(call(path, print, suffixKey));
        }
        return suffix;
    },
    fqnOrRefType(path, print, _, args) {
        var _a;
        const lastSeparator = (_a = args.lastSeparator) !== null && _a !== void 0 ? _a : "";
        const fqnOrRefType = [
            call(path, print, "fqnOrRefTypePartFirst"),
            ...map(path, partPath => {
                const part = print(partPath);
                return partPath.isLast
                    ? [willBreak(part) ? hardline : lastSeparator, part]
                    : part;
            }, "fqnOrRefTypePartRest")
        ];
        fqnOrRefType.push(indent(fqnOrRefType.pop()));
        return path.node.children.dims
            ? [fqnOrRefType, call(path, print, "dims")]
            : fqnOrRefType;
    },
    fqnOrRefTypePartFirst(path, print) {
        return join(" ", [
            ...map(path, print, "annotation"),
            call(path, print, "fqnOrRefTypePartCommon")
        ]);
    },
    fqnOrRefTypePartRest(path, print) {
        const common = call(path, print, "fqnOrRefTypePartCommon");
        const type = path.node.children.typeArguments
            ? [call(path, print, "typeArguments"), common]
            : common;
        return [".", ...join(" ", [...map(path, print, "annotation"), type])];
    },
    fqnOrRefTypePartCommon(path, print) {
        const { children } = path.node;
        const keywordKey = onlyDefinedKey(children, ["Identifier", "Super"]);
        const keyword = call(path, print, keywordKey);
        return children.typeArguments
            ? [keyword, call(path, print, "typeArguments")]
            : keyword;
    },
    parenthesisExpression(path, print) {
        const expression = call(path, print, "expression");
        const primaryAncestor = path.getNode(4);
        const binaryExpressionAncestor = path.getNode(8);
        const outerAncestor = path.getNode(14);
        const { conditionalExpression, lambdaExpression } = path.node.children.expression[0].children;
        const hasLambda = lambdaExpression !== undefined;
        const hasTernary = (conditionalExpression === null || conditionalExpression === void 0 ? void 0 : conditionalExpression[0].children.QuestionMark) !== undefined;
        const hasSuffix = (primaryAncestor === null || primaryAncestor === void 0 ? void 0 : primaryAncestor.children.primarySuffix) !== undefined;
        const isAssignment = ((outerAncestor === null || outerAncestor === void 0 ? void 0 : outerAncestor.name) === "binaryExpression" &&
            hasAssignmentOperators(outerAncestor)) ||
            (outerAncestor === null || outerAncestor === void 0 ? void 0 : outerAncestor.name) === "variableInitializer";
        if (!hasLambda && hasSuffix && (!hasTernary || isAssignment)) {
            return indentInParentheses(hasTernary ? group(expression) : expression);
        }
        else if (binaryExpressionAncestor &&
            Object.keys(binaryExpressionAncestor.children).length === 1 &&
            outerAncestor &&
            ["guard", "returnStatement"].includes(outerAncestor.name)) {
            return indentInParentheses(group(expression));
        }
        else if (hasTernary && hasSuffix && !isAssignment) {
            return group(["(", expression, softline, ")"]);
        }
        else {
            return group([
                "(",
                hasLambda || hasTernary ? expression : indent(expression),
                ")"
            ]);
        }
    },
    castExpression: printSingle,
    primitiveCastExpression(path, print) {
        return [
            "(",
            call(path, print, "primitiveType"),
            ") ",
            call(path, print, "unaryExpression")
        ];
    },
    referenceTypeCastExpression(path, print) {
        const { children } = path.node;
        const type = call(path, print, "referenceType");
        const cast = children.additionalBound
            ? indentInParentheses(join(line, [type, ...map(path, print, "additionalBound")]))
            : ["(", type, ")"];
        const expressionKey = onlyDefinedKey(children, [
            "lambdaExpression",
            "unaryExpressionNotPlusMinus"
        ]);
        return [cast, " ", call(path, print, expressionKey)];
    },
    newExpression: printSingle,
    unqualifiedClassInstanceCreationExpression(path, print) {
        const { children } = path.node;
        const expression = ["new "];
        if (children.typeArguments) {
            expression.push(call(path, print, "typeArguments"));
        }
        expression.push(call(path, print, "classOrInterfaceTypeToInstantiate"), children.argumentList
            ? group(["(", call(path, print, "argumentList"), ")"])
            : "()");
        if (children.classBody) {
            expression.push(" ", call(path, print, "classBody"));
        }
        return expression;
    },
    classOrInterfaceTypeToInstantiate(path, print) {
        const { children } = path.node;
        const type = children.annotation
            ? flatMap(path, childPath => [
                print(childPath),
                isNonTerminal(childPath.node) ? " " : "."
            ], ["annotation", "Identifier"])
            : printName(path, print);
        if (children.typeArgumentsOrDiamond) {
            type.push(call(path, print, "typeArgumentsOrDiamond"));
        }
        return type;
    },
    typeArgumentsOrDiamond: printSingle,
    diamond() {
        return "<>";
    },
    methodInvocationSuffix(path, print) {
        return path.node.children.argumentList
            ? group(["(", call(path, print, "argumentList"), ")"])
            : indentInParentheses(printDanglingComments(path), { shouldBreak: true });
    },
    argumentList(path, print) {
        var _a, _b, _c, _d;
        const expressions = path.node.children.expression;
        const lastExpression = expressions.at(-1);
        const lastExpressionLambdaBodyExpression = (_b = (_a = lastExpression.children.lambdaExpression) === null || _a === void 0 ? void 0 : _a[0].children.lambdaBody[0].children.expression) === null || _b === void 0 ? void 0 : _b[0].children;
        const lastExpressionLambdaBodyTernaryExpression = (_c = lastExpressionLambdaBodyExpression === null || lastExpressionLambdaBodyExpression === void 0 ? void 0 : lastExpressionLambdaBodyExpression.conditionalExpression) === null || _c === void 0 ? void 0 : _c[0].children;
        const isHuggable = !lastExpression.comments &&
            (!lastExpressionLambdaBodyExpression ||
                (lastExpressionLambdaBodyTernaryExpression === null || lastExpressionLambdaBodyTernaryExpression === void 0 ? void 0 : lastExpressionLambdaBodyTernaryExpression.QuestionMark) !== undefined ||
                ((_d = lastExpressionLambdaBodyTernaryExpression === null || lastExpressionLambdaBodyTernaryExpression === void 0 ? void 0 : lastExpressionLambdaBodyTernaryExpression.binaryExpression) === null || _d === void 0 ? void 0 : _d[0].children.unaryExpression.length) === 1) &&
            expressions.findIndex(({ children }) => children.lambdaExpression) ===
                expressions.length - 1;
        const args = map(path, print, "expression");
        const allArgsExpandable = [
            indent([softline, ...join([",", line], args)]),
            softline
        ];
        if (!isHuggable || willBreak(args.at(-1)[0])) {
            return allArgsExpandable;
        }
        const headArgs = args.slice(0, -1);
        const huggedLastArg = path.call(argPath => print(argPath, { hug: true }), "children", "expression", args.length - 1);
        const lastArgExpanded = join(", ", [
            ...headArgs,
            group(huggedLastArg, { shouldBreak: true })
        ]);
        if (willBreak(huggedLastArg)) {
            return [
                breakParent,
                conditionalGroup([lastArgExpanded, allArgsExpandable])
            ];
        }
        return conditionalGroup([
            join(", ", [...headArgs, huggedLastArg]),
            lastArgExpanded,
            allArgsExpandable
        ]);
    },
    arrayCreationExpression(path, print) {
        const { children } = path.node;
        const typeKey = onlyDefinedKey(children, [
            "classOrInterfaceType",
            "primitiveType"
        ]);
        const suffixKey = onlyDefinedKey(children, [
            "arrayCreationExpressionWithoutInitializerSuffix",
            "arrayCreationWithInitializerSuffix"
        ]);
        return ["new ", call(path, print, typeKey), call(path, print, suffixKey)];
    },
    arrayCreationExpressionWithoutInitializerSuffix(path, print) {
        const expressions = call(path, print, "dimExprs");
        return path.node.children.dims
            ? [expressions, call(path, print, "dims")]
            : expressions;
    },
    arrayCreationWithInitializerSuffix(path, print) {
        return [
            call(path, print, "dims"),
            " ",
            call(path, print, "arrayInitializer")
        ];
    },
    dimExprs(path, print) {
        return map(path, print, "dimExpr");
    },
    dimExpr(path, print) {
        return join(" ", [
            ...map(path, print, "annotation"),
            ["[", call(path, print, "expression"), "]"]
        ]);
    },
    classLiteralSuffix(path, print) {
        const lSquares = map(path, print, "LSquare");
        const rSquares = map(path, print, "RSquare");
        return [
            ...lSquares.flatMap((lSquare, index) => [lSquare, rSquares[index]]),
            ".class"
        ];
    },
    arrayAccessSuffix(path, print) {
        return ["[", call(path, print, "expression"), "]"];
    },
    methodReferenceSuffix(path, print) {
        const { children } = path.node;
        const reference = ["::"];
        if (children.typeArguments) {
            reference.push(call(path, print, "typeArguments"));
        }
        reference.push(call(path, print, onlyDefinedKey(children, ["Identifier", "New"])));
        return reference;
    },
    templateArgument: printSingle,
    template: printSingle,
    stringTemplate(path, print) {
        return printTemplate(path, print, "StringTemplateBegin", "StringTemplateMid", "StringTemplateEnd");
    },
    textBlockTemplate(path, print) {
        return printTemplate(path, print, "TextBlockTemplateBegin", "TextBlockTemplateMid", "TextBlockTemplateEnd");
    },
    embeddedExpression: printSingle,
    pattern: printSingle,
    typePattern: printSingle,
    recordPattern(path, print) {
        const patterns = path.node.children.componentPatternList
            ? indentInParentheses(call(path, print, "componentPatternList"))
            : "()";
        return [call(path, print, "referenceType"), patterns];
    },
    componentPatternList(path, print) {
        return printList(path, print, "componentPattern");
    },
    componentPattern: printSingle,
    matchAllPattern: printSingle,
    guard(path, print) {
        var _a;
        const expression = call(path, print, "expression");
        const hasParentheses = ((_a = path.node.children.expression[0].children.conditionalExpression) === null || _a === void 0 ? void 0 : _a[0].children.binaryExpression[0].children.unaryExpression[0].children.primary[0].children.primaryPrefix[0].children.parenthesisExpression) !==
            undefined;
        return [
            "when ",
            hasParentheses
                ? expression
                : group([
                    ifBreak("("),
                    indent([softline, expression]),
                    softline,
                    ifBreak(")")
                ])
        ];
    }
};
function binary(operands, operators, { hasNonAssignmentOperators = false, isInList = false, isRoot = false, operatorPosition }) {
    let levelOperator;
    let levelPrecedence;
    let level = [];
    while (operators.length) {
        const nextOperator = operators[0].image;
        const nextPrecedence = getOperatorPrecedence(nextOperator);
        if (levelPrecedence === undefined || nextPrecedence === levelPrecedence) {
            const { image: operator, doc: operatorDoc } = operators.shift();
            level.push(operands.shift());
            if (levelOperator !== undefined &&
                needsParentheses(levelOperator, operator)) {
                level = [["(", group(indent(level)), ")"]];
            }
            const parts = [" ", operatorDoc, line];
            if (operatorPosition === "start" && !isAssignmentOperator(operator)) {
                parts.reverse();
            }
            level.push(parts);
            levelOperator = operator;
            levelPrecedence = nextPrecedence;
        }
        else if (nextPrecedence < levelPrecedence) {
            if (!isRoot) {
                break;
            }
            level.push(operands.shift());
            const content = group(indent(level));
            operands.unshift(levelOperator !== undefined &&
                needsParentheses(levelOperator, nextOperator)
                ? ["(", content, ")"]
                : content);
            level = [];
            levelOperator = undefined;
            levelPrecedence = undefined;
        }
        else {
            const content = binary(operands, operators, { operatorPosition });
            operands.unshift(levelOperator !== undefined &&
                needsParentheses(nextOperator, levelOperator)
                ? ["(", group(indent(content)), ")"]
                : group(content));
        }
    }
    level.push(operands.shift());
    if (!levelOperator ||
        (!isInList &&
            !isAssignmentOperator(levelOperator) &&
            levelOperator !== "instanceof")) {
        return level;
    }
    if (!isRoot || hasNonAssignmentOperators) {
        return indent(level);
    }
    const groupId = Symbol("assignment");
    return [
        level[0],
        group(indent(level[1]), { id: groupId }),
        indentIfBreak(level[2], { groupId })
    ];
}
const precedencesByOperator = new Map([
    ["||"],
    ["&&"],
    ["|"],
    ["^"],
    ["&"],
    ["==", "!="],
    ["<", ">", "<=", ">=", "instanceof"],
    ["<<", ">>", ">>>"],
    ["+", "-"],
    ["*", "/", "%"]
].flatMap((operators, index) => operators.map(operator => [operator, index])));
function getOperatorPrecedence(operator) {
    var _a;
    return (_a = precedencesByOperator.get(operator)) !== null && _a !== void 0 ? _a : -1;
}
function needsParentheses(operator, parentOperator) {
    return ((operator === "&&" && parentOperator === "||") ||
        (["|", "^", "&", "<<", ">>", ">>>"].includes(parentOperator) &&
            getOperatorPrecedence(operator) >
                getOperatorPrecedence(parentOperator)) ||
        [operator, parentOperator].every(o => ["==", "!="].includes(o)) ||
        [operator, parentOperator].every(o => ["<<", ">>", ">>>"].includes(o)) ||
        (operator === "*" && parentOperator === "/") ||
        (operator === "/" && parentOperator === "*") ||
        (operator === "%" && ["+", "-", "*", "/"].includes(parentOperator)) ||
        (["*", "/"].includes(operator) && parentOperator === "%"));
}
const assignmentOperators = new Set([
    "=",
    "*=",
    "/=",
    "%=",
    "+=",
    "-=",
    "<<=",
    ">>=",
    ">>>=",
    "&=",
    "^=",
    "|="
]);
function isAssignmentOperator(operator) {
    return assignmentOperators.has(operator);
}
function isCapitalizedIdentifier(fqnOrRefType) {
    var _a, _b, _c;
    const nextToLastIdentifier = (_c = (_b = [
        fqnOrRefType.fqnOrRefTypePartFirst[0],
        ...((_a = fqnOrRefType.fqnOrRefTypePartRest) !== null && _a !== void 0 ? _a : [])
    ].at(-2)) === null || _b === void 0 ? void 0 : _b.children.fqnOrRefTypePartCommon[0].children.Identifier) === null || _c === void 0 ? void 0 : _c[0].image;
    return /^\p{Uppercase_Letter}/u.test(nextToLastIdentifier !== null && nextToLastIdentifier !== void 0 ? nextToLastIdentifier : "");
}
function printTemplate(path, print, beginKey, midKey, endKey) {
    const begin = call(path, ({ node }) => node.image, beginKey);
    const mids = map(path, ({ node }) => node.image, midKey);
    const end = call(path, ({ node }) => node.image, endKey);
    const lines = [begin, ...mids, end].join("").split("\n").slice(1);
    const baseIndent = findBaseIndent(lines);
    const prefix = "\n" + " ".repeat(baseIndent);
    const parts = [begin, ...mids, end].map(image => join(hardline, image.split(prefix)));
    return indent([
        parts[0],
        ...map(path, (expressionPath, index) => {
            const expression = group([
                indent([softline, print(expressionPath), lineSuffixBoundary]),
                softline
            ]);
            return index === 0 ? expression : [parts[index], expression];
        }, "embeddedExpression"),
        parts.at(-1)
    ]);
}
