#!/usr/bin/python3
# Shell completion helper for sbo-create / sbc.
# Called by the bash and zsh completion scripts.
# Usage: shell_completion.py <cword_idx> <word0> <word1> ...

import json
import os
import sys
from pathlib import Path

SUBCOMMANDS: list[str] = [
    'create', 'download', 'check', 'search', 'clean', 'info',
    'list', 'scan', 'validate', 'bump', 'package', 'edit', 'rename',
    'fetch', 'deps', 'rdeps', 'repo', 'update', 'maintainer',
    'repology',
]

REPO_SUBCOMMANDS: list[str] = ['diff', 'sync', 'submit', 'copy', 'log']

GLOBAL_FLAGS: list[str] = ['-N', '--no-color', '-v', '--version', '-h', '--help']

SCAN_FORMATS: list[str] = [
    'python', 'rust', 'perl', 'haskell', 'ruby', 'cmake', 'autotools', 'meson',
]

SUBCOMMAND_FLAGS: dict[str, list[str]] = {
    'scan': ['-F', '--format'],
    'deps': ['-t', '--tree'],
    'rdeps': ['-t', '--tree'],
    'fetch': ['-l', '--list', '-f', '--file', '-M', '--maintainer', '-W', '--wide',
              '-V', '--slackware-version'],
    'create': [
        '-n', '--prgnam', '-r', '--prg-version', '-t', '--template',
        '-a', '--arm64', '-o', '--output', '-D', '--dry-run',
        '-s', '--strip-comments',
    ],
    'download': ['-u', '--check-urls'],
    'validate': ['-m', '--md5', '-f', '--fix', '-s', '--strict', '-A', '--all',
                 '-M', '--maintainer', '-p', '--path'],
    'bump': ['-W', '--with-sources'],
    'package': ['-o', '--output'],
    'edit': ['-e', '--editor'],
    'maintainer': ['-S', '--show'],
    'repology': ['-n', '--newest', '-o', '--outdated', '-p', '--problematic',
                 '-m', '--maintainer'],
}

REPO_SUBCOMMAND_FLAGS: dict[str, list[str]] = {
    'diff': ['-U', '--unified'],
}

TEMPLATES: list[str] = [
    'autotools', 'cmake', 'perl', 'python', 'rubygem', 'haskell', 'meson',
]

# Subcommands whose positional argument is a file/path.
FILE_ARG_COMMANDS: set[str] = {'scan'}

# Subcommands whose positional argument is an SBo package name.
PKG_ARG_COMMANDS: set[str] = {'fetch', 'check', 'search', 'deps', 'rdeps'}

_SBO_JSON: Path = Path.home() / '.local' / 'share' / 'sbo-create' / 'repo' / 'slackbuilds.json'


def _sbo_packages(prefix: str) -> list[str]:
    """Return SBo package names from the local slackbuilds.json that match prefix."""
    if not _SBO_JSON.is_file():
        return []
    try:
        data: dict[str, object] = json.loads(_SBO_JSON.read_text(encoding='utf-8'))
        return [k for k in data if k.startswith(prefix)]
    except Exception:  # pylint: disable=broad-except
        return []


# Per-subcommand flags that consume the next word as a value (no completions).
_VALUE_FLAGS: dict[str, set[str]] = {
    'create': {'-n', '--prgnam', '-r', '--prg-version'},
    'diff': {'-U', '--unified'},
    'fetch': {'-V', '--slackware-version'},
    'validate': {'-M', '--maintainer'},
    'repology': {'-m', '--maintainer'},
}

# Per-subcommand flags that consume the next word as a file/path.
_FILE_VALUE_FLAGS: dict[str, set[str]] = {
    'create': {'-o', '--output'},
    'package': {'-o', '--output'},
    'edit': {'-e', '--editor'},
    'validate': {'-p', '--path'},
}


def _file_completions(prefix: str) -> list[str]:
    """Return file and directory completions for the given prefix."""
    expanded = os.path.expanduser(prefix)
    if prefix.endswith('/'):
        directory, base = expanded, ''
    else:
        directory = os.path.dirname(expanded) or '.'
        base = os.path.basename(expanded)

    results: list[str] = []
    try:
        for entry in os.listdir(directory):
            if not entry.startswith(base):
                continue
            full = os.path.join(directory, entry)
            if prefix.startswith('/') or prefix.startswith('~') or '/' in prefix:
                display = os.path.join(directory, entry)
            else:
                display = entry
            results.append(display + '/' if os.path.isdir(full) else display)
    except OSError:
        pass
    return sorted(results)


def _fetch_file_completions(words: list[str], cword_idx: int, current: str) -> list[str]:
    """Return completions for `fetch -f <TAB>`: fixed names and package-derived names.

    Args:
        words: Full command-line word list.
        cword_idx: Index of the word being completed.
        current: The partial word being completed.

    Returns:
        List of candidate filenames derived from the package name already typed.
    """
    fetch_idx: int = next((i for i, w in enumerate(words) if w == 'fetch'), -1)
    pkg_name: str = ''
    i: int = fetch_idx + 1
    _fetch_value_flags: set[str] = {'-V', '--slackware-version', '-f', '--file'}
    while i < cword_idx:
        w: str = words[i]
        if not w.startswith('-'):
            pkg_name = w
            break
        if w in _fetch_value_flags:
            i += 1  # skip value consumed by this flag
        i += 1
    fixed = [f for f in ('README', 'slack-desc') if f.startswith(current)]
    named = [pkg_name + ext for ext in ('.info', '.SlackBuild')
             if pkg_name and (pkg_name + ext).startswith(current)]
    return fixed + named


