from __future__ import annotations

import json
import os
import shutil
import subprocess
import sys
from pathlib import Path

import click

from dont_hallucinate.burn import load_snark_map, pick_roast
from dont_hallucinate.parser import classify_failure
from dont_hallucinate.runtime import append_jsonl, default_stream_file, ensure_runtime_dir, utc_timestamp, workspace_root
from dont_hallucinate.ui import run_ui
from dont_hallucinate.verifier import VerificationResult, verify_command


def _package_root() -> Path:
    return Path(__file__).resolve().parent


def _hook_path(shell: str) -> Path:
    mapping = {
        "powershell": _package_root() / "hooks" / "hook.ps1",
        "bash": _package_root() / "hooks" / "hook.sh",
        "agent-bash": _package_root() / "hooks" / "hook_noninteractive.sh",
    }
    return mapping[shell]


def _quote_powershell(value: str) -> str:
    return "'" + value.replace("'", "''") + "'"


def _quote_bash(value: str) -> str:
    return "'" + value.replace("'", "'\"'\"'") + "'"


def _quote_cmd(value: str) -> str:
    return subprocess.list2cmdline([value])


def _to_bash_path(path: Path) -> str:
    value = str(path.resolve())
    if len(value) >= 3 and value[1:3] == ":\\":
        drive = value[0].lower()
        tail = value[2:].replace("\\", "/")
        return f"/{drive}{tail}"
    return value.replace("\\", "/")


def _activation_env(workspace: Path, stream: Path, actor: str) -> dict[str, str]:
    env = os.environ.copy()
    env["HALLUCINATE_WORKSPACE_ROOT"] = str(workspace)
    env["HALLUCINATE_STREAM_FILE"] = str(stream)
    env["HALLUCINATE_ACTOR"] = actor
    return env


def _command_display(command: tuple[str, ...]) -> str:
    return subprocess.list2cmdline(list(command)) if os.name == "nt" else " ".join(command)


def _verification_for_failure(command_text: str, parsed_error_type: str, bad_flag: str | None) -> VerificationResult:
    if parsed_error_type != "invalid_flag":
        return VerificationResult([], None, None, "none")
    return verify_command(command_text, bad_flag)


def build_failure_report(command_text: str, actor: str, stderr_text: str, exit_code: int) -> str:
    from dont_hallucinate.burn import detect_agent
    from dont_hallucinate.parser import extract_cmd_name

    parsed = classify_failure(command_text, stderr_text, exit_code)
    verification = _verification_for_failure(command_text, parsed.error_type, parsed.hallucinated_flag)

    agent = detect_agent()
    cmd_name = extract_cmd_name(command_text)
    roast = pick_roast(
        load_snark_map(),
        parsed.error_type,
        agent=agent,
        cmd_name=cmd_name,
        flag=parsed.hallucinated_flag or "that flag",
        missing=parsed.missing_command or cmd_name or "that",
    )

    lines = []
    if stderr_text:
        lines.append(stderr_text.rstrip())
    lines.append("")
    lines.append(f"[dont-hallucinate/{actor}] caught: {command_text}")
    lines.append(f"[dont-hallucinate/{actor}] type: {parsed.error_type}")
    if verification.corrected_command:
        lines.append(f"[dont-hallucinate/{actor}] try: {verification.corrected_command}")
    elif verification.suggested_flag:
        lines.append(f"[dont-hallucinate/{actor}] maybe: {verification.suggested_flag}")
    lines.append(f"[dont-hallucinate/{actor}] roast: {roast}")
    return "\n".join(lines).rstrip() + "\n"


def _decorate_failure(command_text: str, actor: str, stderr_text: str, exit_code: int) -> str:
    return build_failure_report(command_text, actor, stderr_text, exit_code)


def _log_exec_event(stream: Path, workspace: Path, actor: str, command_text: str, exit_code: int, stderr_text: str) -> None:
    append_jsonl(
        stream,
        {
            "ts": utc_timestamp(),
            "shell": "exec",
            "actor": actor,
            "session": os.getpid(),
            "pid": os.getpid(),
            "cwd": str(workspace),
            "workspace_root": str(workspace),
            "exit_code": exit_code,
            "command": command_text,
            "stderr": stderr_text,
        },
    )


