#!/usr/bin/env node
/**
 * project-detector.cjs - Project and environment detection logic
 *
 * Extracted from session-init.cjs for reuse in both Claude hooks and OpenCode plugins.
 * Detects project type, package manager, framework, and runtime versions.
 *
 * @module project-detector
 */

const fs = require('fs');
const path = require('path');
const os = require('os');
const { execSync, execFileSync } = require('child_process');

// ═══════════════════════════════════════════════════════════════════════════
// SAFE EXECUTION HELPERS
// ═══════════════════════════════════════════════════════════════════════════

/**
 * Safely execute shell command with optional timeout
 * @param {string} cmd - Command to execute
 * @param {number} [timeoutMs=5000] - Timeout in milliseconds
 * @returns {string|null} Output or null on error
 */
function execSafe(cmd, timeoutMs = 5000) {
  try {
    return execSync(cmd, {
      encoding: 'utf8',
      timeout: timeoutMs,
      stdio: ['pipe', 'pipe', 'pipe']
    }).trim();
  } catch (e) {
    return null;
  }
}

/**
 * Safely execute a binary with arguments (no shell interpolation)
 * @param {string} binary - Path to the executable
 * @param {string[]} args - Arguments array
 * @param {number} [timeoutMs=2000] - Timeout in milliseconds
 * @returns {string|null} Output or null on error
 */
function execFileSafe(binary, args, timeoutMs = 2000) {
  try {
    return execFileSync(binary, args, {
      encoding: 'utf8',
      timeout: timeoutMs,
      stdio: ['pipe', 'pipe', 'pipe']
    }).trim();
  } catch (e) {
    return null;
  }
}

// ═══════════════════════════════════════════════════════════════════════════
// PYTHON DETECTION
// ═══════════════════════════════════════════════════════════════════════════

/**
 * Validate that a path is a file and doesn't contain shell metacharacters
 * @param {string} p - Path to validate
 * @returns {boolean}
 */
