#!/usr/bin/env python3
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

import re
import shutil
import sys
from pathlib import Path

# --- Helper Functions for Updating Files ---


def update_versioning_md(file_path: Path, new_version: str):
    """Updates the version table in Versioning.md."""
    print(f"Checking '{file_path.name}' for version updates...")
    if not file_path.exists():
        print(f"Warning: File not found at '{file_path}'. Skipping.")
        return
    content = file_path.read_text()

    # Find the first version number in the markdown table
    match = re.search(r"^\| ([\d.]+) \|", content, re.MULTILINE)
    if not match:
        print(f"Warning: Could not find current version in '{file_path.name}'. Skipping.")
        return

    current_version = match.group(1)
    print(f"Found current version: {current_version}")

    if new_version != current_version:
        print(f"Updating version in '{file_path.name}' to {new_version}...")
        # Prepare the new row by duplicating the header separator line's structure
        header_separator_match = re.search(r"(\r\n?|\n)(\|---\|.*)", content)
        if not header_separator_match:
            print(f"Warning: Could not find table header separator in '{file_path.name}'. Skipping.")
            return

        header_separator = header_separator_match.group(2)
        # Create a new row based on the separator, replacing dashes with spaces and adding the version
        new_row_parts = [" " + part.replace("-", " ") + " " for part in header_separator.split("|")]
        new_row_parts[1] = f" {new_version} "  # Set the new version
        new_row = "|".join(new_row_parts)

        # Insert the new row right after the header separator line
        insertion_point = header_separator_match.end(0)
        new_content = content[:insertion_point] + "\n" + new_row + content[insertion_point:]
        file_path.write_text(new_content)
        print("Update complete.")
    else:
        print("Version is already up to date.")


def update_readme_rst(file_path: Path, new_version: str):
    """Updates the release history in the Python README.rst."""
    print(f"Checking '{file_path.name}' for version updates...")
    if not file_path.exists():
        print(f"Warning: File not found at '{file_path}'. Skipping.")
        return
    content = file_path.read_text()

    # Find the first version header in the file
    match = re.search(r"^([\d.]+)", content, re.MULTILINE)
    if not match:
        print(f"Warning: Could not find current version in '{file_path.name}'. Skipping.")
        return

    current_version = match.group(1)
    print(f"Found current version: {current_version}")

    if new_version != current_version:
        print(f"Updating version in '{file_path.name}' to {new_version}...")
        new_header = f"{new_version}\n{'^' * len(new_version)}"
        release_notes = f"Release Notes : https://github.com/Microsoft/onnxruntime/releases/tag/v{new_version}"
        new_section = f"{new_header}\n\n{release_notes}\n\n"

        # Insert the new section before the first version header found
        insertion_point = match.start(0)
        new_content = content[:insertion_point] + new_section + content[insertion_point:]
        file_path.write_text(new_content)
        print("Update complete.")
    else:
        print("Version is already up to date.")


def update_init_py(file_path: Path, new_version: str):
    """Updates the __version__ variable in the project's __init__.py."""
    print(f"Checking '{file_path.name}' for version updates...")
    if not file_path.exists():
        print(f"Warning: File not found at '{file_path}'. Skipping.")
        return
    content = file_path.read_text()

    # Find the __version__ line
    match = re.search(r"__version__\s*=\s*[\"']([\d.]+)[\"']", content)
    if not match:
        print(f"Warning: Could not find __version__ in '{file_path.name}'. Skipping.")
        return

    current_version = match.group(1)
    print(f"Found current version: {current_version}")

    if new_version != current_version:
        print(f"Updating version in '{file_path.name}' to {new_version}...")
        new_content = re.sub(r"__version__\s*=\s*[\"'][\d.]+[\"']", f'__version__ = "{new_version}"', content)
        file_path.write_text(new_content)
        print("Update complete.")
    else:
        print("Version is already up to date.")


