/**
 * ESLint Rule: Mutation Must Use Builders
 *
 * Ensures mutations use builders to transform DTOs into ViewData
 * or return appropriate simple types (void, string, etc.)
 */

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'Ensure mutations use builders or return appropriate types, not DTOs',
      category: 'Mutation',
      recommended: true,
    },
    messages: {
      mustUseBuilder: 'Mutations must use ViewDataBuilder to transform DTOs or return simple types - see apps/website/lib/contracts/builders/ViewDataBuilder.ts',
      noDirectDtoReturn: 'Mutations must not return DTOs directly, use builders to create ViewData or return simple types like void, string, or primitive values',
    },
    schema: [],
  },

  create(context) {
    const filename = context.getFilename();
    const isMutation = filename.includes('/lib/mutations/') && filename.endsWith('.ts');

    if (!isMutation) {
      return {};
    }

    let hasBuilderImport = false;
    const returnStatements = [];
    const methodDefinitions = [];

    return {
      // Check for builder imports
      ImportDeclaration(node) {
        const importPath = node.source.value;
        if (importPath.includes('/builders/view-data/')) {
          hasBuilderImport = true;
        }
      },

      // Track method definitions
      MethodDefinition(node) {
        if (node.key.type === 'Identifier' && node.key.name === 'execute') {
          methodDefinitions.push(node);
        }
      },

      // Track return statements in execute method
      ReturnStatement(node) {
        // Only track returns inside execute method
        let parent = node;
        while (parent) {
          if (parent.type === 'MethodDefinition' && 
              parent.key.type === 'Identifier' && 
              parent.key.name === 'execute') {
            if (node.argument) {
              returnStatements.push(node);
            }
            break;
          }
          parent = parent.parent;
        }
      },

      'Program:exit'() {
        if (methodDefinitions.length === 0) {
          return; // No execute method found
        }

        // Check each return statement in execute method
        returnStatements.forEach(returnNode => {
          const returnExpr = returnNode.argument;
          
          if (!returnExpr) return;

          // Check if it's a Result type
          if (returnExpr.type === 'CallExpression') {
            const callee = returnExpr.callee;
            
            // Check for Result.ok() or Result.err()
            if (callee.type === 'MemberExpression' && 
                callee.object.type === 'Identifier' && 
                callee.object.name === 'Result' &&
                callee.property.type === 'Identifier' &&
                (callee.property.name === 'ok' || callee.property.name === 'err')) {
              
              const resultArg = returnExpr.arguments[0];
              
              if (callee.property.name === 'ok') {
                // Check what's being returned in Result.ok()
                
                // If it's a builder call, that's good
                if (resultArg && resultArg.type === 'CallExpression') {
                  const builderCallee = resultArg.callee;
                  if (builderCallee.type === 'MemberExpression' && 
                      builderCallee.property.type === 'Identifier' && 
                      builderCallee.property.name === 'build') {
                    return; // Good: using builder
                  }
                }
                
                // If it's a method call that might be unwrap() or similar
                if (resultArg && resultArg.type === 'CallExpression') {
                  const methodCallee = resultArg.callee;
                  if (methodCallee.type === 'MemberExpression' && 
                      methodCallee.property.type === 'Identifier') {
                    const methodName = methodCallee.property.name;
                    // Common DTO extraction methods
                    if (['unwrap', 'getValue', 'getData', 'getResult'].includes(methodName)) {
                      context.report({
                        node: returnNode,
                        messageId: 'noDirectDtoReturn',
                      });
                      return;
                    }
                  }
                }
                
                // If it's a simple type (string, number, boolean, void), that's OK
                if (resultArg && (
                    resultArg.type === 'Literal' || 
                    resultArg.type === 'Identifier' && 
                    ['void', 'undefined', 'null'].includes(resultArg.name) ||
                    resultArg.type === 'UnaryExpression' // e.g., undefined
                )) {
                  return; // Good: simple type
                }
                
                // If it's an identifier that might be a DTO
                if (resultArg && resultArg.type === 'Identifier') {
                  const varName = resultArg.name;
                  // Check if it's likely a DTO (ends with DTO, or common patterns)
                  if (varName.match(/(DTO|Result|Response|Data)$/i) && 
                      !varName.includes('ViewData') && 
                      !varName.includes('ViewModel')) {
                    context.report({
                      node: returnNode,
                      messageId: 'noDirectDtoReturn',
                    });
                  }
                }
                
                // If it's an object literal with DTO-like properties
                if (resultArg && resultArg.type === 'ObjectExpression') {
                  const hasDtoLikeProps = resultArg.properties.some(prop => {
                    if (prop.type === 'Property' && prop.key.type === 'Identifier') {
                      const keyName = prop.key.name;
                      return keyName.match(/(id|Id|URL|Url|DTO)$/i);
                    }
                    return false;
                  });
                  
                  if (hasDtoLikeProps) {
                    context.report({
                      node: returnNode,
                      messageId: 'noDirectDtoReturn',
                    });
                  }
                }
              }
            }
          }
        });

        // If no builder import and we have DTO returns, report
        if (!hasBuilderImport && returnStatements.length > 0) {
          const hasDtoReturn = returnStatements.some(returnNode => {
            const returnExpr = returnNode.argument;
            if (returnExpr && returnExpr.type === 'CallExpression') {
              const callee = returnExpr.callee;
              if (callee.type === 'MemberExpression' && 
                  callee.object.type === 'Identifier' && 
                  callee.object.name === 'Result' &&
                  callee.property.name === 'ok') {
                const arg = returnExpr.arguments[0];
                
                // Check for method calls like result.unwrap()
                if (arg && arg.type === 'CallExpression') {
                  const methodCallee = arg.callee;
                  if (methodCallee.type === 'MemberExpression' && 
                      methodCallee.property.type === 'Identifier') {
                    const methodName = methodCallee.property.name;
                    if (['unwrap', 'getValue', 'getData', 'getResult'].includes(methodName)) {
                      return true;
                    }
                  }
                }
                
                // Check for identifier
                if (arg && arg.type === 'Identifier') {
                  return arg.name.match(/(DTO|Result|Response|Data)$/i);
                }
              }
            }
            return false;
          });
          
          if (hasDtoReturn) {
            context.report({
              node: methodDefinitions[0],
              messageId: 'mustUseBuilder',
            });
          }
        }
      },
    };
  },
};