function isValidPythonPath(p) {
  if (!p || typeof p !== 'string') return false;
  if (/[;&|`$(){}[\]<>!#*?]/.test(p)) return false;
  try {
    const stat = fs.statSync(p);
    return stat.isFile();
  } catch (e) {
    return false;
  }
}

/**
 * Build platform-specific Python paths for fast filesystem check
 * @returns {string[]} Array of potential Python paths
 */
function getPythonPaths() {
  const paths = [];

  if (process.env.PYTHON_PATH) {
    paths.push(process.env.PYTHON_PATH);
  }

  if (process.platform === 'win32') {
    const localAppData = process.env.LOCALAPPDATA;
    const programFiles = process.env.ProgramFiles || 'C:\\Program Files';
    const programFilesX86 = process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)';

    if (localAppData) {
      paths.push(path.join(localAppData, 'Microsoft', 'WindowsApps', 'python.exe'));
      paths.push(path.join(localAppData, 'Microsoft', 'WindowsApps', 'python3.exe'));
      for (const ver of ['313', '312', '311', '310', '39']) {
        paths.push(path.join(localAppData, 'Programs', 'Python', `Python${ver}`, 'python.exe'));
      }
    }

    for (const ver of ['313', '312', '311', '310', '39']) {
      paths.push(path.join(programFiles, `Python${ver}`, 'python.exe'));
      paths.push(path.join(programFilesX86, `Python${ver}`, 'python.exe'));
    }

    paths.push('C:\\Python313\\python.exe');
    paths.push('C:\\Python312\\python.exe');
    paths.push('C:\\Python311\\python.exe');
    paths.push('C:\\Python310\\python.exe');
    paths.push('C:\\Python39\\python.exe');
  } else {
    paths.push('/usr/bin/python3');
    paths.push('/usr/local/bin/python3');
    paths.push('/opt/homebrew/bin/python3');
    paths.push('/opt/homebrew/bin/python');
    paths.push('/usr/bin/python');
    paths.push('/usr/local/bin/python');
  }

  return paths;
}

/**
 * Find Python binary using fast `which` lookup first, then filesystem check
 * @returns {string|null} Python binary path or null
 */
function findPythonBinary() {
  // Fast path: try `which` command first (10ms vs 2000ms per path)
  if (process.platform !== 'win32') {
    const whichPython3 = execSafe('which python3', 500);
    if (whichPython3 && isValidPythonPath(whichPython3)) return whichPython3;

    const whichPython = execSafe('which python', 500);
    if (whichPython && isValidPythonPath(whichPython)) return whichPython;
  } else {
    // Windows: try `where` command
    const wherePython = execSafe('where python', 500);
    if (wherePython) {
      const firstPath = wherePython.split('\n')[0].trim();
      if (isValidPythonPath(firstPath)) return firstPath;
    }
  }

  // Fallback: check known paths
  const paths = getPythonPaths();
  for (const p of paths) {
    if (isValidPythonPath(p)) return p;
  }
  return null;
}

/**
 * Get Python version with optimized detection
 * @returns {string|null} Python version string or null
 */
function getPythonVersion() {
  const pythonPath = findPythonBinary();
  if (pythonPath) {
    const result = execFileSafe(pythonPath, ['--version']);
    if (result) return result;
  }

  const commands = ['python3', 'python'];
  for (const cmd of commands) {
    const result = execFileSafe(cmd, ['--version']);
    if (result) return result;
  }

  return null;
}

// ═══════════════════════════════════════════════════════════════════════════
// GIT DETECTION
// ═══════════════════════════════════════════════════════════════════════════

/**
 * Check if current directory is inside a git repository (fast check)
 * Uses filesystem traversal instead of git command to avoid command failures
 * @param {string} [startDir] - Directory to check from (defaults to cwd)
 * @returns {boolean}
 */
function isGitRepo(startDir) {
  let dir;
  try {
    dir = startDir || process.cwd();
  } catch (e) {
    // CWD deleted or inaccessible
    return false;
  }
  const root = path.parse(dir).root;

  while (dir !== root) {
    if (fs.existsSync(path.join(dir, '.git'))) return true;
    dir = path.dirname(dir);
  }
  return fs.existsSync(path.join(root, '.git'));
}

/**
 * Get git remote URL
 * @returns {string|null}
 */
function getGitRemoteUrl() {
  if (!isGitRepo()) return null;
  return execFileSafe('git', ['config', '--get', 'remote.origin.url']);
}

/**
 * Get current git branch
 * @returns {string|null}
 */
function getGitBranch() {
  if (!isGitRepo()) return null;
  return execFileSafe('git', ['branch', '--show-current']);
}

/**
 * Get git repository root
 * @returns {string|null}
 */
function getGitRoot() {
  if (!isGitRepo()) return null;
  return execFileSafe('git', ['rev-parse', '--show-toplevel']);
}

// ═══════════════════════════════════════════════════════════════════════════
// PROJECT DETECTION
// ═══════════════════════════════════════════════════════════════════════════

/**
 * Detect project type based on workspace indicators
 * @param {string} [configOverride] - Manual override from config
 * @returns {'monorepo' | 'library' | 'single-repo'}
 */
function detectProjectType(configOverride) {
  if (configOverride && configOverride !== 'auto') return configOverride;

  if (fs.existsSync('pnpm-workspace.yaml')) return 'monorepo';
  if (fs.existsSync('lerna.json')) return 'monorepo';

  if (fs.existsSync('package.json')) {
    try {
      const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
      if (pkg.workspaces) return 'monorepo';
      if (pkg.main || pkg.exports) return 'library';
    } catch (e) { /* ignore */ }
  }

  return 'single-repo';
}

/**
 * Detect package manager from lock files
 * @param {string} [configOverride] - Manual override from config
 * @returns {'npm' | 'pnpm' | 'yarn' | 'bun' | null}
 */
function detectPackageManager(configOverride) {
  if (configOverride && configOverride !== 'auto') return configOverride;

  if (fs.existsSync('bun.lockb')) return 'bun';
  if (fs.existsSync('pnpm-lock.yaml')) return 'pnpm';
  if (fs.existsSync('yarn.lock')) return 'yarn';
  if (fs.existsSync('package-lock.json')) return 'npm';

  return null;
}

/**
 * Detect framework from package.json dependencies
 * @param {string} [configOverride] - Manual override from config
 * @returns {string|null}
 */
function detectFramework(configOverride) {
  if (configOverride && configOverride !== 'auto') return configOverride;
  if (!fs.existsSync('package.json')) return null;

  try {
    const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
    const deps = { ...pkg.dependencies, ...pkg.devDependencies };

    if (deps['next']) return 'next';
    if (deps['nuxt']) return 'nuxt';
    if (deps['astro']) return 'astro';
    if (deps['@remix-run/node'] || deps['@remix-run/react']) return 'remix';
    if (deps['svelte'] || deps['@sveltejs/kit']) return 'svelte';
    if (deps['vue']) return 'vue';
    if (deps['react']) return 'react';
    if (deps['express']) return 'express';
    if (deps['fastify']) return 'fastify';
    if (deps['hono']) return 'hono';
    if (deps['elysia']) return 'elysia';

    return null;
  } catch (e) {
    return null;
  }
}

// ═══════════════════════════════════════════════════════════════════════════
// CODING LEVEL
// ═══════════════════════════════════════════════════════════════════════════

/**
 * Get coding level style name mapping
 * @param {number} level - Coding level (0-5)
 * @returns {string} Style name
 */
function getCodingLevelStyleName(level) {
  const styleMap = {
    0: 'coding-level-0-eli5',
    1: 'coding-level-1-junior',
    2: 'coding-level-2-mid',
    3: 'coding-level-3-senior',
    4: 'coding-level-4-lead',
    5: 'coding-level-5-god'
  };
  return styleMap[level] || 'coding-level-5-god';
}

/**
 * Get coding level guidelines by reading from output-styles .md files
 * @param {number} level - Coding level (-1 to 5)
 * @param {string} [configDir] - Config directory path
 * @returns {string|null} Guidelines text or null if disabled
 */
function getCodingLevelGuidelines(level, configDir) {
  if (level === -1 || level === null || level === undefined) return null;

  const styleName = getCodingLevelStyleName(level);
  const basePath = configDir || path.join(process.cwd(), '.claude');
  const stylePath = path.join(basePath, 'output-styles', `${styleName}.md`);

  try {
    if (!fs.existsSync(stylePath)) return null;
    const content = fs.readFileSync(stylePath, 'utf8');
    const withoutFrontmatter = content.replace(/^---[\s\S]*?---\n*/, '').trim();
    return withoutFrontmatter;
  } catch (e) {
    return null;
  }
}

// ═══════════════════════════════════════════════════════════════════════════
// CONTEXT OUTPUT
// ═══════════════════════════════════════════════════════════════════════════

/**
 * Build context summary for output (compact, single line)
 * @param {Object} config - Loaded config
 * @param {Object} detections - Project detections
 * @param {{ path: string|null, resolvedBy: string|null }} resolved - Plan resolution
 * @param {string|null} gitRoot - Git repository root
 * @returns {string}
 */
function buildContextOutput(config, detections, resolved, gitRoot) {
  const lines = [`Project: ${detections.type || 'unknown'}`];
  if (detections.pm) lines.push(`PM: ${detections.pm}`);
  lines.push(`Plan naming: ${config.plan.namingFormat}`);

  if (gitRoot && gitRoot !== process.cwd()) {
    lines.push(`Root: ${gitRoot}`);
  }

  if (resolved.path) {
    if (resolved.resolvedBy === 'session') {
      lines.push(`Plan: ${resolved.path}`);
    } else {
      lines.push(`Suggested: ${resolved.path}`);
    }
  }

  return lines.join(' | ');
}

// ═══════════════════════════════════════════════════════════════════════════
// MAIN ENTRY POINT
// ═══════════════════════════════════════════════════════════════════════════

/**
 * Detect all project information
 *
 * @param {Object} [options]
 * @param {Object} [options.configOverrides] - Override auto-detection
 * @returns {{
 *   type: 'monorepo' | 'library' | 'single-repo',
 *   packageManager: 'npm' | 'pnpm' | 'yarn' | 'bun' | null,
 *   framework: string | null,
 *   pythonVersion: string | null,
 *   nodeVersion: string,
 *   gitBranch: string | null,
 *   gitRoot: string | null,
 *   gitUrl: string | null,
 *   osPlatform: string,
 *   user: string,
 *   locale: string,
 *   timezone: string
 * }}
 */
function detectProject(options = {}) {
  const { configOverrides = {} } = options;

  return {
    type: detectProjectType(configOverrides.type),
    packageManager: detectPackageManager(configOverrides.packageManager),
    framework: detectFramework(configOverrides.framework),
    pythonVersion: getPythonVersion(),
    nodeVersion: process.version,
    gitBranch: getGitBranch(),
    gitRoot: getGitRoot(),
    gitUrl: getGitRemoteUrl(),
    osPlatform: process.platform,
    user: process.env.USERNAME || process.env.USER || process.env.LOGNAME || os.userInfo().username,
    locale: process.env.LANG || '',
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
  };
}

/**
 * Build static environment info object
 * @param {string} [configDir] - Config directory path
 * @returns {Object} Static environment info
 */
function buildStaticEnv(configDir) {
  return {
    nodeVersion: process.version,
    pythonVersion: getPythonVersion(),
    osPlatform: process.platform,
    gitUrl: getGitRemoteUrl(),
    gitBranch: getGitBranch(),
    gitRoot: getGitRoot(),
    user: process.env.USERNAME || process.env.USER || process.env.LOGNAME || os.userInfo().username,
    locale: process.env.LANG || '',
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
    configDir: configDir || path.join(process.cwd(), '.claude')
  };
}

// ═══════════════════════════════════════════════════════════════════════════
// EXPORTS
// ═══════════════════════════════════════════════════════════════════════════

module.exports = {
  // Main entry points
  detectProject,
  buildStaticEnv,

  // Detection functions
  detectProjectType,
  detectPackageManager,
  detectFramework,

  // Python detection
  getPythonVersion,
  findPythonBinary,
  getPythonPaths,
  isValidPythonPath,

  // Git detection
  isGitRepo,
  getGitRemoteUrl,
  getGitBranch,
  getGitRoot,

  // Coding level
  getCodingLevelStyleName,
  getCodingLevelGuidelines,

  // Output
  buildContextOutput,

  // Helpers
  execSafe,
  execFileSafe
};
