#!/usr/bin/env node
/**
 * CCS Image Analyzer Hook - Read Tool Interceptor
 *
 * Intercepts Claude's Read tool for image/PDF files and analyzes them via CLIProxy.
 * Returns detailed text descriptions instead of allowing direct visual access.
 *
 * Environment Variables (set by CCS):
 *   CCS_IMAGE_ANALYSIS_SKIP=1                 - Skip this hook entirely
 *   CCS_IMAGE_ANALYSIS_ENABLED=1              - Enable image analysis (default: 1)
 *   CCS_IMAGE_ANALYSIS_PROVIDER_MODELS        - Provider:model mapping (e.g., agy:gemini-2.5-flash,gemini:gemini-2.5-flash)
 *   CCS_CURRENT_PROVIDER                      - Current CLIProxy provider (e.g., agy, gemini, codex)
 *   CCS_IMAGE_ANALYSIS_TIMEOUT=60             - Timeout in seconds (default: 60)
 *   CCS_PROFILE_TYPE                          - Profile type (account/default skip)
 *   ANTHROPIC_MODEL                           - Chat model env (not used for image analysis fallback)
 *   CCS_DEBUG=1                               - Enable debug output
 *
 * Exit codes:
 *   0 - Allow tool (pass-through to native Read)
 *   2 - Block tool (deny with analysis/message)
 *
 * @module hooks/image-analyzer-transformer
 */

const fs = require('fs');
const path = require('path');
const { analyzeFile, isAnalyzableFile, parseProviderModels } = require('./image-analysis-runtime.cjs');

// ============================================================================
// PLATFORM DETECTION
// ============================================================================

const isWindows = process.platform === 'win32';

// ============================================================================
// CONFIGURATION
// ============================================================================

const DEFAULT_MODEL = 'gemini-2.5-flash';
const DEFAULT_TIMEOUT_SEC = 60;

// ============================================================================
// ERROR CODES (for categorization)
// ============================================================================

const ERROR_CODES = {
  FILE_TOO_LARGE: 'FILE_TOO_LARGE',
  CLIPROXY_UNAVAILABLE: 'CLIPROXY_UNAVAILABLE',
  AUTH_FAILED: 'AUTH_FAILED',
  TIMEOUT: 'TIMEOUT',
  RATE_LIMIT: 'RATE_LIMIT',
  API_ERROR: 'API_ERROR',
  PARSE_ERROR: 'PARSE_ERROR',
  UNKNOWN: 'UNKNOWN',
};

/**
 * Output debug information to stderr
 * Only outputs when CCS_DEBUG=1
 */
function debugLog(message, data = {}) {
  if (!process.env.CCS_DEBUG) return;

  const lines = [`[CCS Hook] ${message}`];

  for (const [key, value] of Object.entries(data)) {
    if (value !== undefined && value !== null) {
      lines.push(`  ${key}: ${value}`);
    }
  }

  console.error(lines.join('\n'));
}

/**
 * Get detailed debug context
 */
function getDebugContext(filePath, stats) {
  const currentProvider = process.env.CCS_CURRENT_PROVIDER || 'unknown';
  const model =
    process.env.CCS_IMAGE_ANALYSIS_MODEL ||
    parseProviderModels(process.env.CCS_IMAGE_ANALYSIS_PROVIDER_MODELS)[currentProvider] ||
    DEFAULT_MODEL;
  const timeout = parseInt(process.env.CCS_IMAGE_ANALYSIS_TIMEOUT || DEFAULT_TIMEOUT_SEC, 10);

  return {
    file: path.basename(filePath),
    size: stats ? `${(stats.size / 1024).toFixed(1)} KB` : 'unknown',
    provider: currentProvider,
    model: model,
    timeout: `${timeout}s`,
    endpoint: process.env.CCS_IMAGE_ANALYSIS_RUNTIME_BASE_URL || '(runtime fallback)',
  };
}

/**
 * Get current provider/model context for error messages
 */
function getProviderContext() {
  const provider = process.env.CCS_CURRENT_PROVIDER || 'unknown';
  const model =
    process.env.CCS_IMAGE_ANALYSIS_MODEL ||
    parseProviderModels(process.env.CCS_IMAGE_ANALYSIS_PROVIDER_MODELS)[provider] ||
    DEFAULT_MODEL;
  return { provider, model };
}
/**
 * Format analysis description for Claude (matches websearch format)
 */
