import re
import sys
import subprocess
from pathlib import Path

VERBOSE = False
BVERBOSE = False


def log(*args):
    if VERBOSE:
        print(*args)


# ------------------------------------------------------------
# Parsing
# ------------------------------------------------------------

def parse_mfg(file_content: str):
    groups = {}
    args_blocks = []
    raw_run = []
    raw_var = {}

    lines = file_content.split("\n")
    current_group = None
    current_block = None
    current_args = None
    in_code = False
    code_buffer = []
    variables = {}

    def trim(line):
        return line.split("//")[0].strip()

    for i, raw_line in enumerate(lines):
        line = trim(raw_line)
        if not line:
            continue

        log(f"[Line {i+1}] {line}")

        # ------------------- GROUP -------------------
        if line.startswith("group "):
            m = re.match(r"group (\w+)\s*\{", line)
            if not m:
                raise Exception(f"Invalid group at line {i+1}")
            name = m.group(1)
            current_group = name
            groups[name] = {"blocks": [], "vars": {}}
            variables = groups[name]["vars"]
            log(" -> New group:", name)
            continue

        # ------------------- VAR IN GROUP -------------------
        if line.startswith("var ") and current_group and not current_block and not current_args:
            m = re.match(r'var (\w+)\s*=\s*(.+)', line)
            if m:
                name, val = m.group(1), m.group(2)
                variables[name] = val.strip('"')
                log(f" -> Group var: {name}={variables[name]}")
                continue

        # ------------------- VAR (TOP LEVEL) -------------------
        if line.startswith("var ") and not current_group and not current_block and not current_args:
            m = re.match(r'var (\w+)\s*=\s*(.+)', line)
            if m:
                name, val = m.group(1), m.group(2)
                raw_var[name] = val.strip('"')
                log(f" -> Raw var: {name}={raw_var[name]}")
                continue

        # ------------------- BLOCK -------------------
        if line.startswith("block "):
            m = re.match(r"block (\w+)\.(\w+)\s*\{", line)
            if not m:
                raise Exception(f"Invalid block at line {i+1}")
            name, btype = m.group(1), m.group(2)
            current_block = {
                "name": name,
                "type": btype,
                "code": "",
                "run": None,
                "vars": {}
            }
            in_code = False
            code_buffer = []
            log(f" -> New block {name}.{btype}")
            continue

        # ------------------- START CODE BLOCK -------------------
        if current_block and line.startswith("code {"):
            in_code = True
            code_buffer = []
            log(" -> Entering block code")
            continue

        # ------------------- END CODE BLOCK -------------------
        if current_block and in_code and line == "}":
            current_block["code"] = "\n".join(code_buffer)
            in_code = False
            log(" -> Exiting block code")
            continue

        # ------------------- RUN IN BLOCK -------------------
        if current_block and line.startswith("run") and not in_code:
            m = re.match(r'run\s*"(.+)"', line)
            if m:
                current_block["run"] = m.group(1)
                log(" -> Block run:", current_block["run"])
            continue

        # ------------------- TOP LEVEL RUN -------------------
        if not current_block and not current_args and line.startswith("run"):
            m = re.match(r'run\s*"(.+)"', line)
            if m:
                raw_run.append(m.group(1))
                log(" -> Raw run:", m.group(1))
            continue

        # ------------------- INSIDE BLOCK CODE -------------------
        if current_block and in_code:
            code_buffer.append(line)
            continue

        # ------------------- BLOCK END -------------------
        if current_block and line == "}":
            groups[current_group]["blocks"].append(current_block)
            log(" -> End block")
            current_block = None
            continue

        # ------------------- GROUP END -------------------
        if current_group and not current_block and not current_args and line == "}":
            current_group = None
            continue

        # ------------------- ARGS BLOCK -------------------
        if line.startswith("args "):
            m = re.match(r"args (\d+): (\w+)\s*\{", line)
            if not m:
                raise Exception(f"Invalid args block at line {i+1}")
            index, grp = int(m.group(1)), m.group(2)
            current_args = {
                "index": index,
                "group": grp,
                "optional": "optional" in line,
                "vars": {},
                "runs": [],
                "code_buffer": "",
                "in_code": False
            }
            args_blocks.append(current_args)
            log(f" -> New args block {grp} [{index}]")
            continue

        # ------------------- ARGS CODE START -------------------
        if current_args and line.startswith("code {"):
            current_args["in_code"] = True
            current_args["_buffer"] = []
            log(" -> Args code start")
            continue

        # ------------------- ARGS CODE END -------------------
        if current_args and current_args["in_code"] and line == "}":
            current_args["code_buffer"] = "\n".join(current_args["_buffer"])
            current_args["in_code"] = False
            continue

        # ------------------- ARGS CODE LINES -------------------
        if current_args and current_args["in_code"]:
            current_args["_buffer"].append(line)
            continue

        # ------------------- ARGS RUN -------------------
        if current_args and line.startswith("run"):
            m = re.match(r'run\s*"(.+)"', line)
            if m:
                current_args["runs"].append(m.group(1))
                log(" -> Args run:", m.group(1))
            continue

        # ------------------- ARGS VAR -------------------
        if current_args and line.startswith("var "):
            m = re.match(r'var (\w+)\s*=\s*(.+)', line)
            if m:
                name, val = m.group(1), m.group(2).strip('"')
                current_args["vars"][name] = val
                continue

        # ------------------- END ARGS BLOCK -------------------
        if current_args and line == "}":
            log(" -> End args block")
            current_args = None
            continue

    return groups, args_blocks, raw_run, raw_var


