#!/usr/bin/env python3
# vim: set filetype=python fileencoding=utf-8:
# -*- coding: utf-8 -*-

''' Claude Code hook to detect improper Python usage in Bash commands. '''


import json
import shlex
import sys


def main( ):
    event = _acquire_event_data( )
    command_line = _extract_command( event )
    commands = _partition_command_line( command_line )
    for command in commands:
        _check_direct_python_usage( command )
        _check_multiline_python_c( command )
        _check_direct_tool_usage( command )
    raise SystemExit( 0 )


def _acquire_event_data( ):
    try: return json.load( sys.stdin )
    except json.JSONDecodeError:
        _reactor_failure( "Invalid event data." )


def _check_direct_python_usage( tokens ):
    ''' Checks for direct python usage patterns. '''
    emessage = (
        "Warning: Direct Python usage detected in command.\n"
        "Consider using 'hatch run python' or "
        "'hatch --env develop run python' to ensure dependencies "
        "are available." )
    for token in tokens:
        if token == 'hatch': return # noqa: S105
        if _is_python_command( token ): _error( emessage )


def _check_multiline_python_c( tokens ):
    ''' Checks for multi-line python -c scripts using shlex parsing. '''
    emessage = (
        "Warning: Multi-line Python script detected in command.\n"
        "Consider writing the script to a file "
        "in the '.auxiliary/scribbles' directory "
        "instead of using 'python -c' with multi-line code." )
    for i, token in enumerate( tokens ):
        if (    _is_python_command( token )
            and _check_python_c_argument( tokens, i )
        ): _error( emessage )


def _check_direct_tool_usage( tokens ):
    ''' Checks for direct usage of Python tools outside Hatch environment. '''
    emessage = (
        "Warning: Direct Python tool usage detected in command.\n"
        "Use 'hatch --env develop run {tool}' instead to ensure "
        "proper environment and configuration." )
    for token in tokens:
        if token == 'hatch': return # noqa: S105
        if _is_python_tool( token ): 
            _error( emessage.format( tool = token ) )


def _check_python_c_argument( tokens, python_index ):
    ''' Checks if Python -c argument contains multiline code. '''
    for j in range( python_index + 1, len( tokens ) ):
        if tokens[ j ] == '-c' and j + 1 < len( tokens ):
            c_argument = tokens[ j + 1 ]
            return '\n' in c_argument
        if not tokens[ j ].startswith( '-' ):
            # Non-option argument, stop looking for -c
            break
    return False


def _error( message: str ):
    print( message, file = sys.stderr )
    raise SystemExit( 2 )


def _extract_command( event_data ):
    ''' Extracts command from event data, exit if not Bash tool. '''
    tool_name = event_data.get( 'tool_name', '' )
    if tool_name != 'Bash': raise SystemExit( 0 )
    tool_input = event_data.get( 'tool_input', { } )
    return tool_input.get( 'command', '' )


def _is_python_command( token ):
    ''' Checks if token is a Python command. '''
    return (
        token in ( 'python', 'python3' ) or token.startswith( 'python3.' ) )


def _is_python_tool( token ):
    ''' Checks if token is a Python development tool. '''
    return token in ( 'coverage', 'pyright', 'pytest', 'ruff' )


_splitters = frozenset( ( ';', '&', '|', '&&', '||' ) )
def _partition_command_line( command_line ):
    tokens = shlex.split( command_line )
    commands = [ ]
    command_tokens = [ ]
    for token in tokens:
        if token in _splitters:
            commands.append( command_tokens )
            command_tokens = [ ]
            continue
        command_tokens.append( token )
    if command_tokens: commands.append( command_tokens )
    return commands


def _reactor_failure( message ):
    print( "Claude Code Hook Failure: {message}", file = sys.stderr )
    raise SystemExit( 1 )


if __name__ == '__main__': main()
