import { CopyOptions } from 'node:fs';
import fs from 'node:fs/promises';

/**
 * A subset of `fs/promises` methods used by FsFixture.
 * Compatible with Node.js `fs/promises`, `@platformatic/vfs`,
 * `memfs`, and other fs-compatible implementations.
 *
 * Pass a custom implementation to `createFixture({ fs })`
 * to use a virtual filesystem.
 */
type FsPromises = {
    readFile: {
        (path: string, options?: {
            encoding?: null;
        } | null): Promise<Buffer>;
        (path: string, options: BufferEncoding | {
            encoding: BufferEncoding;
        }): Promise<string>;
    };
    writeFile(path: string, data: string | Buffer, options?: BufferEncoding | {
        encoding?: BufferEncoding;
    } | null): Promise<void>;
    readdir: {
        (path: string, options?: {
            withFileTypes?: false;
        }): Promise<string[]>;
        (path: string, options: {
            withFileTypes: true;
        }): Promise<Array<{
            name: string;
            isFile(): boolean;
            isDirectory(): boolean;
        }>>;
    };
    mkdir(path: string, options?: {
        recursive?: boolean;
    }): Promise<string | undefined>;
    rename(oldPath: string, newPath: string): Promise<void>;
    access(path: string, mode?: number): Promise<void>;
    rm?(path: string, options?: {
        recursive?: boolean;
        force?: boolean;
    }): Promise<void>;
    unlink?(path: string): Promise<void>;
    rmdir?(path: string): Promise<void>;
    symlink?(target: string, path: string, type?: string | null): Promise<void>;
    cp?(source: string, destination: string, options?: {
        recursive?: boolean;
    }): Promise<void>;
    mkdtemp?(prefix: string): Promise<string>;
};

declare class FsFixture {
    /**
     * Path to the fixture directory.
     */
    readonly path: string;
    readonly fs: FsPromises;
    /**
     * Create a Fixture instance from a path. Does not create the fixture directory.
     *
     * @param fixturePath - The path to the fixture directory
     * @param fsApi - Optional fs/promises-compatible API. Defaults to real node:fs/promises.
     */
    constructor(fixturePath: string, fsApi?: FsPromises);
    /**
     * Get the full path to a subpath in the fixture directory.
     *
     * @param subpaths - Path segments to join with the fixture directory
     * @returns The absolute path to the subpath
     *
     * @example
     * ```ts
     * fixture.getPath('dir', 'file.txt')
     * // => '/tmp/fs-fixture-123/dir/file.txt'
     * ```
     */
    getPath(...subpaths: string[]): string;
    /**
     * Check if the fixture exists. Pass in a subpath to check if it exists.
     *
     * @param subpath - Optional subpath to check within the fixture directory
     * @returns Promise resolving to true if the path exists, false otherwise
     */
    exists(subpath?: string): Promise<boolean>;
    /**
     * Delete the fixture directory or a subpath within it.
     *
     * @param subpath - Optional subpath to delete within the fixture directory.
     *   Defaults to deleting the entire fixture.
     * @returns Promise that resolves when deletion is complete
     */
    rm(subpath?: string): Promise<void>;
    /**
     * Copy a file or directory into the fixture directory.
     *
     * @param sourcePath - The source path to copy from
     * @param destinationSubpath - Optional destination path within the fixture.
     *   If omitted, uses the basename of sourcePath.
     *   If ends with path separator, appends basename of sourcePath.
     * @param options - Copy options (e.g., recursive, filter)
     * @returns Promise that resolves when copy is complete
     */
    cp(sourcePath: string, destinationSubpath?: string, options?: CopyOptions): Promise<void>;
    /**
     * Create a new folder in the fixture directory (including parent directories).
     *
     * @param folderPath - The folder path to create within the fixture
     * @returns Promise that resolves when directory is created
     */
    mkdir(folderPath: string): Promise<string | undefined>;
    /**
     * Move or rename a file or directory within the fixture.
     * This is a wrapper around `node:fs/promises.rename`.
     *
     * @param sourcePath - The source path relative to the fixture root
     * @param destinationPath - The destination path relative to the fixture root
     * @returns Promise that resolves when the move is complete
     *
     * @example
     * ```ts
     * // Rename a file
     * await fixture.mv('old-name.txt', 'new-name.txt')
     *
     * // Move a file into a directory
     * await fixture.mv('file.txt', 'src/file.txt')
     *
     * // Rename a directory
     * await fixture.mv('src', 'lib')
     * ```
     */
    mv(sourcePath: string, destinationPath: string): Promise<void>;
    /**
     * Read a file from the fixture directory.
     *
     * @param filePath - The file path within the fixture to read
     * @param options - Optional encoding or read options.
     *   When encoding is specified, returns a string; otherwise returns a Buffer.
     * @returns Promise resolving to file contents as string or Buffer
     */
    readFile: typeof fs.readFile;
    /**
     * Read the contents of a directory in the fixture.
     *
     * @param directoryPath - The directory path within the fixture to read.
     *   Defaults to the fixture root when empty string is passed.
     * @param options - Optional read directory options.
     *   Use `{ withFileTypes: true }` to get Dirent objects.
     * @returns Promise resolving to array of file/directory names or Dirent objects
     */
    readdir: typeof fs.readdir;
    /**
     * Create or overwrite a file in the fixture directory.
     *
     * @param filePath - The file path within the fixture to write
     * @param data - The content to write (string or Buffer)
     * @param options - Optional encoding or write options
     * @returns Promise that resolves when file is written
     */
    writeFile: typeof fs.writeFile;
    /**
     * Read and parse a JSON file from the fixture directory.
     *
     * @param filePath - The JSON file path within the fixture to read
     * @returns Promise resolving to the parsed JSON content
     *
     * @example
     * ```ts
     * const data = await fixture.readJson<{ name: string }>('config.json')
     * console.log(data.name) // Typed as string
     * ```
     */
    readJson<T = unknown>(filePath: string): Promise<T>;
    /**
     * Create or overwrite a JSON file in the fixture directory.
     *
     * @param filePath - The JSON file path within the fixture to write
     * @param json - The data to serialize as JSON
     * @param space - Number of spaces or string to use for indentation. Defaults to 2.
     * @returns Promise that resolves when file is written
     *
     * @example
     * ```ts
     * // Default 2-space indentation
     * await fixture.writeJson('config.json', { key: 'value' })
     *
     * // 4-space indentation
     * await fixture.writeJson('config.json', { key: 'value' }, 4)
     *
     * // Tab indentation
     * await fixture.writeJson('config.json', { key: 'value' }, '\t')
     *
     * // Minified (no formatting)
     * await fixture.writeJson('config.json', { key: 'value' }, 0)
     * ```
     */
    writeJson(filePath: string, json: unknown, space?: string | number): Promise<void>;
    /**
     * Resource management cleanup
     * https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html
     */
    [Symbol.asyncDispose](): Promise<void>;
}
type FsFixtureType = FsFixture;

