/**
 * Hand-rolled dispatch-guard route extraction.
 *
 * The gap this closes is a whole TOOL answering empty: `route_map` reported
 * "No routes found in this project" for a repo with seventeen route modules,
 * because every route extractor before this one needs a framework to declare
 * the route. A raw `node:http` server declares it by comparing the path.
 *
 * The bar here is precision, not recall — `route_map` presents its output as
 * fact, so a route that does not exist is worse than a route that is missing.
 * Roughly half of these cases are therefore assertions that something is NOT
 * extracted.
 */
import { describe, expect, it } from 'vitest';
import Parser from 'tree-sitter';
import JavaScript from 'tree-sitter-javascript';
import TypeScript from 'tree-sitter-typescript';
import {
  extractDispatchGuardRoutes,
  reconcileDispatchGuardRoutes,
  regexToRoutePath,
  DISPATCH_GUARD_SOURCE,
} from '../../src/core/ingestion/route-extractors/dispatch-guard.js';

const parser = new Parser();
parser.setLanguage(JavaScript);

const extract = (source: string, filePath = 'src/server/routes.js') =>
  extractDispatchGuardRoutes(parser.parse(source), filePath).map((r) => ({
    routePath: r.routePath,
    httpMethod: r.httpMethod,
    handlerName: r.handlerName,
    source: r.source,
  }));

const paths = (source: string): string[] => extract(source).map((r) => r.routePath);

