import { util } from "prettier";
import parser from "./parser.js";
import { isEmptyStatement, isNonTerminal, isTerminal } from "./printers/helpers.js";
const prettierIgnoreRangesByCst = new WeakMap();
export function determinePrettierIgnoreRanges(cst) {
    const { comments } = cst;
    if (!comments) {
        return;
    }
    const ranges = comments
        .filter(({ image }) => /^\/(?:\/\s*(?:prettier-ignore-(?:start|end)|@formatter:(?:off|on))\s*|\*\s*(?:prettier-ignore-(?:start|end)|@formatter:(?:off|on))\s*\*\/)$/.test(image))
        .reduce((ranges, { image, startOffset }) => {
        const previous = ranges.at(-1);
        if (image.includes("start") || image.includes("off")) {
            if ((previous === null || previous === void 0 ? void 0 : previous.end) !== Infinity) {
                ranges.push({ start: startOffset, end: Infinity });
            }
        }
        else if ((previous === null || previous === void 0 ? void 0 : previous.end) === Infinity) {
            previous.end = startOffset;
        }
        return ranges;
    }, new Array());
    prettierIgnoreRangesByCst.set(cst, ranges);
}
export function isFullyBetweenPrettierIgnore(path) {
    var _a;
    const { node, root } = path;
    const start = parser.locStart(node);
    const end = parser.locEnd(node);
    return (((_a = prettierIgnoreRangesByCst
        .get(root)) === null || _a === void 0 ? void 0 : _a.some(range => range.start < start && end < range.end)) === true);
}
export function canAttachComment(node) {
    var _a, _b, _c;
    if (isTerminal(node)) {
        const { name, CATEGORIES } = node.tokenType;
        return (name === "Identifier" ||
            (CATEGORIES === null || CATEGORIES === void 0 ? void 0 : CATEGORIES.find(({ name }) => name === "BinaryOperator")) !== undefined);
    }
    const { children, name } = node;
    switch (name) {
        case "argumentList":
        case "blockStatements":
        case "emptyStatement":
        case "enumBodyDeclarations":
            return false;
        case "annotationInterfaceMemberDeclaration":
        case "classMemberDeclaration":
        case "interfaceMemberDeclaration":
        case "methodBody":
            return !children.Semicolon;
        case "blockStatement":
            return !children.statement || !isEmptyStatement(children.statement[0]);
        case "classBodyDeclaration":
            return !((_a = children.classMemberDeclaration) === null || _a === void 0 ? void 0 : _a[0].children.Semicolon);
        case "recordBodyDeclaration":
            return !((_c = (_b = children.classBodyDeclaration) === null || _b === void 0 ? void 0 : _b[0].children.classMemberDeclaration) === null || _c === void 0 ? void 0 : _c[0].children.Semicolon);
        case "statement":
            return !isEmptyStatement(node);
        case "statementWithoutTrailingSubstatement":
            return !children.emptyStatement;
        default:
            return true;
    }
}
export function handleLineComment(commentNode, _, options) {
    return [
        handleBinaryExpressionComments,
        handleConditionalExpressionComments,
        handleFqnOrRefTypeComments,
        handleIfStatementComments,
        handleJumpStatementComments,
        handleLabeledStatementComments,
        handleNameComments,
        handleTryStatementComments
    ].some(fn => fn(commentNode, options));
}
export function handleRemainingComment(commentNode) {
    return [
        handleFqnOrRefTypeComments,
        handleMethodDeclaratorComments,
        handleNameComments,
        handleJumpStatementComments
    ].some(fn => fn(commentNode));
}
function handleBinaryExpressionComments(commentNode, options) {
    const { enclosingNode, precedingNode, followingNode } = commentNode;
    if ((enclosingNode === null || enclosingNode === void 0 ? void 0 : enclosingNode.name) === "binaryExpression") {
        if (isBinaryOperator(followingNode)) {
            if (options.experimentalOperatorPosition === "start") {
                util.addLeadingComment(followingNode, commentNode);
            }
            else {
                util.addTrailingComment(followingNode, commentNode);
            }
            return true;
        }
        else if (options.experimentalOperatorPosition === "start" &&
            isBinaryOperator(precedingNode)) {
            util.addLeadingComment(precedingNode, commentNode);
            return true;
        }
    }
    return false;
}
function handleConditionalExpressionComments(commentNode) {
    const { startLine, endLine, enclosingNode, precedingNode, followingNode } = commentNode;
    if ((enclosingNode === null || enclosingNode === void 0 ? void 0 : enclosingNode.name) === "conditionalExpression" &&
        precedingNode &&
        followingNode &&
        isNonTerminal(precedingNode) &&
        isNonTerminal(followingNode) &&
        precedingNode.location.endLine < startLine &&
        endLine < followingNode.location.startLine) {
        util.addLeadingComment(followingNode, commentNode);
        return true;
    }
    return false;
}
function handleFqnOrRefTypeComments(commentNode) {
    const { enclosingNode, followingNode } = commentNode;
    if ((enclosingNode === null || enclosingNode === void 0 ? void 0 : enclosingNode.name) === "fqnOrRefType" && followingNode) {
        util.addLeadingComment(followingNode, commentNode);
        return true;
    }
    return false;
}
function handleIfStatementComments(commentNode) {
    const { enclosingNode, precedingNode } = commentNode;
    if ((enclosingNode === null || enclosingNode === void 0 ? void 0 : enclosingNode.name) === "ifStatement" &&
        precedingNode &&
        isNonTerminal(precedingNode) &&
        precedingNode.name === "statement") {
        util.addDanglingComment(enclosingNode, commentNode, undefined);
        return true;
    }
    return false;
}
function handleJumpStatementComments(commentNode) {
    const { enclosingNode, precedingNode, followingNode } = commentNode;
    if (enclosingNode &&
        !precedingNode &&
        !followingNode &&
        ["breakStatement", "continueStatement", "returnStatement"].includes(enclosingNode.name)) {
        util.addTrailingComment(enclosingNode, commentNode);
        return true;
    }
    return false;
}
function handleLabeledStatementComments(commentNode) {
    const { enclosingNode, precedingNode } = commentNode;
    if ((enclosingNode === null || enclosingNode === void 0 ? void 0 : enclosingNode.name) === "labeledStatement" &&
        precedingNode &&
        isTerminal(precedingNode) &&
        precedingNode.tokenType.name === "Identifier") {
        util.addLeadingComment(precedingNode, commentNode);
        return true;
    }
    return false;
}
function handleMethodDeclaratorComments(commentNode) {
    const { enclosingNode } = commentNode;
    if ((enclosingNode === null || enclosingNode === void 0 ? void 0 : enclosingNode.name) === "methodDeclarator" &&
        !enclosingNode.children.receiverParameter &&
        !enclosingNode.children.formalParameterList &&
        enclosingNode.children.LBrace[0].startOffset < commentNode.startOffset &&
        commentNode.startOffset < enclosingNode.children.RBrace[0].startOffset) {
        util.addDanglingComment(enclosingNode, commentNode, undefined);
        return true;
    }
    return false;
}
function handleNameComments(commentNode) {
    const { enclosingNode, precedingNode } = commentNode;
    if (enclosingNode &&
        precedingNode &&
        isTerminal(precedingNode) &&
        precedingNode.tokenType.name === "Identifier" &&
        [
            "ambiguousName",
            "classOrInterfaceTypeToInstantiate",
            "expressionName",
            "moduleDeclaration",
            "moduleName",
            "packageDeclaration",
            "packageName",
            "packageOrTypeName",
            "typeName"
        ].includes(enclosingNode.name)) {
        util.addTrailingComment(precedingNode, commentNode);
        return true;
    }
    return false;
}
function handleTryStatementComments(commentNode) {
    var _a, _b;
    const { enclosingNode, followingNode } = commentNode;
    if (enclosingNode &&
        ["catches", "tryStatement"].includes(enclosingNode.name) &&
        followingNode &&
        isNonTerminal(followingNode)) {
        const block = (_a = (followingNode.name === "catches"
            ? followingNode.children.catchClause[0]
            : followingNode.name === "catchClause" ||
                followingNode.name === "finally"
                ? followingNode
                : null)) === null || _a === void 0 ? void 0 : _a.children.block[0];
        if (!block) {
            return false;
        }
        const blockStatement = (_b = block.children.blockStatements) === null || _b === void 0 ? void 0 : _b[0].children.blockStatement[0];
        if (blockStatement) {
            util.addLeadingComment(blockStatement, commentNode);
        }
        else {
            util.addDanglingComment(block, commentNode, undefined);
        }
        return true;
    }
    return false;
}
function isBinaryOperator(node) {
    var _a;
    return (node !== undefined &&
        (isNonTerminal(node)
            ? node.name === "shiftOperator"
            : (_a = node.tokenType.CATEGORIES) === null || _a === void 0 ? void 0 : _a.some(({ name }) => name === "BinaryOperator")));
}
