/**
 * Cross-platform Hook shared utility library
 * Provides cross-platform compatible shared functions for Claude Code Hooks
 *
 * @module hook-common
 */

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

/**
 * Get Git status information
 * @param {string} cwd - Current working directory
 * @returns {Object} Git info object
 */
function getGitInfo(cwd) {
  try {
    // Check if this is a Git repository
    execSync('git rev-parse --git-dir', {
      cwd,
      stdio: 'pipe'
    });

    // Get branch name
    let branch = 'unknown';
    try {
      branch = execSync('git branch --show-current', {
        cwd,
        encoding: 'utf8',
        stdio: 'pipe'
      }).trim();
    } catch {
      branch = 'unknown';
    }

    // Get changed files
    let changes = '';
    try {
      changes = execSync('git status --porcelain', {
        cwd,
        encoding: 'utf8',
        stdio: 'pipe'
      });
    } catch {
      changes = '';
    }

    const changeList = changes.trim().split('\n').filter(Boolean);
    const hasChanges = changeList.length > 0;

    return {
      is_repo: true,
      branch,
      changes_count: changeList.length,
      has_changes: hasChanges,
      changes: changeList
    };
  } catch {
    return {
      is_repo: false,
      branch: 'unknown',
      changes_count: 0,
      has_changes: false,
      changes: []
    };
  }
}

/**
 * Get todo item information
 * @param {string} cwd - Current working directory
 * @returns {Object} Todo item information
 */
function getTodoInfo(cwd) {
  const todoFiles = [
    path.join(cwd, 'docs', 'todo.md'),
    path.join(cwd, 'TODO.md'),
    path.join(cwd, '.claude', 'todos.md'),
    path.join(cwd, 'TODO'),
    path.join(cwd, 'notes', 'todo.md')
  ];

  for (const file of todoFiles) {
    if (fs.existsSync(file)) {
      try {
        const content = fs.readFileSync(file, 'utf8');
        const totalMatches = content.match(/^[\-\*] \[[ x]\]/gi) || [];
        const total = totalMatches.length;

        const doneMatches = content.match(/^[\-\*] \[x\]/gi) || [];
        const done = doneMatches.length;

        const pending = total - done;

        return {
          found: true,
          file: path.basename(file),
          path: file,
          total,
          done,
          pending
        };
      } catch {
        continue;
      }
    }
  }

  return {
    found: false,
    file: null,
    path: null,
    total: 0,
    done: 0,
    pending: 0
  };
}


/**
 * Resolve repository root when available
 * @param {string} cwd - Current working directory
 * @returns {string} Repository root or cwd fallback
 */
function getRepoRoot(cwd) {
  try {
    return execSync('git rev-parse --show-toplevel', {
      cwd,
      encoding: 'utf8',
      stdio: 'pipe'
    }).trim();
  } catch {
    return cwd;
  }
}

/**
 * Parse simple YAML frontmatter from a Markdown file.
 * Supports the project-memory binding metadata shape.
 * @param {string} filePath
 * @returns {Object|null}
 */
