"""Slash command metadata and command-palette helpers for the TUI."""

from __future__ import annotations

from nipux_cli.tui_style import _accent, _bold, _fit_ansi, _muted


FIRST_RUN_SLASH_COMMANDS = [
    ("/model", "set model"),
    ("/base-url", "set endpoint"),
    ("/api-key", "save key"),
    ("/api-key-env", "key env var"),
    ("/config", "runtime config"),
    ("/context", "token budget"),
    ("/input-cost", "input $/1M"),
    ("/timeout", "request timeout"),
    ("/browser", "browser on/off"),
    ("/web", "web on/off"),
    ("/cli-access", "CLI on/off"),
    ("/file-access", "files on/off"),
    ("/home", "state directory"),
    ("/step-limit", "worker timeout"),
    ("/output-chars", "output preview size"),
    ("/output-cost", "output $/1M"),
    ("/max-cost", "job cost limit"),
    ("/daily-digest", "daily digest on/off"),
    ("/digest-time", "digest time"),
    ("/doctor", "check setup"),
    ("/init", "write config"),
    ("/help", "show commands"),
    ("/clear", "clear notices"),
    ("/exit", "quit"),
]

CHAT_SLASH_COMMANDS = [
    ("/new", "create and start a worker"),
    ("/run", "resume focused work"),
    ("/jobs", "switch or inspect jobs"),
    ("/settings", "configure provider/tools"),
    ("/status", "focused job state"),
    ("/help", "core commands"),
    ("/outcomes", "durable work"),
    ("/artifacts", "saved files"),
    ("/activity", "tool calls"),
    ("/work", "one step"),
    ("/work-verbose", "verbose step"),
    ("/focus", "set focus"),
    ("/switch", "set focus"),
    ("/history", "timeline"),
    ("/events", "event feed"),
    ("/updates", "durable work"),
    ("/outputs", "raw runs"),
    ("/artifact", "open output"),
    ("/findings", "finding ledger"),
    ("/tasks", "task queue"),
    ("/roadmap", "milestones"),
    ("/experiments", "measurements"),
    ("/sources", "source ledger"),
    ("/memory", "learning"),
    ("/metrics", "counts"),
    ("/lessons", "lessons"),
    ("/usage", "tokens/cost"),
    ("/config", "runtime config"),
    ("/health", "daemon health"),
    ("/start", "start daemon"),
    ("/restart", "restart daemon"),
    ("/model", "set model"),
    ("/base-url", "set endpoint"),
    ("/api-key", "save key"),
    ("/api-key-env", "key env var"),
    ("/context", "token budget"),
    ("/input-cost", "input $/1M"),
    ("/timeout", "request timeout"),
    ("/browser", "browser on/off"),
    ("/web", "web on/off"),
    ("/cli-access", "CLI on/off"),
    ("/file-access", "files on/off"),
    ("/home", "state directory"),
    ("/step-limit", "worker timeout"),
    ("/output-chars", "output preview size"),
    ("/output-cost", "output $/1M"),
    ("/daily-digest", "daily digest on/off"),
    ("/digest-time", "digest time"),
    ("/doctor", "check setup"),
    ("/init", "write config"),
    ("/pause", "pause job"),
    ("/resume", "resume job"),
    ("/stop", "pause job"),
    ("/cancel", "cancel job"),
    ("/delete", "delete job"),
    ("/learn", "save lesson"),
    ("/note", "save note"),
    ("/follow", "queue follow-up"),
    ("/digest", "digest"),
    ("/clear", "clear notices"),
    ("/exit", "quit"),
]

SETTINGS_FIELD_TYPES = {
    "model.name": "str",
    "model.base_url": "str",
    "model.api_key_env": "str",
    "model.context_length": "int",
    "model.request_timeout_seconds": "float",
    "model.input_cost_per_million": "float",
    "model.output_cost_per_million": "float",
    "runtime.home": "path",
    "runtime.max_step_seconds": "int",
    "runtime.artifact_inline_char_limit": "int",
    "runtime.daily_digest_enabled": "bool",
    "runtime.daily_digest_time": "str",
    "runtime.max_job_cost_usd": "float",
    "tools.browser": "bool",
    "tools.web": "bool",
    "tools.shell": "bool",
    "tools.files": "bool",
}

