/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import * as os from 'node:os';

/**
 * Defines the structure of a virtual file system to be created for testing.
 * Keys are file or directory names, and values can be:
 * - A string: The content of a file.
 * - A `FileSystemStructure` object: Represents a subdirectory with its own structure.
 * - An array of strings or `FileSystemStructure` objects: Represents a directory
 *   where strings are empty files and objects are subdirectories.
 *
 * @example
 * // Example 1: Simple files and directories
 * const structure1 = {
 *   'file1.txt': 'Hello, world!',
 *   'empty-dir': [],
 *   'src': {
 *     'main.js': '// Main application file',
 *     'utils.ts': '// Utility functions',
 *   },
 * };
 *
 * @example
 * // Example 2: Nested directories and empty files within an array
 * const structure2 = {
 *   'config.json': '{ "port": 3000 }',
 *   'data': [
 *     'users.csv',
 *     'products.json',
 *     {
 *       'logs': [
 *         'error.log',
 *         'access.log',
 *       ],
 *     },
 *   ],
 * };
 */
export type FileSystemStructure = {
  [name: string]:
    | string
    | FileSystemStructure
    | Array<string | FileSystemStructure>;
};

/**
 * Recursively creates files and directories based on the provided `FileSystemStructure`.
 * @param dir The base directory where the structure will be created.
 * @param structure The `FileSystemStructure` defining the files and directories.
 */
async function create(dir: string, structure: FileSystemStructure) {
  for (const [name, content] of Object.entries(structure)) {
    const newPath = path.join(dir, name);
    if (typeof content === 'string') {
      await fs.writeFile(newPath, content);
    } else if (Array.isArray(content)) {
      await fs.mkdir(newPath, { recursive: true });
      for (const item of content) {
        if (typeof item === 'string') {
          await fs.writeFile(path.join(newPath, item), '');
        } else {
          await create(newPath, item);
        }
      }
    } else if (typeof content === 'object' && content !== null) {
      await fs.mkdir(newPath, { recursive: true });
      await create(newPath, content);
    }
  }
}

/**
 * Creates a temporary directory and populates it with a given file system structure.
 * @param structure The `FileSystemStructure` to create within the temporary directory.
 * @returns A promise that resolves to the absolute path of the created temporary directory.
 */
export async function createTmpDir(
  structure: FileSystemStructure,
): Promise<string> {
  const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gemini-cli-test-'));
  await create(tmpDir, structure);
  return tmpDir;
}

/**
 * Cleans up (deletes) a temporary directory and its contents.
 * @param dir The absolute path to the temporary directory to clean up.
 */
export async function cleanupTmpDir(dir: string | undefined) {
  if (!dir) {
    return;
  }

  try {
    const exists = await fs
      .access(dir)
      .then(() => true)
      .catch(() => false);

    if (exists) {
      if (process.platform === 'win32') {
        // Give Windows a moment to release file handles
        await new Promise((resolve) => setTimeout(resolve, 100));
      }
      await fs.rm(dir, { recursive: true, force: true });
    }
  } catch {
    // Ignore errors during cleanup (e.g., directory already deleted)
  }
}
