"""CLI utility for formatting Tcl code."""

import argparse
import pathlib
import sys

from tclint.cli.utils import resolve_sources, register_codec_warning
from tclint.config import (
    get_config,
    setup_tclfmt_config_cli_args,
    Config,
    ConfigError,
    RunConfig,
)
from tclint.parser import Parser, TclSyntaxError
from tclint.format import Formatter, FormatterOpts

try:
    from tclint._version import __version__  # type: ignore
except ModuleNotFoundError:
    __version__ = "(unknown version)"

# exit code flags
EXIT_OK = 0
EXIT_FORMAT_VIOLATIONS = 1
EXIT_SYNTAX_ERROR = 2
EXIT_INPUT_ERROR = 4


def format(script: str, config: Config, debug=False) -> str:
    plugins = [config.commands] if config.commands is not None else []
    parser = Parser(debug=debug, command_plugins=plugins)

    formatter = Formatter(
        FormatterOpts(
            indent=config.get_indent(),
            spaces_in_braces=config.style_spaces_in_braces,
            max_blank_lines=config.style_max_blank_lines,
            indent_namespace_eval=config.style_indent_namespace_eval,
        )
    )
    return formatter.format_top(script, parser)


def check(path: pathlib.Path, script: str, formatted: str):
    parser = Parser()
    original_tree = parser.parse(script)
    formatted_tree = parser.parse(formatted)
    if original_tree != formatted_tree:
        print(f"Warning: {path} syntax trees don't match", file=sys.stderr)
        print("\n".join(original_tree.diff(formatted_tree)), file=sys.stderr)


def main():
    parser = argparse.ArgumentParser("tclfmt")
    parser.add_argument(
        "--version", action="version", version=f"%(prog)s {__version__}"
    )
    parser.add_argument(
        "source",
        nargs="+",
        help=(
            "files to format. By default, prints formatted files to stdout. Provide '-'"
            " to read from stdin"
        ),
        type=pathlib.Path,
    )

    mode_group = parser.add_argument_group("mode")
    mode_mutex = mode_group.add_mutually_exclusive_group(required=False)
    mode_mutex.add_argument(
        "--in-place", help="update files that require formatting", action="store_true"
    )
    mode_mutex.add_argument(
        "--check",
        help="list files that require formatting and set the exit code",
        action="store_true",
    )
    parser.add_argument(
        "-d",
        "--debug",
        action="count",
        default=0,
        help=(
            "display debug output. Provide additional times to increase the verbosity"
            " of output (e.g. -dd)"
        ),
    )
    parser.add_argument(
        "-c",
        "--config",
        help="path to config file",
        type=pathlib.Path,
        default=None,
        metavar="<path>",
    )
    setup_tclfmt_config_cli_args(parser)
    args = parser.parse_args()

    try:
        config = get_config(args.config, pathlib.Path.cwd())
    except ConfigError as e:
        print(f"Invalid config file: {e}")
        return EXIT_INPUT_ERROR

    if config is None:
        config = RunConfig()

    config.apply_cli_args(args)

    try:
        # TODO: we should eventually allow tclfmt to find a config by walking up
        # directories, at which point exclude_root should be the parent dir of
        # the config file, unless -c is used (eslint rules)
        exclude_root = pathlib.Path.cwd()
        sources = resolve_sources(
            args.source,
            exclude_patterns=config.exclude,
            exclude_root=exclude_root,
            extensions=config.extensions,
        )
    except FileNotFoundError as e:
        print(f"Invalid path provided: {e}")
        return EXIT_INPUT_ERROR

    retcode = EXIT_OK

    register_codec_warning("replace_with_warning")

    reformat_count = 0
    for path in sources:
        if path is None:
            script = sys.stdin.read()
            out_prefix = "(stdin)"
        else:
            with open(path, "r", errors="replace_with_warning") as f:
                script = f.read()
            out_prefix = str(path)

        try:
            formatted = format(
                script, config.get_for_path(path), debug=(args.debug > 1)
            )
            if args.in_place and path:
                with open(path, "w") as f:
                    f.write(formatted)
            elif args.check:
                if script != formatted:
                    print(f"{out_prefix}: needs reformatting")
                    retcode |= EXIT_FORMAT_VIOLATIONS
                    reformat_count += 1
            else:
                if args.in_place:
                    print("Warning: --in-place option ignored when reading from stdin")
                print(formatted, end="")

            if args.debug > 0:
                check(path, script, formatted)
        except TclSyntaxError as e:
            line, col = e.pos
            print(f"{out_prefix}:{line}:{col}: syntax error: {e}", file=sys.stderr)
            retcode |= EXIT_SYNTAX_ERROR
            continue

    if args.check:
        messages = []
        if reformat_count == 0:
            messages.append("Formatting clean!")
        elif reformat_count == 1:
            messages.append("1 file needs reformatting.")
        else:
            messages.append(f"{reformat_count} files need reformatting.")
        messages.append(
            f"Checked {len(sources)} file{'s' if len(sources) != 1 else ''}."
        )
        print(" ".join(messages))

    return retcode


if __name__ == "__main__":
    sys.exit(main())