declare class PathBase {
    readonly path: string;
    constructor(path: string);
}

type SymlinkType = 'file' | 'dir' | 'junction';
declare class Symlink extends PathBase {
    readonly target: string;
    readonly type?: SymlinkType;
    constructor(target: string, type?: SymlinkType, filePath?: string);
}

type ApiBase = {
    fixturePath: string;
    getPath(...subpaths: string[]): string;
    symlink(targetPath: string, 
    /**
     * Symlink type for Windows. Defaults to auto-detect by Node.
     */
    type?: SymlinkType): Symlink;
};
type Api = ApiBase & {
    filePath: string;
};
type FileTree = {
    [path: string]: string | Buffer | FileTree | ((api: Api) => string | Buffer | Symlink);
};

type FilterFunction = CopyOptions['filter'];
type CreateFixtureOptions = {
    /**
     * The temporary directory to create the fixtures in.
     * Defaults to `os.tmpdir()`.
     *
     * Accepts either a string path or a URL object.
     *
     * Tip: use `new URL('.', import.meta.url)` to the get the file's directory (not the file).
     */
    tempDir?: string | URL;
    /**
     * Function to filter files to copy when using a template path.
     * Return `true` to copy the item, `false` to ignore it.
     */
    templateFilter?: FilterFunction;
    /**
     * Custom fs/promises-compatible API for fixture operations.
     * Use this to create fixtures in a virtual filesystem instead of on disk.
     *
     * Required: readFile, writeFile, readdir (with withFileTypes),
     * mkdir, rename, access.
     * Optional: rm (or unlink + rmdir as fallback), symlink, cp, mkdtemp.
     *
     * @example
     * ```ts
     * import { create, MemoryProvider } from '@platformatic/vfs'
     * const vfs = create(new MemoryProvider())
     * const fixture = await createFixture({ 'file.txt': 'hi' }, { fs: vfs.promises })
     * ```
     */
    fs?: FsPromises;
};
/**
 * Create a temporary test fixture directory.
 *
 * @param source - Optional source to create the fixture from:
 *   - If omitted, creates an empty fixture directory
 *   - If a string, copies the directory at that path to the fixture
 *   - If a FileTree object, creates files and directories from the object structure
 * @param options - Optional configuration for fixture creation
 * @returns Promise resolving to an FsFixture instance
 *
 * @example
 * ```ts
 * // Create empty fixture
 * const fixture = await createFixture()
 *
 * // Create from object
 * const fixture = await createFixture({
 *   'file.txt': 'content',
 *   'dir/nested.txt': 'nested content',
 *   'binary.bin': Buffer.from('binary'),
 * })
 *
 * // Create from template directory
 * const fixture = await createFixture('./my-template')
 *
 * // Cleanup
 * await fixture.rm()
 * ```
 */
declare const createFixture: (source?: string | FileTree, options?: CreateFixtureOptions) => Promise<FsFixture>;

export { createFixture };
export type { CreateFixtureOptions, FileTree, FsFixtureType as FsFixture, FsPromises };