function formatDescription(filePath, description, model, fileSize) {
  const sizeKB = fileSize ? (fileSize / 1024).toFixed(1) : '?';
  return [
    `[Image Analysis via CLIProxy]`,
    '',
    `File: ${path.basename(filePath)} (${sizeKB} KB)`,
    `Model: ${model}`,
    '',
    '---',
    '',
    description,
    '',
    '---',
    '*Use this description to understand the image content.*',
  ].join('\n');
}

// ============================================================================
// SPECIALIZED ERROR HANDLERS
// ============================================================================

/**
 * Format error output for Claude hook
 */
function formatErrorOutput(filePath, errorCode, message, troubleshooting) {
  const { provider, model } = getProviderContext();

  const lines = [
    `[Image Analysis - Error]`,
    '',
    `File: ${path.basename(filePath)}`,
    `Provider: ${provider} | Model: ${model}`,
    '',
    `Error: ${message}`,
  ];

  if (troubleshooting && troubleshooting.length > 0) {
    lines.push('');
    lines.push('Troubleshooting:');
    troubleshooting.forEach((step, i) => {
      lines.push(`  ${i + 1}. ${step}`);
    });
  }

  lines.push('');
  lines.push('For help: ccs config image-analysis --help');

  return {
    decision: 'block',
    reason: `Image analysis failed: ${errorCode}`,
    systemMessage: `[Image Analysis] Failed: ${message}`,
    hookSpecificOutput: {
      hookEventName: 'PreToolUse',
      permissionDecision: 'deny',
      permissionDecisionReason: lines.join('\n'),
    },
  };
}

/**
 * File too large error
 */
function outputFileTooLargeError(filePath, actualSizeMB, maxSizeMB) {
  const output = formatErrorOutput(
    filePath,
    ERROR_CODES.FILE_TOO_LARGE,
    `File too large (${actualSizeMB.toFixed(2)}MB > ${maxSizeMB}MB limit)`,
    [
      'Reduce image resolution or use compression',
      'For screenshots: use PNG optimizer (pngquant, optipng)',
      'For photos: resize to max 2048px width',
      `Current limit: ${maxSizeMB}MB per file`,
    ]
  );
  console.log(JSON.stringify(output));
  process.exit(2);
}

function outputAuthError(filePath, statusCode) {
  const { provider } = getProviderContext();
  const output = formatErrorOutput(
    filePath,
    ERROR_CODES.AUTH_FAILED,
    `Authentication failed (HTTP ${statusCode})`,
    [
      `Re-authenticate: ccs ${provider} --auth`,
      `Check accounts: ccs ${provider} --accounts`,
      'Verify OAuth token is valid',
      'Check: ccs doctor',
    ]
  );
  console.log(JSON.stringify(output));
  process.exit(2);
}

/**
 * Timeout error
 */
function outputTimeoutError(filePath, timeoutSec) {
  const { model } = getProviderContext();
  const output = formatErrorOutput(
    filePath,
    ERROR_CODES.TIMEOUT,
    `Request timed out after ${timeoutSec}s`,
    [
      'Large files or complex images take longer',
      `Increase timeout: ccs config image-analysis --timeout ${timeoutSec * 2}`,
      'Or via env: CCS_IMAGE_ANALYSIS_TIMEOUT=120',
      `Current model (${model}) may be slow - try a faster variant`,
      'Check CLIProxy health: curl http://127.0.0.1:8317',
    ]
  );
  console.log(JSON.stringify(output));
  process.exit(2);
}

/**
 * Rate limit error
 */
function outputRateLimitError(filePath, retryAfterSec) {
  const { provider } = getProviderContext();
  const retryHint = retryAfterSec ? `Retry after ${retryAfterSec}s` : 'Wait a moment and retry';
  const output = formatErrorOutput(
    filePath,
    ERROR_CODES.RATE_LIMIT,
    'Rate limit exceeded',
    [
      retryHint,
      `Provider ${provider} has usage limits`,
      'Consider switching accounts: ccs ' + provider + ' --accounts',
      'Check quota: ccs cliproxy doctor',
    ]
  );
  console.log(JSON.stringify(output));
  process.exit(2);
}

/**
 * Generic API error
 */