CHAT_SETTING_COMMANDS = {
    "model": ("model.name", "MODEL"),
    "base-url": ("model.base_url", "URL"),
    "api-key-env": ("model.api_key_env", "ENV_NAME"),
    "context": ("model.context_length", "TOKENS"),
    "input-cost": ("model.input_cost_per_million", "DOLLARS_PER_1M_INPUT_TOKENS"),
    "output-cost": ("model.output_cost_per_million", "DOLLARS_PER_1M_OUTPUT_TOKENS"),
    "max-cost": ("runtime.max_job_cost_usd", "DOLLARS_OR_0"),
    "timeout": ("model.request_timeout_seconds", "SECONDS"),
    "browser": ("tools.browser", "true|false"),
    "web": ("tools.web", "true|false"),
    "cli-access": ("tools.shell", "true|false"),
    "file-access": ("tools.files", "true|false"),
    "home": ("runtime.home", "PATH"),
    "step-limit": ("runtime.max_step_seconds", "SECONDS"),
    "output-chars": ("runtime.artifact_inline_char_limit", "CHARS"),
    "daily-digest": ("runtime.daily_digest_enabled", "true|false"),
    "digest-time": ("runtime.daily_digest_time", "HH:MM"),
}

SLASH_ARGUMENT_HINTS = {
    "new": "OBJECTIVE",
    "focus": "JOB_TITLE",
    "switch": "JOB_TITLE",
    "delete": "JOB_TITLE",
    "history": "LIMIT",
    "events": "LIMIT",
    "outputs": "LIMIT",
    "outcomes": "all",
    "updates": "all",
    "artifact": "QUERY_OR_ID",
    "work": "N",
    "work-verbose": "N",
    "learn": "LESSON",
    "note": "MESSAGE",
    "follow": "MESSAGE",
    **{command: placeholder for command, (_field, placeholder) in CHAT_SETTING_COMMANDS.items()},
    "api-key": "KEY",
    "key": "KEY",
}

REQUIRED_SLASH_ARGUMENTS = {"new", "artifact", "learn", "note", "follow"}