def _prepend_path(env: dict[str, str], prefix: Path) -> None:
    existing = env.get("PATH", "")
    env["PATH"] = str(prefix) if not existing else str(prefix) + os.pathsep + existing


def _shim_dir(workspace: Path) -> Path:
    return workspace / ".hallucinate" / "bin"


def _real_bash_path() -> str | None:
    configured = os.environ.get("HALLUCINATE_REAL_BASH")
    if configured:
        return configured
    return shutil.which("bash")


def _python_command() -> str:
    python = Path(sys.executable or "python")
    if python.name.lower().startswith("python"):
        return python.name
    return str(python)


def _state_home(agent_name: str) -> Path:
    return Path.home() / ".dont-hallucinate" / agent_name


def _agent_stream_file(agent_name: str) -> Path:
    return _state_home(agent_name) / "stream.jsonl"


def _codex_config_path(global_install: bool) -> Path:
    if global_install:
        return Path.home() / ".codex" / "config.toml"
    return Path.cwd() / ".codex" / "config.toml"


def _cursor_powershell_profile() -> Path:
    return Path.home() / "Documents" / "WindowsPowerShell" / "Microsoft.PowerShell_profile.ps1"


def _cursor_shell_profiles() -> list[Path]:
    return [Path.home() / ".bashrc", Path.home() / ".zshrc"]


def _read_json_file(path: Path) -> dict:
    if not path.exists():
        return {}
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except json.JSONDecodeError:
        return {}
    return data if isinstance(data, dict) else {}


def _write_json_file(path: Path, payload: dict) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")


def _read_text_file(path: Path) -> str:
    if not path.exists():
        return ""
    return path.read_text(encoding="utf-8")


def _write_text_file(path: Path, content: str) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(content, encoding="utf-8")


def _append_block_once(path: Path, begin_marker: str, end_marker: str, block: str) -> bool:
    current = _read_text_file(path)
    normalized = current.rstrip()
    if begin_marker in current and end_marker in current:
        start = current.index(begin_marker)
        end = current.index(end_marker, start) + len(end_marker)
        replacement = block.rstrip()
        updated = (current[:start].rstrip() + "\n\n" + replacement + "\n") if current[:start].strip() else (replacement + "\n")
        updated += current[end:].lstrip("\r\n")
        _write_text_file(path, updated)
        return False

    updated = normalized + ("\n\n" if normalized else "") + block.rstrip() + "\n"
    _write_text_file(path, updated)
    return True


def _find_toml_table(lines: list[str], table_name: str) -> tuple[int | None, int | None]:
    header = f"[{table_name}]"
    start: int | None = None
    for idx, line in enumerate(lines):
        if line.strip() == header:
            start = idx
            break
    if start is None:
        return None, None

    end = len(lines)
    for idx in range(start + 1, len(lines)):
        stripped = lines[idx].strip()
        if stripped.startswith("[") and stripped.endswith("]"):
            end = idx
            break
    return start, end


def _ensure_toml_table(lines: list[str], table_name: str) -> tuple[int, int]:
    start, end = _find_toml_table(lines, table_name)
    if start is not None and end is not None:
        return start, end

    if lines and lines[-1].strip():
        lines.append("")
    lines.append(f"[{table_name}]")
    return len(lines) - 1, len(lines)


def _upsert_toml_assignment(lines: list[str], table_name: str, key: str, value: str) -> list[str]:
    start, end = _ensure_toml_table(lines, table_name)
    assignment = f'{key} = {value}'

    for idx in range(start + 1, end):
        stripped = lines[idx].strip()
        if stripped.startswith(f"{key} ="):
            lines[idx] = assignment
            return lines

    lines.insert(end, assignment)
    return lines