def update_npm_packages(js_root: Path, new_version: str):
    """Updates versions for all NPM packages in the js directory."""
    print("\nUpdating NPM package versions...")

    # This script assumes a 'util' module is available in the search path.
    try:
        from util import is_windows  # noqa: PLC0415
        from util import run as run_command  # noqa: PLC0415
    except ImportError:
        print("Error: Could not import 'is_windows' and 'run' from a 'util' module.", file=sys.stderr)
        print("Please ensure the 'util' module is in Python's search path.", file=sys.stderr)
        return

    command_prefix = []
    # Check if node and npm are directly available in the system's PATH.
    if shutil.which("node") and shutil.which("npm"):
        print("Found node and npm in PATH.")
    # If not, and if on Linux, check if 'fnm' is available.
    elif shutil.which("fnm"):
        print("node/npm not in PATH. Found 'fnm' on Linux, will use it to run commands.")
        nvmrc_path = js_root / ".nvmrc"
        # Check for .nvmrc file.
        if not nvmrc_path.exists():
            print(f"Error: 'fnm' is being used, but the version file '{nvmrc_path}' was not found.", file=sys.stderr)
            print(
                "Please create a .nvmrc file in the 'js' directory with the desired Node.js version.", file=sys.stderr
            )
            return

        node_version = nvmrc_path.read_text().strip()
        print(f"Found node version '{node_version}' in .nvmrc.")

        # Ensure the required node version is installed by fnm.
        print(f"Ensuring Node.js version '{node_version}' is installed via fnm...")
        run_command("fnm", "install", node_version, cwd=js_root)

        print(f"Using Node.js version '{node_version}' with fnm.")
        command_prefix = ["fnm", "exec", f"--using={node_version}", "--"]
    # If neither is available, skip the NPM updates.
    else:
        print("Error: Could not find 'node' and 'npm' in your PATH.", file=sys.stderr)
        if sys.platform.startswith("linux"):
            print("Hint: Install 'fnm' (Fast Node Manager) to manage Node.js versions.", file=sys.stderr)
        print("Skipping NPM package updates.", file=sys.stderr)
        return

    def run_npm(args, cwd):
        """Helper to run npm commands, prepending fnm if necessary."""
        full_command = command_prefix + list(args)
        print(full_command)
        run_command(*full_command, cwd=cwd)

    npm_exe = "npm.cmd" if is_windows() else "npm"
    packages = ["common", "node", "web", "react_native"]

    for package in packages:
        print(f"\n--- Updating package: {package} ---")
        # Use npm's --prefix argument and run from js_root.
        # --allow-same-version prevents an error if the version is already correct.
        run_npm([npm_exe, "--prefix", package, "version", new_version, "--allow-same-version"], cwd=js_root)
        run_npm([npm_exe, "--prefix", package, "install", "--package-lock-only", "--ignore-scripts"], cwd=js_root)

    print("\n--- Finalizing JS versions and formatting ---")
    run_npm([npm_exe, "ci"], cwd=js_root)
    for package in packages:
        run_npm([npm_exe, "run", "update-version", package], cwd=js_root)

    run_npm([npm_exe, "run", "format"], cwd=js_root)
    print("NPM package updates complete.")


# Define repository root relative to the script's location
SCRIPT_DIR = Path(__file__).resolve().parent
REPO_DIR = SCRIPT_DIR.parent.parent


def update_version():
    """Main function to read the new version and orchestrate updates across the project."""
    # Read and validate the new version from VERSION_NUMBER
    version_file = REPO_DIR / "VERSION_NUMBER"
    print(f"Reading new version from '{version_file}'...")
    try:
        new_version = version_file.read_text().strip()
    except FileNotFoundError:
        print(f"Error: '{version_file}' not found.", file=sys.stderr)
        sys.exit(1)

    # Validate that the version is in x.y.z format
    if not re.fullmatch(r"\d+\.\d+\.\d+", new_version):
        print(
            f"Error: Version '{new_version}' from '{version_file.name}' is not a valid x.y.z semantic version.",
            file=sys.stderr,
        )
        sys.exit(1)

    print(f"Target version to set: {new_version}\n")

    # Update files using absolute paths from REPO_DIR
    update_versioning_md(REPO_DIR / "docs" / "Versioning.md", new_version)
    update_readme_rst(REPO_DIR / "docs" / "python" / "README.rst", new_version)
    update_init_py(REPO_DIR / "onnxruntime" / "__init__.py", new_version)

    # Update all NPM packages
    update_npm_packages(REPO_DIR / "js", new_version)


if __name__ == "__main__":
    update_version()