function outputApiError(filePath, statusCode, responseBody) {
  // Try to extract error message from response
  let errorDetail = `HTTP ${statusCode}`;
  try {
    const parsed = JSON.parse(responseBody);
    if (parsed.error?.message) {
      errorDetail = parsed.error.message;
    } else if (parsed.message) {
      errorDetail = parsed.message;
    }
  } catch {
    // Use raw body if not JSON (truncated)
    if (responseBody && responseBody.length < 100) {
      errorDetail = responseBody;
    }
  }

  const output = formatErrorOutput(
    filePath,
    ERROR_CODES.API_ERROR,
    `API error: ${errorDetail}`,
    [
      'Check CLIProxy logs: ccs cleanup --show-logs',
      'Verify provider is authenticated: ccs doctor',
      'Try a different provider or model',
      'Report persistent issues: https://github.com/kaitranntt/ccs/issues',
    ]
  );
  console.log(JSON.stringify(output));
  process.exit(2);
}

/**
 * File permission error
 */
function outputFileAccessError(filePath, error) {
  const output = formatErrorOutput(
    filePath,
    ERROR_CODES.UNKNOWN,
    `File access denied: ${error}`,
    [
      'Check file permissions: ls -l ' + filePath,
      isWindows ? 'Run terminal as Administrator if needed' : 'Use sudo or adjust file ownership',
      'Verify file is readable by current user',
      'Move file to accessible location',
    ]
  );
  console.log(JSON.stringify(output));
  process.exit(2);
}

/**
 * Unknown/fallback error (replaces old outputError)
 */
function outputUnknownError(filePath, error) {
  const output = formatErrorOutput(
    filePath,
    ERROR_CODES.UNKNOWN,
    error || 'Unknown error occurred',
    [
      'Check CLIProxy is running: curl http://127.0.0.1:8317',
      'Verify authentication: ccs doctor',
      'Check file is valid image/PDF',
      'Enable debug: CCS_DEBUG=1 ccs <provider>',
    ]
  );
  console.log(JSON.stringify(output));
  process.exit(2);
}

/**
 * CLIProxy unavailable fallback - blocks Read to prevent context overflow
 * When CLIProxy is not running, we cannot analyze the image.
 * Blocking prevents the image from loading into Claude's context (100K+ tokens).
 */
function outputCliProxyUnavailableFallback(filePath) {
  const fileName = filePath.split(/[/\\]/).pop() || filePath;

  // Keep message minimal to avoid context pollution and hallucination
  const message = [
    '[Image Read Blocked]',
    '',
    `File: ${fileName}`,
    '',
    'CLIProxy unavailable. Image blocked to prevent context overflow.',
  ].join('\n');

  const output = {
    decision: 'block',
    reason: 'CLIProxy unavailable - image blocked to prevent context overflow',
    systemMessage: `[Image Blocked] ${fileName} - CLIProxy unavailable. Start: ccs config`,
    hookSpecificOutput: {
      hookEventName: 'PreToolUse',
      permissionDecision: 'deny',
      permissionDecisionReason: message,
    },
  };

  console.log(JSON.stringify(output));
  process.exit(2);
}

/**
 * Output success response and exit
 */
function outputSuccess(filePath, description, model, fileSize) {
  debugLog('Returning analysis result', {
    file: path.basename(filePath),
    model: model,
    descriptionLength: `${description.length} chars`,
  });

  const formattedDescription = formatDescription(filePath, description, model, fileSize);

  const output = {
    decision: 'block',
    reason: `Image analyzed: ${path.basename(filePath)}`,
    systemMessage: `[Image Analysis] ${path.basename(filePath)} analyzed via CLIProxy (${model})`,
    hookSpecificOutput: {
      hookEventName: 'PreToolUse',
      permissionDecision: 'deny',
      permissionDecisionReason: formattedDescription,
    },
  };

  console.log(JSON.stringify(output));
  process.exit(2);
}

/**
 * Determine if hook should skip, with debug logging
 */
function shouldSkipHook() {
  if (process.env.CCS_IMAGE_ANALYSIS_SKIP_HOOK === '1') {
    debugLog('Skipping: CCS_IMAGE_ANALYSIS_SKIP_HOOK=1');
    return true;
  }

  // Explicit skip signal
  if (process.env.CCS_IMAGE_ANALYSIS_SKIP === '1') {
    debugLog('Skipping: CCS_IMAGE_ANALYSIS_SKIP=1');
    return true;
  }

  // Explicit disable
  if (process.env.CCS_IMAGE_ANALYSIS_ENABLED === '0') {
    debugLog('Skipping: image analysis disabled (CCS_IMAGE_ANALYSIS_ENABLED=0)');
    return true;
  }

  // Account/default profiles - use native Read
  const profileType = process.env.CCS_PROFILE_TYPE;
  if (profileType === 'account' || profileType === 'default') {
    debugLog(`Skipping: profile type "${profileType}" uses native Read`);
    return true;
  }

  // Check if current provider has a vision model configured
  const explicitModel = process.env.CCS_IMAGE_ANALYSIS_MODEL;
  const currentProvider = process.env.CCS_CURRENT_PROVIDER || '';
  const providerModels = parseProviderModels(process.env.CCS_IMAGE_ANALYSIS_PROVIDER_MODELS);

  if (!explicitModel?.trim() && !providerModels[currentProvider]) {
    debugLog(`Skipping: provider "${currentProvider}" not in provider_models`, {
      configured_providers: Object.keys(providerModels).join(', ') || 'none',
    });
    return true;
  }

  return false;
}