def _codex_config_with_hook(existing: str, stream_file: Path, hook_file: Path) -> str:
    lines = existing.splitlines()
    shell_start, shell_end = _ensure_toml_table(lines, "shell_environment_policy")
    for idx in range(shell_start + 1, shell_end):
        if lines[idx].strip().startswith("set ="):
            raise click.ClickException(
                "Existing [.shell_environment_policy] uses inline `set = {...}`. "
                "Please migrate it to [shell_environment_policy.set] before installing the Codex hook."
            )

    lines = _upsert_toml_assignment(lines, "shell_environment_policy", "inherit", '"all"')
    lines = _upsert_toml_assignment(lines, "shell_environment_policy.set", "BASH_ENV", json.dumps(_to_bash_path(hook_file)))
    lines = _upsert_toml_assignment(lines, "shell_environment_policy.set", "HALLUCINATE_STREAM_FILE", json.dumps(str(stream_file)))
    lines = _upsert_toml_assignment(lines, "shell_environment_policy.set", "HALLUCINATE_ACTOR", json.dumps("agent"))

    return "\n".join(lines).rstrip() + "\n"


def _cursor_bash_block(stream_file: Path, hook_file: Path) -> str:
    return "\n".join(
        [
            "# >>> dont-hallucinate cursor >>>",
            'if [ -n "${CURSOR_AGENT:-}" ]; then',
            f"  export HALLUCINATE_ACTOR={_quote_bash('agent')}",
            f"  export HALLUCINATE_STREAM_FILE={_quote_bash(str(stream_file))}",
            f"  source {_quote_bash(_to_bash_path(hook_file))}",
            "fi",
            "# <<< dont-hallucinate cursor <<<",
        ]
    )


def _cursor_powershell_block(stream_file: Path, hook_file: Path) -> str:
    return "\n".join(
        [
            "# >>> dont-hallucinate cursor >>>",
            "if ($env:CURSOR_AGENT) {",
            "    $env:HALLUCINATE_ACTOR = 'agent'",
            f"    $env:HALLUCINATE_STREAM_FILE = {_quote_powershell(str(stream_file))}",
            f"    . {_quote_powershell(str(hook_file))}",
            "}",
            "# <<< dont-hallucinate cursor <<<",
        ]
    )


def _install_claude_hook(global_install: bool) -> tuple[Path, Path]:
    if global_install:
        claude_dir = Path.home() / ".claude"
        script_path_in_settings = str(claude_dir / "hallucinate_hook.py")
    else:
        claude_dir = Path.cwd() / ".claude"
        script_path_in_settings = ".claude/hallucinate_hook.py"

    claude_dir.mkdir(parents=True, exist_ok=True)
    settings_file = claude_dir / "settings.json"
    hook_script = claude_dir / "hallucinate_hook.py"
    hook_script.write_text(_HOOK_SCRIPT, encoding="utf-8")

    settings = _read_json_file(settings_file)
    _merge_hook(settings, script_path_in_settings)
    _write_json_file(settings_file, settings)
    return hook_script, settings_file


def _install_codex_hook(global_install: bool) -> tuple[Path, Path]:
    config_path = _codex_config_path(global_install)
    stream_file = _agent_stream_file("codex")
    hook_file = _package_root() / "hooks" / "hook_noninteractive.sh"

    stream_file.parent.mkdir(parents=True, exist_ok=True)
    updated = _codex_config_with_hook(_read_text_file(config_path), stream_file, hook_file)
    _write_text_file(config_path, updated)
    return config_path, stream_file


def _install_cursor_hook() -> tuple[list[Path], Path]:
    stream_file = _agent_stream_file("cursor")
    bash_hook = _package_root() / "hooks" / "hook.sh"
    ps_hook = _package_root() / "hooks" / "hook.ps1"

    stream_file.parent.mkdir(parents=True, exist_ok=True)
    touched: list[Path] = []

    ps_profile = _cursor_powershell_profile()
    _append_block_once(
        ps_profile,
        "# >>> dont-hallucinate cursor >>>",
        "# <<< dont-hallucinate cursor <<<",
        _cursor_powershell_block(stream_file, ps_hook),
    )
    touched.append(ps_profile)

    for profile in _cursor_shell_profiles():
        _append_block_once(
            profile,
            "# >>> dont-hallucinate cursor >>>",
            "# <<< dont-hallucinate cursor <<<",
            _cursor_bash_block(stream_file, bash_hook),
        )
        touched.append(profile)

    return touched, stream_file


