"""
Terminal interaction: tab-completion and prompts.

Every function here is a no-op or returns a default when stdin is not a terminal,
so batch runs never block.
"""

import os
import sys
import glob
import readline

# What counts as yes and as no. The single letters are what the tool has always
# accepted; the words are what people actually type.
YES_WORDS = frozenset({'y', 'ye', 'yes', 'o', 'oui', 't', 'true', '1'})
NO_WORDS = frozenset({'n', 'no', 'nope', 'non', 'f', 'false', '0'})


def complete_filename(text, state):
    """
    Tab-completion for filesystem paths.
    """
    if "*" not in text:
        pattern = text + "*"
    else:
        pattern = text
    matches = glob.glob(os.path.expanduser(pattern))
    matches = [m + "/" if os.path.isdir(m) else m for m in matches]
    try:
        return matches[state]
    except IndexError:
        return None


def make_completer(options):
    """
    Returns a readline completer over a fixed list of words.
    """
    def completer(text, state):
        matches = [name for name in options if name.startswith(text)]
        try:
            return matches[state]
        except IndexError:
            return None
    return completer


# The variable list is just one such fixed list; kept under its own name because
# that is what reads correctly at the call site.
make_varname_completer = make_completer


def resolve_choice(answer, options, aliases=None):
    """
    Match a typed answer against `options`, or None when nothing fits.

    Tried in order: exact, case-insensitive, unique case-insensitive prefix, and
    finally `aliases`, a {other name: option} map. The aliases are what let the
    coordinate name a file advertises - 'longitude' - resolve to the dimension
    the plot is actually keyed on, 'lon', instead of falling through as
    unrecognized.
    """
    answer = (answer or '').strip()
    if not answer:
        return None
    if answer in options:
        return answer

    folded = answer.lower()
    exact = [name for name in options if name.lower() == folded]
    if len(exact) == 1:
        return exact[0]

    for key, value in (aliases or {}).items():
        if key.lower() == folded and value in options:
            return value

    prefixed = [name for name in options if name.lower().startswith(folded)]
    if len(prefixed) == 1:
        return prefixed[0]
    return None


def prompt_choice(question, options, aliases=None, default=None, interactive=True):
    """
    Ask the user to pick one of `options`, with tab-completion over exactly those.

    The completer is installed for the duration of the prompt and the previous
    one is put back afterwards: the caller's own completion - variable names, in
    the interactive loop - would otherwise offer words that are not valid answers
    here. An unrecognized answer says so and asks again rather than silently
    falling back on the default, which is what made a wrong answer look right.
    """
    options = list(options)
    if not (interactive and sys.stdin.isatty()) or not options:
        return default

    previous = readline.get_completer()
    readline.set_completer(make_completer(options))
    readline.parse_and_bind("tab: complete")
    try:
        while True:
            try:
                answer = input(f"{question} {options}: ").strip()
            except EOFError:
                print()
                return default
            if not answer:
                return default
            chosen = resolve_choice(answer, options, aliases)
            if chosen is not None:
                return chosen
            print(f"  '{answer}' is not one of {options}. Press Tab to complete, "
                  f"or Enter to keep {default!r}.")
    finally:
        readline.set_completer(previous)


def ask(question, interactive, default=False):
    """
    Ask a yes/no question, but only when running interactively on a terminal.
    Returns `default` otherwise, so batch runs never block on stdin.

    Both the letter and the whole word are accepted, in either case, because
    typing 'yes' and getting 'no' is a trap rather than a shortcut.
    """
    if not (interactive and sys.stdin.isatty()):
        return default

    hint = 'yes' if default else 'no'
    while True:
        try:
            answer = input(f"{question} [y/n] (Enter = {hint}): ").strip().lower()
        except EOFError:
            print()
            return default
        if not answer:
            return default
        if answer in YES_WORDS:
            return True
        if answer in NO_WORDS:
            return False
        print(f"  Please answer yes or no (got '{answer}').")