# ------------------------------------------------------------
# Variable Expansion
# ------------------------------------------------------------

def expand(text, vars, block_name, groups):
    if not isinstance(text, str):
        text = str(text)

    def repl(match):
        tag = match.group(1)
        if tag.startswith("var:"):
            return vars.get(tag[4:], "")
        if tag == "arg":
            return vars.get("arg", "")
        if tag.startswith("block:"):
            return tag[6:]
        if tag.startswith("group:"):
            g = tag[6:]
            grp = groups.get(g)
            if not grp:
                return ""
            return " ".join(f"{b['name']}.{b['type']}" for b in grp["blocks"])
        return match.group(0)

    return re.sub(r"\$(\w+:\w+|\w+)", repl, text)


# ------------------------------------------------------------
# Execution Functions
# ------------------------------------------------------------

def write_blocks(groups):
    for gname, grp in groups.items():
        for blk in grp["blocks"]:
            code = expand(blk["code"], grp["vars"], blk["name"], groups)
            filename = f"{blk['name']}.{blk['type']}"
            log("Writing:", filename)
            Path(filename).write_text(code, encoding="utf8")


def run_blocks(groups):
    for gname, grp in groups.items():
        for blk in grp["blocks"]:
            if blk["run"]:
                cmd = expand(blk["run"], grp["vars"], blk["name"], groups)
                log("Running block:", cmd)
                subprocess.run(cmd, shell=True, check=True)


def run_args_blocks(args_blocks, groups):
    argv = sys.argv
    for ab in args_blocks:
        idx = ab["index"] + 1 + (1 if BVERBOSE else 0)
        arg_given = argv[idx] if idx < len(argv) else ""

        if not arg_given or arg_given != ab["group"]:
            continue

        ctx = {**ab["vars"], "arg": arg_given}

        if ab["code_buffer"]:
            codefile = f"{ab['group']}.args.py"
            Path(codefile).write_text(expand(ab["code_buffer"], ctx, None, groups))

        for cmd in ab["runs"]:
            subprocess.run(expand(cmd, ctx, None, groups), shell=True, check=True)


def run_raw(raw_run, raw_var, groups):
    for cmd in raw_run:
        exec_cmd = expand(cmd, {**raw_var, "arg": None}, None, groups)
        log("Running raw:", exec_cmd)
        subprocess.run(exec_cmd, shell=True, check=True)


# ------------------------------------------------------------
# Main Compiler
# ------------------------------------------------------------

def run_compiler(filename):
    content = Path(filename).read_text(encoding="utf8")
    groups, args_blocks, raw_run, raw_var = parse_mfg(content)

    write_blocks(groups)
    run_blocks(groups)
    run_args_blocks(args_blocks, groups)
    run_raw(raw_run, raw_var, groups)


# ------------------------------------------------------------
# CLI
# ------------------------------------------------------------

if __name__ == "__main__":
    argv = sys.argv

    if "-v" in argv or "--verbose" in argv:
        VERBOSE = True

    if len(argv) >= 4 and argv[3] in ("-v", "--verbose"):
        BVERBOSE = True

    mfg_file = next((a for a in argv if a.endswith(".mfg")), None)
    if not mfg_file:
        print("Missing .mfg file")
        sys.exit(1)

    run_compiler(mfg_file)
