#!/usr/bin/env python3
"""Fix the Router scrollTo useLayoutEffect so it does not crash on Chrome 150+.

Chrome 150 changed window.scrollTo() to return a non-undefined value. The DAI
UI bundle calls it from a concise-body arrow inside useLayoutEffect:

    useLayoutEffect(() => window.scrollTo(0, 0))

React then treats that returned value as the effect's cleanup function. On
mount/commit it tries to call it and the app dies with either
"useLayoutEffect must not return anything besides a function" (dev) or
"TypeError: r is not a function" (prod), producing a blank page.

The fix is to give the arrow a BLOCK body so nothing is implicitly returned:

    useLayoutEffect(() => { window.scrollTo(0, 0) })

Usage:
    python patchUseLayoutEffect.py <directory>

Examples:
    26.x / current : "C:/Program Files/Digital Automation Intelligence/www/ui-assets"
    7.x / 25.x     : "C:/Program Files/Digital Automation Intelligence/www/static/js"

This script is safe to run repeatedly (idempotent) and it will REPAIR files
that were corrupted by the earlier version of this patch.
"""

from __future__ import annotations

import argparse
from pathlib import Path

# 1) Fresh install: concise-body arrow -> block body. No deps argument is added,
#    so this stays valid whether or not the build wraps the arrow in parens.
FRESH_SEARCH = "()=>window.scrollTo(0,0)"
FRESH_REPLACE = "()=>{window.scrollTo(0,0)}"

# 2) Repair: the earlier patch appended ",[]" which, inside a wrapped arrow,
#    became a comma/sequence expression -> useLayoutEffect([]) -> crash.
#    This exact double-paren form only exists in the corrupted (wrapped) builds,
#    so it will not touch a build that was patched correctly.
BROKEN_SEARCH = "((()=>{window.scrollTo(0,0)},[]))"
BROKEN_REPLACE = "(()=>{window.scrollTo(0,0)})"


def process_file(file_path: Path) -> int:
    """Repair/patch one file and return the number of replacements made."""
    try:
        content = file_path.read_text(encoding="utf-8")
    except (UnicodeDecodeError, OSError):
        return 0

    repaired = content.count(BROKEN_SEARCH)
    patched = content.count(FRESH_SEARCH)
    total = repaired + patched
    if total == 0:
        return 0

    updated = content.replace(BROKEN_SEARCH, BROKEN_REPLACE)
    updated = updated.replace(FRESH_SEARCH, FRESH_REPLACE)

    # Keep a one-time backup so the change can be rolled back.
    backup = file_path.with_suffix(file_path.suffix + ".bak")
    if not backup.exists():
        backup.write_text(content, encoding="utf-8")

    file_path.write_text(updated, encoding="utf-8")

    if repaired:
        print(f"  repaired {repaired} corrupted sequence-expression call(s)")
    if patched:
        print(f"  patched {patched} concise-body call(s)")

    return total


def run(directory: Path) -> int:
    if not directory.exists() or not directory.is_dir():
        print(f"Error: '{directory}' is not a valid directory.")
        return 1

    total_files = 0
    total_replacements = 0

    for path in directory.rglob("*"):
        if not path.is_file() or path.suffix == ".bak":
            continue

        replacements = process_file(path)
        if replacements > 0:
            total_files += 1
            total_replacements += replacements
            print(f"Updated: {path} ({replacements} replacement(s))")

    print("\nSummary")
    print(f"Files updated: {total_files}")
    print(f"Total replacements: {total_replacements}")

    if total_replacements > 0:
        print(
            "\nIMPORTANT: the bundle filename (e.g. main.<hash>.js) did NOT change, "
            "so browsers/proxies will keep serving the OLD cached copy.\n"
            "In each affected browser do a hard refresh (Ctrl+F5) or clear site data, "
            "and unregister any service worker, then reload. Otherwise the page will "
            "still look broken even though the file on disk is fixed."
        )

    if total_replacements == 0:
        print(
            "\nNo matching code found. Either the path is wrong, or the bundle "
            "is already fixed. Double-check the directory (ui-assets vs static/js)."
        )

    return 0


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description=(
            "Fix (and repair) the Router scrollTo useLayoutEffect so DAI does "
            "not show a blank page on Chrome 150+."
        )
    )
    parser.add_argument("directory", type=Path, help="Directory to scan recursively")
    return parser.parse_args()


if __name__ == "__main__":
    args = parse_args()
    raise SystemExit(run(args.directory))
