/**
 * Disallow passing full error objects to logger methods. Full error objects
 * contain stack traces, provider response bodies, and potentially credentials.
 * Extract specific fields instead.
 *
 * Bad:
 *   catch (error) { logger.error(error); }
 *   logger.warn(err);
 *
 * Good:
 *   catch (error) { logger.error('upload failed', { message: error.message }); }
 *   logger.warn({ message: err.message, code: err.code });
 *
 * Detection tiers:
 *   1. (high confidence) Inside a CatchClause, the caught parameter is passed
 *      directly to a logger call — regardless of the parameter's name.
 *   2. (medium confidence) Any logger call with an argument matching the
 *      common error identifier pattern /^(e|err|error|ex|exception|cause)$/i.
 */
import type { Rule } from 'eslint';
import type * as ESTree from 'estree';
import { walkMemberChain, getCatchClauseParam, LOGGER_NAME_BROAD_RE } from '../utils.js';

const LOGGER_METHOD_RE = /^(error|warn|info|debug)$/;
const ERROR_IDENTIFIER_RE = /^(e|err|error|ex|exception|cause)$/i;
const SCRIPTS_FILE_RE = /\/scripts?\//;

function isLoggerCall(node: ESTree.CallExpression): boolean {
  const callee = node.callee;
  if (!callee || callee.type !== 'MemberExpression') {
    return false;
  }

  const methodProp = callee.property;
  if (!methodProp || methodProp.type !== 'Identifier') {
    return false;
  }
  if (!LOGGER_METHOD_RE.test(methodProp.name)) {
    return false;
  }

  const { root, props } = walkMemberChain(callee.object);
  return [root, ...props].some(p => p !== null && LOGGER_NAME_BROAD_RE.test(p));
}

const rule: Rule.RuleModule = {
  meta: {
    type: 'problem',
    docs: {
      description:
        'Disallow passing full error objects to logger methods; extract specific fields like error.message instead',
      recommended: true,
    },
    schema: [],
    messages: {
      fullErrorObjectLogged:
        "Do not pass the full error object to {{method}}(). Full error objects contain stack traces, provider response bodies, and potentially credentials. Extract specific fields instead: {{method}}('description', { message: {{identifier}}.message })",
    },
  },

  create(context) {
    const filename = context.filename ?? '';
    if (SCRIPTS_FILE_RE.test(filename)) {
      return {};
    }

    return {
      CallExpression(node) {
        if (!isLoggerCall(node)) {
          return;
        }

        const callee = node.callee as ESTree.MemberExpression;
        const methodProp = callee.property as ESTree.Identifier;
        const method = methodProp.name;
        const hasIdentifierArg = node.arguments.some(arg => arg.type === 'Identifier');
        if (!hasIdentifierArg) {
          return;
        }

        const catchParam = getCatchClauseParam(node as Rule.Node);

        for (const arg of node.arguments) {
          if (arg.type !== 'Identifier') {
            continue;
          }

          if ((catchParam && arg.name === catchParam) || ERROR_IDENTIFIER_RE.test(arg.name)) {
            context.report({
              node,
              messageId: 'fullErrorObjectLogged',
              data: { method, identifier: arg.name },
            });
            return;
          }
        }
      },
    };
  },
};

export default rule;