def _install_bash_shim(workspace: Path, actor: str) -> Path:
    shim_dir = _shim_dir(workspace)
    shim_dir.mkdir(parents=True, exist_ok=True)

    bash_shim = shim_dir / "bash"
    bash_cmd_shim = shim_dir / "bash.cmd"
    workspace_arg = str(workspace)

    bash_shim.write_text(
        "\n".join(
            [
                "#!/usr/bin/env sh",
                f"exec dont-hallucinate bash --workspace { _quote_bash(workspace_arg) } --actor { _quote_bash(actor) } -- \"$@\"",
            ]
        )
        + "\n",
        encoding="utf-8",
    )
    os.chmod(bash_shim, 0o755)

    bash_cmd_shim.write_text(
        "\r\n".join(
            [
                "@echo off",
                f'dont-hallucinate bash --workspace "{workspace_arg}" --actor "{actor}" -- %*',
            ]
        )
        + "\r\n",
        encoding="utf-8",
    )

    return shim_dir


def _run_wrapped_command(workspace: Path, actor: str, command: tuple[str, ...], shell: bool = False) -> None:
    ensure_runtime_dir(workspace)
    stream = default_stream_file(workspace)
    command_text = _command_display(command) if not shell else command[0]

    # shell=True uses cmd.exe on Windows, /bin/sh on Unix — no bash dependency
    run_arg: str | list[str] = command[0] if shell else list(command)

    completed = subprocess.run(
        run_arg,
        shell=shell,
        cwd=workspace,
        capture_output=True,
        text=True,
        check=False,
    )

    if completed.stdout:
        click.echo(completed.stdout, nl=False)

    stderr_text = completed.stderr or ""
    if completed.returncode != 0:
        decorated = _decorate_failure(command_text, actor, stderr_text, completed.returncode)
        click.echo(decorated, nl=False, err=True)
        _log_exec_event(stream, workspace, actor, command_text, completed.returncode, stderr_text)
        raise SystemExit(completed.returncode)

    if stderr_text:
        click.echo(stderr_text, nl=False, err=True)
    _log_exec_event(stream, workspace, actor, command_text, completed.returncode, stderr_text)


@click.group(invoke_without_command=True)
@click.pass_context
def main(ctx: click.Context) -> None:
    """hallucinate CLI."""
    if ctx.invoked_subcommand is None:
        current_workspace = workspace_root()
        stream_file = default_stream_file(current_workspace)
        ensure_runtime_dir(current_workspace)
        run_ui(stream_file=stream_file, poll_interval=0.2, from_start=True, workspace=current_workspace)


@main.command("watch")
@click.option(
    "--stream-file",
    type=click.Path(path_type=Path, dir_okay=False),
    default=None,
    help="JSONL stream generated by the shell hook. Defaults to .hallucinate/stream.jsonl in the workspace.",
)
@click.option(
    "--poll-interval",
    type=float,
    default=0.2,
    show_default=True,
    help="File polling interval in seconds.",
)
@click.option(
    "--from-start/--follow",
    default=False,
    show_default=True,
    help="Read existing events first instead of seeking to EOF.",
)
@click.option(
    "--workspace",
    type=click.Path(path_type=Path, file_okay=False, dir_okay=True),
    default=Path.cwd,
    show_default="current directory",
    help="Workspace root to watch.",
)
def watch(stream_file: Path | None, poll_interval: float, from_start: bool, workspace: Path) -> None:
    """Run the Rich split-screen watcher UI."""
    current_workspace = workspace_root(workspace)
    ensure_runtime_dir(current_workspace)
    stream = stream_file.resolve() if stream_file else default_stream_file(current_workspace)
    run_ui(stream_file=stream, poll_interval=poll_interval, from_start=from_start, workspace=current_workspace)


