#!/usr/bin/env ruby
# frozen_string_literal: true

require 'claude_hooks'
require 'open3'
require 'shellwords'

# Auto Format Handler
#
# PURPOSE: Automatically format/lint files after Write, Edit, or MultiEdit operations
# DESIGN: Lightweight, obvious, maintainable using claude_hooks DSL patterns
# ALIGNMENT: Mirrors pre-commit configuration for consistency
#
# SUPPORTED FORMATTERS:
# - Ruby: RuboCop with auto-correct (-A flag)
# - Markdown: markdownlint with --fix (matching pre-commit rules)
# - Shell: shfmt with 2-space indentation (matching pre-commit args)
# - Lua: stylua with custom configuration (160 column width, Unix line endings, etc.)
# - Rust: rustfmt with default configuration
# - Python: ruff format (automatically uses existing Black/isort/flake8 configs)
# - YAML: yamlfmt with default configuration, prettier as fallback
# - JavaScript/TypeScript: eslint with --fix, prettier as fallback

class AutoFormatHandler < ClaudeHooks::PostToolUse
  DEFAULT_SKIP_PATTERNS = %w[
    node_modules/
    dist/
    build/
    .git/
    *.min.js
    *.min.css
    vendor/
    tmp/
    .bundle/
  ].freeze

  def call
    log "Auto-format handler triggered for #{tool_name}"

    return output_data unless should_process_tool?
    return output_data unless file_path_available?
    return output_data if should_skip_file?

    perform_formatting

    output_data
  end

  private

  def should_process_tool?
    file_modification_tools.include?(tool_name) && tool_successful?
  end

  def file_modification_tools
    %w[Write Edit MultiEdit]
  end

  def tool_successful?
    return false if tool_response&.dig('error')
    return false if tool_response&.dig('isError')
    return false if tool_response&.dig('success') == false
    return false if tool_response&.dig('exitCode')&.nonzero?

    true
  end

  def file_path_available?
    return true if current_file_path && File.exist?(current_file_path)

    log "File path not available or doesn't exist: #{current_file_path}", level: :warn
    false
  end

  def current_file_path
    @current_file_path ||= tool_input&.dig('file_path')
  end

  def should_skip_file?
    return false unless current_file_path

    relative_path = relative_file_path

    # Check against simple, obvious skip patterns
    if skip_patterns.any? { |pattern| matches_skip_pattern?(relative_path, pattern) }
      log "Skipping #{relative_path} - matches ignore pattern"
      return true
    end

    false
  end

  def relative_file_path
    # Use claude_hooks cwd helper instead of Dir.pwd
    File.expand_path(current_file_path).sub("#{cwd}/", '')
  end

  def skip_patterns
    DEFAULT_SKIP_PATTERNS
  end

  def matches_skip_pattern?(file_path, pattern)
    if pattern.end_with?('/')
      # Directory pattern
      file_path.start_with?(pattern[0..-2])
    elsif pattern.include?('*')
      # Simple glob pattern using Ruby's built-in File.fnmatch
      File.fnmatch(pattern, file_path)
    else
      # Exact match or basename match
      file_path == pattern || File.basename(file_path) == pattern
    end
  end

  def perform_formatting
    formatter = detect_formatter
    unless formatter
      log "No formatter available for #{current_file_path}"
      return
    end

    log "Formatting #{current_file_path} with #{formatter[:name]}"

    result = run_formatter(formatter)

    if result[:success]
      log "Successfully formatted #{current_file_path}"
      add_success_feedback(formatter[:name])
    else
      log "Formatting failed: #{result[:error]}", level: :error
      add_error_feedback(formatter[:name], result[:error])
    end
  end

  def detect_formatter
    extension = File.extname(current_file_path).downcase

    case extension
    when '.rb'
      command_available?('rubocop') ? { name: 'RuboCop', command: 'rubocop', args: ['-A'] } : nil
    when '.md'
      if command_available?('markdownlint')
        {
          name: 'markdownlint',
          command: 'markdownlint',
          args: [
            '--fix',
            '--disable',
            'MD013,MD041,MD026,MD012,MD024'
          ]
        }
      end
    when '.sh', '.bash'
      if command_available?('shfmt')
        {
          name: 'shfmt',
          command: 'shfmt',
          args: ['-w', '-i', '2'] # 2-space indentation
        }
      end
    when '.lua'
      if command_available?('stylua')
        {
          name: 'stylua',
          command: 'stylua',
          args: [
            '--column-width', '160',
            '--line-endings', 'Unix',
            '--indent-type', 'Spaces',
            '--indent-width', '2',
            '--quote-style', 'AutoPreferSingle',
            '--call-parentheses', 'None'
          ]
        }
      end
    when '.rs'
      command_available?('rustfmt') ? { name: 'rustfmt', command: 'rustfmt', args: [] } : nil
    when '.py'
      command_available?('ruff') ? { name: 'ruff', command: 'ruff', args: ['format'] } : nil
    when '.yml', '.yaml'
      if command_available?('yamlfmt')
        { name: 'yamlfmt', command: 'yamlfmt', args: ['-w'] }
      elsif command_available?('prettier')
        { name: 'prettier', command: 'prettier', args: ['--write', '--parser', 'yaml'] }
      end
    when '.js', '.jsx', '.ts', '.tsx', '.json'
      if command_available?('eslint')
        { name: 'eslint', command: 'eslint', args: ['--fix'] }
      elsif command_available?('prettier')
        { name: 'prettier', command: 'prettier', args: ['--write'] }
      end
    end
  end

  def command_available?(command)
    # Cache command availability to avoid repeated system calls
    @command_cache ||= {}

    return @command_cache[command] if @command_cache.key?(command)

    @command_cache[command] = system("which #{command} > /dev/null 2>&1")
  end

  def run_formatter(formatter)
    command_parts = [formatter[:command]] + formatter[:args] + [current_file_path]
    command = command_parts.map { |part| Shellwords.escape(part) }.join(' ')

    log "Running: #{command}"

    stdout_err, status = Open3.capture2e(command)

    if status.success?
      { success: true, output: stdout_err }
    else
      { success: false, error: stdout_err.strip }
    end
  rescue StandardError => e
    { success: false, error: e.message }
  end

  def add_success_feedback(formatter_name)
    # Use output_data hash directly - obvious and DSL-idiomatic
    output_data['feedback'] ||= []
    output_data['feedback'] << "✓ Auto-formatted with #{formatter_name}"
  end

  def add_error_feedback(formatter_name, error)
    output_data['feedback'] ||= []
    output_data['feedback'] << "⚠ Auto-formatting failed (#{formatter_name}): #{error}"
  end
end

# Testing support - claude_hooks DSL pattern
if __FILE__ == $PROGRAM_NAME
  ClaudeHooks::CLI.test_runner(AutoFormatHandler) do |input_data|
    input_data['tool_name'] = 'Write'
    input_data['tool_input'] = { 'file_path' => '/tmp/test.rb' }
    input_data['tool_response'] = { 'success' => true }
    input_data['session_id'] = 'test-session-01'

    # Create a test file for demonstration
    File.write('/tmp/test.rb', "def hello\nputs 'world'\nend")
  end
end
