/**
 * File filtering logic for the GitHub crawler (TRUEREF-0003).
 *
 * Determines whether a file in the repository tree should be downloaded
 * and indexed based on its extension, size, and the trueref.json config.
 */

import { extname, basename } from 'node:path';
import type { RepoConfig } from './types.js';

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

/** File extensions that the indexer can meaningfully process. */
export const INDEXABLE_EXTENSIONS = new Set([
	// Documentation
	'.md',
	'.mdx',
	'.txt',
	'.rst',
	// Code
	'.ts',
	'.tsx',
	'.js',
	'.jsx',
	'.py',
	'.rb',
	'.go',
	'.rs',
	'.java',
	'.cs',
	'.cpp',
	'.c',
	'.h',
	'.swift',
	'.kt',
	'.php',
	'.scala',
	'.clj',
	'.ex',
	'.exs',
	'.sh',
	'.bash',
	'.zsh',
	'.fish',
	// Config / data
	'.json',
	'.yaml',
	'.yml',
	'.toml',
	// Web
	'.html',
	'.css',
	'.svelte',
	'.vue'
]);

/** Maximum file size we are willing to download (500 KB). */
export const MAX_FILE_SIZE_BYTES = 500_000;

/**
 * Default path prefixes that are always excluded regardless of config.
 * These directories contain generated or dependency files that should never
 * be indexed.
 */
const DEFAULT_EXCLUDES: string[] = [
	'node_modules/',
	'.git/',
	'dist/',
	'build/',
	'coverage/',
	'.next/',
	'__pycache__/',
	'vendor/',
	'target/',
	'.cache/'
];

// ---------------------------------------------------------------------------
// Language detection
// ---------------------------------------------------------------------------

const EXTENSION_TO_LANGUAGE: Record<string, string> = {
	'.ts': 'typescript',
	'.tsx': 'typescript',
	'.js': 'javascript',
	'.jsx': 'javascript',
	'.py': 'python',
	'.rb': 'ruby',
	'.go': 'go',
	'.rs': 'rust',
	'.java': 'java',
	'.cs': 'csharp',
	'.cpp': 'cpp',
	'.c': 'c',
	'.h': 'c',
	'.swift': 'swift',
	'.kt': 'kotlin',
	'.php': 'php',
	'.scala': 'scala',
	'.clj': 'clojure',
	'.ex': 'elixir',
	'.exs': 'elixir',
	'.sh': 'shell',
	'.bash': 'shell',
	'.zsh': 'shell',
	'.fish': 'shell',
	'.json': 'json',
	'.yaml': 'yaml',
	'.yml': 'yaml',
	'.toml': 'toml',
	'.html': 'html',
	'.css': 'css',
	'.svelte': 'svelte',
	'.vue': 'vue',
	'.md': 'markdown',
	'.mdx': 'markdown',
	'.txt': 'text',
	'.rst': 'rst'
};

/**
 * Detect a human-readable language name from a file extension.
 * Returns an empty string when the extension is unknown.
 */
export function detectLanguage(filePath: string): string {
	const ext = extname(filePath).toLowerCase();
	return EXTENSION_TO_LANGUAGE[ext] ?? '';
}

// ---------------------------------------------------------------------------
// Filter predicate
// ---------------------------------------------------------------------------

/**
 * Decide whether a file from the repository tree should be downloaded.
 *
 * Rules (applied in order):
 * 1. Must have an indexable extension.
 * 2. Must not exceed the size limit.
 * 3. Must not match config.excludeFiles (exact basename match).
 * 4. Must not be under a config.excludeFolders path / regex.
 * 5. Must be under a config.folders allowlist path / regex (if specified).
 * 6. Must not start with a default-excluded prefix.
 */
export function shouldIndexFile(
	filePath: string,
	fileSize: number,
	config?: RepoConfig
): boolean {
	const ext = extname(filePath).toLowerCase();

	// 1. Extension allow-list
	if (!INDEXABLE_EXTENSIONS.has(ext)) return false;

	// 2. Size limit
	if (fileSize > MAX_FILE_SIZE_BYTES) return false;

	// 3. Config excludeFiles — exact basename match
	if (config?.excludeFiles?.includes(basename(filePath))) return false;

	// 4. Config excludeFolders — prefix or regex match
	if (
		config?.excludeFolders?.some(
			(folder) => filePath.startsWith(folder) || new RegExp(folder).test(filePath)
		)
	)
		return false;

	// 5. Config folders allowlist — if provided, the file must match at least one
	if (config?.folders?.length) {
		const inAllowedFolder = config.folders.some(
			(folder) => filePath.startsWith(folder) || new RegExp(folder).test(filePath)
		);
		if (!inAllowedFolder) return false;
	}

	// 6. Default excludes
	if (DEFAULT_EXCLUDES.some((ex) => filePath.startsWith(ex))) return false;

	return true;
}