@main.command("activate")
@click.option(
    "--shell",
    type=click.Choice(["powershell", "bash", "agent-bash"], case_sensitive=False),
    required=True,
    help="Shell type to emit activation commands for.",
)
@click.option(
    "--workspace",
    type=click.Path(path_type=Path, file_okay=False, dir_okay=True),
    default=Path.cwd,
    show_default="current directory",
    help="Workspace root whose failures should be logged.",
)
@click.option(
    "--stream-file",
    type=click.Path(path_type=Path, dir_okay=False),
    default=None,
    help="Override the JSONL stream path. Defaults to .hallucinate/stream.jsonl in the workspace.",
)
@click.option(
    "--actor",
    type=click.Choice(["human", "agent"], case_sensitive=False),
    default=None,
    help="Tag events from this shell session. Defaults to human, except agent-bash defaults to agent.",
)
def activate(shell: str, workspace: Path, stream_file: Path | None, actor: str | None) -> None:
    """Print shell commands to activate the hook for the current workspace."""
    current_workspace = workspace_root(workspace)
    ensure_runtime_dir(current_workspace)
    stream = stream_file.resolve() if stream_file else default_stream_file(current_workspace)
    hook = _hook_path(shell).resolve()
    session_actor = actor or ("agent" if shell == "agent-bash" else "human")
    shim_dir = _install_bash_shim(current_workspace, session_actor)
    real_bash = _real_bash_path()

    if shell == "powershell":
        snippet = "; ".join(
            [
                f"$env:HALLUCINATE_WORKSPACE_ROOT = {_quote_powershell(str(current_workspace))}",
                f"$env:HALLUCINATE_STREAM_FILE = {_quote_powershell(str(stream))}",
                f"$env:HALLUCINATE_ACTOR = {_quote_powershell(session_actor)}",
                f"$env:PATH = {_quote_powershell(str(shim_dir) + ';')} + $env:PATH",
                *( [f"$env:HALLUCINATE_REAL_BASH = {_quote_powershell(real_bash)}"] if real_bash else [] ),
                f". {_quote_powershell(str(hook))}",
            ]
        )
    elif shell == "bash":
        snippet = "\n".join(
            [
                f"export HALLUCINATE_WORKSPACE_ROOT={_quote_bash(_to_bash_path(current_workspace))}",
                f"export HALLUCINATE_STREAM_FILE={_quote_bash(_to_bash_path(stream))}",
                f"export HALLUCINATE_ACTOR={_quote_bash(session_actor)}",
                f"export PATH={_quote_bash(_to_bash_path(shim_dir))}:$PATH",
                *( [f"export HALLUCINATE_REAL_BASH={_quote_bash(_to_bash_path(Path(real_bash)))}"] if real_bash else [] ),
                f"source {_quote_bash(_to_bash_path(hook))}",
            ]
        )
    else:
        snippet = "\n".join(
            [
                f"export HALLUCINATE_WORKSPACE_ROOT={_quote_bash(_to_bash_path(current_workspace))}",
                f"export HALLUCINATE_STREAM_FILE={_quote_bash(_to_bash_path(stream))}",
                f"export HALLUCINATE_ACTOR={_quote_bash(session_actor)}",
                f"export PATH={_quote_bash(_to_bash_path(shim_dir))}:$PATH",
                *( [f"export HALLUCINATE_REAL_BASH={_quote_bash(_to_bash_path(Path(real_bash)))}"] if real_bash else [] ),
                f"export BASH_ENV={_quote_bash(_to_bash_path(hook))}",
            ]
        )

    click.echo(snippet)