def slash_suggestion_lines(
    input_buffer: str,
    commands: list[tuple[str, str]],
    *,
    width: int,
    limit: int = 5,
) -> list[str]:
    if not input_buffer.startswith("/"):
        return []
    parts = input_buffer[1:].split(maxsplit=1)
    token = parts[0].lower() if parts else ""
    if " " in input_buffer[1:]:
        hint = SLASH_ARGUMENT_HINTS.get(token)
        description = next((desc for cmd, desc in commands if cmd == f"/{token}"), "")
        if not hint:
            return []
        body = f"{_accent('/' + token)} {_muted(hint)}"
        if description:
            body += f"  {_muted(description)}"
        return [
            _muted("╭─ command " + "─" * max(0, width - 11)),
            _fit_ansi(_muted("│ ") + body, width),
            _fit_ansi(_muted(_slash_argument_footer(parts, hint)), width),
        ]
    command_names = [cmd for cmd, _desc in commands]
    selected_command = f"/{token}" if token else ""
    exact_selection = selected_command in command_names and input_buffer.rstrip() == selected_command
    if exact_selection:
        all_matches = commands
    else:
        all_matches = [(cmd, desc) for cmd, desc in commands if cmd[1:].startswith(token)]
        if not all_matches and token:
            all_matches = [(cmd, desc) for cmd, desc in commands if token in cmd[1:]]
    if exact_selection:
        selected_index = next((index for index, (cmd, _desc) in enumerate(all_matches) if cmd == selected_command), 0)
        start = max(0, min(selected_index - max(0, limit // 2), max(0, len(all_matches) - limit)))
        matches = all_matches[start : start + limit]
    else:
        matches = all_matches[:limit]
    if not matches:
        return [
            _muted("╭─ commands " + "─" * max(0, width - 12)),
            _fit_ansi(_muted("│ no matches"), width),
            _muted("╰" + "─" * max(0, width - 1)),
        ]
    cmd_width = min(14, max(len(cmd) for cmd, _ in matches) + 2)
    lines = [_fit_ansi(_muted("╭─ commands " + "─" * max(0, width - 12)), width)]
    for index, (cmd, desc) in enumerate(matches):
        active = cmd == selected_command if exact_selection else index == 0
        marker = _accent("›") if active else _muted(" ")
        hint = SLASH_ARGUMENT_HINTS.get(cmd[1:])
        command_text = cmd if not hint else f"{cmd} {hint}"
        command_width = cmd_width + (len(hint) + 1 if hint else 0)
        name = _bold(_accent(command_text)) if active else _accent(command_text)
        body = f"{_muted('│')} {marker} {_fit_ansi(name, command_width)} {_muted(desc)}"
        lines.append(_fit_ansi(body, width))
    hidden = max(0, len(all_matches) - len(matches))
    if hidden:
        lines.append(_fit_ansi(_muted("╰─ type to filter · enter selects first match"), width))
    else:
        lines.append(_fit_ansi(_muted("╰─ enter selects · tab fills · ↑↓ moves"), width))
    return lines


def autocomplete_slash(input_buffer: str, commands: list[tuple[str, str]]) -> str:
    if not input_buffer.startswith("/") or " " in input_buffer[1:]:
        return input_buffer
    matches = _slash_command_matches(input_buffer, commands)
    if not matches:
        return input_buffer
    return matches[0] + " "


def slash_completion_for_submit(input_buffer: str, commands: list[tuple[str, str]]) -> tuple[str, bool]:
    """Return the buffer to use and whether Enter should submit it now."""

    if not input_buffer.startswith("/"):
        return input_buffer, True
    if " " in input_buffer[1:]:
        command = input_buffer[1:].split(maxsplit=1)[0].lower()
        if command in REQUIRED_SLASH_ARGUMENTS and not _slash_argument_text(input_buffer).strip():
            return input_buffer, False
        return input_buffer, True
    current = input_buffer.rstrip()
    if not current:
        return input_buffer, True
    command_names = {cmd for cmd, _desc in commands}
    token = current[1:].lower()
    exact = current in command_names
    if exact and token not in REQUIRED_SLASH_ARGUMENTS:
        return input_buffer, True
    matches = _slash_command_matches(input_buffer, commands)
    if not matches:
        return input_buffer, True
    selected = current if exact else matches[0]
    if selected[1:] not in REQUIRED_SLASH_ARGUMENTS:
        return selected, True
    suffix = " "
    completed = selected + suffix
    return completed, completed == input_buffer


def _slash_argument_text(input_buffer: str) -> str:
    parts = input_buffer[1:].split(maxsplit=1)
    return parts[1] if len(parts) > 1 else ""


def _slash_argument_footer(parts: list[str], hint: str) -> str:
    if len(parts) == 1:
        return f"╰─ type {hint}, then enter"
    return "╰─ enter sends"


def cycle_slash(input_buffer: str, commands: list[tuple[str, str]], *, direction: int) -> str:
    if not input_buffer.startswith("/") or " " in input_buffer[1:]:
        return input_buffer
    current = input_buffer.rstrip()
    command_names = [cmd for cmd, _desc in commands]
    matches = command_names if current in command_names else _slash_command_matches(input_buffer, commands)
    if not matches:
        return input_buffer
    try:
        index = matches.index(current)
    except ValueError:
        return matches[0] if direction >= 0 else matches[-1]
    return matches[(index + direction) % len(matches)]


def _slash_command_matches(input_buffer: str, commands: list[tuple[str, str]]) -> list[str]:
    token = input_buffer.strip()[1:].lower()
    matches = [cmd for cmd, _desc in commands if cmd[1:].startswith(token)]
    if not matches:
        matches = [cmd for cmd, _desc in commands if token in cmd[1:]]
    return matches
