import itertools
import click
import json
import sys

def add_completion_spec_command(cli):
    if isinstance(cli, click.Group):
      command = click.Command("generate-fig-spec", callback=lambda: print(generate_completion_spec(cli)))
      cli.add_command(command)
    else:
      sys.exit("""
Cannot add the completion spec command to an object which is not a `click.Group` instance.
- If you are tring to add the command to a `click.Command` instance use `generate_completion_spec` 
  or convert `click.Command` to a `click.Group` instance.
- Useful links: https://click.palletsprojects.com/en/8.0.x/commands/#commands-and-groups
""")



def generate_completion_spec(cli):
    with click.Context(cli) as ctx:
        info = ctx.to_info_dict()
        out = generate_command(info["command"])
        return get_template(out)

def get_value(dict, keys):
    for key in keys:
        value = dict[key]
        if value:
            return value

    return None

def flat_map(fn, iterables):
    return itertools.chain.from_iterable(map(fn, iterables))

def generate_options(option):

    opt = {
        "name": option["opts"],
    }

    if option["help"] and len(option["help"]) > 0:
        opt["description"] = option["help"]

    if option["required"]:
        opt["isRequired"] = True

    if option["multiple"]:
        opt["isRepeatable"] = True

    if not option["is_flag"]:
        opt["args"] = list(generate_args(option["type"], option["nargs"]))

    if len(option["secondary_opts"]) > 0:
        opt["exclusiveOn"] = option["secondary_opts"]
        inverse = opt.copy()
        inverse["name"] = option["secondary_opts"]
        inverse["exclusiveOn"] = opt["name"]
        return [
            opt,
            inverse
        ]

    return [ opt ]

# https://click.palletsprojects.com/en/8.0.x/parameters/
def generate_args(info, nargs):
    param_type = info["param_type"]

    arg = {
        "name": info.get("name", "argument")
    }

    if param_type == "Choice":
        arg["suggestions"] = info["choices"]
    elif param_type == "Tuple":
        return list(map(lambda raw: { "name": raw["name"] }, info["types"]) or [])
    elif param_type == "File":
        arg["template"] = "filepaths"
    elif param_type == "Path":

        templates = []

        if info["dir_okay"]:
            templates.append("folders")
        if info["file_okay"]:
            templates.append("filepaths")

        if len(templates) > 0:
            arg["template"] = templates

        if not info["exists"]:
            arg["suggestCurrentToken"] = True


    if nargs == -1:
        arg["isVariadic"] = True
        nargs = 1
    
    return [ arg ] * nargs


def generate_command(command, integration_command_name="generate-fig-spec"):
    command_name = command.get("name", "")
    if command_name == integration_command_name:
        return None

    spec = {
        "name": command_name,
    }

    description = get_value(command, ["help", "short_help"])
    if description and len(description) > 0:
        spec["description"] = description
    
    if command["deprecated"]:
        spec["deprecated"] = True

    if command["hidden"]:
        spec["hidden"] = True

    # get options
    raw_options = filter(lambda param: param["param_type_name"] == "option", command.get("params", []))

    options = list(flat_map(lambda raw: generate_options(raw), raw_options) or [])
    if len(options) > 0:
        spec["options"] = options

    # get args
    raw_args = filter(lambda param: param["param_type_name"] == "argument", command.get("params", []))
    args = list(flat_map(lambda raw: generate_args(raw["type"], raw["nargs"]), raw_args) or [])

    if len(args) > 0:
        spec["args"] = args


    # get subcommands
    subcommands = list(map(lambda raw: generate_command(raw), (command.get("commands", {})).values()))
    if len(subcommands) > 0:
        spec["subcommands"] = list(filter(lambda sub: sub != None, subcommands))

    return spec

def get_template(spec):
    return """
// Autogenerated by click_complete_fig
const completionSpec: Fig.Spec = {}

export default completionSpec;
""".format(json.dumps(spec, indent=2))