import fsExtra from 'fs-extra';
// import { RouterContract } from '@ioc:Adonis/Core/Route';
// import { Visibility, DriveFileStats, ContentHeaders, LocalDriverConfig, LocalDriverContract, DirectoryListingContract, LocalDriveListItem } from '@ioc:Adonis/Core/Drive';

import {
    CannotGetMetaDataException,
    CannotDeleteFileException,
    CannotListDirectoryException,
    CannotMoveFileException,
    CannotGenerateUrlException,
} from '../exceptions/index.js';
import PathPrefixer from '../path_prefixer/index.js';
import { LocalDriverContract, LocalDriverConfig, DriverContract, DriveListItem } from '../src/types/drive.js';
import { AsyncIterableArray } from '../src/iterable_array.js';
import router from '@adonisjs/core/services/router'
/**
 * Local driver interacts with the local file system
 */
export class LocalDriver implements LocalDriverContract {
    private diskName: string;
    private routeName: string;
    private config;
    // private router;
    // private routeName;

    /**
     * Reference to the underlying adapter. Which is
     * fs-extra
     */
    adapter: typeof fsExtra;

    /**
     * Name of the driver
     */
    name: 'local';
    /**
     * Path prefixer used for prefixing paths with disk root
     */
    private prefixer;

    // constructor(diskName: string, config: LocalDriverConfig, router: RouterContract);
    constructor(diskName: string, config: LocalDriverConfig) {
        this.diskName = diskName;
        this.config = config;
        // this.router = router;
        this.routeName = ""; //LocalFileServer_1.LocalFileServer.makeRouteName(this.diskName);
        /**
         * Reference to the underlying adapter. Which is
         * fs-extra
         */
        this.adapter = fsExtra;
        /**
         * Name of the driver
         */
        this.name = 'local';
        /**
         * Path prefixer used for prefixing paths with disk root
         */
        this.prefixer = PathPrefixer.fromPath(this.config.root); //root: '/storage/app/public',
    }

      /**
     * Returns a URL for a given location path
     */
    public  async getUrl(location: string) {
        if (!this.config.serveFiles) {
            throw CannotGenerateUrlException.invoke(location, this.diskName);
        }
        return router.makeUrl(this.routeName);
    }

    /**
     * A boolean to find if the location path exists or not
     */
    public async exists(location: string): Promise<boolean> {
        try {
            return await this.adapter.pathExists(this.makePath(location));
        } catch (error) {
            throw CannotGetMetaDataException.invoke(location, 'exists', error);
        }
    }

    /**
     * Make absolute path to a given location
     */
    public makePath(location: string): string {
        return this.prefixer.prefixPath(location);
    }

    /**
     * Remove a given location path
     */
    // delete(location: string, ...args: any[]): Promise<void>;
    public async delete(location: string): Promise<void> {
        try {
            await this.adapter.remove(this.makePath(location));
        } catch (error) {
            throw CannotDeleteFileException.invoke(location, error);
        }
    }

    public async move(source: string, destination: string): Promise<void> {
        try {
            let sourcePath = this.makePath(source); //'/storage/app/public/tmp/rtwgliatumwwpd4vpayo4ymg'
            let destinationPath = this.makePath(destination);
            await this.adapter.move(sourcePath, destinationPath, {
                overwrite: true,
            });
            await this.adapter.move(this.makePath(source), this.makePath(destination), {
                overwrite:true
            });
        } catch (error) {
            throw CannotMoveFileException.invoke(source, destination, error);
        }
    }

    /**
     * Return a listing directory iterator for given location.
     */
    public list(location: string): AsyncIterableArray<DriverContract, DriveListItem<any>> {
        // public async list(location: string): Promise<DirectoryListing<DriverContract, DriveListItem<any>>> {
        const fullPath = this.makePath(location); //'/storage/app/public/files/307'

        // let dirListing: DirectoryListing<DriverContract, DriveListItem<any>> = new DirectoryListing(this, () => this.getListing(fullPath, location));
        let dirListing: AsyncIterableArray<DriverContract, DriveListItem<any>> = new AsyncIterableArray(this, () =>
            this.getListing(fullPath, location),
        );
        return dirListing;
        // let listing: DriveListItem<fsExtra.Dirent>[] = await this.getListing(fullPath, location);
        // let test = new DirectoryListing(this, listing);
        // return test;
    }

    // Example usage
    // private async *generateNumbers(): AsyncGenerator<number, void, unknown> {
    //     yield 1;
    //     yield 2;
    //     yield 3;
    // }

    private async *getListing(fullPath: string, location: string): AsyncGenerator<DriveListItem<fsExtra.Dirent>, void, unknown> {
        // private async getListing(fullPath: string, location: string): Promise<DriveListItem<fsExtra.Dirent>[]> {
        try {
            const dir = await this.adapter.opendir(fullPath);
            const prefixer = this.prefixer.withStrippedPrefix(fullPath);
            // const listing: DriveListItem<fsExtra.Dirent>[] = new Array();
            for await (const dirent of dir) {
                yield {
                    location: prefixer.prefixPath(dirent.name),
                    isFile: dirent.isFile(),
                    original: dirent,
                };
            }
            // return listing;
        } catch (error) {
            throw CannotListDirectoryException.invoke(location, error);
        }
    }
}