@main.command("shell")
@click.option(
    "--shell",
    "shell_name",
    type=click.Choice(["powershell", "bash"], case_sensitive=False),
    default="powershell" if os.name == "nt" else "bash",
    show_default=True,
    help="Spawn a hooked shell in the current workspace.",
)
@click.option(
    "--workspace",
    type=click.Path(path_type=Path, file_okay=False, dir_okay=True),
    default=Path.cwd,
    show_default="current directory",
    help="Workspace root whose failures should be logged.",
)
@click.option(
    "--actor",
    type=click.Choice(["human", "agent"], case_sensitive=False),
    default="human",
    show_default=True,
    help="Tag events from the spawned shell session.",
)
def shell(shell_name: str, workspace: Path, actor: str) -> None:
    """Spawn a shell with hallucinate already activated."""
    current_workspace = workspace_root(workspace)
    ensure_runtime_dir(current_workspace)
    stream = default_stream_file(current_workspace)
    env = _activation_env(current_workspace, stream, actor)
    shim_dir = _install_bash_shim(current_workspace, actor)
    _prepend_path(env, shim_dir)
    real_bash = _real_bash_path()
    if real_bash:
        env["HALLUCINATE_REAL_BASH"] = real_bash

    if shell_name == "powershell":
        powershell = shutil.which("powershell") or shutil.which("pwsh")
        if not powershell:
            raise click.ClickException("PowerShell is not available on PATH.")
        hook = _hook_path("powershell").resolve()
        command = (
            f". {_quote_powershell(str(hook))}; "
            f"Set-Location {_quote_powershell(str(current_workspace))}"
        )
        raise SystemExit(
            subprocess.call(
                [powershell, "-NoExit", "-ExecutionPolicy", "Bypass", "-Command", command],
                env=env,
            )
        )

    bash = shutil.which("bash")
    if not bash:
        raise click.ClickException("bash is not available on PATH.")
    hook = _hook_path("bash").resolve()
    rcfile = _to_bash_path(hook)
    env["HALLUCINATE_WORKSPACE_ROOT"] = _to_bash_path(current_workspace)
    env["HALLUCINATE_STREAM_FILE"] = _to_bash_path(stream)
    raise SystemExit(
        subprocess.call(
            [bash, "--noprofile", "--rcfile", rcfile, "-i"],
            cwd=current_workspace,
            env=env,
        )
    )


@main.command(
    "bash",
    context_settings={"ignore_unknown_options": True, "allow_extra_args": True},
)
@click.option(
    "--workspace",
    type=click.Path(path_type=Path, file_okay=False, dir_okay=True),
    default=Path.cwd,
    show_default="current directory",
    help="Workspace root whose failures should be logged.",
)
@click.option(
    "--actor",
    type=click.Choice(["human", "agent"], case_sensitive=False),
    default="agent",
    show_default=True,
    help="Tag this bash invocation in the watcher and decorated stderr.",
)
@click.argument("bash_args", nargs=-1, type=click.UNPROCESSED)
def bash_command(workspace: Path, actor: str, bash_args: tuple[str, ...]) -> None:
    """Run the real bash through hallucinate."""
    real_bash = _real_bash_path()
    if not real_bash:
        raise click.ClickException("A real bash binary could not be found. Set HALLUCINATE_REAL_BASH or install bash.")
    current_workspace = workspace_root(workspace)
    _run_wrapped_command(current_workspace, actor, (real_bash, *bash_args))


@main.command("explain")
@click.option("--actor", default="agent", show_default=True, help="Actor label to include in the report.")
@click.option("--command", "command_text", required=True, help="Command text that failed.")
@click.option("--exit-code", type=int, required=True, help="Exit code from the failed command.")
@click.option(
    "--stderr-file",
    type=click.Path(path_type=Path, dir_okay=False, exists=True),
    default=None,
    help="Path to a stderr capture file.",
)
@click.option("--stderr-text", default="", help="Fallback stderr text when no file is provided.")
def explain(actor: str, command_text: str, exit_code: int, stderr_file: Path | None, stderr_text: str) -> None:
    """Render the hallucinate failure report for shell hooks and wrappers."""
    stderr_value = stderr_file.read_text(encoding="utf-8", errors="replace") if stderr_file else stderr_text
    click.echo(build_failure_report(command_text, actor, stderr_value, exit_code), nl=False)


@main.command(
    "exec",
    context_settings={"ignore_unknown_options": True, "allow_extra_args": True},
)
@click.option(
    "--workspace",
    type=click.Path(path_type=Path, file_okay=False, dir_okay=True),
    default=Path.cwd,
    show_default="current directory",
    help="Workspace root whose failures should be logged.",
)
@click.option(
    "--actor",
    type=click.Choice(["human", "agent"], case_sensitive=False),
    default="agent",
    show_default=True,
    help="Tag this execution in the watcher and the decorated error output.",
)
@click.option(
    "--shell", "use_shell", is_flag=True, default=False,
    help="Run command string via system shell (cmd.exe/sh) instead of direct exec. Use this from hooks to avoid bash dependency.",
)
@click.argument("command", nargs=-1, required=True, type=click.UNPROCESSED)
def exec_command(workspace: Path, actor: str, use_shell: bool, command: tuple[str, ...]) -> None:
    """Run a command through dont-hallucinate and decorate failures."""
    current_workspace = workspace_root(workspace)
    _run_wrapped_command(current_workspace, actor, command, shell=use_shell)


