function isStyleObject(node) {
    if (!node || !node.properties) return false;
    if (node.type !== "ObjectExpression") return false;
    if (node.properties.length <= 1) return false;

    if (node.parent && node.parent.type === "CallExpression" &&
        node.parent.callee.type === "MemberExpression" &&
        node.parent.callee.object.name === "StyleSheet" &&
        node.parent.callee.property.name === "create") {
        return true;
    }

    if (node.parent && node.parent.type === "Property" &&
        node.parent.parent && node.parent.parent.type === "ObjectExpression" &&
        node.parent.parent.parent && node.parent.parent.parent.type === "CallExpression" &&
        node.parent.parent.parent.callee.type === "MemberExpression" &&
        node.parent.parent.parent.callee.object.name === "StyleSheet") {
        return true;
    }

    const knownStyleKeys = new Set([
        "alignItems",
        "justifyContent",
        "flexDirection",
        "flexWrap",
        "flex",
        "padding",
        "margin",
        "paddingTop",
        "marginTop",
        "paddingBottom",
        "marginBottom",
        "paddingLeft",
        "marginLeft",
        "paddingRight",
        "marginRight",
        "paddingHorizontal",
        "marginHorizontal",
        "paddingVertical",
        "marginVertical",
        "backgroundColor",
        "color",
        "fontSize",
        "fontWeight",
        "lineHeight",
        "textAlign",
        "borderWidth",
        "borderRadius",
        "borderColor",
        "borderStyle",
        "position",
        "top",
        "bottom",
        "left",
        "right",
        "display",
        "width",
        "height",
        "minWidth",
        "minHeight",
        "maxWidth",
        "maxHeight",
        "alignSelf"
    ]);

    let hasStyleKey = false;
    for (const prop of node.properties) {
        if (prop.type === "Property" && prop.key && prop.key.type === "Identifier") {
            if (knownStyleKeys.has(prop.key.name)) {
                hasStyleKey = true;
                break;
            }
        }
    }
    if (hasStyleKey) return true;

    if (node.parent && node.parent.type === "VariableDeclarator" && node.parent.id &&
        (node.parent.id.name === "styles" || node.parent.id.name === "stylesheet" || node.parent.id.name === "style")) {
        return true;
    }

    if (node.parent && (node.parent.type === "TSAsExpression" || node.parent.type === "TypeAssertion")) {
        const typeName = node.parent.typeAnnotation?.typeName?.name;
        if (typeName && (typeName === "ViewStyle" || typeName === "TextStyle" || typeName === "ImageStyle")) {
            return true;
        }
    }

    if (node.parent && node.parent.type === "Property" && node.parent.parent && node.parent.parent.type === "ObjectExpression") {
        if (node.parent.parent.parent && node.parent.parent.parent.type === "VariableDeclarator" && node.parent.parent.parent.id &&
            (node.parent.parent.parent.id.name === "styles" || node.parent.parent.parent.id.name === "stylesheet")) {
            return true;
        }
    }

    return false;
}