def _pkg_arg_completions(  # pylint: disable=too-many-arguments,too-many-positional-arguments
    words: list[str], subcmd: str, cword_idx: int, current: str,
    prev: str, value_flags: set[str], file_value_flags: set[str],
) -> list[str]:
    """Return package-name completions for PKG_ARG_COMMANDS, respecting boolean-flag context.

    After a boolean flag, suppress package completion only when a positional argument
    has already been given (e.g. ``deps neovim-qt -t [TAB]`` → ``[]``). If no
    positional is present yet (e.g. ``fetch -M [TAB]``), still offer packages.

    Args:
        words: Full command-line word list.
        subcmd: The detected subcommand.
        cword_idx: Index of the word being completed.
        current: The partial word being completed.
        prev: The word immediately before the cursor.
        value_flags: Flags that consume the next token as a plain value.
        file_value_flags: Flags that consume the next token as a file path.

    Returns:
        List of matching SBo package names, or ``[]`` when suppressed.
    """
    if prev.startswith('-') and prev not in value_flags and prev not in file_value_flags:
        subcmd_idx: int = next((i for i, w in enumerate(words) if w == subcmd), -1)
        positional_seen: bool = any(
            not w.startswith('-') for w in words[subcmd_idx + 1:cword_idx]
        )
        if positional_seen:
            return []
    return _sbo_packages(current)


def _resolve_repo_completions(
    words: list[str], current: str, prev: str,
) -> list[str]:
    """Return completion candidates when the top-level subcommand is 'repo'.

    Args:
        words: Full command-line word list.
        current: The partial word being completed.
        prev: The word immediately before the cursor.

    Returns:
        List of completion candidates for the repo namespace.
    """
    repo_idx: int = next((i for i, w in enumerate(words) if w == 'repo'), -1)
    repo_cmd: str = next(
        (w for w in words[repo_idx + 1:] if not w.startswith('-') and w in REPO_SUBCOMMANDS),
        '',
    ) if repo_idx >= 0 else ''

    if not repo_cmd:
        return REPO_SUBCOMMANDS if not current.startswith('-') else []

    value_flags: set[str] = _VALUE_FLAGS.get(repo_cmd, set())
    if prev in value_flags:
        return []
    if repo_cmd == 'diff' and not current.startswith('-'):
        return _file_completions(current)
    return REPO_SUBCOMMAND_FLAGS.get(repo_cmd, [])


def _resolve_completions(  # pylint: disable=too-many-return-statements
    words: list[str], cword_idx: int,
) -> list[str]:
    """Return the list of completion candidates for the current word.

    Args:
        words: Full command-line word list (argv[2:] from the completion helper).
        cword_idx: Index into *words* of the word currently being completed.

    Returns:
        List of completion candidates matching the current partial word.
    """
    current: str = words[cword_idx] if cword_idx < len(words) else ''
    prev: str = words[cword_idx - 1] if cword_idx > 0 else ''

    subcmd: str = next(
        (w for w in words[1:] if not w.startswith('-') and w in SUBCOMMANDS), ''
    )

    value_flags: set[str] = _VALUE_FLAGS.get(subcmd, set())
    file_value_flags: set[str] = _FILE_VALUE_FLAGS.get(subcmd, set())

    if prev in value_flags:
        return []
    if prev in ('-F', '--format'):
        return SCAN_FORMATS
    if prev in ('-t', '--template') and subcmd == 'create':
        return TEMPLATES
    if prev in ('-f', '--file') and subcmd == 'fetch':
        return _fetch_file_completions(words, cword_idx, current)
    if prev in file_value_flags:
        return _file_completions(current)

    if not subcmd:
        return GLOBAL_FLAGS if current.startswith('-') else SUBCOMMANDS + GLOBAL_FLAGS

    if subcmd == 'repo':
        return _resolve_repo_completions(words, current, prev)

    if subcmd in FILE_ARG_COMMANDS and not current.startswith('-'):
        return _file_completions(current)

    if subcmd in PKG_ARG_COMMANDS and not current.startswith('-'):
        return _pkg_arg_completions(
            words, subcmd, cword_idx, current, prev, value_flags, file_value_flags,
        )

    return SUBCOMMAND_FLAGS.get(subcmd, [])


def main() -> None:
    """ Entry point for shell completion. """
    try:
        if len(sys.argv) < 3:
            return

        cword_idx: int = int(sys.argv[1])
        words: list[str] = sys.argv[2:]
        current: str = words[cword_idx] if cword_idx < len(words) else ''

        completions: list[str] = _resolve_completions(words, cword_idx)
        for c in completions:
            if c.startswith(current):
                print(c)

    except Exception:  # pylint: disable=broad-except
        pass


if __name__ == '__main__':
    main()