_HOOK_SCRIPT = """\
#!/usr/bin/env python3
\"\"\"PreToolUse hook: wraps every Bash tool call through hallucinate exec.\"\"\"
import json
import shlex
import sys

data = json.load(sys.stdin)
cmd = data.get("tool_input", {}).get("command", "")

if not cmd or cmd.lstrip().startswith("hallucinate"):
    print(json.dumps({"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "allow"}}))
    sys.exit(0)

wrapped = "dont-hallucinate exec --shell --actor agent -- " + shlex.quote(cmd)
print(json.dumps({
    "hookSpecificOutput": {
        "hookEventName": "PreToolUse",
        "permissionDecision": "allow",
        "updatedInput": {"command": wrapped},
    }
}))
"""

_HOOK_ENTRY = {
    "matcher": "Bash",
    "hooks": [{"type": "command", "command": "{python} {script}"}],
}


def _merge_hook(settings: dict, script_path: str) -> dict:
    python_cmd = _quote_cmd(_python_command())
    script_cmd = _quote_cmd(script_path)
    hooks = settings.setdefault("hooks", {})
    pre = hooks.setdefault("PreToolUse", [])
    # Avoid duplicates
    for entry in pre:
        if entry.get("matcher") == "Bash" and any(
            "hallucinate_hook" in h.get("command", "") for h in entry.get("hooks", [])
        ):
            return settings
    entry = {"matcher": "Bash", "hooks": [{"type": "command", "command": f"{python_cmd} {script_cmd}"}]}
    pre.append(entry)
    return settings


@main.command("install-hook-claude")
@click.option(
    "--global", "global_install", is_flag=True, default=False,
    help="Install into ~/.claude/settings.json so all projects are covered, including future ones.",
)
def install_hook_claude(global_install: bool) -> None:
    """Install the Claude Code PreToolUse hook for automatic bash interception."""
    hook_script, settings_file = _install_claude_hook(global_install)
    click.echo(f"Wrote {hook_script}")
    click.echo(f"Updated {settings_file}")
    scope = "all projects (global)" if global_install else "this project"
    click.echo(f"Done. Hallucinate will intercept bash commands for {scope}.")
    click.echo("Restart Claude Code to activate.")


@main.command("install-hook")
@click.option(
    "--global", "global_install", is_flag=True, default=False,
    help="Install into ~/.claude/settings.json so all projects are covered, including future ones.",
)
def install_hook_alias(global_install: bool) -> None:
    """Backward-compatible alias for install-hook-claude."""
    hook_script, settings_file = _install_claude_hook(global_install)
    click.echo(f"Wrote {hook_script}")
    click.echo(f"Updated {settings_file}")
    scope = "all projects (global)" if global_install else "this project"
    click.echo(f"Done. Hallucinate will intercept bash commands for {scope}.")
    click.echo("Restart Claude Code to activate.")


@main.command("install-hook-codex")
@click.option(
    "--global/--project", "global_install", default=True,
    help="Install into ~/.codex/config.toml. Project-local installs write .codex/config.toml in the current repo.",
)
def install_hook_codex(global_install: bool) -> None:
    """Install a Codex CLI shell hook via shell_environment_policy."""
    config_path, stream_file = _install_codex_hook(global_install)
    scope = "global" if global_install else "project-local"
    click.echo(f"Updated {config_path}")
    click.echo(f"Stream file: {stream_file}")
    click.echo(f"Done. Hallucinate will intercept Codex bash subprocess failures ({scope}).")
    click.echo("Restart Codex sessions to activate.")


@main.command("install-hook-cursor")
def install_hook_cursor() -> None:
    """Install shell-profile hooks that activate only inside Cursor agent terminals."""
    touched, stream_file = _install_cursor_hook()
    for path in touched:
        click.echo(f"Updated {path}")
    click.echo(f"Stream file: {stream_file}")
    click.echo("Done. Hallucinate will activate when Cursor sets CURSOR_AGENT in the terminal.")
    click.echo("Restart Cursor terminals to activate.")


if __name__ == "__main__":
    main()