module.exports = {
    "ncore-sort-styles": {
        create(context) {
            const sourceCode = context.getSourceCode();

            const shorthandDependencies = {
                borderRadius: [
                    "borderTopLeftRadius",
                    "borderTopRightRadius",
                    "borderBottomLeftRadius",
                    "borderBottomRightRadius",
                    "borderTopStartRadius",
                    "borderTopEndRadius",
                    "borderBottomStartRadius",
                    "borderBottomEndRadius"
                ],
                padding: [
                    "paddingHorizontal",
                    "paddingVertical",
                    "paddingTop",
                    "paddingRight",
                    "paddingBottom",
                    "paddingLeft",
                    "paddingStart",
                    "paddingEnd"
                ],
                borderColor: [
                    "borderTopColor",
                    "borderRightColor",
                    "borderBottomColor",
                    "borderLeftColor",
                    "borderStartColor",
                    "borderEndColor"
                ],
                borderWidth: [
                    "borderTopWidth",
                    "borderRightWidth",
                    "borderBottomWidth",
                    "borderLeftWidth",
                    "borderStartWidth",
                    "borderEndWidth"
                ],
                borderStyle: [
                    "borderTopStyle",
                    "borderRightStyle",
                    "borderBottomStyle",
                    "borderLeftStyle"
                ],
                margin: [
                    "marginHorizontal",
                    "marginVertical",
                    "marginTop",
                    "marginRight",
                    "marginBottom",
                    "marginLeft",
                    "marginStart",
                    "marginEnd"
                ],
                paddingHorizontal: [
                    "paddingLeft",
                    "paddingRight",
                    "paddingStart",
                    "paddingEnd"
                ],
                marginHorizontal: [
                    "marginLeft",
                    "marginRight",
                    "marginStart",
                    "marginEnd"
                ],
                paddingVertical: [
                    "paddingTop",
                    "paddingBottom"
                ],
                marginVertical: [
                    "marginTop",
                    "marginBottom"
                ],
                flex: [
                    "flexGrow",
                    "flexShrink",
                    "flexBasis"
                ]
            };

            function getSortedProperties(properties) {
                const webStyles = [];
                const others = [];
                for (const p of properties) {
                    if (p.type === "SpreadElement" && p.argument.type === "CallExpression" && p.argument.callee.name === "webStyle") {
                        webStyles.push(p);
                    } else {
                        others.push(p);
                    }
                }

                const chunks = [];
                let currentChunk = [];
                const chunkSeparators = [];

                for (const p of others) {
                    if (p.type === "SpreadElement") {
                        chunks.push(currentChunk);
                        chunkSeparators.push(p);
                        currentChunk = [];
                    } else {
                        currentChunk.push(p);
                    }
                }
                chunks.push(currentChunk);

                function sortChunk(chunk) {
                    if (chunk.length <= 1) return chunk;

                    const getLen = (node) => {
                        let maxLen = 0;

                        for (let i = node.loc.start.line - 1; i < node.loc.end.line; i++) {
                            const lineLen = sourceCode.lines[i].trimRight().length;

                            if (lineLen > maxLen) {
                                maxLen = lineLen;
                            }
                        }

                        return maxLen;
                    };

                    const initialSorted = chunk.map((node, i) => ({
                        length: getLen(node),
                        originalIndex: i,
                        node
                    })).sort((a, b) => {
                        if (a.length !== b.length) return b.length - a.length;
                        return a.originalIndex - b.originalIndex;
                    });

                    const propNames = chunk.map(p => {
                        if (p.type === "Property") {
                            if (p.key.type === "Identifier") return p.key.name;
                            if (p.key.type === "Literal") return p.key.value;
                        }
                        return null;
                    });

                    const prerequisites = new Map();
                    for (let i = 0; i < chunk.length; i++) {
                        prerequisites.set(i, new Set());
                    }
                    for (let i = 0; i < propNames.length; i++) {
                        const nameA = propNames[i];
                        if (!nameA) continue;
                        const deps = shorthandDependencies[nameA];
                        if (deps) {
                            for (let j = 0; j < propNames.length; j++) {
                                if (i === j) continue;
                                if (deps.includes(propNames[j])) {
                                    prerequisites.get(j).add(i);
                                }
                            }
                        }
                    }

                    const result = [];
                    const placedIndices = new Set();
                    const pool = [...initialSorted];

                    while (pool.length > 0) {
                        let placedAny = false;
                        for (let i = 0; i < pool.length; i++) {
                            const item = pool[i];
                            const itemOriginalIndex = item.originalIndex;
                            const reqs = prerequisites.get(itemOriginalIndex);

                            let canPlace = true;
                            for (const req of reqs) {
                                if (!placedIndices.has(req)) {
                                    canPlace = false;
                                    break;
                                }
                            }

                            if (canPlace) {
                                result.push(item.node);
                                placedIndices.add(itemOriginalIndex);
                                pool.splice(i, 1);
                                placedAny = true;
                                break;
                            }
                        }
                        if (!placedAny) {
                            const item = pool.shift();
                            result.push(item.node);
                            placedIndices.add(item.originalIndex);
                        }
                    }
                    return result;
                }

                const finalResult = [];
                for (let i = 0; i < chunks.length; i++) {
                    finalResult.push(...sortChunk(chunks[i]));
                    if (i < chunkSeparators.length) {
                        finalResult.push(chunkSeparators[i]);
                    }
                }
                finalResult.push(...webStyles);
                return finalResult;
            }

            return {
                ObjectExpression(node) {
                    if (!isStyleObject(node)) return;

                    const oldOrder = node.properties;
                    if (oldOrder.length <= 1) return;

                    const newOrder = getSortedProperties(oldOrder);

                    let changed = false;
                    for (let i = 0; i < oldOrder.length; i++) {
                        if (oldOrder[i] !== newOrder[i]) {
                            changed = true;
                            break;
                        }
                    }

                    if (changed) {
                        context.report({
                            message: "Style properties should be sorted by length (longest to shortest), with shorthand properties appropriately ordered and spread elements at the bottom.",
                            fix(fixer) {
                                const fixes = [];
                                for (let i = 0; i < oldOrder.length; i++) {
                                    fixes.push(fixer.replaceText(oldOrder[i], sourceCode.getText(newOrder[i])));
                                }
                                return fixes;
                            },
                            node: node
                        });
                    }
                }
            };
        },
        meta: {
            fixable: "code",
            type: "layout",
            schema: []
        }
    },
    "no-jsx-parens-in-return": {
        create(context) {
            return {
                ReturnStatement(node) {
                    if (!node.argument) return;
                    if (node.argument.type === "JSXElement" || node.argument.type === "JSXFragment") {
                        const sourceCode = context.getSourceCode();
                        const returnToken = sourceCode.getFirstToken(node);
                        const firstChildToken = sourceCode.getFirstToken(node.argument);

                        const tokensBetween = sourceCode.getTokensBetween(returnToken, firstChildToken);
                        const openParen = tokensBetween.find(t => t.value === "(");

                        if (openParen) {
                            const closeParen = sourceCode.getTokenAfter(node.argument);
                            if (closeParen && closeParen.value === ")") {
                                context.report({
                                    fix(fixer) {
                                        const fixes = [
                                            fixer.remove(closeParen)
                                        ];

                                        if (returnToken.loc.end.line !== firstChildToken.loc.start.line) {
                                            fixes.push(fixer.replaceTextRange([
                                                returnToken.range[1],
                                                firstChildToken.range[0]
                                            ], " "));
                                        } else {
                                            fixes.push(fixer.remove(openParen));
                                        }

                                        return fixes;
                                    },
                                    message: "Returned JSX element should not be wrapped in parentheses",
                                    node: node.argument
                                });
                            }
                        } else if (returnToken.loc.end.line !== firstChildToken.loc.start.line) {
                            context.report({
                                message: "Returned JSX element must start on the same line as 'return' to avoid Automatic Semicolon Insertion (ASI)",
                                fix(fixer) {
                                    return fixer.replaceTextRange([
                                        returnToken.range[1],
                                        firstChildToken.range[0]
                                    ], " ");
                                },
                                node: node.argument
                            });
                        }
                    }
                },
                ArrowFunctionExpression(node) {
                    if (node.body.type === "JSXElement" || node.body.type === "JSXFragment") {
                        const sourceCode = context.getSourceCode();
                        const arrowToken = sourceCode.getTokenBefore(node.body, t => t.value === "=>");
                        const firstChildToken = sourceCode.getFirstToken(node.body);

                        const tokensBetween = sourceCode.getTokensBetween(arrowToken, firstChildToken);
                        const openParen = tokensBetween.find(t => t.value === "(");

                        if (openParen) {
                            const closeParen = sourceCode.getTokenAfter(node.body);
                            if (closeParen && closeParen.value === ")") {
                                context.report({
                                    message: "Implicitly returned JSX element should not be wrapped in parentheses",
                                    fix(fixer) {
                                        const fixes = [
                                            fixer.remove(closeParen)
                                        ];

                                        if (arrowToken.loc.end.line !== firstChildToken.loc.start.line) {
                                            fixes.push(fixer.replaceTextRange([
                                                arrowToken.range[1],
                                                firstChildToken.range[0]
                                            ], " "));
                                        } else {
                                            fixes.push(fixer.remove(openParen));
                                        }

                                        return fixes;
                                    },
                                    node: node.body
                                });
                            }
                        } else if (arrowToken.loc.end.line !== firstChildToken.loc.start.line) {
                            context.report({
                                message: "Implicitly returned JSX element must start on the same line as '=>'",
                                fix(fixer) {
                                    return fixer.replaceTextRange([
                                        arrowToken.range[1],
                                        firstChildToken.range[0]
                                    ], " ");
                                },
                                node: node.body
                            });
                        }
                    }
                }
            };
        },
        meta: {
            fixable: "code",
            type: "layout",
            schema: []
        }
    },
    "ncore-format-union-types": {
        create(context) {
            const sourceCode = context.getSourceCode();
            return {
                TSUnionType(node) {
                    if (node.types.length < 2) return;
                    if (node.loc.start.line === node.loc.end.line) return;

                    let parentStmt = node.parent;
                    while (parentStmt && parentStmt.type !== "ExportNamedDeclaration" && parentStmt.type !== "TSTypeAliasDeclaration") {
                        parentStmt = parentStmt.parent;
                    }

                    const baseIndentLine = parentStmt ? sourceCode.lines[parentStmt.loc.start.line - 1] : "";
                    const baseIndent = baseIndentLine ? baseIndentLine.match(/^\s*/)[0] : "";
                    const indent = baseIndent + "    ";

                    let hasError = false;

                    const beforeToken = sourceCode.getTokenBefore(node);
                    const firstToken = sourceCode.getFirstToken(node);

                    let leadingPipeToken = null;
                    if (firstToken && firstToken.value === "|") {
                        leadingPipeToken = firstToken;
                    } else if (beforeToken && beforeToken.value === "|") {
                        leadingPipeToken = beforeToken;
                    }

                    const hasLeadingPipe = leadingPipeToken !== null;

                    if (hasLeadingPipe) {
                        const prevToken = sourceCode.getTokenBefore(leadingPipeToken);
                        if (prevToken && prevToken.loc.end.line !== leadingPipeToken.loc.start.line) {
                            hasError = true;
                        }
                        const tokenAfterLeadingPipe = sourceCode.getTokenAfter(leadingPipeToken);
                        if (tokenAfterLeadingPipe && leadingPipeToken.loc.end.line === tokenAfterLeadingPipe.loc.start.line) {
                            hasError = true;
                        }
                    } else {
                        hasError = true;
                    }

                    for (let i = 0; i < node.types.length; i++) {
                        const currentType = node.types[i];
                        if (i < node.types.length - 1) {
                            const pipeToken = sourceCode.getTokenAfter(currentType, token => token.value === "|");
                            if (pipeToken) {
                                if (pipeToken.loc.start.line !== currentType.loc.end.line) {
                                    hasError = true;
                                }

                                const nextType = node.types[i + 1];

                                if (pipeToken.loc.end.line === nextType.loc.start.line) {
                                    hasError = true;
                                }
                            } else {
                                hasError = true;
                            }
                        }
                    }

                    if (hasError) {
                        context.report({
                            fix(fixer) {
                                let expectedText = " |\n";
                                for (let i = 0; i < node.types.length; i++) {
                                    const typeText = sourceCode.getText(node.types[i]);

                                    let formattedText = typeText;
                                    if (node.types[i].type === "TSTypeLiteral" && node.types[i].loc.start.line === node.types[i].loc.end.line) {
                                        const membersText = node.types[i].members.map(m => indent + "    " + sourceCode.getText(m)).join("\n");
                                        formattedText = "{\n" + membersText + "\n" + indent + "}";
                                    }

                                    expectedText += indent + formattedText;
                                    if (i < node.types.length - 1) {
                                        expectedText += " |\n";
                                    }
                                }

                                const replaceStart = leadingPipeToken ? leadingPipeToken.range[0] : node.range[0];
                                const prevToken = sourceCode.getTokenBefore(leadingPipeToken || node);
                                const actualStart = prevToken ? prevToken.range[1] : replaceStart;

                                return fixer.replaceTextRange([
                                    actualStart,
                                    node.range[1]
                                ], expectedText);
                            },
                            message: "Multiline union types should have a leading pipe and trailing pipes at the end of each line.",
                            node
                        });
                    }
                }
            };
        },
        meta: {
            fixable: "code",
            type: "layout",
            schema: []
        }
    },
    "custom-import-order": {
        create(context) {
            const sourceCode = context.getSourceCode();
            const imports = [];

            return {
                "Program:exit"() {
                    if (imports.length <= 1) return;

                    const categorized = categorizeImports(imports, sourceCode);

                    let isSorted = true;
                    let errorNode = null;

                    for (let i = 0; i < imports.length; i++) {
                        if (imports[i] !== categorized[i].node) {
                            isSorted = false;
                            errorNode = imports[i];
                            break;
                        }
                    }

                    if (!isSorted) {
                        context.report({
                            message: "Imports are not sorted correctly according to custom rules.",
                            fix(fixer) {
                                return fixImportOrder(fixer, imports, categorized, sourceCode);
                            },
                            node: errorNode
                        });
                    }
                },
                ImportDeclaration(node) {
                    imports.push(node);
                }
            };

            function getImportCategory(node, sourceCode) {
                const source = node.source.value;
                const code = sourceCode.getText(node);

                if (source === "react") return 1;

                if (source === "react-native") return 2;

                if (source.startsWith("./")) {
                    if (code.includes("import type") || source.includes(".types") || source.includes("/type")) {
                        return 3.1;
                    }

                    if (source.includes("style") || source.includes("Style")) {
                        return 3.2;
                    }

                    return 3.3;
                }

                const specifiers = node.specifiers
                    .filter(s => s.type === "ImportSpecifier")
                    .map(s => s.imported ? s.imported.name : "");

                const hasNCoreHook = specifiers.some(name => name.startsWith("NCore"));
                const hasUseHook = specifiers.some(name => name.startsWith("use"));

                if (hasNCoreHook) return 4.1;
                if (hasUseHook) return 4.2;

                if (code.includes("import type") || source.includes("/types/") || source.includes("/type") || source.includes(".types")) {
                    return 5;
                }

                if (source.startsWith("@/") || source.startsWith("~/")) return 6.3;
                if (!source.startsWith(".")) return 6.1;
                if (source.startsWith("../")) return 6.2;

                return 6.4;
            }

            function categorizeImports(imports, sourceCode) {
                return imports.map(imp => {
                    const isDefaultImport = imp.specifiers.some(s => s.type === "ImportDefaultSpecifier");
                    const textLength = sourceCode.getText(imp).replace(/\s+/g, "").length;
                    return {
                        category: getImportCategory(imp, sourceCode),
                        source: imp.source.value,
                        isDefaultImport,
                        textLength,
                        node: imp
                    };
                }).sort((a, b) => {
                    if (a.category !== b.category) {
                        return a.category - b.category;
                    }

                    if (a.category >= 6) {
                        if (a.isDefaultImport !== b.isDefaultImport) {
                            return a.isDefaultImport ? 1 : -1;
                        }

                        if (a.textLength !== b.textLength) {
                            return b.textLength - a.textLength;
                        }
                    }

                    return a.source.localeCompare(b.source);
                });
            }

            function fixImportOrder(fixer, imports, categorized, sourceCode) {
                const firstImport = imports[0];
                const lastImport = imports[imports.length - 1];

                const rangeStart = firstImport.range[0];
                const rangeEnd = lastImport.range[1];

                let newImports = "";

                categorized.forEach((imp, index) => {
                    newImports += sourceCode.getText(imp.node);

                    if (index < categorized.length - 1) {
                        newImports += "\n";
                    }
                });

                return fixer.replaceTextRange([
                    rangeStart,
                    rangeEnd
                ], newImports);
            }
        },
        meta: {
            type: "suggestion",
            fixable: "code",
            schema: []
        }
    },
    "sort-objects-by-length": {
        create(context) {
            const sourceCode = context.getSourceCode();

            function sortGroup(group) {
                const getLen = (n) => {
                    let maxLen = 0;
                    for (let i = n.loc.start.line - 1; i < n.loc.end.line; i++) {
                        const lineLen = sourceCode.lines[i].trimRight().length;
                        if (lineLen > maxLen) {
                            maxLen = lineLen;
                        }
                    }
                    return maxLen;
                };

                const sortedGroup = [...group].sort((a, b) => {
                    const lenA = getLen(a);
                    const lenB = getLen(b);

                    if (lenA !== lenB) {
                        return lenB - lenA;
                    }

                    return sourceCode.getText(a).localeCompare(sourceCode.getText(b));
                });

                let isSorted = true;

                for (let i = 0; i < group.length; i++) {
                    if (group[i] !== sortedGroup[i]) {
                        isSorted = false;

                        break;
                    }
                }

                if (!isSorted) {
                    context.report({
                        message: "Object properties must be sorted from longest to shortest string length.",
                        fix(fixer) {
                            const fixes = [];
                            const texts = sortedGroup.map(n => sourceCode.getText(n));

                            for (let i = 0; i < group.length; i++) {
                                fixes.push(fixer.replaceText(group[i], texts[i]));
                            }

                            return fixes;
                        },
                        node: group[0]
                    });
                }
            }

            function checkProperties(node) {
                if (isStyleObject(node)) return;

                let properties = node.properties || node.members;

                if (!properties && node.body && Array.isArray(node.body)) {
                    properties = node.body;
                }

                if (!properties || !Array.isArray(properties) || properties.length < 2) return;

                const groups = [];
                let currentGroup = [];

                for (let i = 0; i < properties.length; i++) {
                    const prop = properties[i];

                    if (prop && (prop.type === "Property" || prop.type === "TSPropertySignature" || prop.type === "TSMethodSignature")) {
                        currentGroup.push(prop);
                    } else {
                        if (currentGroup.length > 1) groups.push(currentGroup);

                        currentGroup = [];
                    }
                }

                if (currentGroup.length > 1) groups.push(currentGroup);

                groups.forEach(sortGroup);
            }

            return {
                ObjectExpression: checkProperties,
                ObjectPattern: checkProperties
            };
        },
        meta: {
            type: "suggestion",
            fixable: "code",
            schema: []
        }
    },
    "no-newline-after-jsx": {
        create(context) {
            function checkPunctuation(node) {
                const sourceCode = context.getSourceCode();
                const lastToken = sourceCode.getLastToken(node);
                const nextToken = sourceCode.getTokenAfter(node);

                if (nextToken && (nextToken.value === ";" || nextToken.value === "," || nextToken.value === ")")) {
                    if (lastToken.loc.end.line !== nextToken.loc.start.line) {
                        if (sourceCode.commentsExistBetween(node, nextToken)) return;

                        context.report({
                            message: `Punctuation '${nextToken.value}' should be on the same line as the preceding JSX element`,
                            fix(fixer) {
                                return fixer.replaceTextRange([
                                    node.range[1],
                                    nextToken.range[0]
                                ], "");
                            },
                            node: nextToken
                        });
                    }
                }
            }

            return {
                JSXFragment: checkPunctuation,
                JSXElement: checkPunctuation
            };
        },
        meta: {
            fixable: "whitespace",
            type: "layout",
            schema: []
        }
    },
    "multiline-import-specifiers": {
        create(context) {
            return {
                ImportDeclaration(node) {
                    const specifiers = node.specifiers.filter(
                        s => s.type === "ImportSpecifier"
                    );

                    if (specifiers.length === 0) return;

                    const sourceCode = context.getSourceCode();
                    const openBrace = sourceCode.getFirstToken(node, t => t.value === "{");
                    const closeBrace = sourceCode.getLastToken(node, t => t.value === "}");

                    if (!openBrace || !closeBrace) return;

                    const firstSpecifier = specifiers[0];
                    if (firstSpecifier.loc.start.line - openBrace.loc.end.line !== 1) {
                        context.report({
                            message: "There should be exactly one newline after '{'.",
                            fix(fixer) {
                                return fixer.replaceTextRange([
                                    openBrace.range[1],
                                    firstSpecifier.range[0]
                                ], "\n    ");
                            },
                            node: firstSpecifier
                        });
                    }

                    for (let i = 0; i < specifiers.length - 1; i++) {
                        const current = specifiers[i];
                        const next = specifiers[i + 1];

                        if (next.loc.start.line - current.loc.end.line !== 1) {
                            context.report({
                                message: "Each import specifier should be on a new line without blank lines between them.",
                                fix(fixer) {
                                    const comma = sourceCode.getTokenBefore(next);
                                    return fixer.replaceTextRange(
                                        [
                                            comma.range[1],
                                            next.range[0]
                                        ],
                                        "\n    "
                                    );
                                },
                                node: next
                            });
                        }
                    }

                    const lastSpecifier = specifiers[specifiers.length - 1];
                    const tokenAfter = sourceCode.getTokenAfter(lastSpecifier);

                    if (tokenAfter && tokenAfter.value === ",") {
                        context.report({
                            message: "No trailing comma in imports",
                            fix(fixer) {
                                return fixer.remove(tokenAfter);
                            },
                            node: lastSpecifier
                        });
                    }

                    const tokenBeforeClose = sourceCode.getTokenBefore(closeBrace);
                    if (closeBrace.loc.start.line - tokenBeforeClose.loc.end.line !== 1) {
                        context.report({
                            message: "There should be exactly one newline before '}'.",
                            fix(fixer) {
                                return fixer.replaceTextRange([
                                    tokenBeforeClose.range[1],
                                    closeBrace.range[0]
                                ], "\n");
                            },
                            node: closeBrace
                        });
                    }
                }
            };
        },
        meta: {
            fixable: "whitespace",
            type: "layout",
            schema: []
        }
    },
    "no-static-inline-styles": {
        create(context) {
            function checkProperty(propertyNode) {
                if (!propertyNode || propertyNode.type !== "Property") return;

                const keyName = propertyNode.key && propertyNode.key.name;
                const allowedStaticProps = new Set([
                    "alignSelf",
                    "textAlign",
                    "display",
                    "position",
                    "flexDirection",
                    "justifyContent",
                    "alignItems",
                    "flexWrap",
                    "overflow",
                    "textDecorationLine",
                    "textTransform",
                    "fontStyle",
                    "fontWeight",
                    "resizeMode",
                    "lineHeight",
                    "flex",
                    "flexGrow",
                    "flexShrink"
                ]);
                if (keyName && allowedStaticProps.has(keyName)) {
                    return;
                }

                const value = propertyNode.value;
                if (!value) return;

                if (value.type === "Literal" && value.value === "transparent") {
                    return;
                }

                let isStatic = false;

                if (value.type === "Literal") {
                    if (typeof value.value === "string" || typeof value.value === "number") {
                        isStatic = true;
                    }
                } else if (value.type === "UnaryExpression" && value.operator === "-") {
                    if (value.argument && value.argument.type === "Literal" && typeof value.argument.value === "number") {
                        isStatic = true;
                    }
                } else if (value.type === "TemplateLiteral" && value.expressions.length === 0) {
                    isStatic = true;
                }

                if (isStatic) {
                    context.report({
                        messageId: "staticStyle",
                        node: propertyNode
                    });
                }
            }

            function checkObjectExpression(node) {
                if (node.type !== "ObjectExpression") return;
                node.properties.forEach(checkProperty);
            }

            function checkStyleValue(node) {
                if (!node) return;

                if (node.type === "ObjectExpression") {
                    checkObjectExpression(node);
                } else if (node.type === "ArrayExpression") {
                    node.elements.forEach(element => {
                        if (element && element.type === "ObjectExpression") {
                            checkObjectExpression(element);
                        }
                    });
                }
            }

            return {
                VariableDeclarator(node) {
                    if (node.id && node.id.name === "styles" && node.init && node.init.type === "ObjectExpression") {
                        let parent = node.parent;
                        let isInsideUseStyles = false;
                        while (parent) {
                            if (parent.type === "VariableDeclarator" && parent.id && parent.id.name === "useStyles") {
                                isInsideUseStyles = true;
                                break;
                            }
                            if (parent.type === "FunctionDeclaration" && parent.id && parent.id.name === "useStyles") {
                                isInsideUseStyles = true;
                                break;
                            }
                            parent = parent.parent;
                        }

                        if (isInsideUseStyles) {
                            node.init.properties.forEach(styleClass => {
                                let styleObj = styleClass.value;
                                if (styleObj && styleObj.type === "TSAsExpression") {
                                    styleObj = styleObj.expression;
                                }
                                if (styleObj && styleObj.type === "ObjectExpression") {
                                    styleObj.properties.forEach(prop => {
                                        checkProperty(prop);
                                    });
                                }
                            });
                        }
                    }
                },
                JSXAttribute(node) {
                    const attrName = node.name.name;
                    if (typeof attrName !== "string") return;

                    if (attrName === "style" || attrName.endsWith("Style")) {
                        if (node.value && node.value.type === "JSXExpressionContainer") {
                            checkStyleValue(node.value.expression);
                        }
                    }
                }
            };
        },
        meta: {
            messages: {
                staticStyle: "Static inline styles are not allowed. Please move them to a stylesheet file."
            },
            type: "suggestion",
            schema: []
        }
    },
    "sort-jsx-props": {
        create(context) {
            return {
                JSXOpeningElement(node) {
                    const attributes = node.attributes;
                    if (!attributes || attributes.length <= 1) return;

                    const sourceCode = context.getSourceCode();

                    const getAttrLength = (attr) => {
                        let maxLen = 0;

                        for (let i = attr.loc.start.line - 1; i < attr.loc.end.line; i++) {
                            const lineLen = sourceCode.lines[i].trimRight().length;

                            if (lineLen > maxLen) {
                                maxLen = lineLen;
                            }
                        }

                        return maxLen;
                    };

                    const groups = [];
                    let currentGroup = [];

                    for (const attr of attributes) {
                        if (attr.type === "JSXSpreadAttribute") {
                            if (currentGroup.length > 1) {
                                groups.push(currentGroup);
                            }

                            currentGroup = [];
                        } else {
                            currentGroup.push(attr);
                        }
                    }
                    if (currentGroup.length > 1) {
                        groups.push(currentGroup);
                    }

                    for (const group of groups) {
                        const sortedGroup = [...group].sort((a, b) => {
                            return getAttrLength(b) - getAttrLength(a);
                        });

                        let isSorted = true;

                        for (let i = 0; i < group.length; i++) {
                            if (group[i] !== sortedGroup[i]) {
                                isSorted = false;
                                break;
                            }
                        }

                        if (!isSorted) {
                            context.report({
                                fix(fixer) {
                                    const firstAttr = group[0];
                                    const startLoc = firstAttr.loc.start;
                                    const indent = " ".repeat(startLoc.column);

                                    const newText = sortedGroup.map(attr => sourceCode.getText(attr)).join("\n" + indent);

                                    const start = group[0].range[0];
                                    const end = group[group.length - 1].range[1];

                                    return fixer.replaceTextRange([
                                        start,
                                        end
                                    ], newText);
                                },
                                message: "JSX attributes should be sorted from longest to shortest.",
                                node: group[0]
                            });
                        }
                    }
                }
            };
        },
        meta: {
            fixable: "code",
            type: "layout",
            schema: []
        }
    },
    "sort-translations": {
        create(context) {
            const sourceCode = context.getSourceCode();

            function checkObject(node) {
                if (!node.properties || node.properties.length <= 1) return;

                let hasIsRTL = false;
                let hasLocale = false;
                let translationsProp = null;

                for (const prop of node.properties) {
                    const keyNode = prop.key;
                    if (!keyNode) continue;

                    let keyName = "";
                    if (keyNode.type === "Identifier") keyName = keyNode.name;
                    else if (keyNode.type === "Literal" || keyNode.type === "JSONLiteral") keyName = keyNode.value;

                    if (keyName === "isRTL") hasIsRTL = true;
                    if (keyName === "locale") hasLocale = true;
                    if (keyName === "translations") translationsProp = prop;
                }

                if (hasIsRTL && hasLocale && translationsProp) {
                    const translationsObj = translationsProp.value;
                    if (!translationsObj || !translationsObj.properties || translationsObj.properties.length <= 1) return;

                    const props = translationsObj.properties;

                    const sortedProps = [...props].sort((a, b) => {
                        const lenA = sourceCode.getText(a.key).length + sourceCode.getText(a.value).length;
                        const lenB = sourceCode.getText(b.key).length + sourceCode.getText(b.value).length;

                        if (lenA !== lenB) {
                            return lenB - lenA;
                        }

                        const keyA = a.key.value || a.key.name || "";
                        const keyB = b.key.value || b.key.name || "";
                        return keyA.localeCompare(keyB);
                    });

                    let isSorted = true;
                    let errorNode = null;
                    for (let i = 0; i < props.length; i++) {
                        if (props[i] !== sortedProps[i]) {
                            isSorted = false;
                            errorNode = props[i];
                            break;
                        }
                    }

                    if (!isSorted) {
                        context.report({
                            message: "Translations keys must be sorted by length (longest to shortest).",
                            fix(fixer) {
                                const firstProp = props[0];
                                const lastProp = props[props.length - 1];

                                const startLoc = firstProp.loc.start;
                                const indent = " ".repeat(startLoc.column);

                                let newText = "";
                                sortedProps.forEach((p, index) => {
                                    newText += sourceCode.getText(p);
                                    if (index < sortedProps.length - 1) {
                                        newText += ",\n" + indent;
                                    }
                                });

                                return fixer.replaceTextRange([
                                    firstProp.range[0],
                                    lastProp.range[1]
                                ], newText);
                            },
                            node: errorNode
                        });
                    }
                }
            }

            return {
                JSONObjectExpression: checkObject,
                ObjectExpression: checkObject
            };
        },
        meta: {
            fixable: "code",
            type: "layout",
            schema: []
        }
    },
    "sort-import-specifiers": {
        create(context) {
            return {
                ExportNamedDeclaration(node) {
                    checkSpecifiers(node, "ExportSpecifier", "Export");
                },
                ImportDeclaration(node) {
                    checkSpecifiers(node, "ImportSpecifier", "Import");
                }
            };

            function checkSpecifiers(node, specifierType, typeName) {
                if (!node.specifiers) return;
                const specifiers = node.specifiers.filter(
                    s => s.type === specifierType
                );

                if (specifiers.length <= 1) return;

                const sourceCode = context.getSourceCode();

                const getSpecLength = (spec) => {
                    return sourceCode.getText(spec).replace(/\s+/g, "").length;
                };

                const sortedSpecifiers = [...specifiers].sort((a, b) => {
                    return getSpecLength(b) - getSpecLength(a);
                });

                let isSorted = true;
                for (let i = 0; i < specifiers.length; i++) {
                    if (specifiers[i] !== sortedSpecifiers[i]) {
                        isSorted = false;
                        break;
                    }
                }

                if (!isSorted) {
                    context.report({
                        fix(fixer) {
                            const firstSpec = specifiers[0];
                            const lastSpec = specifiers[specifiers.length - 1];

                            const startLoc = firstSpec.loc.start;

                            let indent = "    ";
                            if (firstSpec.loc.start.line !== node.loc.start.line) {
                                indent = " ".repeat(startLoc.column);
                            }

                            const newText = sortedSpecifiers.map(spec => sourceCode.getText(spec)).join(",\n" + indent);

                            return fixer.replaceTextRange([
                                firstSpec.range[0],
                                lastSpec.range[1]
                            ], newText);
                        },
                        message: `${typeName} specifiers should be sorted from longest to shortest.`,
                        node
                    });
                }
            }
        },
        meta: {
            fixable: "code",
            type: "layout",
            schema: []
        }
    },
    "jsx-ternary-formatting": {
        create(context) {
            return {
                JSXExpressionContainer(node) {
                    if (node.expression.type !== "ConditionalExpression") return;

                    const sourceCode = context.getSourceCode();
                    const openBrace = sourceCode.getFirstToken(node);
                    const closeBrace = sourceCode.getLastToken(node);
                    const cond = node.expression;

                    let hasJSX = false;
                    if (
                        cond.consequent.type === "JSXElement" ||
                        cond.consequent.type === "JSXFragment" ||
                        cond.alternate.type === "JSXElement" ||
                        cond.alternate.type === "JSXFragment"
                    ) {
                        hasJSX = true;
                    }

                    if (!hasJSX) return;

                    const checkAndRemoveParens = (nodeToUnparen) => {
                        const currentToken = sourceCode.getTokenBefore(nodeToUnparen);
                        const afterToken = sourceCode.getTokenAfter(nodeToUnparen);
                        let removed = false;
                        while (currentToken && currentToken.value === "(" && afterToken && afterToken.value === ")") {
                            context.report({
                                message: "Parentheses are not allowed around JSX in ternaries.",
                                fix(fixer) {
                                    return [
                                        fixer.remove(currentToken),
                                        fixer.remove(afterToken)
                                    ];
                                },
                                node: currentToken
                            });
                            removed = true;

                            break;
                        }
                        return removed;
                    };

                    let hasParens = false;
                    if (cond.consequent.type === "JSXElement" || cond.consequent.type === "JSXFragment") {
                        if (checkAndRemoveParens(cond.consequent)) hasParens = true;
                    }
                    if (cond.alternate.type === "JSXElement" || cond.alternate.type === "JSXFragment") {
                        if (checkAndRemoveParens(cond.alternate)) hasParens = true;
                    }

                    if (hasParens) return;

                    const questionToken = sourceCode.getTokenAfter(cond.test, {
                        filter: t => t.value === "?"
                    });
                    const colonToken = sourceCode.getTokenAfter(cond.consequent, {
                        filter: t => t.value === ":"
                    });

                    const getIndent = (token) => {
                        const line = sourceCode.lines[token.loc.start.line - 1];
                        return line.match(/^\s*/)[0];
                    };

                    const openIndent = getIndent(openBrace);
                    const baseIndent = openIndent + "    ";
                    const innerIndent = baseIndent + "    ";

                    const checkAndFix = (token1, token2, expectedWhitespace) => {
                        let textBetween = sourceCode.getText().slice(token1.range[1], token2.range[0]);

                        textBetween = textBetween.replace(/\r\n/g, "\n");
                        if (textBetween !== expectedWhitespace) {
                            context.report({
                                message: "Incorrect ternary formatting",
                                fix(fixer) {
                                    return fixer.replaceTextRange([
                                        token1.range[1],
                                        token2.range[0]
                                    ], expectedWhitespace);
                                },
                                node: token2
                            });
                        }
                    };

                    const firstTestToken = sourceCode.getTokenAfter(openBrace);
                    checkAndFix(openBrace, firstTestToken, "\n" + baseIndent);

                    const firstConsequentToken = sourceCode.getTokenAfter(questionToken);
                    checkAndFix(questionToken, firstConsequentToken, "\n" + innerIndent);

                    const firstColonToken = colonToken;
                    const lastConsequentToken = sourceCode.getTokenBefore(colonToken);
                    checkAndFix(lastConsequentToken, firstColonToken, "\n" + baseIndent);

                    const firstAlternateToken = sourceCode.getTokenAfter(colonToken);
                    checkAndFix(colonToken, firstAlternateToken, "\n" + innerIndent);

                    const lastAlternateToken = sourceCode.getTokenBefore(closeBrace);
                    checkAndFix(lastAlternateToken, closeBrace, "\n" + openIndent);
                }
            };
        },
        meta: {
            fixable: "whitespace",
            type: "layout",
            schema: []
        }
    },
    "multiline-jsx-children": {
        create(context) {
            return {
                JSXElement(node) {
                    if (!node.closingElement) return;
                    if (node.children.length === 0) return;

                    const meaningfulChildren = node.children.filter(child => {
                        if (child.type === "JSXText" && child.value.trim() === "") return false;
                        return true;
                    });

                    if (meaningfulChildren.length === 0) return;

                    const openingElement = node.openingElement;
                    const closingElement = node.closingElement;
                    const isMultilineOpening = openingElement.loc.start.line !== openingElement.loc.end.line;

                    if (!isMultilineOpening) return;

                    const sourceCode = context.getSourceCode();

                    if (meaningfulChildren.length === 1 && meaningfulChildren[0].type === "JSXText") {
                        const child = meaningfulChildren[0];
                        const text = sourceCode.getText(child);
                        const hasLeadingNewline = /^\s*\n/.test(text);
                        const hasTrailingNewline = /\n\s*$/.test(text);

                        if (!hasLeadingNewline || !hasTrailingNewline) {
                            context.report({
                                fix(fixer) {
                                    const openBracketLine = sourceCode.lines[openingElement.loc.start.line - 1];
                                    const baseIndent = openBracketLine.match(/^\s*/)[0];
                                    const indent = baseIndent + "    ";
                                    return fixer.replaceText(child, "\n" + indent + text.trim() + "\n" + baseIndent);
                                },
                                message: "Multiline JSX element text content must be on its own line",
                                node: child
                            });
                        }
                        return;
                    }

                    const firstChild = meaningfulChildren[0];
                    const openingEndLine = openingElement.loc.end.line;

                    let needsFirstChildFix = false;
                    if (firstChild.type === "JSXText") {
                        const text = sourceCode.getText(firstChild);
                        if (!/^\s*\n/.test(text)) needsFirstChildFix = true;
                    } else if (firstChild.loc.start.line === openingEndLine) {
                        needsFirstChildFix = true;
                    }

                    if (needsFirstChildFix) {
                        context.report({
                            fix(fixer) {
                                const openBracketLine = sourceCode.lines[openingElement.loc.start.line - 1];
                                const baseIndent = openBracketLine.match(/^\s*/)[0];
                                const indent = baseIndent + "    ";

                                if (firstChild.type === "JSXText") {
                                    const text = sourceCode.getText(firstChild);
                                    return fixer.replaceText(firstChild, "\n" + indent + text.replace(/^\s+/, ""));
                                }

                                return fixer.insertTextBefore(firstChild, "\n" + indent);
                            },
                            message: "First child of a multiline JSX element must be on a new line",
                            node: firstChild
                        });
                    }

                    const lastChild = meaningfulChildren[meaningfulChildren.length - 1];
                    let needsLastChildFix = false;

                    if (lastChild.type === "JSXText") {
                        const text = sourceCode.getText(lastChild);
                        if (!/\n\s*$/.test(text)) needsLastChildFix = true;
                    } else if (lastChild.loc.end.line === closingElement.loc.start.line) {
                        needsLastChildFix = true;
                    }

                    if (needsLastChildFix) {
                        context.report({
                            fix(fixer) {
                                const openBracketLine = sourceCode.lines[openingElement.loc.start.line - 1];
                                const baseIndent = openBracketLine.match(/^\s*/)[0];

                                if (lastChild.type === "JSXText") {
                                    const text = sourceCode.getText(lastChild);
                                    return fixer.replaceText(lastChild, text.replace(/\s+$/, "") + "\n" + baseIndent);
                                }

                                return fixer.insertTextBefore(closingElement, "\n" + baseIndent);
                            },
                            message: "Closing tag of a multiline JSX element must be on a new line",
                            node: closingElement
                        });
                    }
                }
            };
        },
        meta: {
            fixable: "whitespace",
            type: "layout",
            schema: []
        }
    },
    "ncore-sort-types": {
        create(context) {
            const sourceCode = context.getSourceCode();

            function sortMembers(node, members) {
                if (!members || members.length <= 1) return;

                const getLen = (n) => {
                    let maxLen = 0;
                    for (let i = n.loc.start.line - 1; i < n.loc.end.line; i++) {
                        const lineLen = sourceCode.lines[i].trimRight().length;
                        if (lineLen > maxLen) {
                            maxLen = lineLen;
                        }
                    }
                    return maxLen;
                };

                const initialSorted = members.map((member, i) => ({
                    length: getLen(member),
                    originalIndex: i,
                    node: member
                })).sort((a, b) => {
                    if (a.length !== b.length) return b.length - a.length;
                    return a.originalIndex - b.originalIndex;
                });

                let changed = false;
                for (let i = 0; i < members.length; i++) {
                    if (members[i] !== initialSorted[i].node) {
                        changed = true;
                        break;
                    }
                }

                if (changed) {
                    context.report({
                        fix(fixer) {
                            const fixes = [];
                            for (let i = 0; i < members.length; i++) {
                                fixes.push(fixer.replaceText(members[i], sourceCode.getText(initialSorted[i].node)));
                            }
                            return fixes;
                        },
                        message: "Type properties should be sorted by length (longest to shortest).",
                        node: node
                    });
                }
            }

            return {
                TSTypeLiteral(node) {
                    sortMembers(node, node.members);
                },
                TSInterfaceBody(node) {
                    sortMembers(node, node.body);
                }
            };
        },
        meta: {
            fixable: "code",
            type: "layout",
            schema: []
        }
    },
    "multiline-array-elements": {
        create(context) {
            const sourceCode = context.getSourceCode();

            return {
                "ArrayExpression, ArrayPattern"(node) {
                    if (node.elements.length === 0) return;

                    const openBracket = sourceCode.getFirstToken(node);
                    const closingBracket = sourceCode.getLastToken(node);

                    if (openBracket.loc.start.line === closingBracket.loc.end.line) {
                        let isJSXProp = false;
                        let current = node;

                        while (current.parent) {
                            if (current.parent.type === "JSXExpressionContainer") {
                                isJSXProp = true;
                                break;
                            }

                            current = current.parent;
                        }

                        if (!isJSXProp) return;
                    }

                    const firstElement = node.elements[0];

                    if (firstElement && openBracket.loc.end.line !== firstElement.loc.start.line - 1) {
                        context.report({
                            fix(fixer) {
                                const openBracketLine = sourceCode.lines[openBracket.loc.start.line - 1];
                                const baseIndent = openBracketLine.match(/^\s*/)[0];
                                const indent = baseIndent + "    ";

                                return fixer.replaceTextRange(
                                    [
                                        openBracket.range[1],
                                        firstElement.range[0]
                                    ],
                                    "\n" + indent
                                );
                            },
                            message: "First array element should be exactly on the next line",
                            node: firstElement
                        });
                    }

                    for (let i = 0; i < node.elements.length - 1; i++) {
                        const current = node.elements[i];
                        const next = node.elements[i + 1];

                        if (!current || !next) continue;

                        if (current.loc.end.line !== next.loc.start.line - 1) {
                            context.report({
                                message: "Each array element should be exactly on the next line (no empty lines)",
                                fix(fixer) {
                                    const openBracketLine = sourceCode.lines[openBracket.loc.start.line - 1];
                                    const baseIndent = openBracketLine.match(/^\s*/)[0];
                                    const indent = baseIndent + "    ";

                                    const comma = sourceCode.getTokenBefore(next);

                                    return fixer.replaceTextRange(
                                        [
                                            comma.range[1],
                                            next.range[0]
                                        ],
                                        "\n" + indent
                                    );
                                },
                                node: next
                            });
                        }
                    }

                    const lastElement = node.elements[node.elements.length - 1];

                    if (lastElement && lastElement.loc.end.line !== closingBracket.loc.start.line - 1) {
                        context.report({
                            fix(fixer) {
                                const openBracketLine = sourceCode.lines[openBracket.loc.start.line - 1];
                                const baseIndent = openBracketLine.match(/^\s*/)[0];

                                const tokenAfter = sourceCode.getTokenAfter(lastElement);
                                const hasTrailingComma = tokenAfter && tokenAfter.value === ",";
                                const startRange = hasTrailingComma ? tokenAfter.range[1] : lastElement.range[1];

                                return fixer.replaceTextRange(
                                    [
                                        startRange,
                                        closingBracket.range[0]
                                    ],
                                    "\n" + baseIndent
                                );
                            },
                            message: "Array closing bracket should be exactly on the next line",
                            node: closingBracket
                        });
                    }

                    const tokenAfter = sourceCode.getTokenAfter(lastElement);

                    if (tokenAfter && tokenAfter.value === "," && tokenAfter.range[1] <= closingBracket.range[0]) {
                        context.report({
                            message: "No trailing comma in arrays",
                            fix(fixer) {
                                return fixer.remove(tokenAfter);
                            },
                            node: lastElement
                        });
                    }
                }
            };
        },
        meta: {
            fixable: "whitespace",
            type: "layout",
            schema: []
        }
    },
    "multiline-jsx-attributes": {
        create(context) {
            const sourceCode = context.getSourceCode();

            return {
                JSXOpeningElement(node) {
                    if (node.attributes.length === 0) return;

                    const firstToken = sourceCode.getFirstToken(node);
                    const tagNameToken = sourceCode.getTokenAfter(firstToken);
                    const firstAttr = node.attributes[0];

                    if (tagNameToken.loc.end.line !== firstAttr.loc.start.line - 1) {
                        context.report({
                            fix(fixer) {
                                const openBraceLine = sourceCode.lines[firstToken.loc.start.line - 1];
                                const baseIndent = openBraceLine.match(/^\s*/)[0];
                                const indent = baseIndent + "    ";

                                return fixer.replaceTextRange(
                                    [
                                        tagNameToken.range[1],
                                        firstAttr.range[0]
                                    ],
                                    "\n" + indent
                                );
                            },
                            message: "First JSX attribute should be exactly on the next line",
                            node: firstAttr
                        });
                    }

                    for (let i = 0; i < node.attributes.length - 1; i++) {
                        const current = node.attributes[i];
                        const next = node.attributes[i + 1];

                        if (current.loc.end.line !== next.loc.start.line - 1) {
                            context.report({
                                message: "Each JSX attribute should be exactly on the next line (no empty lines)",
                                fix(fixer) {
                                    const openBraceLine = sourceCode.lines[firstToken.loc.start.line - 1];
                                    const baseIndent = openBraceLine.match(/^\s*/)[0];
                                    const indent = baseIndent + "    ";

                                    return fixer.replaceTextRange(
                                        [
                                            current.range[1],
                                            next.range[0]
                                        ],
                                        "\n" + indent
                                    );
                                },
                                node: next
                            });
                        }
                    }

                    const lastAttr = node.attributes[node.attributes.length - 1];

                    const allTokens = [];
                    let currentToken = sourceCode.getTokenAfter(lastAttr);

                    while (currentToken && currentToken.range[1] <= node.range[1]) {
                        allTokens.push(currentToken);
                        currentToken = sourceCode.getTokenAfter(currentToken);
                    }

                    const closingBracket = allTokens[allTokens.length - 1];

                    let hasSelfClosingSlash = false;
                    let slashToken = null;

                    if (node.selfClosing && allTokens.length >= 2) {
                        slashToken = allTokens[allTokens.length - 2];
                        if (slashToken && slashToken.value === "/") {
                            hasSelfClosingSlash = true;
                        }
                    }

                    if (hasSelfClosingSlash) {
                        if (lastAttr.loc.end.line !== slashToken.loc.start.line - 1) {
                            context.report({
                                fix(fixer) {
                                    const openBraceLine = sourceCode.lines[firstToken.loc.start.line - 1];
                                    const baseIndent = openBraceLine.match(/^\s*/)[0];
                                    return fixer.replaceTextRange(
                                        [
                                            lastAttr.range[1],
                                            slashToken.range[0]
                                        ],
                                        "\n" + baseIndent
                                    );
                                },
                                message: "JSX closing slash should be exactly on the next line",
                                node: slashToken
                            });
                        }
                    } else {
                        if (lastAttr.loc.end.line !== closingBracket.loc.start.line - 1) {
                            context.report({
                                fix(fixer) {
                                    const openBraceLine = sourceCode.lines[firstToken.loc.start.line - 1];
                                    const baseIndent = openBraceLine.match(/^\s*/)[0];
                                    return fixer.replaceTextRange(
                                        [
                                            lastAttr.range[1],
                                            closingBracket.range[0]
                                        ],
                                        "\n" + baseIndent
                                    );
                                },
                                message: "JSX closing bracket should be exactly on the next line",
                                node: closingBracket
                            });
                        }
                    }
                }
            };
        },
        meta: {
            fixable: "whitespace",
            type: "layout",
            schema: []
        }
    },
    "multiline-object-properties": {
        create(context) {
            const sourceCode = context.getSourceCode();

            function checkProperties(node, properties) {
                if (properties.length === 0) return;

                const openBrace = sourceCode.getFirstToken(node);

                let closingBraceToken = sourceCode.getTokenAfter(properties[properties.length - 1]);
                while (closingBraceToken && closingBraceToken.value !== "}") {
                    closingBraceToken = sourceCode.getTokenAfter(closingBraceToken);
                }
                const closingBrace = closingBraceToken;

                if (openBrace.loc.start.line === closingBrace.loc.end.line) return;

                const firstProperty = properties[0];

                if (openBrace.loc.end.line !== firstProperty.loc.start.line - 1) {
                    context.report({
                        fix(fixer) {
                            const openBraceLine = sourceCode.lines[openBrace.loc.start.line - 1];
                            const baseIndent = openBraceLine.match(/^\s*/)[0];
                            const indent = baseIndent + "    ";

                            return fixer.replaceTextRange(
                                [
                                    openBrace.range[1],
                                    firstProperty.range[0]
                                ],
                                "\n" + indent
                            );
                        },
                        message: "First property should be exactly on the next line",
                        node: firstProperty
                    });
                }

                if (properties.length > 1) {
                    for (let i = 0; i < properties.length - 1; i++) {
                        const current = properties[i];
                        const next = properties[i + 1];

                        if (current.loc.end.line !== next.loc.start.line - 1) {
                            context.report({
                                message: "Each property should be exactly on the next line (no empty lines)",
                                fix(fixer) {
                                    const openBraceLine = sourceCode.lines[openBrace.loc.start.line - 1];
                                    const baseIndent = openBraceLine.match(/^\s*/)[0];
                                    const indent = baseIndent + "    ";

                                    const comma = sourceCode.getTokenBefore(next);

                                    return fixer.replaceTextRange(
                                        [
                                            comma.range[1],
                                            next.range[0]
                                        ],
                                        "\n" + indent
                                    );
                                },
                                node: next
                            });
                        }
                    }
                }

                const lastProperty = properties[properties.length - 1];

                if (lastProperty.loc.end.line !== closingBrace.loc.start.line - 1) {
                    context.report({
                        fix(fixer) {
                            const openBraceLine = sourceCode.lines[openBrace.loc.start.line - 1];
                            const baseIndent = openBraceLine.match(/^\s*/)[0];

                            const tokenAfter = sourceCode.getTokenAfter(lastProperty);
                            const hasTrailingComma = tokenAfter && tokenAfter.value === ",";
                            const startRange = hasTrailingComma ? tokenAfter.range[1] : lastProperty.range[1];

                            return fixer.replaceTextRange(
                                [
                                    startRange,
                                    closingBrace.range[0]
                                ],
                                "\n" + baseIndent
                            );
                        },
                        message: "Object closing brace should be exactly on the next line",
                        node: closingBrace
                    });
                }

                const tokenAfter = sourceCode.getTokenAfter(lastProperty);
                if (tokenAfter && tokenAfter.value === ",") {
                    context.report({
                        message: "No trailing comma in objects",
                        fix(fixer) {
                            return fixer.remove(tokenAfter);
                        },
                        node: lastProperty
                    });
                }
            }

            return {
                ObjectExpression(node) {
                    checkProperties(node, node.properties);
                },
                ObjectPattern(node) {
                    checkProperties(node, node.properties);
                },
                TSTypeLiteral(node) {
                    checkProperties(node, node.members);
                }
            };
        },
        meta: {
            fixable: "whitespace",
            type: "layout",
            schema: []
        }
    },
    "jsx-no-blank-lines": {
        create(context) {
            return {
                JSXText(node) {
                    const text = node.value;
                    if (!/^\s*$/.test(text)) {
                        return;
                    }

                    const newlines = text.match(/\n/g);
                    if (newlines && newlines.length > 1) {
                        context.report({
                            message: "No blank lines allowed between JSX elements",
                            fix(fixer) {
                                const match = text.match(/\n[ \t]*$/);
                                if (match) {
                                    return fixer.replaceText(node, match[0]);
                                }
                            },
                            node
                        });
                    }
                }
            };
        },
        meta: {
            fixable: "whitespace",
            type: "layout",
            schema: []
        }
    }
};
