export default {
    meta: {
        type: 'layout',
        docs: {
            description: 'enforce spacing inside parentheses for control structures only',
            category: 'Stylistic Issues',
        },
        fixable: 'whitespace',
        schema: [],
        messages: {
            missingSpaceAfterOpen: 'Missing space after opening parenthesis in control structure.',
            missingSpaceBeforeClose: 'Missing space before closing parenthesis in control structure.',
            unexpectedSpaceAfterOpen: 'Unexpected space after opening parenthesis in function call.',
            unexpectedSpaceBeforeClose: 'Unexpected space before closing parenthesis in function call.',
        },
    },

    create (context) {
        const sourceCode = context.getSourceCode();

        function checkControlStructureSpacing (node) {
            // For control structures, we need to find the parentheses around the condition/test
            let conditionNode;

            if ( node.type === 'IfStatement' || node.type === 'WhileStatement' || node.type === 'DoWhileStatement' ) {
                conditionNode = node.test;
            } else if ( node.type === 'ForStatement' || node.type === 'ForInStatement' || node.type === 'ForOfStatement' ) {
                // For loops, we want the parentheses around the entire for clause
                conditionNode = node;
            } else if ( node.type === 'SwitchStatement' ) {
                conditionNode = node.discriminant;
            } else if ( node.type === 'CatchClause' ) {
                conditionNode = node.param;
            }

            if ( ! conditionNode ) return;

            // Find the opening paren - it should be right before the condition starts
            const openParen = sourceCode.getTokenBefore(conditionNode, token => token.value === '(');
            if ( !openParen || openParen.value !== '(' ) return;

            // Find the closing paren - it should be right after the condition ends
            const closeParen = sourceCode.getTokenAfter(conditionNode, token => token.value === ')');
            if ( !closeParen || closeParen.value !== ')' ) return;

            const afterOpen = sourceCode.getTokenAfter(openParen);
            const beforeClose = sourceCode.getTokenBefore(closeParen);

            {
                const contentBetweenParens = sourceCode.getText().slice(openParen.range[1], closeParen.range[0]);
                const isSingleCharVariable = /^\s*[a-zA-Z_$]\s*$/.test(contentBetweenParens);

                // Skip spacing requirements for single character variables
                if ( isSingleCharVariable ) {
                    return;
                }
            }

            // Control structures should have spacing
            if ( afterOpen && openParen.range[1] === afterOpen.range[0] ) {
                context.report({
                    node,
                    loc: openParen.loc,
                    messageId: 'missingSpaceAfterOpen',
                    fix (fixer) {
                        return fixer.insertTextAfter(openParen, ' ');
                    },
                });
            }

            if ( beforeClose && beforeClose.range[1] === closeParen.range[0] ) {
                context.report({
                    node,
                    loc: closeParen.loc,
                    messageId: 'missingSpaceBeforeClose',
                    fix (fixer) {
                        return fixer.insertTextBefore(closeParen, ' ');
                    },
                });
            }
        }

        function checkForLoopSpacing (node) {
            // For loops are special - we need to find the opening paren after the 'for' keyword
            // and the closing paren before the body
            const forKeyword = sourceCode.getFirstToken(node);
            if ( !forKeyword || forKeyword.value !== 'for' ) return;

            const openParen = sourceCode.getTokenAfter(forKeyword, token => token.value === '(');
            if ( ! openParen ) return;

            // The closing paren should be right before the body
            const closeParen = sourceCode.getTokenBefore(node.body, token => token.value === ')');
            if ( ! closeParen ) return;

            const afterOpen = sourceCode.getTokenAfter(openParen);
            const beforeClose = sourceCode.getTokenBefore(closeParen);

            if ( afterOpen && openParen.range[1] === afterOpen.range[0] ) {
                context.report({
                    node,
                    loc: openParen.loc,
                    messageId: 'missingSpaceAfterOpen',
                    fix (fixer) {
                        return fixer.insertTextAfter(openParen, ' ');
                    },
                });
            }

            if ( beforeClose && beforeClose.range[1] === closeParen.range[0] ) {
                context.report({
                    node,
                    loc: closeParen.loc,
                    messageId: 'missingSpaceBeforeClose',
                    fix (fixer) {
                        return fixer.insertTextBefore(closeParen, ' ');
                    },
                });
            }
        }

        function checkFunctionCallSpacing (node) {
            // Find the opening parenthesis for this function call
            const openParen = sourceCode.getFirstToken(node, token => token.value === '(');
            const closeParen = sourceCode.getLastToken(node, token => token.value === ')');

            if ( !openParen || !closeParen ) return;

            const afterOpen = sourceCode.getTokenAfter(openParen);
            const beforeClose = sourceCode.getTokenBefore(closeParen);

            // Function calls should NOT have spacing
            if ( afterOpen && openParen.range[1] !== afterOpen.range[0] ) {
                const spaceAfter = sourceCode.getText().slice(openParen.range[1], afterOpen.range[0]);
                if ( /^\s+$/.test(spaceAfter) ) {
                    context.report({
                        node,
                        loc: openParen.loc,
                        messageId: 'unexpectedSpaceAfterOpen',
                        fix (fixer) {
                            return fixer.removeRange([openParen.range[1], afterOpen.range[0]]);
                        },
                    });
                }
            }

            if ( beforeClose && beforeClose.range[1] !== closeParen.range[0] ) {
                const spaceBefore = sourceCode.getText().slice(beforeClose.range[1], closeParen.range[0]);
                if ( /^\s+$/.test(spaceBefore) ) {
                    context.report({
                        node,
                        loc: closeParen.loc,
                        messageId: 'unexpectedSpaceBeforeClose',
                        fix (fixer) {
                            return fixer.removeRange([beforeClose.range[1], closeParen.range[0]]);
                        },
                    });
                }
            }
        }

        return {
            // Control structures that should have spacing
            IfStatement (node) {
                checkControlStructureSpacing(node);
            },
            WhileStatement (node) {
                checkControlStructureSpacing(node);
            },
            DoWhileStatement (node) {
                checkControlStructureSpacing(node);
            },
            SwitchStatement (node) {
                checkControlStructureSpacing(node);
            },
            CatchClause (node) {
                if ( node.param ) {
                    checkControlStructureSpacing(node);
                }
            },

            // For loops need special handling
            ForStatement (node) {
                checkForLoopSpacing(node);
            },
            ForInStatement (node) {
                checkForLoopSpacing(node);
            },
            ForOfStatement (node) {
                checkForLoopSpacing(node);
            },

            // Function calls that should NOT have spacing
            CallExpression (node) {
                checkFunctionCallSpacing(node);
            },
            NewExpression (node) {
                if ( node.arguments.length > 0 || sourceCode.getLastToken(node).value === ')' ) {
                    checkFunctionCallSpacing(node);
                }
            },
        };
    },
};