function parseFrontmatterFile(filePath) {
  try {
    const content = fs.readFileSync(filePath, 'utf8');
    const match = content.match(/^---\n([\s\S]*?)\n---/);
    if (!match) return null;

    const data = {};
    for (const line of match[1].split('\n')) {
      const parts = line.match(/^([A-Za-z0-9_]+):\s*(.*)$/);
      if (!parts) continue;
      const key = parts[1];
      let value = parts[2].trim();

      if (value === 'true') value = true;
      else if (value === 'false') value = false;
      else value = value.replace(/^['"]|['"]$/g, '');

      data[key] = value;
    }
    return data;
  } catch {
    return null;
  }
}

/**
 * Detect whether the current repository is bound to Obsidian project memory
 * @param {string} cwd - Current working directory
 * @returns {Object} Binding info
 */
function getProjectMemoryBinding(cwd) {
  const repoRoot = getRepoRoot(cwd);
  const projectMemoryDir = path.join(repoRoot, '.claude', 'project-memory');
  const registryPath = path.join(projectMemoryDir, 'registry.yaml');

  if (!fs.existsSync(registryPath)) {
    return {
      bound: false,
      repoRoot,
      registryPath,
      projectId: null,
      status: null,
      autoSync: false,
      vaultRoot: null,
      hubNote: null,
      memoryPath: null
    };
  }

  let project = null;
  let memoryPath = null;

  if (fs.existsSync(projectMemoryDir)) {
    const memoryFiles = fs.readdirSync(projectMemoryDir)
      .filter(name => name.endsWith('.md'))
      .map(name => path.join(projectMemoryDir, name));

    for (const candidate of memoryFiles) {
      const frontmatter = parseFrontmatterFile(candidate);
      if (!frontmatter) continue;
      if (frontmatter.repo_root && (frontmatter.repo_root === repoRoot || cwd.startsWith(frontmatter.repo_root))) {
        project = frontmatter;
        memoryPath = candidate;
        break;
      }
    }

    if (!memoryPath && memoryFiles.length > 0) {
      memoryPath = memoryFiles[0];
      project = parseFrontmatterFile(memoryPath) || null;
    }
  }

  return {
    bound: true,
    repoRoot,
    registryPath,
    projectId: project ? project.project_id || null : null,
    status: project ? project.status || null : null,
    autoSync: Boolean(project && project.auto_sync),
    vaultRoot: project ? project.vault_root || null : null,
    hubNote: project ? project.hub_note || null : null,
    memoryPath
  };
}

/**
 * Heuristically detect whether a directory looks like a research repo
 * @param {string} cwd - Current working directory
 * @returns {Object} Detection result
 */
function detectResearchProject(cwd) {
  const repoRoot = getRepoRoot(cwd);
  const markers = [
    '.git',
    'README.md',
    'docs',
    'notes',
    'plan',
    'results',
    'outputs',
    'src',
    'scripts'
  ];

  const hits = markers.filter(marker => fs.existsSync(path.join(repoRoot, marker)));
  const candidate = hits.length >= 3 || (hits.includes('.git') && hits.includes('README.md') && hits.length >= 2);

  return {
    repoRoot,
    candidate,
    markers: hits
  };
}

/**
 * Detect whether a user prompt looks research-related
 * @param {string} prompt - User prompt
 * @returns {boolean} Whether the prompt looks research-related
 */
function promptLooksResearchRelated(prompt) {
  return /\b(obsidian|zotero|paper|papers|literature|review|experiment|results?|finding|analysis|research|plan|todo|daily|meeting|writing|draft|proposal|rebuttal|claim|method)\b|文献|实验|结果|研究|计划|待办|知识库|论文|综述|笔记|项目记忆|obsidian|zotero/i.test(prompt);
}

/**
 * Get Git change details
 * @param {string} cwd - Current working directory
 * @returns {Object} Change statistics
 */
function getChangesDetails(cwd) {
  try {
    let added = 0;
    let modified = 0;
    let deleted = 0;

    // 解析 name-status 输出，每行格式: "A\tfilename" 或 "M\tfilename"
    const parseNameStatus = (output) => {
      for (const line of output.trim().split('\n').filter(Boolean)) {
        const status = line.charAt(0);
        if (status === 'A') added++;
        else if (status === 'M') modified++;
        else if (status === 'D') deleted++;
      }
    };

    // 工作区变更（unstaged）
    const unstaged = execSync('git diff --name-status', {
      cwd,
      encoding: 'utf8',
      stdio: 'pipe'
    });
    parseNameStatus(unstaged);

    // 暂存区变更（staged）
    const staged = execSync('git diff --cached --name-status', {
      cwd,
      encoding: 'utf8',
      stdio: 'pipe'
    });
    parseNameStatus(staged);

    // 未跟踪文件算作 added
    const untracked = execSync('git status --porcelain', {
      cwd,
      encoding: 'utf8',
      stdio: 'pipe'
    });
    for (const line of untracked.trim().split('\n').filter(Boolean)) {
      if (line.startsWith('??')) added++;
    }

    return { added, modified, deleted };
  } catch {
    return { added: 0, modified: 0, deleted: 0 };
  }
}

/**
 * Analyze changes by file type
 * @param {string} cwd - Current working directory
 * @returns {Object} File type statistics
 */
function analyzeChangesByType(cwd) {
  const gitInfo = getGitInfo(cwd);

  if (!gitInfo.is_repo || !gitInfo.has_changes) {
    return {
      test_files: 0,
      docs_files: 0,
      sql_files: 0,
      config_files: 0,
      service_files: 0
    };
  }

  // 逐行匹配文件路径（git status --porcelain 格式: "XY filename"）
  const files = gitInfo.changes.map(line => line.substring(3).trim());

  const testRegex = /(?:^|[\/\\])tests?[\/\\]|[\/\\._]test[_.]|\.test\.|_test\./i;
  const docsRegex = /\.(md|txt|rst)$/i;
  const sqlRegex = /\.sql$/i;
  const configRegex = /\.(json|yaml|yml|toml|ini|conf)$/i;
  const serviceRegex = /(service|controller)/i;

  let testFiles = 0;
  let docsFiles = 0;
  let sqlFiles = 0;
  let configFiles = 0;
  let serviceFiles = 0;

  for (const file of files) {
    if (testRegex.test(file)) testFiles++;
    if (docsRegex.test(file)) docsFiles++;
    if (sqlRegex.test(file)) sqlFiles++;
    if (configRegex.test(file)) configFiles++;
    if (serviceRegex.test(file)) serviceFiles++;
  }

  return {
    test_files: testFiles,
    docs_files: docsFiles,
    sql_files: sqlFiles,
    config_files: configFiles,
    service_files: serviceFiles
  };
}

/**
 * Detect temporary files
 * @param {string} cwd - Current working directory
 * @returns {Object} Temporary file information
 */
function detectTempFiles(cwd) {
  const tempFiles = new Set();
  const gitInfo = getGitInfo(cwd);

  // Find temp files from Git untracked files
  if (gitInfo.is_repo) {
    for (const change of gitInfo.changes) {
      if (change.startsWith('??')) {
        const file = change.substring(3).trim();
        if (/plan|draft|tmp|temp|scratch/i.test(file)) {
          tempFiles.add(file);
        }
      }
    }
  }

  // Check known temporary directories
  const tempDirs = ['plan', 'docs/plans', '.claude/temp', 'tmp', 'temp'];
  for (const dir of tempDirs) {
    const dirPath = path.join(cwd, dir);
    if (fs.existsSync(dirPath)) {
      try {
        const files = getAllFiles(dirPath);
        for (const file of files) {
          tempFiles.add(path.relative(cwd, file));
        }
      } catch {
        // Ignore errors
      }
    }
  }

  return {
    files: Array.from(tempFiles),
    count: tempFiles.size
  };
}

/**
 * Recursively get all files in a directory
 * @param {string} dirPath - Directory path
 * @returns {Array<string>} List of file paths
 */
function getAllFiles(dirPath) {
  const files = [];
  const items = fs.readdirSync(dirPath);

  for (const item of items) {
    const fullPath = path.join(dirPath, item);
    const stat = fs.statSync(fullPath);

    if (stat.isDirectory()) {
      files.push(...getAllFiles(fullPath));
    } else {
      files.push(fullPath);
    }
  }

  return files;
}

/**
 * Generate smart recommendations
 * @param {string} cwd - Current working directory
 * @param {Object} gitInfo - Git information
 * @returns {Array<string>} List of recommendations
 */
function generateRecommendations(cwd, gitInfo) {
  const recommendations = [];

  if (gitInfo.is_repo && gitInfo.has_changes) {
    const changesDetails = getChangesDetails(cwd);
    const typeAnalysis = analyzeChangesByType(cwd);

    if (changesDetails.added > 0 || changesDetails.modified > 0) {
      recommendations.push('git add . && git commit -m "feat: xxx"');
    }

    if (typeAnalysis.test_files > 0) {
      recommendations.push('Run test suite to verify changes');
    }
    if (typeAnalysis.docs_files > 0) {
      recommendations.push('Check documentation is in sync with code');
    }
    if (typeAnalysis.sql_files > 0) {
      recommendations.push('Update all related database scripts');
    }
    if (typeAnalysis.config_files > 0) {
      recommendations.push('Check if environment variables need updating');
    }
    if (typeAnalysis.service_files > 0) {
      recommendations.push('Update API documentation');
    }
  }

  // Todo reminder
  const todoInfo = getTodoInfo(cwd);
  if (todoInfo.found && todoInfo.pending > 0) {
    recommendations.push(`Check todos: ${todoInfo.file} (${todoInfo.pending} items remaining)`);
  }

  // Non-repo environment reminder
  if (!gitInfo.is_repo) {
    recommendations.push('Remember to back up important files to a git repo or cloud storage');
  }

  return recommendations;
}

/**
 * Get list of enabled plugins
 * @param {string} homeDir - User home directory
 * @returns {Array<Object>} Plugin list
 */
function getEnabledPlugins(homeDir) {
  const settingsFile = path.join(homeDir, '.claude', 'settings.json');

  if (!fs.existsSync(settingsFile)) {
    return [];
  }

  try {
    const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
    const enabledPlugins = settings.enabledPlugins || {};

    const plugins = [];
    for (const [pluginId, enabled] of Object.entries(enabledPlugins)) {
      if (enabled) {
        const pluginName = pluginId.split('@')[0];
        plugins.push({
          id: pluginId,
          name: pluginName
        });
      }
    }

    return plugins;
  } catch {
    return [];
  }
}

/**
 * Get list of available commands
 * @param {string} homeDir - User home directory
 * @returns {Array<Object>} Command list
 */
function getAvailableCommands(homeDir) {
  const commands = [];

  // Only collect local commands, not plugin commands
  const localCommandsDir = path.join(homeDir, '.claude', 'commands');
  if (fs.existsSync(localCommandsDir)) {
    const commandFiles = fs.readdirSync(localCommandsDir)
      .filter(f => f.endsWith('.md'));

    for (const cmdFile of commandFiles) {
      const cmdName = cmdFile.replace('.md', '');
      commands.push({
        plugin: 'local',
        name: cmdName,
        path: path.join(localCommandsDir, cmdFile)
      });
    }
  }

  return commands;
}

/**
 * Get command description
 * @param {string} cmdPath - Command file path
 * @returns {string} Command description
 */
function getCommandDescription(cmdPath) {
  try {
    const content = fs.readFileSync(cmdPath, 'utf8');
    const lines = content.split('\n');

    // Try to get description from frontmatter
    let inFrontmatter = false;
    for (const line of lines) {
      if (line.trim() === '---') {
        if (!inFrontmatter) {
          inFrontmatter = true;
        } else {
          break;
        }
        continue;
      }

      if (inFrontmatter && line.trim().startsWith('description:')) {
        const match = line.match(/description:\s*["']?(.+?)["']?$/);
        if (match) {
          return match[1].trim();
        }
      }
    }

    // Try to get from heading
    for (const line of lines) {
      const match = line.match(/^#+\s*(.+)$/);
      if (match) {
        return match[1].trim().substring(0, 50);
      }
    }

    return '';
  } catch {
    return '';
  }
}

/**
 * Collect local skills
 * @param {string} homeDir - User home directory
 * @returns {Array<Object>} Skill list
 */
function collectLocalSkills(homeDir) {
  const skills = [];
  const skillsDir = path.join(homeDir, '.claude', 'skills');

  if (!fs.existsSync(skillsDir)) {
    return skills;
  }

  const skillDirs = fs.readdirSync(skillsDir, { withFileTypes: true })
    .filter(d => d.isDirectory())
    .map(d => d.name);

  for (const skillName of skillDirs) {
    const skillFile = path.join(skillsDir, skillName, 'SKILL.md');
    let description = '';

    if (fs.existsSync(skillFile)) {
      try {
        const content = fs.readFileSync(skillFile, 'utf8');
        const match = content.match(/description:\s*(.+)$/im);
        if (match) {
          description = match[1].trim();
        }
      } catch {
        // Ignore
      }
    }

    skills.push({
      name: skillName,
      description,
      type: 'local'
    });
  }

  return skills;
}

/**
 * Collect plugin skills
 * @param {string} homeDir - User home directory
 * @returns {Array<Object>} Skill list
 */
function collectPluginSkills(homeDir) {
  const skills = [];
  const pluginsCache = path.join(homeDir, '.claude', 'plugins', 'cache');

  if (!fs.existsSync(pluginsCache)) {
    return skills;
  }

  const marketplaces = fs.readdirSync(pluginsCache, { withFileTypes: true })
    .filter(d => d.isDirectory())
    .map(d => d.name);

  for (const marketplace of marketplaces) {
    // Skip ai-research-skills
    if (marketplace === 'ai-research-skills') continue;

    const marketplacePath = path.join(pluginsCache, marketplace);
    const plugins = fs.readdirSync(marketplacePath, { withFileTypes: true })
      .filter(d => d.isDirectory() && !d.name.startsWith('.'))
      .map(d => d.name);

    for (const plugin of plugins) {
      const pluginPath = path.join(marketplacePath, plugin);
      const versions = fs.readdirSync(pluginPath, { withFileTypes: true })
        .filter(d => d.isDirectory())
        .map(d => d.name)
        .sort()
        .reverse();

      if (versions.length === 0) continue;

      const latestVersion = versions[0];
      const pluginRoot = path.join(pluginPath, latestVersion);
      const skillsDir = path.join(pluginRoot, 'skills');

      if (fs.existsSync(skillsDir)) {
        const skillDirs = fs.readdirSync(skillsDir, { withFileTypes: true })
          .filter(d => d.isDirectory())
          .map(d => d.name);

        for (const skillName of skillDirs) {
          skills.push({
            name: `${plugin}:${skillName}`,
            plugin,
            skill: skillName,
            type: 'plugin'
          });
        }
      }
    }
  }

  return skills;
}

/**
 * Format date and time
 * @param {Date} date - Date object
 * @returns {string} Formatted date-time string
 */
function formatDateTime(date = new Date()) {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  const hours = String(date.getHours()).padStart(2, '0');
  const minutes = String(date.getMinutes()).padStart(2, '0');
  const seconds = String(date.getSeconds()).padStart(2, '0');

  return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
}

/**
 * Check if CLAUDE.md needs updating
 * @param {string} homeDir - User home directory
 * @returns {Object} Check result
 */
function checkClaudeMdUpdate(homeDir) {
  const claudeMdPath = path.join(homeDir, '.claude', 'CLAUDE.md');
  const lastSyncPath = path.join(homeDir, '.claude', '.last-memory-sync');

  // If CLAUDE.md does not exist, return immediately
  if (!fs.existsSync(claudeMdPath)) {
    return { needsUpdate: false, reason: 'CLAUDE.md not found' };
  }

  // Get CLAUDE.md modification time
  const claudeMdMtime = fs.statSync(claudeMdPath).mtimeMs;

  // Get last sync time (use CLAUDE.md creation time if not available)
  let lastSyncMtime = claudeMdMtime;
  if (fs.existsSync(lastSyncPath)) {
    try {
      lastSyncMtime = parseInt(fs.readFileSync(lastSyncPath, 'utf8').trim(), 10);
    } catch {
      lastSyncMtime = claudeMdMtime;
    }
  }

  const referenceTime = Math.max(claudeMdMtime, lastSyncMtime);

  // Define source file directories to monitor
  const sourceDirs = [
    { dir: path.join(homeDir, '.claude', 'skills'), pattern: /SKILL\.md$/, type: 'skill' },
    { dir: path.join(homeDir, '.claude', 'commands'), pattern: /\.md$/, type: 'command' },
    { dir: path.join(homeDir, '.claude', 'agents'), pattern: /\.md$/, type: 'agent' },
    { dir: path.join(homeDir, '.claude', 'hooks'), pattern: /\.(js|json)$/, type: 'hook' }
  ];

  const changedFiles = [];
  let totalSkills = 0;
  let totalCommands = 0;
  let totalAgents = 0;
  let totalHooks = 0;

  // Scan each source directory
  for (const { dir, pattern, type } of sourceDirs) {
    if (!fs.existsSync(dir)) continue;

    const files = getAllFiles(dir).filter(f => pattern.test(f));

    for (const file of files) {
      try {
        const mtime = fs.statSync(file).mtimeMs;

        // Count totals
        if (type === 'skill') totalSkills++;
        else if (type === 'command') totalCommands++;
        else if (type === 'agent') totalAgents++;
        else if (type === 'hook') totalHooks++;

        // Check if newer than reference time
        if (mtime > referenceTime) {
          changedFiles.push({
            path: file,
            type,
            relativePath: path.relative(homeDir, file),
            mtime: new Date(mtime).toLocaleString('en-US')
          });
        }
      } catch {
        // Ignore inaccessible files
      }
    }
  }

  return {
    needsUpdate: changedFiles.length > 0,
    changedFiles,
    stats: {
      skills: totalSkills,
      commands: totalCommands,
      agents: totalAgents,
      hooks: totalHooks
    },
    claudeMdMtime: new Date(claudeMdMtime).toLocaleString('en-US'),
    lastSyncMtime: new Date(lastSyncMtime).toLocaleString('en-US')
  };
}

/**
 * Update sync timestamp
 * @param {string} homeDir - User home directory
 */
function updateSyncTimestamp(homeDir) {
  const lastSyncPath = path.join(homeDir, '.claude', '.last-memory-sync');
  try {
    fs.writeFileSync(lastSyncPath, Date.now().toString(), 'utf8');
  } catch {
    // Ignore write errors
  }
}

/**
 * Create a temporary file
 * @param {string} prefix - Filename prefix
 * @returns {string} Temporary file path
 */
function createTempFile(prefix = 'claude-temp') {
  const os = require('os');
  const tmpDir = os.tmpdir();
  const randomSuffix = Math.random().toString(36).substring(2, 8);
  return path.join(tmpDir, `${prefix}-${randomSuffix}.tmp`);
}

// Export all functions
module.exports = {
  getGitInfo,
  getTodoInfo,
  getRepoRoot,
  getProjectMemoryBinding,
  detectResearchProject,
  promptLooksResearchRelated,
  getChangesDetails,
  analyzeChangesByType,
  detectTempFiles,
  generateRecommendations,
  getEnabledPlugins,
  getAvailableCommands,
  getCommandDescription,
  collectLocalSkills,
  collectPluginSkills,
  formatDateTime,
  createTempFile,
  getAllFiles,
  checkClaudeMdUpdate,
  updateSyncTimestamp
};
