# NOTE: this file cannot be in a class. See: https://github.com/pallets/click/issues/601
# https://click.palletsprojects.com/en/8.1.x/commands/#nested-handling-and-contexts

from typing import List, Tuple

import click

# Commands are imported when the function is run, to increase performance.
from todotree.Managers.TaskManager import TaskManager
from todotree.Config.Config import Config
from todotree.MainUtils import MainUtils


@click.group()
@MainUtils.common_options
def root(ctx: click.Context, **kwargs):
    """
    The main list of todotree's command.

    For more information on a command, use `todotree COMMAND --help`.
    """
    # ^ This text also shows up in the help command.
    # Parse options given before the command.
    # So the `--todo-file task.txt` option in the help text example.
    ctx.ensure_object(dict)
    ctx.obj["config"] = Config()
    MainUtils.parse_common_options(ctx.obj["config"], **kwargs)


@root.command("add", short_help="Add a task to the task list")
@click.argument("task", type=str, nargs=-1)
@MainUtils.common_options
def add(ctx: click.Context, task: Tuple, **kwargs):
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.Add import Add

    Add(ctx.obj["config"], TaskManager(ctx.obj["config"])).run(task)


@root.command("addx", short_help="Add a task and immediately mark it as done")
@click.argument("task", type=str, nargs=-1)
@MainUtils.common_options
def add_x(ctx: click.Context, task: Tuple, **kwargs):
    """
    Adds a completed task to done.txt. The task is not added to todo.txt.
    :param task: The task to add to done.txt.
    """
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.AddX import AddX

    AddX(ctx.obj["config"], TaskManager(ctx.obj["config"])).run(task)


@root.command("append", short_help="Append `append_string` to `task_nr`")
@click.argument("task_nr", type=int)
@click.argument("append_string", type=str, nargs=-1)
@MainUtils.common_options
def append(ctx: click.Context, task_nr: int, append_string: Tuple[str], **kwargs):
    """
    Appends the contents of append_string to the task represented by task_nr.
    A space is inserted between the two tasks, so you do not have to worry that words aretogether.

    Example: todotree append 1 "some text"
    """
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.Append import Append

    Append(ctx.obj["config"], TaskManager(ctx.obj["config"])).run(task_nr, append_string)


@root.command("cd", short_help="Print directory of the todo.txt directory")
@MainUtils.common_options
def cd(ctx: click.Context, **kwargs):
    """print directory of the todo.txt directory"""
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.Cd import Cd

    Cd(ctx.obj["config"], TaskManager(ctx.obj["config"])).run()


@root.command("context", short_help="List task in a tree, by context")
@MainUtils.common_options
def context(ctx: click.Context, **kwargs):
    """list a tree, of which the first node is the context, the second nodes are the tasks"""
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.Context import Context

    Context(ctx.obj["config"], TaskManager(ctx.obj["config"])).run()


@root.command("do", short_help="Mark task as done and move it to the done.txt")
@click.argument("task_numbers", type=list, nargs=-1)  # type=list[int]
@MainUtils.common_options
def do(ctx: click.Context, task_numbers: List[Tuple], **kwargs):
    """
    Mark tasks as done, therefor moving them to done.txt with a date stamp of today.
    :param task_numbers: The list of tasks which are completed.
    """

    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.Do import Do

    Do(ctx.obj["config"], TaskManager(ctx.obj["config"])).run(task_numbers)


@root.command("due", short_help="List tasks by their due date")
@MainUtils.common_options
def due(ctx: click.Context, **kwargs):
    """List tasks in a tree by their due date."""
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.Due import Due

    Due(ctx.obj["config"], TaskManager(ctx.obj["config"])).run()


@root.command("edit", short_help="Open the todo.txt in an editor.")
@MainUtils.common_options
def edit(ctx: click.Context, **kwargs):
    """
    Open the todo.txt in an editor for manual editing of tasks.
    This is useful when you need to modify a lot of tasks, which would be complicated when doing it with todotree.
    """
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.Edit import Edit

    Edit(ctx.obj["config"], TaskManager(ctx.obj["config"])).run()


