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

                    if (specifiers.length <= 1) return;

                    const sourceCode = context.getSourceCode();

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

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

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

                    if (tokenAfter && tokenAfter.value === ",") {
                        context.report({
                            node: lastSpecifier,
                            message: "No trailing comma in imports",
                            fix(fixer) {
                                return fixer.remove(tokenAfter);
                            }
                        });
                    }
                }
            };
        }
    },
    "multiline-object-properties": {
        meta: {
            type: "layout",
            fixable: "whitespace",
            schema: []
        },
        create(context) {
            const sourceCode = context.getSourceCode();

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

                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) {
                        context.report({
                            node: next,
                            message: "Each property should be on a new line",
                            fix(fixer) {
                                const openBrace = sourceCode.getFirstToken(node);
                                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
                                );
                            }
                        });
                    }
                }

                const lastProperty = properties[properties.length - 1];
                const tokenAfter = sourceCode.getTokenAfter(lastProperty);

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

            return {
                ObjectExpression(node) {
                    checkProperties(node, node.properties);
                },

                ObjectPattern(node) {
                    checkProperties(node, node.properties);
                }
            };
        }
    },
    "multiline-jsx-attributes": {
        meta: {
            type: "layout",
            fixable: "whitespace",
            schema: []
        },
        create(context) {
            const sourceCode = context.getSourceCode();

            return {
                JSXOpeningElement(node) {
                    if (node.attributes.length <= 1) 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) {
                        context.report({
                            node: firstAttr,
                            message: "First JSX attribute should be on a new line",
                            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
                                );
                            }
                        });
                    }

                    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) {
                            context.report({
                                node: next,
                                message: "Each JSX attribute should be on a new line",
                                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
                                    );
                                }
                            });
                        }
                    }

                    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];

                    if (lastAttr.loc.end.line === closingBracket.loc.start.line) {
                        context.report({
                            node: closingBracket,
                            message: "JSX closing bracket should be on a new line",
                            fix(fixer) {
                                const openBraceLine = sourceCode.lines[firstToken.loc.start.line - 1];
                                const baseIndent = openBraceLine.match(/^\s*/)[0];

                                if (node.selfClosing && allTokens.length >= 2) {
                                    const slashToken = allTokens[allTokens.length - 2];

                                    if (slashToken && slashToken.value === "/") {
                                        return fixer.replaceTextRange(
                                            [
                                                lastAttr.range[1],
                                                slashToken.range[0]
                                            ],
                                            "\n" + baseIndent
                                        );
                                    }
                                }

                                return fixer.replaceTextRange(
                                    [
                                        lastAttr.range[1],
                                        closingBracket.range[0]
                                    ],
                                    "\n" + baseIndent
                                );
                            }
                        });
                    }
                }
            };
        }
    },
    "multiline-array-elements": {
        meta: {
            type: "layout",
            fixable: "whitespace",
            schema: []
        },
        create(context) {
            const sourceCode = context.getSourceCode();

            return {
                ArrayExpression(node) {
                    if (node.elements.length <= 1) return;

                    const openBracket = sourceCode.getFirstToken(node);
                    const firstElement = node.elements[0];

                    if (openBracket.loc.end.line === firstElement.loc.start.line) {
                        context.report({
                            node: firstElement,
                            message: "First array element should be on a new line",
                            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
                                );
                            }
                        });
                    }

                    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) {
                            context.report({
                                node: next,
                                message: "Each array element should be on a new line",
                                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
                                    );
                                }
                            });
                        }
                    }

                    const lastElement = node.elements[node.elements.length - 1];
                    const closingBracket = sourceCode.getLastToken(node);

                    if (lastElement && lastElement.loc.end.line === closingBracket.loc.start.line) {
                        context.report({
                            node: closingBracket,
                            message: "Array closing bracket should be on a new line",
                            fix(fixer) {
                                const openBracketLine = sourceCode.lines[openBracket.loc.start.line - 1];
                                const baseIndent = openBracketLine.match(/^\s*/)[0];

                                return fixer.replaceTextRange(
                                    [
                                        lastElement.range[1],
                                        closingBracket.range[0]
                                    ],
                                    "\n" + baseIndent
                                );
                            }
                        });
                    }

                    const tokenAfter = sourceCode.getTokenAfter(lastElement);

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

            return {
                ImportDeclaration(node) {
                    imports.push(node);
                },

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

                    const categorized = categorizeImports(imports, sourceCode);

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

                        const currentCategory = getImportCategory(current, sourceCode);
                        const nextCategory = getImportCategory(next, sourceCode);

                        if (currentCategory > nextCategory) {
                            context.report({
                                node: next,
                                message: `Import from "${next.source.value}" should come before "${current.source.value}"`,
                                fix(fixer) {
                                    return fixImportOrder(fixer, imports, categorized, sourceCode);
                                }
                            });
                            break;
                        }
                    }
                }
            };

            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 => ({
                    node: imp,
                    category: getImportCategory(imp, sourceCode),
                    source: imp.source.value
                })).sort((a, b) => {
                    if (a.category !== b.category) {
                        return a.category - b.category;
                    }

                    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);
            }
        }
    }
};
