/**
 * File reference expansion for TUI chat input.
 *
 * Detects @path/to/file patterns in user messages, reads the file contents,
 * and inlines them as fenced code blocks in the message.
 *
 * Supports:
 *   - @relative/path.ts
 *   - @./relative/path.ts
 *   - @/absolute/path.ts
 *   - @~/home-relative/path.ts
 *
 * Also provides an /attach <path> command handler.
 */

import { readFile, stat } from 'node:fs/promises';
import { resolve, extname, basename } from 'node:path';
import { homedir } from 'node:os';

const MAX_FILE_SIZE = 256 * 1024; // 256 KB
const MAX_FILES_PER_MESSAGE = 10;

/**
 * Regex to detect @file references in user input.
 * Matches @<path> where path starts with /, ./, ~/, or a word char,
 * and continues until whitespace or end of string.
 * Excludes @mentions that look like usernames (no dots/slashes).
 */
const FILE_REF_PATTERN = /(?:^|\s)@((?:\.{0,2}\/|~\/|[a-zA-Z0-9_])[^\s]+)/g;

interface FileRefResult {
  /** The expanded message text with file contents inlined */
  expandedMessage: string;
  /** Files that were successfully read */
  filesAttached: string[];
  /** Errors encountered while reading files */
  errors: string[];
}

function resolveFilePath(ref: string): string {
  if (ref.startsWith('~/')) {
    return resolve(homedir(), ref.slice(2));
  }
  return resolve(process.cwd(), ref);
}

function getLanguageHint(filePath: string): string {
  const ext = extname(filePath).toLowerCase();
  const map: Record<string, string> = {
    '.ts': 'typescript',
    '.tsx': 'typescript',
    '.js': 'javascript',
    '.jsx': 'javascript',
    '.py': 'python',
    '.rb': 'ruby',
    '.rs': 'rust',
    '.go': 'go',
    '.java': 'java',
    '.c': 'c',
    '.cpp': 'cpp',
    '.h': 'c',
    '.hpp': 'cpp',
    '.cs': 'csharp',
    '.sh': 'bash',
    '.bash': 'bash',
    '.zsh': 'zsh',
    '.fish': 'fish',
    '.json': 'json',
    '.yaml': 'yaml',
    '.yml': 'yaml',
    '.toml': 'toml',
    '.xml': 'xml',
    '.html': 'html',
    '.css': 'css',
    '.scss': 'scss',
    '.md': 'markdown',
    '.sql': 'sql',
    '.graphql': 'graphql',
    '.dockerfile': 'dockerfile',
    '.tf': 'terraform',
    '.vue': 'vue',
    '.svelte': 'svelte',
  };
  return map[ext] ?? '';
}

/**
 * Check if the input contains any @file references.
 */
export function hasFileRefs(input: string): boolean {
  FILE_REF_PATTERN.lastIndex = 0;
  return FILE_REF_PATTERN.test(input);
}

/**
 * Expand @file references in a message by reading file contents
 * and appending them as fenced code blocks.
 */
export async function expandFileRefs(input: string): Promise<FileRefResult> {
  const refs: string[] = [];
  FILE_REF_PATTERN.lastIndex = 0;
  let match;
  while ((match = FILE_REF_PATTERN.exec(input)) !== null) {
    const ref = match[1]!;
    if (!refs.includes(ref)) {
      refs.push(ref);
    }
  }

  if (refs.length === 0) {
    return { expandedMessage: input, filesAttached: [], errors: [] };
  }

  if (refs.length > MAX_FILES_PER_MESSAGE) {
    return {
      expandedMessage: input,
      filesAttached: [],
      errors: [`Too many file references (${refs.length}). Maximum is ${MAX_FILES_PER_MESSAGE}.`],
    };
  }

  const filesAttached: string[] = [];
  const errors: string[] = [];
  const attachments: string[] = [];

  for (const ref of refs) {
    const filePath = resolveFilePath(ref);
    try {
      const info = await stat(filePath);
      if (!info.isFile()) {
        errors.push(`@${ref}: not a file`);
        continue;
      }
      if (info.size > MAX_FILE_SIZE) {
        errors.push(
          `@${ref}: file too large (${(info.size / 1024).toFixed(0)} KB, limit ${MAX_FILE_SIZE / 1024} KB)`,
        );
        continue;
      }
      const content = await readFile(filePath, 'utf8');
      const lang = getLanguageHint(filePath);
      const name = basename(filePath);
      attachments.push(`\n📎 ${ref} (${name}):\n\`\`\`${lang}\n${content}\n\`\`\``);
      filesAttached.push(ref);
    } catch (err) {
      const msg = err instanceof Error ? err.message : String(err);
      // Only report meaningful errors — ENOENT is common for false @mention matches
      if (msg.includes('ENOENT')) {
        // Check if this looks like a file path (has extension or slash)
        if (ref.includes('/') || ref.includes('.')) {
          errors.push(`@${ref}: file not found`);
        }
        // Otherwise silently skip — likely an @mention, not a file ref
      } else {
        errors.push(`@${ref}: ${msg}`);
      }
    }
  }

  if (attachments.length === 0) {
    return { expandedMessage: input, filesAttached, errors };
  }

  const expandedMessage = input + '\n' + attachments.join('\n');
  return { expandedMessage, filesAttached, errors };
}

/**
 * Handle the /attach <path> command.
 * Reads a file and returns the content formatted for inclusion in the chat.
 */
export async function handleAttachCommand(
  args: string,
): Promise<{ content: string; error?: string }> {
  const filePath = args.trim();
  if (!filePath) {
    return { content: '', error: 'Usage: /attach <file-path>' };
  }

  const resolved = resolveFilePath(filePath);
  try {
    const info = await stat(resolved);
    if (!info.isFile()) {
      return { content: '', error: `Not a file: ${filePath}` };
    }
    if (info.size > MAX_FILE_SIZE) {
      return {
        content: '',
        error: `File too large (${(info.size / 1024).toFixed(0)} KB, limit ${MAX_FILE_SIZE / 1024} KB)`,
      };
    }
    const content = await readFile(resolved, 'utf8');
    const lang = getLanguageHint(resolved);
    const name = basename(resolved);
    return {
      content: `📎 Attached file: ${name} (${filePath})\n\`\`\`${lang}\n${content}\n\`\`\``,
    };
  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    return { content: '', error: `Failed to read file: ${msg}` };
  }
}