@root.command("filter", short_help="Only show tasks containing the search term.")
@click.argument("search_term")
@MainUtils.common_options
def filter_list(ctx: click.Context, search_term, **kwargs):
    """
    Only show tasks which have search term in them. This can also be a keyword.

    :param search_term: The term to search.
    """
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.Filter import Filter

    Filter(ctx.obj["config"], TaskManager(ctx.obj["config"])).run(search_term)


@root.command("help", short_help="Print help text.")
@click.pass_context
def help_text(ctx: click.Context):
    print(ctx.parent.get_help())


@root.command("init", short_help="Initialize folder for first use.")
@MainUtils.common_options
def init(ctx: click.Context, **kwargs):
    """
    Initializes the files and folders needed for a functioning todotree according to the user's prompts.
    """
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.Init import Init

    Init().run()


@root.command("list", short_help="List tasks")
@MainUtils.common_options
def list_tasks(ctx: click.Context, **kwargs):
    """
    Print a flat list of tasks, sorted by their priority.
    """
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.List import List

    MainUtils.parse_common_options(ctx.obj["config"], **kwargs)
    List(ctx.obj["config"], TaskManager(ctx.obj["config"])).run()


@root.command("list_done", short_help="List tasks which are marked as done")
@MainUtils.common_options
def list_done(ctx: click.Context, **kwargs):
    """
    List tasks which are marked as done. The numbers can be used with the revive command.
    """
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.ListDone import ListDone

    ListDone(ctx.obj["config"], TaskManager(ctx.obj["config"])).run()


@root.command("print_raw", short_help="Print todo.txt without any formatting or filtering")
@MainUtils.common_options
def print_raw(ctx: click.Context, **kwargs):
    """
    Output the todo.txt without any processing.
    This is equivalent to `cat $(todo cd)/todo.txt` in bash.
    """
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.PrintRaw import PrintRaw

    PrintRaw(ctx.obj["config"], TaskManager(ctx.obj["config"])).run()


@root.command("priority", short_help="Set new priority to task")
@click.argument("task_number", type=int)
@click.argument("new_priority", type=str)
@MainUtils.common_options
def priority(ctx: click.Context, task_number, new_priority, **kwargs):
    """
    Adds or updates the priority of the task.
    :param task_number: The task to re-prioritize.
    :param new_priority: The new priority.
    """
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.Priority import Priority

    Priority(ctx.obj["config"], TaskManager(ctx.obj["config"])).run(task_number, new_priority)


@root.command("project", short_help="Print tree by project")
@MainUtils.common_options
def project(ctx: click.Context, **kwargs):
    """
    Print the task list in a tree by project.
    """
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.Project import Project

    Project(ctx.obj["config"], TaskManager(ctx.obj["config"])).run()


@root.command("recur", short_help="Add recurring tasks defined in recur.txt")
@MainUtils.common_options
def recur(ctx: click.Context, **kwargs):
    """
    Add tasks according to recur.txt.

    In recur.txt you can add tasks that have to be done periodically, such as doing taxes.
    The format in recur.txt is:

        `<start date> ; interval ; <end date> ; <task string>`

    The end date is optional. This is a valid line for example:
    2020-01-01 ; yearly ; (C) Celebrate the new year.

    This is certainly not the only program which can do these.
    Different solutions to this command exist, here are a few alternatives:
     - https://www.rememberthemilk.com/
     - https://github.com/smartlitchi/ice-recur
    You can use those with the `addons` command after installing a suitable addon script.
    """
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.Recur import Recur

    Recur(ctx.obj["config"], TaskManager(ctx.obj["config"])).run()


