#!/usr/bin/env python3
import argparse
import json
import os
import subprocess
import sys
from typing import NoReturn

sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

os.chdir(os.path.join(os.path.dirname(__file__), "../web"))


from version import ZULIP_VERSION


def build_for_prod_or_puppeteer(quiet: bool, config_name: str | None = None) -> NoReturn:
    """Builds for production, writing the output to disk"""

    with open("/proc/meminfo") as meminfo:
        if int(next(meminfo).split()[1]) < 3 * 1024 * 1024:
            os.environ["NODE_OPTIONS"] = "--max-old-space-size=1536"
    webpack_args = [
        "../node_modules/.bin/webpack-cli",
        "--mode=production",
        f"--env=ZULIP_VERSION={ZULIP_VERSION}",
    ]
    if quiet:
        webpack_args += ["--stats=errors-only"]
    if config_name is not None:
        webpack_args += [f"--config-name={config_name}"]

    # Silence warnings from "browserslist" about using old data; those
    # warnings are only useful for development
    os.environ["BROWSERSLIST_IGNORE_OLD_DATA"] = "1"

    os.execvp(webpack_args[0], webpack_args)


def build_for_dev_server(host: str, port: str, minify: bool, disable_host_check: bool) -> None:
    """watches and rebuilds on changes, serving files from memory via webpack-dev-server"""

    # This is our most dynamic configuration, which we use for our
    # dev server.  The key piece here is that we identify changes to
    # files as devs make edits and serve new assets on the fly.
    webpack_args = ["../node_modules/.bin/webpack-cli", "serve"]
    webpack_args += [
        # webpack-cli has a bug where it ignores --watch-poll with
        # multi-config, and we don't need the katex part anyway.
        "--config-name=frontend",
        f"--host={host}",
        f"--port={port}",
        # We add the hot flag using the cli because it takes care
        # of addition to entry points and adding the plugin
        # automatically
        "--hot",
    ]
    if minify:
        webpack_args.append("--env=minimize")
    if disable_host_check:
        webpack_args.append("--allowed-hosts=all")
    else:
        webpack_args.append(f"--allowed-hosts={host},.zulipdev.com,.zulipdev.org")

    # Tell webpack-dev-server to fall back to periodic polling on
    # filesystems where inotify is known to be broken.
    cwd = os.getcwd()
    inotify_broken = False
    with open("/proc/self/mounts") as mounts:
        for line in mounts:
            fields = line.split()
            if fields[1] == cwd:
                inotify_broken = fields[2] in ["nfs", "vboxsf", "prl_fs"]
    if inotify_broken:
        os.environ["CHOKIDAR_USEPOLLING"] = "1"
        os.environ["CHOKIDAR_INTERVAL"] = "1000"

    # TODO: This process should also fall back to periodic polling if
    # inotify_broken.
    import pyinotify

    try:
        webpack_process = subprocess.Popen(webpack_args)

        class WebpackConfigFileChangeHandler(pyinotify.ProcessEvent):
            def process_default(self, event: pyinotify.Event) -> None:
                nonlocal webpack_process
                print("Restarting webpack-dev-server due to config changes...")
                webpack_process.terminate()
                webpack_process.wait()
                webpack_process = subprocess.Popen(webpack_args)

        watch_manager = pyinotify.WatchManager()
        event_notifier = pyinotify.Notifier(watch_manager, WebpackConfigFileChangeHandler())
        # We did a chdir to the root of the Zulip checkout above.
        for filepath in [
            "webpack.config.ts",
            "webpack.assets.json",
            "webpack.dev-assets.json",
        ]:
            watch_manager.add_watch(filepath, pyinotify.IN_MODIFY)
        event_notifier.loop()
    finally:
        webpack_process.terminate()
        webpack_process.wait()


def build_for_most_tests() -> None:
    """Generates a stub asset stat file for django so backend test can render a page"""

    # Tests like test-backend, test-api, and test-home-documentation use
    # our "test" configuration.  The one exception is Puppeteer, which uses
    # our production configuration.
    #
    # It's not completely clear why we don't also use the same
    # configuration for ALL tests, but figuring out the full history here
    # was out of the scope of the effort here to add some comments and
    # clean up names.
    with open("webpack.assets.json") as json_data:
        entries = json.load(json_data)

    with open("webpack.dev-assets.json") as json_data:
        entries.update(json.load(json_data))

    stat_data = {
        "status": "done",
        "chunks": {entry: [f"{entry}-stubentry.js"] for entry in entries},
    }
    directory = "../var"
    if not os.path.exists(directory):
        os.makedirs(directory)
    with open(os.path.join(directory, "webpack-stats-test.json"), "w") as outfile:
        json.dump(stat_data, outfile)


parser = argparse.ArgumentParser()
parser.add_argument(
    "--test",
    action="store_true",
    help="generate a stub webpack-stats.json file (for backend testing)",
)
parser.add_argument("--quiet", action="store_true", help="Minimizes webpack output while running")
parser.add_argument(
    "--watch", action="store_true", help="watch for changes to source files (for development)"
)
parser.add_argument(
    "--host", default="127.0.0.1", help="set the host for the webpack server to run on"
)
parser.add_argument("--port", default="9994", help="set the port for the webpack server to run on")
parser.add_argument(
    "--minify", action="store_true", help="Minify and optimize the assets (for development)"
)
parser.add_argument(
    "--disable-host-check", action="store_true", help="Disable host check for webpack-dev-server"
)
parser.add_argument("--config-name", help="Limit production building to only one config-name")

args = parser.parse_args()
if "PUPPETEER_TESTS" in os.environ:
    build_for_prod_or_puppeteer(args.quiet)
elif args.test:
    build_for_most_tests()
elif args.watch:
    build_for_dev_server(args.host, args.port, args.minify, args.disable_host_check)
else:
    build_for_prod_or_puppeteer(args.quiet, args.config_name)
