/**
 * ESLint Rule: Mutation Contract
 *
 * Enforces the basic Mutation contract:
 * - Mutation classes should `implement Mutation<...>` (type-level contract)
 * - `execute()` must return `Promise<Result<...>>`
 */

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'Ensure mutations implement the Mutation contract and return Result',
      category: 'Mutation Contract',
      recommended: true,
    },
    messages: {
      mustImplementMutationInterface:
        'Mutation classes must implement Mutation<TInput, TOutput, TError> - see apps/website/lib/contracts/mutations/Mutation.ts',
      wrongReturnType:
        'Mutation execute() must return Promise<Result<TOutput, TError>> - see apps/website/lib/contracts/Result.ts',
    },
    schema: [],
  },

  create(context) {
    const filename = context.getFilename();

    if (!filename.includes('/lib/mutations/') || !filename.endsWith('.ts')) {
      return {};
    }

    const mutationInterfaceNames = new Set(['Mutation']);

    function isIdentifier(node, nameSet) {
      return node && node.type === 'Identifier' && nameSet.has(node.name);
    }

    function isPromiseType(typeAnnotation) {
      if (!typeAnnotation || typeAnnotation.type !== 'TSTypeReference') return false;
      const typeName = typeAnnotation.typeName;
      return typeName && typeName.type === 'Identifier' && typeName.name === 'Promise';
    }

    function isResultType(typeNode) {
      if (!typeNode || typeNode.type !== 'TSTypeReference') return false;
      const typeName = typeNode.typeName;
      if (!typeName) return false;

      // Common case: Result<...>
      if (typeName.type === 'Identifier') {
        return typeName.name === 'Result';
      }

      // Fallback: handle qualified names (rare)
      if (typeName.type === 'TSQualifiedName') {
        return typeName.right && typeName.right.type === 'Identifier' && typeName.right.name === 'Result';
      }

      return false;
    }

    return {
      ImportDeclaration(node) {
        const importPath = node.source && node.source.value;
        if (typeof importPath !== 'string') return;

        // Accept both alias and relative imports.
        const isMutationContractImport =
          importPath.includes('/lib/contracts/mutations/Mutation') ||
          importPath.endsWith('lib/contracts/mutations/Mutation') ||
          importPath.endsWith('contracts/mutations/Mutation') ||
          importPath.endsWith('contracts/mutations/Mutation.ts');

        if (!isMutationContractImport) return;

        for (const spec of node.specifiers || []) {
          // import { Mutation as X } from '...'
          if (spec.type === 'ImportSpecifier' && spec.imported && spec.imported.type === 'Identifier') {
            mutationInterfaceNames.add(spec.local.name);
          }
        }
      },

      ClassDeclaration(node) {
        if (!node.id || node.id.type !== 'Identifier') return;
        if (!node.id.name.endsWith('Mutation')) return;

        const implementsNodes = node.implements || [];
        const implementsMutation = implementsNodes.some((impl) => {
          // `implements Mutation<...>`
          return impl && isIdentifier(impl.expression, mutationInterfaceNames);
        });

        if (!implementsMutation) {
          context.report({
            node: node.id,
            messageId: 'mustImplementMutationInterface',
          });
        }
      },

      MethodDefinition(node) {
        if (node.key.type !== 'Identifier' || node.key.name !== 'execute') return;
        if (!node.value) return;

        const returnType = node.value.returnType;
        const typeAnnotation = returnType && returnType.typeAnnotation;

        // Must be Promise<...>
        if (!isPromiseType(typeAnnotation)) {
          context.report({
            node,
            messageId: 'wrongReturnType',
          });
          return;
        }

        const promiseTypeArgs = typeAnnotation.typeParameters;
        if (!promiseTypeArgs || !promiseTypeArgs.params || promiseTypeArgs.params.length === 0) {
          context.report({
            node,
            messageId: 'wrongReturnType',
          });
          return;
        }

        // Must be Promise<Result<...>> (we don't constrain the generics here)
        const inner = promiseTypeArgs.params[0];
        if (!isResultType(inner)) {
          context.report({
            node,
            messageId: 'wrongReturnType',
          });
        }
      },
    };
  },
};
