/**
 * File Index Module
 *
 * 使用 ripgrep 索引项目文件，支持模糊搜索
 */

import fuzzysort from 'fuzzysort';
import { spawn } from 'node:child_process';

export interface FileIndexEntry {
  path: string;
  name: string;
  type: 'file' | 'directory';
  extension?: string;
}

export interface FileSearchOptions {
  query: string;
  limit?: number;
  type?: 'file' | 'directory' | 'all';
}

/**
 * 文件索引类
 */
class FileIndex {
  private files: FileIndexEntry[] = [];
  private dirs: Set<string> = new Set();
  private cwd: string;
  private initialized = false;
  private initializing: Promise<void> | null = null;

  constructor(cwd: string) {
    this.cwd = cwd;
  }

  /**
   * 初始化索引（使用 ripgrep 或 fallback 到 find）
   */
  async initialize(): Promise<void> {
    if (this.initialized) return;

    // 防止并发初始化
    if (this.initializing) {
      return this.initializing;
    }

    this.initializing = this._doInitialize();
    await this.initializing;
    this.initializing = null;
  }

  private async _doInitialize(): Promise<void> {
    try {
      // 尝试使用 ripgrep
      const paths = await this.listFilesWithRipgrep();
      this.processFilePaths(paths);
      this.initialized = true;
    } catch {
      // ripgrep 不可用，使用 find 命令 (macOS/Linux) 或 fallback
      try {
        const paths = await this.listFilesWithFind();
        this.processFilePaths(paths);
        this.initialized = true;
      } catch (error) {
        console.error('Failed to initialize file index:', error);
        this.files = [];
        this.dirs = new Set();
        this.initialized = true;
      }
    }
  }

  private processFilePaths(paths: string[]): void {
    this.files = [];
    this.dirs = new Set();

    for (const path of paths) {
      if (!path) continue;

      const name = path.split('/').pop() || path;
      const ext = name.includes('.') ? name.split('.').pop() : undefined;

      this.files.push({
        path,
        name,
        type: 'file',
        extension: ext,
      });

      // 收集目录
      const parts = path.split('/');
      for (let i = 1; i < parts.length; i++) {
        const dir = parts.slice(0, i).join('/');
        if (dir) this.dirs.add(dir);
      }
    }
  }

  private listFilesWithRipgrep(): Promise<string[]> {
    return new Promise((resolve, reject) => {
      const args = [
        '--files',
        '--follow',
        '--hidden',
        '--glob=!.git/*',
        '--glob=!node_modules/*',
        '--glob=!.next/*',
        '--glob=!dist/*',
        '--glob=!build/*',
        '--glob=!coverage/*',
        '--glob=!.turbo/*',
        '--glob=!*.lock',
        '--glob=!package-lock.json',
      ];

      const proc = spawn('rg', args, {
        cwd: this.cwd,
        stdio: ['ignore', 'pipe', 'pipe'],
      });

      let output = '';
      let error = '';

      proc.stdout.on('data', (data) => {
        output += data.toString();
      });

      proc.stderr.on('data', (data) => {
        error += data.toString();
      });

      proc.on('close', (code) => {
        if (code === 0 || code === 1) {
          // ripgrep returns 1 if no matches, which is fine
          const paths = output.trim().split('\n').filter(Boolean);
          resolve(paths);
        } else {
          reject(new Error(`ripgrep failed: ${error}`));
        }
      });

      proc.on('error', reject);
    });
  }

  private listFilesWithFind(): Promise<string[]> {
    return new Promise((resolve, reject) => {
      const args = [
        '.',
        '-type', 'f',
        '-not', '-path', '*/.git/*',
        '-not', '-path', '*/node_modules/*',
        '-not', '-path', '*/.next/*',
        '-not', '-path', '*/dist/*',
        '-not', '-path', '*/build/*',
        '-not', '-path', '*/coverage/*',
        '-not', '-name', '*.lock',
        '-not', '-name', 'package-lock.json',
      ];

      const proc = spawn('find', args, {
        cwd: this.cwd,
        stdio: ['ignore', 'pipe', 'pipe'],
      });

      let output = '';
      let error = '';

      proc.stdout.on('data', (data) => {
        output += data.toString();
      });

      proc.stderr.on('data', (data) => {
        error += data.toString();
      });

      proc.on('close', (code) => {
        if (code === 0) {
          const paths = output
            .trim()
            .split('\n')
            .filter(Boolean)
            .map((p) => p.replace(/^\.\//, '')); // 移除开头的 ./
          resolve(paths);
        } else {
          reject(new Error(`find failed: ${error}`));
        }
      });

      proc.on('error', reject);
    });
  }

  /**
   * 模糊搜索文件
   */
  async search(options: FileSearchOptions): Promise<FileIndexEntry[]> {
    await this.initialize();

    const { query, limit = 10, type = 'file' } = options;

    // 构建搜索列表
    let items: FileIndexEntry[];

    if (type === 'directory') {
      items = Array.from(this.dirs).map((d) => ({
        path: d,
        name: d.split('/').pop()!,
        type: 'directory' as const,
      }));
    } else if (type === 'all') {
      const dirItems = Array.from(this.dirs).map((d) => ({
        path: d,
        name: d.split('/').pop()!,
        type: 'directory' as const,
      }));
      items = [...this.files, ...dirItems];
    } else {
      items = this.files;
    }

    // 如果没有查询，返回前 N 个
    if (!query) {
      return items.slice(0, limit);
    }

    // 模糊搜索
    const results = fuzzysort.go(query, items, {
      key: 'path',
      limit,
      threshold: -10000,
    });

    return results.map((r) => r.obj);
  }

  /**
   * 刷新索引
   */
  async refresh(): Promise<void> {
    this.initialized = false;
    await this.initialize();
  }

  /**
   * 获取索引统计
   */
  getStats(): { files: number; directories: number } {
    return {
      files: this.files.length,
      directories: this.dirs.size,
    };
  }
}

// 索引实例缓存
const indexCache = new Map<string, FileIndex>();

/**
 * 获取文件索引实例
 */
export function getFileIndex(cwd: string): FileIndex {
  let index = indexCache.get(cwd);
  if (!index) {
    index = new FileIndex(cwd);
    indexCache.set(cwd, index);
  }
  return index;
}

/**
 * 搜索文件（便捷函数）
 */
export async function searchFiles(options: FileSearchOptions & { cwd: string }): Promise<FileIndexEntry[]> {
  const { cwd, ...searchOptions } = options;
  const index = getFileIndex(cwd);
  return index.search(searchOptions);
}

/**
 * 刷新文件索引
 */
export async function refreshFileIndex(cwd: string): Promise<void> {
  const index = getFileIndex(cwd);
  await index.refresh();
}

/**
 * 获取索引统计
 */
export async function getFileIndexStats(cwd: string): Promise<{ files: number; directories: number }> {
  const index = getFileIndex(cwd);
  await index.initialize();
  return index.getStats();
}