@root.command("revive", short_help="Revive a task that was accidentally marked as done.")
@click.argument("done_number", type=int)
@MainUtils.common_options
def revive(ctx: click.Context, done_number, **kwargs):
    """
    Move a task from done.txt to todo.txt.
    The `done_number` can be found using the `list_done` command.
    :param done_number: The number of the task to revive.
    """
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.Revive import Revive

    Revive(ctx.obj["config"], TaskManager(ctx.obj["config"])).run(done_number)


@root.command("schedule", short_help="Hide task until date.")
@click.argument("task_number", type=int)
@click.argument("new_date", type=str, nargs=-1)
@MainUtils.common_options
def schedule(ctx: click.Context, task_number: int, new_date: Tuple[str], **kwargs):
    """
    hide the task until the date given. If new_date is not in ISO format (yyyy-mm-dd) such as "Next Wednesday",
    then it tries to figure out the date with the `date` program, which is only in linux.
    """
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.Schedule import Schedule

    Schedule(ctx.obj["config"], TaskManager(ctx.obj["config"])).run(new_date, task_number)


@root.group("stale", short_help="Manage the stale.txt task list.")
@MainUtils.common_options
def stale(ctx: click.Context, **kwargs):
    # process for stale.txt
    pass


@stale.command("list", short_help="List the stale tasks.")
@MainUtils.common_options
def stale_list(ctx: click.Context, **kwargs):
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.Stale_list import StaleList
    StaleList(ctx.obj["config"], TaskManager(ctx.obj["config"])).run()


@stale.command("revive", short_help="Move task from stale.txt to todo.txt")
@click.argument("task_number", type=int)
@MainUtils.common_options
def stale_revive(ctx: click.Context, task_number: int, **kwargs):
    """Remove task with index `task_number` from stale.txt and adds it back to todo.txt."""
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.Stale_revive import StaleRevive
    StaleRevive(ctx.obj["config"], TaskManager(ctx.obj["config"])).run(task_number)


@stale.command("add", short_help="Move task from todo.txt to stale.txt")
@click.argument("task_number", type=int)
@MainUtils.common_options
def stale_add(ctx: click.Context, task_number: int, **kwargs):
    """Removes the task with the index `task_number` from todo.txt and adds it to stale.txt."""
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.Stale_add import StaleAdd
    StaleAdd(ctx.obj["config"], TaskManager(ctx.obj["config"])).run(task_number)


@root.command("version", short_help="Show the version of TodoTree.")
@MainUtils.common_options
def version(ctx: click.Context, **kwargs):
    MainUtils.initialize(ctx.obj["config"], **kwargs)
    from todotree.Commands.Version import Version

    Version(ctx.obj["config"], TaskManager(ctx.obj["config"])).run()


@root.command("addons", short_help="Run an addon script")
@click.argument("command", type=str)
@MainUtils.common_options
def addons_command(ctx: click.Context, command: str, **kwargs):
    """
    Run an addon script.
    The script must be in the addons_folder. It can be any language you like: It does not have to be python.
    However, it must have the executable bit set.

    :param command: The script/command to run.
    """
    from todotree.Addons import Addons

    MainUtils.initialize(ctx.obj["config"], **kwargs)
    result = Addons(ctx.obj["config"]).run(command)
    click.echo(result.stdout, nl=False)
    if result.returncode != 0:
        click.echo(result.stderr)
    ctx.obj["config"].git.commit_and_push(f"addons {command}")


#  End Region Command Definitions.
#  Setup Click

CONTEXT_SETTINGS: dict = dict(help_option_names=["-h", "--help"])
"""Click context settings. See https://click.palletsprojects.com/en/8.1.x/complex/ for more information."""
cli: click.CommandCollection = click.CommandCollection(sources=[root], context_settings=CONTEXT_SETTINGS)
"""Command Collection defining defaults. https://click.palletsprojects.com/en/8.1.x/api/#click.CommandCollection ."""

if __name__ == "__main__":
    cli(obj={})