describe('dispatch-guard route extraction', () => {
  describe('the dominant idiom', () => {
    it('extracts a verb-qualified path comparison', () => {
      const routes = extract(`
        export async function handle(req, res, reqCtx) {
          const { pathname } = reqCtx
          if (req.method === 'GET' && pathname === '/api/live/portfolio') {
            return sendJson(res, await loadPortfolio())
          }
        }
      `);
      expect(routes).toEqual([
        {
          routePath: '/api/live/portfolio',
          httpMethod: 'GET',
          handlerName: 'handle',
          source: DISPATCH_GUARD_SOURCE,
        },
      ]);
    });

    it('reads the verb when the comparison order is reversed', () => {
      expect(
        extract(`
          function handle(req) {
            if ('POST' === req.method && '/api/orders' === pathname) { return 1 }
          }
        `),
      ).toMatchObject([{ routePath: '/api/orders', httpMethod: 'POST' }]);
    });

    it('distributes an outer verb across an inner OR of paths', () => {
      const routes = extract(`
        function handle(req) {
          if (req.method === 'GET' && (pathname === '/api/a' || pathname === '/api/b')) { return 1 }
        }
      `);
      expect(routes).toMatchObject([
        { routePath: '/api/a', httpMethod: 'GET' },
        { routePath: '/api/b', httpMethod: 'GET' },
      ]);
    });

    it('inherits a verb from an ENCLOSING if, not just a sibling', () => {
      expect(
        extract(`
          function handle(req) {
            if (req.method === 'DELETE') {
              if (pathname === '/api/session') { return 1 }
            }
          }
        `),
      ).toMatchObject([{ routePath: '/api/session', httpMethod: 'DELETE' }]);
    });

    // The inverted case, and the reason the ancestor walk tracks which branch it
    // came from: in the `else`, the method is precisely NOT POST, so inheriting
    // POST would label the route with the one verb it cannot have.
    it('refuses to inherit a verb from an if whose ELSE branch holds the comparison', () => {
      const routes = extract(`
        function handle(req) {
          if (req.method === 'POST') {
            save()
          } else if (pathname === '/api/report') {
            return 1
          }
        }
      `);
      expect(routes).toMatchObject([{ routePath: '/api/report', httpMethod: '' }]);
    });

    it('extracts a verb-less path guard', () => {
      expect(
        extract(`
          function match(method, pathname) {
            return pathname === '/api/health'
          }
        `),
      ).toMatchObject([{ routePath: '/api/health', httpMethod: '', handlerName: 'match' }]);
    });
  });

  describe('what must NOT become a route', () => {
    it('ignores a comparison against something that is not a request path', () => {
      // `mode` is not a path expression, so `/full` is just a string.
      expect(paths(`function f() { if (mode === '/full') { return 1 } }`)).toEqual([]);
    });

    it('ignores a path-shaped literal compared to a filesystem path variable', () => {
      // `path` is excluded on purpose — in Node it is overwhelmingly node:path
      // or a file location, never the request path.
      expect(paths(`function f() { if (path === '/tmp/cache') { return 1 } }`)).toEqual([]);
    });

    it('ignores startsWith namespace tests', () => {
      // A prefix test asks "do I own this?" — minting `/api/` would claim a
      // route nothing serves.
      expect(
        paths(`function f() { if (pathname.startsWith('/api/')) { return route(pathname) } }`),
      ).toEqual([]);
    });

    it('ignores a bare "/" normalisation with no verb', () => {
      // The static-file idiom, verbatim from the reporting repo.
      expect(
        paths(`function serve() { const file = pathname === '/' ? '/index.html' : pathname }`),
      ).toEqual([]);
    });

    it('DOES extract a bare "/" when a verb makes the intent unambiguous', () => {
      expect(
        extract(
          `function handle(req) { if (req.method === 'GET' && pathname === '/') { return 1 } }`,
        ),
      ).toMatchObject([{ routePath: '/', httpMethod: 'GET' }]);
    });

    it('ignores a non-equality comparison', () => {
      expect(paths(`function f() { if (pathname !== '/api/health') { return 1 } }`)).toEqual([]);
    });

    it('ignores an absolute URL', () => {
      expect(
        paths(`function f() { if (pathname === 'https://x.test/api/a') { return 1 } }`),
      ).toEqual([]);
    });

    it('ignores a template string whose substitution is not a known constant', () => {
      expect(paths('function f() { if (pathname === `/api/${id}`) { return 1 } }')).toEqual([]);
    });

    it('ignores a verb literal that is not an HTTP verb', () => {
      const routes = extract(`
        function handle(req) {
          if (req.method === 'SUBSCRIBE' && pathname === '/api/feed') { return 1 }
        }
      `);
      expect(routes).toMatchObject([{ routePath: '/api/feed', httpMethod: '' }]);
    });
  });

  // BOOLEAN POLARITY. The module refuses to inherit a verb from an `if` whose
  // `else` branch holds the comparison, because that branch runs precisely when
  // the condition did NOT hold. `!` is the same fact written as an operator, and
  // it was not handled — a stated invariant with half an implementation, which
  // is worse than an absent one because the doc comment reads as covered.
  //
  // Every case below was reproduced against the unguarded extractor before the
  // fix: `!(path)` INVENTED a route, and `!(verb) && path` emitted the one verb
  // the branch guarantees the request does not have.
  describe('negated conditions', () => {
    it('claims nothing when the path comparison is negated', () => {
      expect(paths(`function h(req) { if (!(pathname === '/api/admin')) { return 1 } }`)).toEqual(
        [],
      );
    });

    it('claims nothing when the whole guard is negated', () => {
      expect(
        paths(
          `function h(req) { if (!(req.method === 'POST' && pathname === '/api/w')) { return 1 } }`,
        ),
      ).toEqual([]);
    });

    it('keeps the path but drops a NEGATED verb rather than inverting it', () => {
      // The path is still evidence — this branch is reached for `/api/x`. The
      // verb is not: `!(method === 'GET')` says every method EXCEPT GET, which
      // no single value can express, so the honest answer is verb-less.
      expect(
        extract(
          `function h(req) { if (!(req.method === 'GET') && pathname === '/api/x') { return 1 } }`,
        ),
      ).toMatchObject([{ routePath: '/api/x', httpMethod: '' }]);
    });

    it('treats double negation as positive', () => {
      // PARITY, not presence. A rule keyed on "is there a `!` above me" would
      // refuse this, which is a real route.
      expect(paths(`function h(req) { if (!!(pathname === '/api/z')) { return 1 } }`)).toEqual([
        '/api/z',
      ]);
    });

    it('does not let an outer negation leak into the branch BODY', () => {
      // Polarity is a property of the expression, not of the statements a branch
      // contains: the inner comparison is positive where it is written.
      expect(
        paths(`
          function h(req) {
            if (!(req.method === 'GET')) {
              if (pathname === '/api/inner') { return 1 }
            }
          }
        `),
      ).toEqual(['/api/inner']);
    });

    it('negates a regex path test too', () => {
      expect(
        paths(`function h(req) { if (!/^\\/api\\/runs\\/[^/]+$/.test(pathname)) { return 1 } }`),
      ).toEqual([]);
    });

    it('reads a doubly-negated VERB, not just a doubly-negated path', () => {
      // The parity rule was stated for `!` but the verb walk refused on the mere
      // PRESENCE of one, so this lost a verb the source states outright. The
      // path-position case above passed throughout and hid it.
      expect(
        extract(
          `function h(req) { if (!!(req.method === 'GET') && pathname === '/api/dn') { return 1 } }`,
        ),
      ).toMatchObject([{ routePath: '/api/dn', httpMethod: 'GET' }]);
    });
  });

  // A ternary SELECTS between its arms, so a verb inside one is not reached
  // merely because the whole condition is truthy. Reproduced against the
  // extractor before fixing: the first case emitted `GET /api/i`, the one method
  // that branch guarantees the request does NOT have — the same inversion `!`
  // produced before it was handled, one level up.
  //
  // The last three cases were ALREADY correct and are here to pin them: refusing
  // every ternary would fix the bug and silently drop three real verbs.
  describe('ternary polarity', () => {
    const guard = (cond: string, path = '/api/i') =>
      extract(`function h(req) { if (${cond} && pathname === '${path}') { return 1 } }`);

    it('drops the verb when a ternary INVERTS it', () => {
      // `c ? false : true` is `!c`: the branch runs for every method except GET.
      expect(guard(`(req.method === 'GET' ? false : true)`)).toMatchObject([
        { routePath: '/api/i', httpMethod: '' },
      ]);
    });

    it('keeps the verb when a ternary is the identity', () => {
      // `c ? true : false` is `c`. Verb-less would be safe but wrong to settle for.
      expect(guard(`(req.method === 'GET' ? true : false)`)).toMatchObject([
        { routePath: '/api/i', httpMethod: 'GET' },
      ]);
    });

    it('keeps a verb in the consequence when the alternative is `false`', () => {
      // `c ? A : false` is `c && A` — reaching the body requires BOTH.
      expect(guard(`(isAdmin ? req.method === 'GET' : false)`)).toMatchObject([
        { routePath: '/api/i', httpMethod: 'GET' },
      ]);
    });

    it('keeps a verb in the alternative when the consequence is `false`', () => {
      // `c ? false : B` is `!c && B`.
      expect(guard(`(isAdmin ? false : req.method === 'GET')`)).toMatchObject([
        { routePath: '/api/i', httpMethod: 'GET' },
      ]);
    });

    it('claims no verb when both arms are live comparisons', () => {
      // Which verb this serves depends on `isAdmin`, so naming either is a guess.
      expect(guard(`(isAdmin ? req.method === 'GET' : req.method === 'POST')`)).toMatchObject([
        { routePath: '/api/i', httpMethod: '' },
      ]);
    });

    it('claims no verb when a `true` arm makes the ternary a disjunction', () => {
      // `c ? true : B` is `c || B`: the body is also reached for any method when
      // `isAdmin` holds, so `GET` would present a broader route as a narrow one.
      expect(guard(`(req.method === 'GET' ? true : isAdmin)`)).toMatchObject([
        { routePath: '/api/i', httpMethod: '' },
      ]);
    });

    it('claims no verb when the whole ternary is negated', () => {
      // `!(c ? A : false)` is `!(c && A)`, i.e. `!c || !A` — a disjunction, which
      // guarantees nothing. Without the parity guard the conjunction rule would
      // read `GET` straight out of the consequence and invert it exactly as the
      // un-negated form did.
      expect(guard(`!(isAdmin ? req.method === 'GET' : false)`, '/api/n')).toMatchObject([
        { routePath: '/api/n', httpMethod: '' },
      ]);
    });

    // `c ? A : false` is `c && A`, and the methods a CONJUNCTION guarantees are
    // the ones both sides admit — their intersection. Taking the first side that
    // named a verb reported one operand's set unintersected, which is how a verb
    // the guard excludes got minted as a route of its own.
    it('INTERSECTS the two sides of the conjunction instead of taking the first', () => {
      // {GET,POST} ∩ {POST,PUT} is POST alone. GET reaches the ternary but not
      // its consequence, so `GET /api/t1` was a route no request can take.
      expect(
        guard(
          `((req.method === 'GET' || req.method === 'POST') ? (req.method === 'POST' || req.method === 'PUT') : false)`,
          '/api/t1',
        ),
      ).toMatchObject([{ routePath: '/api/t1', httpMethod: 'POST' }]);
    });

    it('claims no verb when the two sides cannot both hold', () => {
      // `GET && POST` is unsatisfiable — no method satisfies this guard, so
      // naming either side invents a route. Verb-less is the honest answer, and
      // the path itself is still proven.
      expect(
        guard(`(req.method === 'GET' ? req.method === 'POST' : false)`, '/api/t2'),
      ).toMatchObject([{ routePath: '/api/t2', httpMethod: '' }]);
    });

    it('intersects the other conjunction too, at flipped parity', () => {
      // `c ? false : B` is `!c && B`. With `c` = `!(method === 'GET')` the guard
      // reads `GET && POST` — the same contradiction, reached through the arm
      // that searches the condition negated.
      expect(
        guard(`(!(req.method === 'GET') ? false : req.method === 'POST')`, '/api/t4'),
      ).toMatchObject([{ routePath: '/api/t4', httpMethod: '' }]);
    });

    it('still reads a conjunction where only ONE side names a verb', () => {
      // The fallthrough the intersection replaces stays right when a side is
      // simply silent about the method: `isReady && POST` serves POST. An empty
      // side means "names no method", not "admits none".
      expect(guard(`(isReady ? req.method === 'POST' : false)`, '/api/t3')).toMatchObject([
        { routePath: '/api/t3', httpMethod: 'POST' },
      ]);
    });
  });

  // One guard, several methods. Verbatim from the reporting repo:
  //   if ((req.method === 'GET' || req.method === 'POST') && bundlesMatch) { … }
  // Taking the FIRST verb reported this as GET-only, so `route_map` presented a
  // route open to two methods as restricted to one, and `impact` on the POST
  // path found nothing.
  describe('multi-method guards', () => {
    const guard = (cond: string) =>
      extract(`function h(req) { if (${cond} && pathname === '/api/i') { return 1 } }`).map(
        (r) => r.httpMethod,
      );

    it('emits one route per method in a verb disjunction', () => {
      expect(guard(`(req.method === 'GET' || req.method === 'POST')`)).toEqual(['GET', 'POST']);
    });

    it('handles more than two', () => {
      expect(
        guard(`(req.method === 'GET' || req.method === 'POST' || req.method === 'PUT')`),
      ).toEqual(['GET', 'POST', 'PUT']);
    });

    it('claims NO verb when a disjunct is not a verb test', () => {
      // `GET || isAdmin` is reached for ANY method when `isAdmin` holds. Naming
      // GET would describe a route open to everything as single-method — the
      // direction this module treats as more expensive than saying nothing.
      expect(guard(`(req.method === 'GET' || isAdmin)`)).toEqual(['']);
    });

    it('claims no verb when the disjunction is negated', () => {
      // `!(GET || POST)` excludes both rather than offering either.
      expect(guard(`!(req.method === 'GET' || req.method === 'POST')`)).toEqual(['']);
    });

    it('still distributes ONE verb across an OR of paths', () => {
      // The pre-existing rule, pinned against the disjunction change: here the
      // `||` joins PATHS, not verbs, and must not start multiplying methods.
      expect(
        extract(`
          function h(req) {
            if (req.method === 'GET' && (pathname === '/api/a' || pathname === '/api/b')) { return 1 }
          }
        `),
      ).toMatchObject([
        { routePath: '/api/a', httpMethod: 'GET' },
        { routePath: '/api/b', httpMethod: 'GET' },
      ]);
    });

    it('gives every switch arm the full method set', () => {
      expect(
        extract(`
          function h(req) {
            if (req.method === 'GET' || req.method === 'POST') {
              switch (pathname) {
                case '/api/a': return 1
                case '/api/b': return 2
              }
            }
          }
        `),
      ).toMatchObject([
        { routePath: '/api/a', httpMethod: 'GET' },
        { routePath: '/api/a', httpMethod: 'POST' },
        { routePath: '/api/b', httpMethod: 'GET' },
        { routePath: '/api/b', httpMethod: 'POST' },
      ]);
    });
  });

  // Not in any report — the same dispatch written with different syntax. A
  // graph that waits for a bug report per shape stays permanently one idiom
  // behind the code it indexes.
  describe('switch dispatch', () => {
    it('extracts every string-literal case of a switch on the path', () => {
      expect(
        extract(`
          function handle(req, pathname) {
            switch (pathname) {
              case '/api/health': return ok()
              case '/api/version': return version()
              default: return notFound()
            }
          }
        `),
      ).toMatchObject([
        { routePath: '/api/health', httpMethod: '', handlerName: 'handle' },
        { routePath: '/api/version', httpMethod: '', handlerName: 'handle' },
      ]);
    });

    it('applies a verb governing the whole switch to every arm', () => {
      expect(
        extract(`
          function handle(req, pathname) {
            if (req.method === 'POST') {
              switch (pathname) {
                case '/api/a': return a()
                case '/api/b': return b()
              }
            }
          }
        `),
      ).toMatchObject([
        { routePath: '/api/a', httpMethod: 'POST' },
        { routePath: '/api/b', httpMethod: 'POST' },
      ]);
    });

    it('ignores a switch on something that is not a request path', () => {
      // The file must mention a path token, or PATH_TOKEN_HINT skips it before
      // the discriminant rule is ever consulted and this asserts nothing. The
      // real route below is the proof the walk ran.
      expect(
        paths(`
          function f(kind, pathname) {
            switch (kind) { case '/full': return 1 }
            if (pathname === '/api/real') { return 2 }
          }
        `),
      ).toEqual(['/api/real']);
    });

    it('ignores non-path cases in a switch that is on the path', () => {
      expect(
        paths(`
          function handle(pathname) {
            switch (pathname) {
              case '/api/a': return 1
              case 'unknown': return 2
            }
          }
        `),
      ).toEqual(['/api/a']);
    });
  });

  // A composed path is not an exotic shape — one of the reporting repo's
  // seventeen route modules writes every one of its ~20 routes this way, and
  // without folding that file contributes NOTHING while looking exactly like a
  // file that has no routes.
  describe('paths composed from same-file constants', () => {
    it('folds a template substitution naming a module-level constant', () => {
      expect(
        extract(
          'const BASE = "/api/live/auto-trade"\n' +
            'function handle(req) {\n' +
            '  if (req.method === "GET" && pathname === `${BASE}/rules`) { return 1 }\n' +
            '}',
        ),
      ).toMatchObject([{ routePath: '/api/live/auto-trade/rules', httpMethod: 'GET' }]);
    });

    it('follows an alias hop, which is how the reporting repo writes it', () => {
      // `const autoTradeBasePath = AUTO_TRADE_BASE_PATH` inside the handler,
      // with the literal at module scope.
      expect(
        paths(
          'const AUTO_TRADE_BASE_PATH = "/api/live/auto-trade"\n' +
            'function handle(req) {\n' +
            '  const autoTradeBasePath = AUTO_TRADE_BASE_PATH\n' +
            '  if (pathname === `${autoTradeBasePath}/positions`) { return 1 }\n' +
            '}',
        ),
      ).toEqual(['/api/live/auto-trade/positions']);
    });

    it('folds + concatenation', () => {
      expect(
        paths(
          'const BASE = "/api/v2"\n' +
            'function handle() { if (pathname === BASE + "/orders") { return 1 } }',
        ),
      ).toEqual(['/api/v2/orders']);
    });

    it('folds a bare constant with no suffix', () => {
      expect(
        paths(
          'const HEALTH = "/api/health"\nfunction handle() { if (pathname === HEALTH) { return 1 } }',
        ),
      ).toEqual(['/api/health']);
    });

    // The refusals. A partially-folded path is a WRONG route, and a wrong route
    // is worse than a missing one — the whole premise of this module.
    it('refuses a name declared twice with different values', () => {
      expect(
        paths(
          'const BASE = "/api/a"\n' +
            'function other() { const BASE = "/api/b"; return BASE }\n' +
            'function handle() { if (pathname === `${BASE}/x`) { return 1 } }',
        ),
      ).toEqual([]);
    });

    it('refuses when only part of the template resolves', () => {
      expect(
        paths(
          'const BASE = "/api"\n' +
            'function handle(id) { if (pathname === `${BASE}/x/${id}`) { return 1 } }',
        ),
      ).toEqual([]);
    });

    it('refuses a constant bound to a call result', () => {
      expect(
        paths(
          'const BASE = buildBase()\nfunction handle() { if (pathname === `${BASE}/x`) { return 1 } }',
        ),
      ).toEqual([]);
    });

    it('still rejects a folded value that is not path-shaped', () => {
      expect(
        paths(
          'const MODE = "full"\nfunction handle() { if (pathname === `${MODE}/x`) { return 1 } }',
        ),
      ).toEqual([]);
    });
  });

  describe('parameterised routes from anchored regexes', () => {
    it('converts a single-segment wildcard to a named parameter', () => {
      expect(
        extract(`
          function handle(req) {
            if (req.method === 'GET' && /^\\/api\\/research-runs\\/[^/]+$/.test(pathname)) { return 1 }
          }
        `),
      ).toMatchObject([{ routePath: '/api/research-runs/{param1}', httpMethod: 'GET' }]);
    });

    it('numbers multiple parameters in order', () => {
      expect(regexToRoutePath('^\\/api\\/runs\\/[^/]+\\/experiments\\/[^/]+$')).toBe(
        '/api/runs/{param1}/experiments/{param2}',
      );
    });

    it('accepts an escaped slash inside the wildcard class', () => {
      expect(regexToRoutePath('^\\/api\\/x\\/[^\\/]+$')).toBe('/api/x/{param1}');
    });

    // Bail cases. A route path is a claim about what the server serves, so a
    // pattern that cannot be translated exactly is dropped, not approximated.
    it('refuses an unanchored pattern', () => {
      expect(regexToRoutePath('\\/api\\/x')).toBeNull();
      expect(regexToRoutePath('^\\/api\\/x')).toBeNull();
    });

    it('refuses an optional group', () => {
      expect(regexToRoutePath('^\\/api\\/runs\\/[^/]+\\/artifacts(?:\\/.*)?$')).toBeNull();
    });

    it('refuses an alternation and a bare wildcard', () => {
      expect(regexToRoutePath('^\\/api\\/(a|b)$')).toBeNull();
      expect(regexToRoutePath('^\\/api\\/.*$')).toBeNull();
    });

    it('refuses a character-class escape', () => {
      expect(regexToRoutePath('^\\/api\\/runs\\/\\d+$')).toBeNull();
    });

    it('ignores a regex tested against something that is not a request path', () => {
      expect(paths(`function f() { if (/^\\/api\\/x$/.test(filename)) { return 1 } }`)).toEqual([]);
    });

    // The form every real dispatcher writes, and the one this converter refused.
    // `(` fell through to the metacharacter bail, so the capturing pattern
    // translated to nothing while its non-capturing twin translated fine — which
    // is exactly why every test above passed. The reporting repo does not contain
    // a single non-capturing path wildcard: a dispatcher captures the segment
    // because it needs the id.
    it('converts a CAPTURING single-segment wildcard', () => {
      expect(regexToRoutePath('^\\/api\\/research-runs\\/([^/]+)$')).toBe(
        '/api/research-runs/{param1}',
      );
    });

    it('converts a capturing wildcard followed by more literal path', () => {
      expect(regexToRoutePath('^\\/api\\/live\\/positions\\/([^/]+)\\/replay$')).toBe(
        '/api/live/positions/{param1}/replay',
      );
    });

    it('still refuses a capture group around anything that is not one segment', () => {
      // `.+` spans slashes, so it is not a single segment and cannot be one
      // `{param}`. Accepting `(` must not mean accepting every group.
      expect(regexToRoutePath('^\\/api\\/x\\/(.+)$')).toBeNull();
      expect(regexToRoutePath('^\\/api\\/x\\/(a|b)$')).toBeNull();
    });

    it('refuses an unbalanced capture group', () => {
      // A stray `)` would otherwise be read as a literal path character.
      expect(regexToRoutePath('^\\/api\\/x\\/([^/]+$')).toBeNull();
    });
  });

  // `RE.test(pathname)` and `pathname.match(RE)` are the same test with the
  // operands swapped. Only `.test` was read, which is why 28 of the reporting
  // repo's 75 routes still named the shared route table as their handler: their
  // modules dispatch with `.match`.
  //
  // `.match` differs in one way that matters — its result is USED, so it is
  // almost always BOUND, and the verb then lives in a later `if` rather than
  // around the call.
  describe('bound .match() dispatch', () => {
    const RUNS = `/^\\/api\\/research-runs\\/([^/]+)$/`;

    it('reads the verb from where the binding is TESTED, not where it is bound', () => {
      expect(
        extract(`
          function handle(req) {
            const runMatch = pathname.match(${RUNS})
            if (req.method === 'GET' && runMatch) { return runMatch[1] }
          }
        `),
      ).toMatchObject([
        { routePath: '/api/research-runs/{param1}', httpMethod: 'GET', handlerName: 'handle' },
      ]);
    });

    it('emits a route per method for a multi-method bound match', () => {
      // Verbatim shape from researchRunRoutes.js.
      expect(
        extract(`
          function handle(req) {
            const bundlesMatch = pathname.match(/^\\/api\\/runs\\/([^/]+)\\/bundles$/)
            if ((req.method === 'GET' || req.method === 'POST') && bundlesMatch) { return 1 }
          }
        `).map((r) => r.httpMethod),
      ).toEqual(['GET', 'POST']);
    });

    it('emits once per TEST SITE, not once per capture read', () => {
      // `m[1]` is a read of the captured segment. It says nothing about
      // dispatch, and counting it would mint a duplicate route per use of the id.
      expect(
        extract(`
          function handle(req) {
            const m = pathname.match(/^\\/api\\/w\\/([^/]+)$/)
            if (req.method === 'GET' && m) { return [m[1], m[2], m[1]] }
          }
        `),
      ).toMatchObject([{ routePath: '/api/w/{param1}', httpMethod: 'GET' }]);
    });

    it('emits a route per test site when one binding is tested for two methods', () => {
      expect(
        extract(`
          function handle(req) {
            const m = pathname.match(/^\\/api\\/t\\/([^/]+)$/)
            if (req.method === 'GET' && m) { return 1 }
            if (req.method === 'PUT' && m) { return 2 }
          }
        `).map((r) => r.httpMethod),
      ).toEqual(['GET', 'PUT']);
    });

    it('does not inherit a verb across a NEGATED guard clause', () => {
      // `if (!m) return` is the early-out. The `if (method === 'GET')` after it
      // governs the rest of the function, not this binding's test.
      expect(
        extract(`
          function handle(req) {
            const m = pathname.match(/^\\/api\\/z\\/([^/]+)$/)
            if (!m) { return false }
            if (req.method === 'GET') { return 1 }
          }
        `),
      ).toMatchObject([{ routePath: '/api/z/{param1}', httpMethod: '' }]);
    });

    it('keeps the path when a binding is never tested', () => {
      // The code still computed an anchored match against the request path —
      // the same evidence an unbound `.test` carries.
      expect(
        extract(`
          function handle(req) {
            const m = pathname.match(/^\\/api\\/y\\/([^/]+)$/)
            return m[1]
          }
        `),
      ).toMatchObject([{ routePath: '/api/y/{param1}', httpMethod: '' }]);
    });

    it('reads an UNBOUND .match like a .test', () => {
      expect(
        extract(
          `function handle(req) { if (req.method === 'GET' && pathname.match(/^\\/api\\/x$/)) { return 1 } }`,
        ),
      ).toMatchObject([{ routePath: '/api/x', httpMethod: 'GET' }]);
    });

    it('resolves a regex named by a same-file const, both ways round', () => {
      // positionReplayRoutes.js declares the pattern once and uses it both ways.
      const re = `const RE = /^\\/api\\/positions\\/([^/]+)\\/replay$/`;
      expect(
        extract(`
          ${re}
          function handle(req) {
            const routeMatch = pathname.match(RE)
            if (req.method === 'DELETE' && routeMatch) { return 1 }
          }
        `),
      ).toMatchObject([{ routePath: '/api/positions/{param1}/replay', httpMethod: 'DELETE' }]);
      expect(
        extract(`
          ${re}
          function handle(req) { if (req.method === 'GET' && RE.test(pathname)) { return 1 } }
        `),
      ).toMatchObject([{ routePath: '/api/positions/{param1}/replay', httpMethod: 'GET' }]);
    });

    it('refuses .match on a receiver that is not a request path', () => {
      // The genuine route alongside it is load-bearing, NOT decoration: without
      // a path token somewhere in the file, PATH_TOKEN_HINT skips the walk
      // entirely and this assertion is satisfied by a file that was never
      // examined. It proves the file WAS processed and `userAgent` was refused
      // on its merits.
      expect(
        paths(`
          function handle(req) {
            const m = userAgent.match(/^\\/api\\/nope$/)
            if (req.method === 'GET' && m) { return 1 }
            if (req.method === 'GET' && pathname === '/api/real') { return 2 }
          }
        `),
      ).toEqual(['/api/real']);
    });

    it('claims no verb when the test site itself sits under a negation', () => {
      // `if (!(method === 'GET' && m))` runs precisely when the path did NOT
      // match, so attributing GET is backwards. `!m` alone never reaches this
      // check — a `unary_expression` parent is not a truthiness position to
      // begin with — so the wrapped conjunction is the shape that exercises it.
      expect(
        extract(`
          function handle(req) {
            const m = pathname.match(/^\\/api\\/n\\/([^/]+)$/)
            if (!(req.method === 'GET' && m)) { return false }
          }
        `),
      ).toMatchObject([{ routePath: '/api/n/{param1}', httpMethod: '' }]);
    });

    it('refuses a regex const bound twice to different patterns', () => {
      // Same ambiguity refusal the string-constant map applies: a half-right
      // regex is a wrong route.
      expect(
        paths(`
          const RE = /^\\/api\\/a\\/([^/]+)$/
          const RE = /^\\/api\\/b\\/([^/]+)$/
          function handle(req) { if (req.method === 'GET' && RE.test(pathname)) { return 1 } }
        `),
      ).toEqual([]);
    });

    // That refusal only ever compared regex LITERALS, so the two rebindings that
    // actually occur walked straight past it and the literal's route was minted
    // as though the name still held it.
    it('refuses a regex const REASSIGNED to something dynamic', () => {
      expect(
        paths(`
          let RE = /^\\/api\\/re\\/([^/]+)$/
          RE = buildDynamic(req)
          function handle(req) { if (req.method === 'GET' && RE.test(pathname)) { return 1 } }
        `),
      ).toEqual([]);
    });

    it('refuses a regex const with a non-literal twin in another function', () => {
      // The map is flat, so a same-named binding anywhere in the file is the
      // ambiguity it claims to refuse — `new RegExp(userPrefix + '/x')` is not a
      // `regex` node, which is the only reason it used to survive.
      expect(
        paths(`
          const RE = /^\\/api\\/twin\\/([^/]+)$/
          function other(userPrefix) { const RE = new RegExp(userPrefix + '/x'); return RE }
          function handle(req) { if (req.method === 'GET' && RE.test(pathname)) { return 1 } }
        `),
      ).toEqual([]);
    });

    // A match binding is keyed by the FUNCTION it is bound in, not by its bare
    // name. `m`, `match` and `result` are the commonest locals in dispatcher
    // code, so a file with two handlers routinely binds one of them twice to
    // unrelated things — and the poison rule never fired, because it only ran
    // when a second REGEX MATCH bound the name.
    it('does not resolve a same-named local in ANOTHER function to this binding', () => {
      // Measured before fixing: this emitted a second route,
      // `DELETE /api/live/positions/{param1}/replay @9 handler=handleSettings`
      // — wrong verb, wrong handler, wrong line, for a path that handler never
      // serves. Being VERBED, it also outranked the real route in
      // `reconcileDispatchGuardRoutes`, which drops a verb-less URL claimed with
      // a verb anywhere in the repo.
      expect(
        extract(`
          function handleReplay(req, res) {
            const pathname = new URL(req.url, 'http://x').pathname
            const m = pathname.match(/^\\/api\\/live\\/positions\\/([^/]+)\\/replay$/)
            if (req.method === 'GET' && m) { return replay(m[1]) }
          }
          function handleSettings(req, res) {
            const m = req.headers['x-mode']
            if (req.method === 'DELETE' && m) { return wipeEverything() }
          }
        `),
      ).toEqual([
        {
          routePath: '/api/live/positions/{param1}/replay',
          httpMethod: 'GET',
          handlerName: 'handleReplay',
          source: DISPATCH_GUARD_SOURCE,
        },
      ]);
    });

    it('keeps an untested binding verb-less when another function reuses the name', () => {
      // The second loss channel of the same defect: `tested` was keyed by bare
      // name too, so the unrelated `if (m)` below marked `m` tested and
      // SUPPRESSED this binding's own verb-less emit. The honest route was not
      // merely joined by a fabricated one — it was replaced by it, reporting
      // `handleSettings` as the handler for a path only `handleReplay` serves.
      expect(
        extract(`
          function handleReplay(req, res) {
            const m = pathname.match(/^\\/api\\/live\\/positions\\/([^/]+)\\/replay$/)
            return m[1]
          }
          function handleSettings(req, res) {
            const m = req.headers['x-mode']
            if (m) { return wipeEverything() }
          }
        `),
      ).toEqual([
        {
          routePath: '/api/live/positions/{param1}/replay',
          httpMethod: '',
          handlerName: 'handleReplay',
          source: DISPATCH_GUARD_SOURCE,
        },
      ]);
    });

    it('refuses a name shadowed by a second declarator in the SAME function', () => {
      // The key is the function, not the block, so a shadow inside one handler
      // is ambiguity this walk cannot order — refused whole, the way
      // `buildConstantMap` refuses a constant declared twice. It costs the real
      // GET alongside the DELETE the shadow would have fabricated, which is the
      // cheaper of the two failures.
      expect(
        extract(`
          function handle(req) {
            const m = pathname.match(/^\\/api\\/bs\\/([^/]+)$/)
            if (req.method === 'GET' && m) { return 1 }
            { const m = req.headers['x']; if (req.method === 'DELETE' && m) { return wipe() } }
          }
        `),
      ).toEqual([]);
    });

    it('refuses a match binding REASSIGNED later in the same function', () => {
      // `m` no longer holds the match by the time it is tested, so the
      // declaration is not evidence of what the `if` asks about.
      expect(
        paths(`
          function handle(req) {
            let m = pathname.match(/^\\/api\\/ra\\/([^/]+)$/)
            m = req.headers['x-mode']
            if (req.method === 'GET' && m) { return 1 }
          }
        `),
      ).toEqual([]);
    });
  });

  describe('handler attribution', () => {
    it('names an object-literal method handler', () => {
      // The route-module shape the reporting repo uses throughout.
      expect(
        extract(`
          export function createRoutes(ctx) {
            return {
              async handle(req, res, reqCtx) {
                const { pathname } = reqCtx
                if (req.method === 'GET' && pathname === '/api/live/events') { return 1 }
              },
            }
          }
        `),
      ).toMatchObject([{ routePath: '/api/live/events', handlerName: 'handle' }]);
    });

    it('names an arrow function bound to a const', () => {
      expect(
        extract(`
          const dispatch = (req) => {
            if (req.method === 'GET' && pathname === '/api/ping') { return 1 }
          }
        `),
      ).toMatchObject([{ routePath: '/api/ping', handlerName: 'dispatch' }]);
    });

    it('reports no handler for a top-level comparison', () => {
      expect(extract(`if (pathname === '/api/top') { go() }`)).toMatchObject([
        { routePath: '/api/top', handlerName: undefined },
      ]);
    });
  });

  describe('per-file dedup', () => {
    it('collapses a repeated (url, verb) pair', () => {
      const routes = extract(`
        function handle(req) {
          if (req.method === 'GET' && pathname === '/api/a') { return 1 }
          if (req.method === 'GET' && pathname === '/api/a') { return 2 }
        }
      `);
      expect(routes).toHaveLength(1);
    });

    it('keeps distinct verbs on the same URL as separate routes', () => {
      expect(
        extract(`
          function handle(req) {
            if (req.method === 'GET' && pathname === '/api/a') { return 1 }
            if (req.method === 'DELETE' && pathname === '/api/a') { return 2 }
          }
        `),
      ).toHaveLength(2);
    });
  });

  // Whole-repo reconciliation. Deliberately NOT per-file: the reporting repo
  // keeps its path table (`isKnownApiPath`) in one module and its handlers in
  // sixteen others, so a per-file rule sees each half separately and the map
  // ends up listing every route twice — once verb-less with the table as its
  // "handler", once properly. Measured there: 94 routes, 34 of them shadows.
  describe('cross-file reconciliation', () => {
    const route = (routePath: string, httpMethod: string, source = DISPATCH_GUARD_SOURCE) => ({
      routePath,
      httpMethod,
      source,
    });

    it('drops a verb-less guard route when another file claims the URL with a verb', () => {
      expect(
        reconcileDispatchGuardRoutes([
          route('/api/live/health', ''), // the table
          route('/api/live/health', 'GET'), // the handler
        ]),
      ).toEqual([route('/api/live/health', 'GET')]);
    });

    it('keeps a verb-less guard route no verb claims', () => {
      const only = [route('/api/plans/examples/{param1}', ''), route('/api/other', 'GET')];
      expect(reconcileDispatchGuardRoutes(only)).toEqual(only);
    });

    it('keeps every verb on a multi-verb URL', () => {
      const multi = [route('/api/x', 'GET'), route('/api/x', 'POST'), route('/api/x', '')];
      expect(reconcileDispatchGuardRoutes(multi)).toEqual([
        route('/api/x', 'GET'),
        route('/api/x', 'POST'),
      ]);
    });

    // A framework route without a verb is method-agnostic BY DECLARATION — a
    // Django function view, a Laravel resource. That is a fact, not a weaker
    // observation of the same thing, so the rule must not reach it.
    it('never drops a non-dispatch-guard route', () => {
      const mixed = [
        { routePath: '/api/x', httpMethod: '', source: undefined },
        route('/api/x', 'GET'),
      ];
      expect(reconcileDispatchGuardRoutes(mixed)).toEqual(mixed);
    });

    it('does not let a framework verb suppress a guard route', () => {
      const mixed = [
        route('/api/x', ''),
        { routePath: '/api/x', httpMethod: 'GET', source: undefined },
      ];
      expect(reconcileDispatchGuardRoutes(mixed)).toEqual(mixed);
    });
  });

  // Both providers are wired to this extractor, and TypeScript is where the
  // grammar can differ — an annotated parameter, a non-null assertion, an `as`
  // cast all wrap nodes the rules read. Asserted rather than assumed.
  describe('TypeScript', () => {
    const tsParser = new Parser();
    tsParser.setLanguage(TypeScript.typescript);

    const tsPaths = (source: string): string[] =>
      extractDispatchGuardRoutes(tsParser.parse(source), 'src/server/routes.ts').map(
        (r) => r.routePath,
      );

    it('extracts through annotated parameters', () => {
      expect(
        tsPaths(`
          export async function handle(req: IncomingMessage, pathname: string): Promise<void> {
            if (req.method === 'GET' && pathname === '/api/live/portfolio') { return }
          }
        `),
      ).toEqual(['/api/live/portfolio']);
    });

    it('extracts a switch on a typed discriminant', () => {
      expect(
        tsPaths(`
          function handle(pathname: string): number {
            switch (pathname) {
              case '/api/health': return 1
              default: return 0
            }
          }
        `),
      ).toEqual(['/api/health']);
    });

    it('folds a typed constant', () => {
      expect(
        tsPaths(
          'const BASE: string = "/api/v1"\n' +
            'function handle(pathname: string) { if (pathname === `${BASE}/orders`) { return 1 } }',
        ),
      ).toEqual(['/api/v1/orders']);
    });
  });

  describe('path expressions the rule accepts', () => {
    it('accepts a member access ending in .pathname', () => {
      expect(
        paths(`function f(req) { if (new URL(req.url, base).pathname === '/api/x') { return 1 } }`),
      ).toEqual(['/api/x']);
    });

    it('accepts raw req.url', () => {
      expect(paths(`function f(req) { if (req.url === '/api/x') { return 1 } }`)).toEqual([
        '/api/x',
      ]);
    });

    it('rejects a bare .url on an unrelated receiver', () => {
      // `link.url` is not a request path; only req/request carry the raw form.
      expect(paths(`function f(link) { if (link.url === '/api/x') { return 1 } }`)).toEqual([]);
    });
  });
});