// ============================================================================
// MAIN HOOK LOGIC
// ============================================================================

// Read input from stdin
let input = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (chunk) => {
  input += chunk;
});
process.stdin.on('end', () => {
  processHook();
});

// Handle stdin not being available
process.stdin.on('error', () => {
  process.exit(0);
});

/**
 * Main hook processing logic
 *
 * Two-phase design: Phase 1 filters non-image Read calls silently (exit 0).
 * Phase 2 only runs for confirmed image/PDF files, so error messages are
 * always relevant and never confuse users reading code or text files.
 */
async function processHook() {
  // Phase 1: Fast bail-out for non-image files
  // Any failure here → pass through silently to native Read
  let filePath;
  try {
    const data = JSON.parse(input);

    // Only handle Read tool
    if (data.tool_name !== 'Read') {
      process.exit(0);
    }

    filePath = data.tool_input?.file_path || '';

    if (!filePath) {
      process.exit(0);
    }

    // Check file extension BEFORE any other processing — this is the key gate
    // that ensures non-image Read calls never see hook errors
    if (!isAnalyzableFile(filePath)) {
      process.exit(0);
    }
  } catch {
    // stdin parse failure or unexpected error → pass through silently
    process.exit(0);
  }

  // Phase 2: Image/PDF file processing — errors here are relevant to the user
  try {
    // Skip for native accounts or explicit disable
    if (shouldSkipHook()) {
      process.exit(0);
    }

    if (!fs.existsSync(filePath)) {
      process.exit(0);
    }

    const debugContext = getDebugContext(filePath, null);
    debugLog('Image analysis runtime prepared', debugContext);

    const result = await analyzeFile(filePath);
    outputSuccess(filePath, result.description, result.model, result.fileSize);
  } catch (err) {
    if (process.env.CCS_DEBUG) {
      console.error('[CCS Hook] Error:', err.message);
    }

    // filePath is guaranteed set by Phase 1 — only image files reach here

    // Categorize error by message pattern
    const errMsg = err.message || '';

    if (errMsg.startsWith('FILE_TOO_LARGE:')) {
      const fileSizeMb = Number.parseInt(errMsg.split(':')[1], 10) / 1024 / 1024;
      outputFileTooLargeError(filePath, fileSizeMb, 10);
    } else if (errMsg.startsWith('AUTH_ERROR:')) {
      const statusCode = parseInt(errMsg.split(':')[1], 10);
      outputAuthError(filePath, statusCode);
    } else if (errMsg.startsWith('RATE_LIMIT:')) {
      const retryAfter = errMsg.split(':')[1];
      outputRateLimitError(filePath, retryAfter ? parseInt(retryAfter, 10) : null);
    } else if (errMsg.startsWith('API_ERROR:')) {
      const parts = errMsg.split(':');
      const statusCode = parseInt(parts[1], 10);
      const body = parts.slice(2).join(':');
      outputApiError(filePath, statusCode, body);
    } else if (errMsg === 'TIMEOUT' || errMsg.includes('timed out') || errMsg.includes('timeout')) {
      const timeout = parseInt(process.env.CCS_IMAGE_ANALYSIS_TIMEOUT || DEFAULT_TIMEOUT_SEC, 10);
      outputTimeoutError(filePath, timeout);
    } else if (
      errMsg.includes('ECONNREFUSED') ||
      errMsg.includes('ENOTFOUND') ||
      errMsg.includes('ENETUNREACH') ||
      errMsg.includes('EAI_AGAIN')
    ) {
      outputCliProxyUnavailableFallback(filePath);
    } else if (errMsg.includes('EACCES') || errMsg.includes('EPERM')) {
      outputFileAccessError(filePath, errMsg);
    } else {
      outputUnknownError(filePath, errMsg);
    }
  }
}
