"""Utility to generate some complex patterns."""

import copy
import json
import keyword
import sys
from pathlib import Path
from typing import Any

play_keywords = list(
    filter(
        None,
        [
            "any_errors_fatal",
            "become",
            "become_exe",
            "become_flags",
            "become_method",
            "become_user",
            "check_mode",
            "collections",
            "connection",
            "debugger",
            "diff",
            "environment",
            "fact_path",
            "force_handlers",
            "gather_facts",
            "gather_subset",
            "gather_timeout",
            "handlers",
            "hosts",
            "ignore_errors",
            "ignore_unreachable",
            "max_fail_percentage",
            "module_defaults",
            "name",
            "no_log",
            "order",
            "port",
            "post_tasks",
            "pre_tasks",
            "remote_user",
            "roles",
            "run_once",
            "serial",
            "strategy",
            "tags",
            "tasks",
            "throttle",
            "timeout",
            "vars",
            "vars_files",
            "vars_prompt",
        ],
    ),
)


def is_ref_used(obj: Any, ref: str) -> bool:
    """Return a reference use from a schema."""
    ref_use = f"#/$defs/{ref}"
    if isinstance(obj, dict):
        if obj.get("$ref", None) == ref_use:
            return True
        for _ in obj.values():
            if isinstance(_, dict | list) and is_ref_used(_, ref):
                return True
    elif isinstance(obj, list):
        for _ in obj:
            if isinstance(_, dict | list) and is_ref_used(_, ref):
                return True
    return False


if __name__ == "__main__":
    invalid_var_names = sorted(list(keyword.kwlist) + play_keywords)
    if "__peg_parser__" in invalid_var_names:
        invalid_var_names.remove("__peg_parser__")
    print("Updating invalid var names")  # noqa: T201

    with Path("f/vars.json").open("r+", encoding="utf-8") as f:
        vars_schema = json.load(f)
        vars_schema["anyOf"][0]["patternProperties"] = {
            f"^(?!({'|'.join(invalid_var_names)})$)[a-zA-Z_][\\w]*$": {},
        }
        f.seek(0)
        json.dump(vars_schema, f, indent=2)
        f.write("\n")
        f.truncate()

    print("Compiling subschemas...")  # noqa: T201
    with Path("f/ansible.json").open(encoding="utf-8") as f:
        combined_json = json.load(f)

    for subschema in ["tasks", "playbook"]:
        sub_json = copy.deepcopy(combined_json)
        # remove unsafe keys from root
        for key in [
            "$id",
            "id",
            "title",
            "description",
            "type",
            "default",
            "items",
            "properties",
            "additionalProperties",
            "examples",
        ]:
            if key in sub_json:
                del sub_json[key]
        for key in sub_json:
            if key not in ["$schema", "$defs"]:
                print(  # noqa: T201
                    f"Unexpected key found at combined schema root: ${key}",
                )
                sys.exit(2)
        # Copy keys from subschema to root
        for key, value in combined_json["$defs"][subschema].items():
            sub_json[key] = value
        sub_json["$comment"] = "Generated from ansible.json, do not edit."
        sub_json["$id"] = (
            f"https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/{subschema}.json"
        )

        # Remove all unreferenced ($ref) definitions ($defs) recursively
        while True:
            spare = [k for k in sub_json["$defs"] if not is_ref_used(sub_json, k)]
            for k in spare:
                print(f"{subschema}: deleting unused '{k}' definition")  # noqa: T201
                del sub_json["$defs"][k]
            if not spare:
                break

        with Path(f"f/{subschema}.json").open("w", encoding="utf-8") as f:
            json.dump(sub_json, f, indent=2, sort_keys=True)
            f.write("\n")
