import type { IFileSystem } from "../interfaces/IFileSystem";
import type { IReactiveFileSystem } from "../interfaces/IReactiveFileSystem";
import type { FileEntry, FileMetadata } from "../types/FileEntry";
import type { FileSystemEventPayloads } from "../types/FileSystemEvents";
import { TypedEventEmitter } from "../utils/EventEmitter";
import { FileSystemEvents } from "../types/FileSystemEvents";

/**
 * Constructor type helper
 */
type Constructor<T = {}> = new (...args: any[]) => T;

/**
 * Mixin to add event support to any file system implementation
 */
export function withEvents<TBase extends Constructor<IFileSystem>>(
  Base: TBase,
): TBase & Constructor<IReactiveFileSystem> {
  return class extends Base {
    public readonly events = new TypedEventEmitter<FileSystemEventPayloads>();

    // Override methods to emit events
    async readFile(path: string): Promise<FileEntry> {
      const startTime = Date.now();
      const operationId = `read-${path}-${startTime}`;

      this.events.emit(FileSystemEvents.OPERATION_START, {
        operation: "readFile",
        path,
        id: operationId,
      });

      this.events.emit(FileSystemEvents.FILE_READING, { path });

      try {
        const result = await super.readFile(path);

        this.events.emit(FileSystemEvents.FILE_READ, {
          path,
          size: result.size || 0,
        });

        this.events.emit(FileSystemEvents.OPERATION_END, {
          operation: "readFile",
          path,
          id: operationId,
          duration: Date.now() - startTime,
        });

        return result;
      } catch (error) {
        this.events.emit(FileSystemEvents.OPERATION_ERROR, {
          operation: "readFile",
          path,
          error: error as Error,
        });
        throw error;
      }
    }

    async writeFile(
      path: string,
      content: any,
      metadata?: FileMetadata,
    ): Promise<FileEntry> {
      const startTime = Date.now();
      const operationId = `write-${path}-${startTime}`;
      const size =
        typeof content === "string"
          ? content.length
          : JSON.stringify(content).length;

      this.events.emit(FileSystemEvents.OPERATION_START, {
        operation: "writeFile",
        path,
        id: operationId,
      });

      this.events.emit(FileSystemEvents.FILE_WRITING, { path, size });

      try {
        const result = await super.writeFile(path, content, metadata);

        this.events.emit(FileSystemEvents.FILE_WRITTEN, { path, size });

        this.events.emit(FileSystemEvents.OPERATION_END, {
          operation: "writeFile",
          path,
          id: operationId,
          duration: Date.now() - startTime,
        });

        return result;
      } catch (error) {
        this.events.emit(FileSystemEvents.OPERATION_ERROR, {
          operation: "writeFile",
          path,
          error: error as Error,
        });
        throw error;
      }
    }

    async deleteFile(path: string): Promise<void> {
      const startTime = Date.now();
      const operationId = `delete-${path}-${startTime}`;

      this.events.emit(FileSystemEvents.OPERATION_START, {
        operation: "deleteFile",
        path,
        id: operationId,
      });

      this.events.emit(FileSystemEvents.FILE_DELETING, { path });

      try {
        await super.deleteFile(path);

        this.events.emit(FileSystemEvents.FILE_DELETED, { path });

        this.events.emit(FileSystemEvents.OPERATION_END, {
          operation: "deleteFile",
          path,
          id: operationId,
          duration: Date.now() - startTime,
        });
      } catch (error) {
        this.events.emit(FileSystemEvents.OPERATION_ERROR, {
          operation: "deleteFile",
          path,
          error: error as Error,
        });
        throw error;
      }
    }

    async readDir(path: string): Promise<FileEntry[]> {
      const startTime = Date.now();
      const operationId = `readDir-${path}-${startTime}`;

      this.events.emit(FileSystemEvents.OPERATION_START, {
        operation: "readDir",
        path,
        id: operationId,
      });

      this.events.emit(FileSystemEvents.DIR_READING, { path });

      try {
        const result = await super.readDir(path);

        this.events.emit(FileSystemEvents.DIR_READ, {
          path,
          count: result.length,
        });

        this.events.emit(FileSystemEvents.OPERATION_END, {
          operation: "readDir",
          path,
          id: operationId,
          duration: Date.now() - startTime,
        });

        return result;
      } catch (error) {
        this.events.emit(FileSystemEvents.OPERATION_ERROR, {
          operation: "readDir",
          path,
          error: error as Error,
        });
        throw error;
      }
    }

    async mkdir(path: string, recursive: boolean = false): Promise<FileEntry> {
      const startTime = Date.now();
      const operationId = `mkdir-${path}-${startTime}`;

      this.events.emit(FileSystemEvents.OPERATION_START, {
        operation: "mkdir",
        path,
        id: operationId,
      });

      this.events.emit(FileSystemEvents.DIR_CREATING, { path, recursive });

      try {
        const result = await super.mkdir(path, recursive);

        this.events.emit(FileSystemEvents.DIR_CREATED, { path });

        this.events.emit(FileSystemEvents.OPERATION_END, {
          operation: "mkdir",
          path,
          id: operationId,
          duration: Date.now() - startTime,
        });

        return result;
      } catch (error) {
        this.events.emit(FileSystemEvents.OPERATION_ERROR, {
          operation: "mkdir",
          path,
          error: error as Error,
        });
        throw error;
      }
    }

    async rmdir(path: string, recursive: boolean = false): Promise<void> {
      const startTime = Date.now();
      const operationId = `rmdir-${path}-${startTime}`;

      this.events.emit(FileSystemEvents.OPERATION_START, {
        operation: "rmdir",
        path,
        id: operationId,
      });

      this.events.emit(FileSystemEvents.DIR_DELETING, { path, recursive });

      try {
        await super.rmdir(path, recursive);

        this.events.emit(FileSystemEvents.DIR_DELETED, { path });

        this.events.emit(FileSystemEvents.OPERATION_END, {
          operation: "rmdir",
          path,
          id: operationId,
          duration: Date.now() - startTime,
        });
      } catch (error) {
        this.events.emit(FileSystemEvents.OPERATION_ERROR, {
          operation: "rmdir",
          path,
          error: error as Error,
        });
        throw error;
      }
    }

    async clear(): Promise<void> {
      const startTime = Date.now();
      const operationId = `clear-${startTime}`;

      this.events.emit(FileSystemEvents.OPERATION_START, {
        operation: "clear",
        id: operationId,
      });

      this.events.emit(FileSystemEvents.STORAGE_CLEARING, undefined as any);

      try {
        await super.clear();

        const duration = Date.now() - startTime;
        this.events.emit(FileSystemEvents.STORAGE_CLEARED, { duration });

        this.events.emit(FileSystemEvents.OPERATION_END, {
          operation: "clear",
          id: operationId,
          duration,
        });
      } catch (error) {
        this.events.emit(FileSystemEvents.OPERATION_ERROR, {
          operation: "clear",
          error: error as Error,
        });
        throw error;
      }
    }

    async size(): Promise<number> {
      const startTime = Date.now();
      const operationId = `size-${startTime}`;

      this.events.emit(FileSystemEvents.OPERATION_START, {
        operation: "size",
        id: operationId,
      });

      try {
        const size = await super.size();

        this.events.emit(FileSystemEvents.STORAGE_SIZE_CALCULATED, { size });

        this.events.emit(FileSystemEvents.OPERATION_END, {
          operation: "size",
          id: operationId,
          duration: Date.now() - startTime,
        });

        return size;
      } catch (error) {
        this.events.emit(FileSystemEvents.OPERATION_ERROR, {
          operation: "size",
          error: error as Error,
        });
        throw error;
      }
    }
  };
}
