/**
 * @file no-hardcoded-search-params.js
 * Enforces use of SearchParam system instead of manual URLSearchParams manipulation
 */

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'Enforce use of SearchParamBuilder/SearchParamParser instead of manual URLSearchParams manipulation',
      category: 'Best Practices',
      recommended: true,
    },
    fixable: 'code',
    schema: [],
    messages: {
      manualSearchParams: 'Manual URLSearchParams construction. Use SearchParamBuilder instead: import { SearchParamBuilder } from "@/lib/routing/search-params"',
      manualGetParam: 'Manual search param access with get(). Use SearchParamParser instead: import { SearchParamParser } from "@/lib/routing/search-params"',
      manualSetParam: 'Manual search param setting with set(). Use SearchParamBuilder instead',
      manualQueryString:
        'Manual query-string construction detected (e.g. "?returnTo=..."). Use SearchParamBuilder instead: import { SearchParamBuilder } from "@/lib/routing/search-params/SearchParamBuilder"',
    },
  },

  create(context) {
    const SEARCH_PARAM_KEYS = new Set([
      // Auth
      'returnTo',
      'token',
      'email',
      'error',
      'message',
      // Sponsor
      'type',
      'campaignId',
      // Pagination
      'page',
      'limit',
      'offset',
      // Sorting
      'sortBy',
      'order',
      // Filters
      'status',
      'role',
      'tier',
    ]);

    /**
     * Detect patterns like:
     * - "?returnTo="
     * - "&returnTo="
     * - "?page="
     * - "returnTo=" (within a URL string)
     */
    function containsManualQueryParamFragment(raw) {
      if (typeof raw !== 'string' || raw.length === 0) return false;

      // Fast pre-check
      if (!raw.includes('?') && !raw.includes('&') && !raw.includes('=')) return false;

      for (const key of SEARCH_PARAM_KEYS) {
        if (
          raw.includes(`?${key}=`) ||
          raw.includes(`&${key}=`) ||
          // catches "...returnTo=..." in some string-building scenarios
          raw.includes(`${key}=`)
        ) {
          return true;
        }
      }
      return false;
    }

    return {
      // Detect: new URLSearchParams()
      NewExpression(node) {
        if (node.callee.type === 'Identifier' && node.callee.name === 'URLSearchParams') {
          // Check if it's being used for construction (not just parsing)
          const parent = node.parent;
          
          // If it's in a call expression like new URLSearchParams().toString()
          // or new URLSearchParams().get()
          if (parent.type === 'MemberExpression') {
            const property = parent.property.name;
            if (property === 'toString' || property === 'set' || property === 'append') {
              context.report({
                node,
                messageId: 'manualSearchParams',
              });
            }
          }
          // If it's just new URLSearchParams() without further use
          else if (parent.type === 'VariableDeclarator' || parent.type === 'AssignmentExpression') {
            context.report({
              node,
              messageId: 'manualSearchParams',
            });
          }
        }
      },

      // Detect manual query strings, e.g.
      //   `${routes.auth.login}?returnTo=${routes.protected.onboarding}`
      //   routes.auth.login + '?returnTo=' + routes.protected.onboarding
      TemplateLiteral(node) {
        // If any static chunk contains a query-param fragment, treat it as manual.
        for (const quasi of node.quasis) {
          const raw = quasi.value && (quasi.value.raw ?? quasi.value.cooked);
          if (containsManualQueryParamFragment(raw)) {
            context.report({
              node,
              messageId: 'manualQueryString',
            });
            return;
          }
        }
      },

      BinaryExpression(node) {
        // String concatenation patterns, e.g. a + '?returnTo=' + b
        if (node.operator !== '+') return;

        // If either side is a literal string containing query params, report.
        const left = node.left;
        const right = node.right;

        if (left && left.type === 'Literal' && typeof left.value === 'string' && containsManualQueryParamFragment(left.value)) {
          context.report({ node, messageId: 'manualQueryString' });
          return;
        }

        if (right && right.type === 'Literal' && typeof right.value === 'string' && containsManualQueryParamFragment(right.value)) {
          context.report({ node, messageId: 'manualQueryString' });
          return;
        }
      },

      // Detect: params.get() or params.set()
      CallExpression(node) {
        if (node.callee.type === 'MemberExpression') {
          const object = node.callee.object;
          const property = node.callee.property.name;
          
          // Check if it's a URLSearchParams instance
          if (object.type === 'Identifier' && 
              (property === 'get' || property === 'set' || property === 'append' || property === 'delete')) {
            
            // Try to trace back to see if it's URLSearchParams
            let current = object;
            let isUrlSearchParams = false;
            
            // Check if it's from URLSearchParams constructor
            const scope = context.getScope();
            const variable = scope.variables.find(v => v.name === current.name);
            
            if (variable && variable.defs.length > 0) {
              const def = variable.defs[0];
              if (def.node.init && 
                  def.node.init.type === 'NewExpression' &&
                  def.node.init.callee.name === 'URLSearchParams') {
                isUrlSearchParams = true;
              }
            }
            
            if (isUrlSearchParams) {
              if (property === 'get') {
                context.report({
                  node,
                  messageId: 'manualGetParam',
                });
              } else if (property === 'set' || property === 'append') {
                context.report({
                  node,
                  messageId: 'manualSetParam',
                });
              }
            }
          }
        }
      },

      // Detect: searchParams.get() directly (from function parameter)
      MemberExpression(node) {
        if (node.property.type === 'Identifier' && 
            (node.property.name === 'get' || node.property.name === 'set')) {
          
          // Check if object is named "searchParams" or "params"
          if (node.object.type === 'Identifier' && 
              (node.object.name === 'searchParams' || node.object.name === 'params')) {
            
            // Check parent is a call expression
            if (node.parent.type === 'CallExpression' && node.parent.callee === node) {
              if (node.property.name === 'get') {
                context.report({
                  node,
                  messageId: 'manualGetParam',
                });
              } else if (node.property.name === 'set') {
                context.report({
                  node,
                  messageId: 'manualSetParam',
                });
              }
            }
          }
        }
      },
    };
  },
};
