"""
Strip `currentFixture` local-variable and instance-field aliases.

Older test projects routinely cached the cucumber World in a local
variable or page-object field:

    const currentFixture = fixture();
    currentFixture.loginPage.click();
    await currentFixture.locatorHealer.X(...);

…or as a private field on page-object/helper classes:

    private currentFixture: PageFixture;
    private currentFixture: ReturnType<typeof fixture>;
    constructor(page: Page) {
        this.currentFixture = fixture();
        // …
    }

Both patterns are pure aliases — `fixture()` is a one-line property
read with no measurable overhead, so caching it adds noise without
buying anything. This transformer removes them in three passes:

  1. Rewrite `<x>currentFixture.<member>` → `fixture().<member>` where
     `<x>` is empty (local var) or `this\\.` (page-object field).
     Negative lookbehind preserves `global.currentFixture` (the
     bootstrap mechanism — `fixture()` itself reads from it).

    1b. Rewrite remaining bare `currentFixture` identifier usages to
            `fixture()` when they are used as values (e.g. destructuring or
            argument passing), while preserving declarations / typed params.

  2. Delete `const|let|var currentFixture = fixture();` declarations.

  3. Delete page-object/helper field declarations
      (`private|public|protected currentFixture: PageFixture;` or
        `private|public|protected currentFixture: ReturnType<typeof fixture>;`) and
     the matching constructor assignments
     (`this.currentFixture = fixture();`).

Idempotent — running on already-clean content is a no-op.
"""
from __future__ import annotations
import re

# 1. Direct access rewrite. The left-side anchor `(?<![\w$.])` refuses to
#    match if `currentFixture` is preceded by a word char or a dot, which
#    skips `global.currentFixture.X` and `someObj.currentFixture.X` patterns
#    safely. `this\.` is consumed when present so `this.currentFixture.X`
#    → `fixture().X` (not `fixture().this.X`).
_ACCESS_RE = re.compile(
    r"(?<![\w$.])(?:this\s*\.\s*)?currentFixture\s*\.\s*([A-Za-z_$][\w$]*)",
)

# 2. Local var declarations.
_LOCAL_DECL_RE = re.compile(
    r"^[ \t]*(?:const|let|var)\s+currentFixture\s*=\s*fixture\(\)\s*;\s*\n",
    re.MULTILINE,
)

# 2b. Bare identifier usages that remain after the member-access rewrite,
# e.g. `const { env } = currentFixture;` or `fn(currentFixture)`. Skip typed
# declarations / params (`currentFixture: T`) and property access handled by
# _ACCESS_RE (`currentFixture.foo`).
_BARE_IDENTIFIER_RE = re.compile(
    r"(?<![\w$.])currentFixture(?![\w$])(?!\s*:)(?!\s*\.)"
)

# 3a. Page-object/helper field declarations.
_FIELD_DECL_RE = re.compile(
    r"^[ \t]*(?:private|public|protected|readonly)\s+currentFixture\s*:\s*"
    r"(?:PageFixture|ReturnType\s*<\s*typeof\s+fixture\s*>)\s*;\s*\n",
    re.MULTILINE,
)

# 3b. Constructor assignments `this.currentFixture = fixture();`.
_CTOR_ASSIGN_RE = re.compile(
    r"^[ \t]*this\.currentFixture\s*=\s*fixture\(\)\s*;\s*\n",
    re.MULTILINE,
)


def remove_currentfixture_aliases(content: str) -> tuple[str, list[str]]:
    """
    Apply the three passes. Returns `(new_content, changes)` where `changes`
    is a list of short human-readable change descriptions.
    """
    changes: list[str] = []

    def access_sub(m: re.Match) -> str:
        return f"fixture().{m.group(1)}"

    new_content, n_access = _ACCESS_RE.subn(access_sub, content)
    if n_access:
        changes.append(f"currentFixture.X → fixture().X ({n_access} call sites)")

    new_content, n_local = _LOCAL_DECL_RE.subn("", new_content)
    if n_local:
        changes.append(f"Removed {n_local} `const currentFixture = fixture();` declaration(s)")

    new_content, n_bare = _BARE_IDENTIFIER_RE.subn("fixture()", new_content)
    if n_bare:
        changes.append(f"currentFixture → fixture() ({n_bare} bare identifier use sites)")

    new_content, n_field = _FIELD_DECL_RE.subn("", new_content)
    if n_field:
        changes.append(f"Removed {n_field} `currentFixture` field alias declaration(s)")

    new_content, n_ctor = _CTOR_ASSIGN_RE.subn("", new_content)
    if n_ctor:
        changes.append(f"Removed {n_ctor} `this.currentFixture = fixture();` constructor assignment(s)")

    return new_content, changes
