import type {
  FileEntry,
  FileStat,
  FileMetadata,
  FSEvent,
  Disposable,
  FileSystemEventPayloads,
  IFileSystemCapabilities,
} from "@firesystem/core";
import {
  normalizePath,
  dirname,
  basename,
  join,
  TypedEventEmitter,
  FileSystemEvents,
  BaseFileSystem,
} from "@firesystem/core";
import type { IndexedDBConfig } from "./types";

interface StoredFile {
  path: string;
  type: "file" | "directory";
  content?: any;
  size: number;
  created: Date;
  modified: Date;
  metadata?: FileMetadata;
  parent: string;
}

interface WatchListener {
  id: string;
  pattern: string;
  callback: (event: FSEvent) => void;
}

interface FileSnapshot {
  path: string;
  type: "file" | "directory";
  modified: Date;
  size: number;
  contentHash?: string;
  metadataHash?: string;
}

export class IndexedDBFileSystem extends BaseFileSystem {
  private db: IDBDatabase | null = null;
  private config: Required<
    Omit<IndexedDBConfig, "enableExternalSync" | "deepChangeDetection">
  > & {
    enableExternalSync: boolean;
    deepChangeDetection: boolean;
  };
  private watchers: WatchListener[] = [];
  private isInitialized = false;
  public readonly events = new TypedEventEmitter<FileSystemEventPayloads>();
  private broadcastChannel: BroadcastChannel | null = null;
  private fileSnapshots: Map<string, FileSnapshot> = new Map();
  private pollingInterval: NodeJS.Timeout | null = null;
  private enableExternalSync = true;
  private deepChangeDetection = false;

  // Capabilities do IndexedDB
  readonly capabilities: IFileSystemCapabilities = {
    readonly: false,
    caseSensitive: true,
    atomicRename: false, // IndexedDB não tem rename atômico verdadeiro
    supportsWatch: true,
    supportsMetadata: true,
    supportsGlob: true,
    maxFileSize: undefined, // Depende do browser e quota
    maxPathLength: 1024,
  };

  constructor(config: IndexedDBConfig) {
    super();
    this.config = {
      dbName: config.dbName,
      version: config.version || 1,
      storeName: config.storeName || "files",
      enableExternalSync: config.enableExternalSync ?? true,
      deepChangeDetection: config.deepChangeDetection ?? false,
    };
    this.enableExternalSync = config.enableExternalSync ?? true;
    this.deepChangeDetection = config.deepChangeDetection ?? false;

    if (this.enableExternalSync && typeof BroadcastChannel !== "undefined") {
      this.setupBroadcastChannel();
    }
  }

  private async ensureDB(): Promise<IDBDatabase> {
    if (this.db && this.db.name === this.config.dbName) {
      return this.db;
    }

    return new Promise((resolve, reject) => {
      const openWithVersion = async (version: number): Promise<void> => {
        const request = indexedDB.open(this.config.dbName, version);

        request.onerror = () => {
          reject(new Error(`Failed to open IndexedDB: ${request.error}`));
        };

        request.onsuccess = async () => {
          const db = request.result;

          // Check if the expected object store exists
          if (!db.objectStoreNames.contains(this.config.storeName)) {
            // Database exists but with wrong schema - need to recreate
            console.warn(
              `Database '${this.config.dbName}' exists but missing object store '${this.config.storeName}'. Recreating database...`,
            );
            db.close();

            // Delete the corrupted database
            await new Promise<void>((resolve, reject) => {
              const deleteRequest = indexedDB.deleteDatabase(
                this.config.dbName,
              );
              deleteRequest.onsuccess = () => resolve();
              deleteRequest.onerror = () =>
                reject(
                  new Error(
                    `Failed to delete corrupted database: ${deleteRequest.error}`,
                  ),
                );
            });

            // Reopen with the same version - will trigger onupgradeneeded since DB was deleted
            await openWithVersion(version);
            return;
          }

          this.db = db;

          this.isInitialized = true;

          // Handle connection close
          this.db.onclose = () => {
            this.db = null;
            this.isInitialized = false;
          };

          // Ensure root exists
          await this.ensureRootExists();

          resolve(this.db);
        };

        request.onupgradeneeded = (event) => {
          const db = (event.target as IDBOpenDBRequest).result;

          // Create object store if it doesn't exist
          if (!db.objectStoreNames.contains(this.config.storeName)) {
            const store = db.createObjectStore(this.config.storeName, {
              keyPath: "path",
            });

            // Create indexes for efficient queries
            store.createIndex("type", "type", { unique: false });
            store.createIndex("parent", "parent", { unique: false });
            store.createIndex("modified", "modified", { unique: false });

            // Add root directory
            const now = new Date();
            store.add({
              path: "/",
              type: "directory",
              size: 0,
              created: now,
              modified: now,
              parent: "",
            });
          }
        };
      };

      // Start with the configured version
      openWithVersion(this.config.version);
    });
  }

  async initialize(): Promise<void> {
    const startTime = Date.now();
    this.events.emit(FileSystemEvents.INITIALIZING, undefined as any);

    try {
      // Ensure database connection
      const db = await this.ensureDB();

      // Load ALL files from database
      const allFiles = await this.loadAllFiles();
      const fileCount = allFiles.length;

      // Emit progress for each file loaded
      for (let i = 0; i < allFiles.length; i++) {
        const file = allFiles[i];

        // Emit individual file events
        if (file.type === "file") {
          this.events.emit(FileSystemEvents.FILE_READ, {
            path: file.path,
            size: file.size,
          });
        } else {
          this.events.emit(FileSystemEvents.DIR_READ, {
            path: file.path,
            count: 0, // We'll count children later if needed
          });
        }

        // Emit progress
        this.events.emit(FileSystemEvents.INIT_PROGRESS, {
          loaded: i + 1,
          total: fileCount,
          phase: "loading-files",
        });
      }

      this.events.emit(FileSystemEvents.INITIALIZED, {
        duration: Date.now() - startTime,
      });
    } catch (error) {
      this.events.emit(FileSystemEvents.INIT_ERROR, {
        error: error as Error,
      });
      throw error;
    }
  }

  private async loadAllFiles(): Promise<StoredFile[]> {
    const db = await this.ensureDB();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.config.storeName], "readonly");
      const store = transaction.objectStore(this.config.storeName);
      const request = store.getAll();

      request.onsuccess = () => {
        const files = request.result as StoredFile[];
        // Exclude root directory from the list
        const userFiles = files.filter((f) => f.path !== "/");
        resolve(userFiles);
      };

      request.onerror = () => {
        reject(new Error(`Failed to load files: ${request.error}`));
      };
    });
  }

  async readFile(path: string): Promise<FileEntry> {
    const normalized = normalizePath(path);
    const db = await this.ensureDB();

    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.config.storeName], "readonly");
      const store = transaction.objectStore(this.config.storeName);
      const request = store.get(normalized);

      request.onsuccess = () => {
        const file = request.result as StoredFile;
        if (!file) {
          reject(
            new Error(`ENOENT: no such file or directory, open '${path}'`),
          );
          return;
        }
        if (file.type === "directory") {
          reject(
            new Error(
              `EISDIR: illegal operation on a directory, read '${path}'`,
            ),
          );
          return;
        }
        resolve({
          path: file.path,
          name: basename(file.path),
          type: file.type,
          size: file.size,
          created: file.created,
          modified: file.modified,
          metadata: file.metadata,
          content: file.content,
        });
      };

      request.onerror = () => {
        reject(new Error(`Failed to read file: ${request.error}`));
      };
    });
  }

  async writeFile(
    path: string,
    content: any,
    metadata?: FileMetadata,
  ): Promise<FileEntry> {
    const normalized = normalizePath(path);
    const db = await this.ensureDB();

    // Ensure parent directory exists
    await this.ensureParentExists(normalized);

    const now = new Date();
    const size = this.calculateSize(content);

    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.config.storeName], "readwrite");
      const store = transaction.objectStore(this.config.storeName);

      // Check if file exists to preserve created date
      const getRequest = store.get(normalized);

      getRequest.onsuccess = () => {
        const existing = getRequest.result as StoredFile | undefined;
        const isUpdate = !!existing;

        const storedFile: StoredFile = {
          path: normalized,
          type: "file",
          content,
          size,
          created: existing?.created || now,
          modified: now,
          metadata,
          parent: dirname(normalized),
        } as StoredFile;

        const putRequest = store.put(storedFile);

        putRequest.onsuccess = () => {
          const entry: FileEntry = {
            path: normalized,
            name: basename(normalized),
            type: "file",
            size,
            created: storedFile.created,
            modified: storedFile.modified,
            metadata,
            content,
          };

          // Notify watchers
          const event = {
            type: isUpdate ? "updated" : ("created" as "updated" | "created"),
            path: normalized,
            timestamp: now,
          };
          this.notifyWatchers(event);

          // Broadcast to other tabs/windows
          this.broadcastChange({
            ...event,
            size,
            isDirectory: false,
            fileType: "file" as const,
          });

          resolve(entry);
        };

        putRequest.onerror = () => {
          reject(new Error(`Failed to write file: ${putRequest.error}`));
        };
      };

      getRequest.onerror = () => {
        reject(new Error(`Failed to check existing file: ${getRequest.error}`));
      };
    });
  }

  async deleteFile(path: string): Promise<void> {
    const normalized = normalizePath(path);
    const db = await this.ensureDB();

    // First check if it exists and get type
    const file = await this.stat(normalized).catch(() => null);
    if (!file) {
      throw new Error(`ENOENT: no such file or directory, unlink '${path}'`);
    }

    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.config.storeName], "readwrite");
      const store = transaction.objectStore(this.config.storeName);

      if (file.type === "directory") {
        // Check if directory is empty
        const index = store.index("parent");
        const request = index.count(normalized);

        request.onsuccess = () => {
          if (request.result > 0) {
            reject(
              new Error(`ENOTEMPTY: directory not empty, rmdir '${path}'`),
            );
            return;
          }

          // Directory is empty, delete it
          const deleteRequest = store.delete(normalized);

          deleteRequest.onsuccess = () => {
            const event = {
              type: "deleted" as const,
              path: normalized,
              timestamp: new Date(),
            };
            this.notifyWatchers(event);
            this.broadcastChange(event);
            resolve();
          };

          deleteRequest.onerror = () => {
            reject(
              new Error(`Failed to delete directory: ${deleteRequest.error}`),
            );
          };
        };

        request.onerror = () => {
          reject(
            new Error(`Failed to check directory contents: ${request.error}`),
          );
        };
      } else {
        // It's a file, delete directly
        const request = store.delete(normalized);

        request.onsuccess = () => {
          const event = {
            type: "deleted" as const,
            path: normalized,
            timestamp: new Date(),
          };
          this.notifyWatchers(event);
          this.broadcastChange(event);
          resolve();
        };

        request.onerror = () => {
          reject(new Error(`Failed to delete file: ${request.error}`));
        };
      }
    });
  }

  async exists(path: string): Promise<boolean> {
    const normalized = normalizePath(path);
    const db = await this.ensureDB();

    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.config.storeName], "readonly");
      const store = transaction.objectStore(this.config.storeName);
      const request = store.count(normalized);

      request.onsuccess = () => {
        resolve(request.result > 0);
      };

      request.onerror = () => {
        reject(new Error(`Failed to check existence: ${request.error}`));
      };
    });
  }

  async readDir(path: string): Promise<FileEntry[]> {
    const normalized = normalizePath(path);
    const db = await this.ensureDB();

    // First check if directory exists
    if (normalized !== "/" && !(await this.exists(normalized))) {
      throw new Error(`ENOENT: no such file or directory, scandir '${path}'`);
    }

    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.config.storeName], "readonly");
      const store = transaction.objectStore(this.config.storeName);
      const index = store.index("parent");
      const request = index.getAll(normalized);

      request.onsuccess = () => {
        const files = request.result as StoredFile[];
        const entries = files.map((file) => ({
          path: file.path,
          name: basename(file.path),
          type: file.type,
          size: file.size,
          created: file.created,
          modified: file.modified,
          metadata: file.metadata,
        }));

        resolve(entries);
      };

      request.onerror = () => {
        reject(new Error(`Failed to read directory: ${request.error}`));
      };
    });
  }

  async mkdir(path: string, recursive: boolean = false): Promise<FileEntry> {
    const normalized = normalizePath(path);

    if (normalized === "/") {
      throw new Error(`EEXIST: file already exists, mkdir '${path}'`);
    }

    const db = await this.ensureDB();

    // Check if already exists
    if (await this.exists(normalized)) {
      throw new Error(`EEXIST: file already exists, mkdir '${path}'`);
    }

    // Ensure parent exists if not recursive
    const parent = dirname(normalized);
    if (!recursive && parent !== "/" && !(await this.exists(parent))) {
      throw new Error(`ENOENT: no such file or directory, mkdir '${path}'`);
    }

    // Create parent directories if recursive
    if (recursive && parent !== "/" && !(await this.exists(parent))) {
      await this.mkdir(parent, true);
    }

    const now = new Date();

    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.config.storeName], "readwrite");
      const store = transaction.objectStore(this.config.storeName);

      const storedFile: StoredFile = {
        path: normalized,
        type: "directory",
        size: 0,
        created: now,
        modified: now,
        parent: dirname(normalized),
      } as StoredFile;

      const request = store.add(storedFile);

      request.onsuccess = () => {
        const entry: FileEntry = {
          path: normalized,
          name: basename(normalized),
          type: "directory",
          size: 0,
          created: now,
          modified: now,
        };

        const event = {
          type: "created" as const,
          path: normalized,
          timestamp: now,
        };
        this.notifyWatchers(event);
        this.broadcastChange({
          ...event,
          isDirectory: true,
          fileType: "directory" as const,
        });

        resolve(entry);
      };

      request.onerror = () => {
        reject(new Error(`Failed to create directory: ${request.error}`));
      };
    });
  }

  async rmdir(path: string, recursive: boolean = false): Promise<void> {
    const normalized = normalizePath(path);

    if (normalized === "/") {
      throw new Error(`EBUSY: resource busy or locked, rmdir '${path}'`);
    }

    const db = await this.ensureDB();

    // Check if directory exists
    const stat = await this.stat(normalized).catch(() => null);
    if (!stat) {
      throw new Error(`ENOENT: no such file or directory, rmdir '${path}'`);
    }
    if (stat.type !== "directory") {
      throw new Error(`ENOTDIR: not a directory, rmdir '${path}'`);
    }

    if (recursive) {
      // Delete all contents recursively
      await this.deleteRecursive(normalized);
    } else {
      // Check if empty
      const contents = await this.readDir(normalized);
      if (contents.length > 0) {
        throw new Error(`ENOTEMPTY: directory not empty, rmdir '${path}'`);
      }
    }

    // Delete the directory itself
    await this.deleteFile(normalized);
  }

  async rename(oldPath: string, newPath: string): Promise<FileEntry> {
    const oldNormalized = normalizePath(oldPath);
    const newNormalized = normalizePath(newPath);
    const db = await this.ensureDB();

    // Get the file/directory
    const oldStat = await this.stat(oldNormalized);

    // Ensure new parent exists
    await this.ensureParentExists(newNormalized);

    // Check if new path already exists
    if (await this.exists(newNormalized)) {
      throw new Error(
        `EEXIST: file already exists, rename '${oldPath}' -> '${newPath}'`,
      );
    }

    const now = new Date();

    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.config.storeName], "readwrite");
      const store = transaction.objectStore(this.config.storeName);

      // If it's a directory, we need to update all children
      if (oldStat.type === "directory") {
        // Get all descendants
        const request = store.openCursor();
        const updates: StoredFile[] = [];

        request.onsuccess = (event) => {
          const cursor = (event.target as IDBRequest).result;

          if (cursor) {
            const file = cursor.value as StoredFile;

            // Check if this file is under the old path
            if (
              file.path.startsWith(oldNormalized + "/") ||
              file.path === oldNormalized
            ) {
              const newFilePath = file.path.replace(
                oldNormalized,
                newNormalized,
              );
              updates.push({
                ...file,
                path: newFilePath,
                parent: dirname(newFilePath),
                modified: file.path === oldNormalized ? now : file.modified,
              });
            }

            cursor.continue();
          } else {
            // All files collected, now update them
            let completed = 0;
            const total = updates.length;

            if (total === 0) {
              reject(new Error(`Directory not found: ${oldPath}`));
              return;
            }

            updates.forEach((file) => {
              // Delete old entry
              store.delete(file.path.replace(newNormalized, oldNormalized));

              // Add new entry
              const addRequest = store.add(file);

              addRequest.onsuccess = () => {
                completed++;
                if (completed === total) {
                  const entry = updates.find((f) => f.path === newNormalized)!;

                  const event = {
                    type: "renamed" as const,
                    path: newNormalized,
                    oldPath: oldNormalized,
                    timestamp: now,
                  };
                  this.notifyWatchers(event);
                  this.broadcastChange(event);

                  resolve({
                    path: entry.path,
                    name: basename(entry.path),
                    type: entry.type,
                    size: entry.size,
                    created: entry.created,
                    modified: entry.modified,
                    metadata: entry.metadata,
                  });
                }
              };

              addRequest.onerror = () => {
                reject(
                  new Error(`Failed to rename directory: ${addRequest.error}`),
                );
              };
            });
          }
        };

        request.onerror = () => {
          reject(new Error(`Failed to read files: ${request.error}`));
        };
      } else {
        // It's a file, simpler case
        const getRequest = store.get(oldNormalized);

        getRequest.onsuccess = () => {
          const file = getRequest.result as StoredFile;
          if (!file) {
            reject(new Error(`File not found: ${oldPath}`));
            return;
          }

          // Delete old entry
          store.delete(oldNormalized);

          // Create new entry
          const newFile: StoredFile = {
            ...file,
            path: newNormalized,
            parent: dirname(newNormalized),
            modified: now,
          };

          const addRequest = store.add(newFile);

          addRequest.onsuccess = () => {
            const entry: FileEntry = {
              path: newNormalized,
              name: basename(newNormalized),
              type: file.type,
              size: file.size,
              created: file.created,
              modified: now,
              metadata: file.metadata,
              content: file.content,
            };

            const event = {
              type: "renamed" as const,
              path: newNormalized,
              oldPath: oldNormalized,
              timestamp: now,
            };
            this.notifyWatchers(event);
            this.broadcastChange(event);

            resolve(entry);
          };

          addRequest.onerror = () => {
            reject(new Error(`Failed to rename file: ${addRequest.error}`));
          };
        };

        getRequest.onerror = () => {
          reject(new Error(`Failed to read file: ${getRequest.error}`));
        };
      }
    });
  }

  async move(sourcePaths: string[], targetPath: string): Promise<void> {
    const targetNormalized = normalizePath(targetPath);

    // Ensure target is a directory
    const targetStat = await this.stat(targetNormalized).catch(() => null);
    if (!targetStat || targetStat.type !== "directory") {
      throw new Error(`ENOTDIR: not a directory, move to '${targetPath}'`);
    }

    for (const sourcePath of sourcePaths) {
      const name = basename(sourcePath);
      const newPath = join(targetNormalized, name);
      await this.rename(sourcePath, newPath);
    }
  }

  async copy(sourcePath: string, targetPath: string): Promise<FileEntry> {
    const sourceNormalized = normalizePath(sourcePath);
    const targetNormalized = normalizePath(targetPath);

    // Read source file
    const source = await this.readFile(sourceNormalized);

    // Write to target
    return this.writeFile(targetNormalized, source.content, source.metadata);
  }

  watch(pattern: string, callback: (event: FSEvent) => void): Disposable {
    const id = this.generateWatcherId();
    const listener: WatchListener = { id, pattern, callback };

    this.watchers.push(listener);

    return {
      dispose: () => {
        const index = this.watchers.findIndex((w) => w.id === id);
        if (index !== -1) {
          this.watchers.splice(index, 1);
        }
      },
    };
  }

  async stat(path: string): Promise<FileStat> {
    const normalized = normalizePath(path);
    const db = await this.ensureDB();

    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.config.storeName], "readonly");
      const store = transaction.objectStore(this.config.storeName);
      const request = store.get(normalized);

      request.onsuccess = () => {
        const file = request.result as StoredFile;
        if (!file) {
          reject(
            new Error(`ENOENT: no such file or directory, stat '${path}'`),
          );
          return;
        }

        resolve({
          path: file.path,
          size: file.size,
          type: file.type,
          created: file.created,
          modified: file.modified,
          readonly: false, // Sempre false em IndexedDB
        });
      };

      request.onerror = () => {
        reject(new Error(`Failed to stat file: ${request.error}`));
      };
    });
  }

  async glob(pattern: string): Promise<string[]> {
    const db = await this.ensureDB();

    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.config.storeName], "readonly");
      const store = transaction.objectStore(this.config.storeName);
      const request = store.getAll();

      request.onsuccess = () => {
        const files = request.result as StoredFile[];
        const paths = files
          .map((file) => file.path)
          .filter((path) => this.matchesPattern(path, pattern))
          .sort();

        resolve(paths);
      };

      request.onerror = () => {
        reject(new Error(`Failed to glob: ${request.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 {
      const db = await this.ensureDB();

      await new Promise<void>((resolve, reject) => {
        const transaction = db.transaction(
          [this.config.storeName],
          "readwrite",
        );
        const store = transaction.objectStore(this.config.storeName);
        const request = store.clear();

        request.onsuccess = () => {
          // Always ensure root directory exists after clear
          const rootTransaction = db.transaction(
            [this.config.storeName],
            "readwrite",
          );
          const rootStore = rootTransaction.objectStore(this.config.storeName);
          const now = new Date();

          const rootDir: StoredFile = {
            path: "/",
            type: "directory",
            size: 0,
            created: now,
            modified: now,
            parent: "",
          };

          const rootRequest = rootStore.add(rootDir);
          rootRequest.onsuccess = () => resolve();
          rootRequest.onerror = () =>
            reject(new Error(`Failed to recreate root: ${rootRequest.error}`));
        };

        request.onerror = () => {
          reject(new Error(`Failed to clear store: ${request.error}`));
        };
      });

      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 db = await this.ensureDB();

      const totalSize = await new Promise<number>((resolve, reject) => {
        const transaction = db.transaction([this.config.storeName], "readonly");
        const store = transaction.objectStore(this.config.storeName);
        const request = store.getAll();

        request.onsuccess = () => {
          const files = request.result as StoredFile[];
          const size = files.reduce((sum, file) => sum + file.size, 0);
          resolve(size);
        };

        request.onerror = () => {
          reject(new Error(`Failed to calculate size: ${request.error}`));
        };
      });

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

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

  // Helper methods

  private async calculateHash(data: any): Promise<string> {
    // Simple hash for content comparison
    const str = typeof data === "string" ? data : JSON.stringify(data);
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      const char = str.charCodeAt(i);
      hash = (hash << 5) - hash + char;
      hash = hash & hash; // Convert to 32-bit integer
    }
    return hash.toString(36);
  }

  private calculateSize(content: any): number {
    if (content === null || content === undefined) {
      return 0;
    }
    if (typeof content === "string") {
      return new Blob([content]).size;
    }
    if (content instanceof ArrayBuffer) {
      return content.byteLength;
    }
    if (content instanceof Blob) {
      return content.size;
    }
    if (typeof content === "object") {
      return new Blob([JSON.stringify(content)]).size;
    }
    return 0;
  }

  private async ensureParentExists(path: string): Promise<void> {
    const parent = dirname(path);
    if (parent === "/" || parent === path) return;

    if (!(await this.exists(parent))) {
      throw new Error(`ENOENT: no such file or directory, open '${path}'`);
    }
  }

  private async deleteRecursive(path: string): Promise<void> {
    const entries = await this.readDir(path);

    for (const entry of entries) {
      if (entry.type === "directory") {
        await this.deleteRecursive(entry.path);
      }
      await this.deleteFile(entry.path);
    }
  }

  private matchesPattern(path: string, pattern: string): boolean {
    // Handle simple cases
    if (pattern === "**" || pattern === "**/*") {
      return true;
    }

    if (pattern === "*") {
      // * should only match files in root directory
      return path.split("/").length === 2 && path !== "/";
    }

    // Convert glob pattern to regex
    let regex = pattern
      // Escape special regex characters except glob ones
      .replace(/[.+^${}()|[\]\\]/g, "\\$&")
      // Handle ** (matches any number of directories)
      .replace(/\*\*/g, "___GLOBSTAR___")
      // Handle * (matches any characters except /)
      .replace(/\*/g, "[^/]*")
      // Handle ? (matches single character except /)
      .replace(/\?/g, "[^/]")
      // Restore ** handling
      .replace(/___GLOBSTAR___\//g, "(.*/)?")
      .replace(/\/___GLOBSTAR___/g, "(/.*)?")
      .replace(/___GLOBSTAR___/g, ".*");

    // Anchor the pattern
    regex = "^" + regex + "$";

    return new RegExp(regex).test(path);
  }

  private notifyWatchers(event: FSEvent): void {
    for (const watcher of this.watchers) {
      if (this.matchesPattern(event.path, watcher.pattern)) {
        try {
          watcher.callback(event);
        } catch (error) {
          console.error(
            `Error in watch callback for pattern "${watcher.pattern}":`,
            error,
          );
        }
      }
    }
  }

  private generateWatcherId(): string {
    return `watch_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  private async ensureRootExists(): Promise<void> {
    if (!this.db) return;

    const transaction = this.db.transaction(
      [this.config.storeName],
      "readonly",
    );
    const store = transaction.objectStore(this.config.storeName);
    const request = store.get("/");

    return new Promise((resolve) => {
      request.onsuccess = async () => {
        if (!request.result) {
          // Root doesn't exist, create it
          const writeTransaction = this.db!.transaction(
            [this.config.storeName],
            "readwrite",
          );
          const writeStore = writeTransaction.objectStore(
            this.config.storeName,
          );
          const now = new Date();

          await new Promise<void>((res, rej) => {
            const addRequest = writeStore.add({
              path: "/",
              type: "directory",
              size: 0,
              created: now,
              modified: now,
              parent: "",
            });

            addRequest.onsuccess = () => res();
            addRequest.onerror = () =>
              rej(new Error("Failed to create root directory"));
          });
        }
        resolve();
      };

      request.onerror = () => resolve(); // Ignore errors, we'll try next time
    });
  }

  private setupBroadcastChannel(): void {
    this.broadcastChannel = new BroadcastChannel(`vfs-${this.config.dbName}`);

    this.broadcastChannel.onmessage = (event) => {
      const { type, path, oldPath, timestamp } = event.data;

      // Notify local watchers about external changes
      this.notifyWatchers({
        type,
        path,
        oldPath,
        timestamp: new Date(timestamp),
      });

      // Emit events for external changes
      switch (type) {
        case "created":
          if (event.data.isDirectory) {
            this.events.emit(FileSystemEvents.DIR_CREATED, { path });
          } else {
            this.events.emit(FileSystemEvents.FILE_WRITTEN, {
              path,
              size: event.data.size || 0,
            });
          }
          break;
        case "updated":
          this.events.emit(FileSystemEvents.FILE_WRITTEN, {
            path,
            size: event.data.size || 0,
          });
          break;
        case "deleted":
          this.events.emit(FileSystemEvents.FILE_DELETED, { path });
          break;
        case "renamed":
          // Emit as both deleted and created since there's no FILE_RENAMED event
          if (oldPath) {
            this.events.emit(FileSystemEvents.FILE_DELETED, { path: oldPath });
          }
          this.events.emit(FileSystemEvents.FILE_WRITTEN, {
            path,
            size: event.data.size || 0,
          });
          break;
      }
    };
  }

  private broadcastChange(
    event: FSEvent & {
      size?: number;
      isDirectory?: boolean;
      fileType?: "file" | "directory";
    },
  ): void {
    if (this.broadcastChannel) {
      this.broadcastChannel.postMessage({
        ...event,
        timestamp: event.timestamp.toISOString(),
      });
    }
  }

  async startWatching(pollingIntervalMs: number = 250): Promise<void> {
    if (!this.enableExternalSync) return;

    // Take initial snapshot
    await this.updateFileSnapshots();

    // Start polling for changes
    this.pollingInterval = setInterval(async () => {
      await this.checkForExternalChanges();
    }, pollingIntervalMs);
  }

  async stopWatching(): Promise<void> {
    if (this.pollingInterval) {
      clearInterval(this.pollingInterval);
      this.pollingInterval = null;
    }
  }

  private async updateFileSnapshots(): Promise<void> {
    const db = await this.ensureDB();

    return new Promise((resolve, reject) => {
      const transaction = db.transaction([this.config.storeName], "readonly");
      const store = transaction.objectStore(this.config.storeName);
      const request = store.getAll();

      request.onsuccess = async () => {
        const files = request.result as StoredFile[];
        this.fileSnapshots.clear();

        for (const file of files) {
          // Include both files and directories in snapshots
          if (file.path !== "/") {
            // Skip root directory
            const snapshot: FileSnapshot = {
              path: file.path,
              type: file.type,
              modified: file.modified,
              size: file.size,
            };

            // Calculate content hash if deep detection is enabled
            if (this.deepChangeDetection && file.type === "file") {
              snapshot.contentHash = await this.calculateHash(file.content);
              if (file.metadata) {
                snapshot.metadataHash = await this.calculateHash(file.metadata);
              }
            }

            this.fileSnapshots.set(file.path, snapshot);
          }
        }

        resolve();
      };

      request.onerror = () => {
        reject(new Error(`Failed to update snapshots: ${request.error}`));
      };
    });
  }

  private async checkForExternalChanges(): Promise<void> {
    const db = await this.ensureDB();
    const currentFiles = new Map<string, FileSnapshot>();
    const filesToDeepCheck: string[] = [];

    return new Promise((resolve) => {
      const transaction = db.transaction([this.config.storeName], "readonly");
      const store = transaction.objectStore(this.config.storeName);
      const request = store.getAll();

      request.onsuccess = async () => {
        const files = request.result as StoredFile[];

        // Build current state map - include both files and directories
        files.forEach((file) => {
          if (file.path !== "/") {
            // Skip root directory
            currentFiles.set(file.path, {
              path: file.path,
              type: file.type,
              modified: file.modified,
              size: file.size,
            });
          }
        });

        // Phase 1: Quick check (date/size)
        currentFiles.forEach((current, path) => {
          const snapshot = this.fileSnapshots.get(path);

          if (!snapshot) {
            // New file or directory
            this.notifyWatchers({
              type: "created",
              path,
              timestamp: new Date(),
            });

            // Also emit proper events
            if (current.type === "directory") {
              this.events.emit(FileSystemEvents.DIR_CREATED, { path });
            } else {
              this.events.emit(FileSystemEvents.FILE_WRITTEN, {
                path,
                size: current.size,
              });
            }
          } else if (
            current.modified.getTime() !== snapshot.modified.getTime() ||
            current.size !== snapshot.size
          ) {
            // Modified file - detected by date/size
            this.notifyWatchers({
              type: "updated",
              path,
              timestamp: new Date(),
            });

            if (current.type === "file") {
              this.events.emit(FileSystemEvents.FILE_WRITTEN, {
                path,
                size: current.size,
              });
            }
          } else if (this.deepChangeDetection && current.type === "file") {
            // Mark for deep check
            filesToDeepCheck.push(path);
          }
        });

        // Check for deleted files/directories
        this.fileSnapshots.forEach((snapshot, path) => {
          if (!currentFiles.has(path)) {
            this.notifyWatchers({
              type: "deleted",
              path,
              timestamp: new Date(),
            });

            // Also emit proper events
            if (snapshot.type === "directory") {
              this.events.emit(FileSystemEvents.DIR_DELETED, { path });
            } else {
              this.events.emit(FileSystemEvents.FILE_DELETED, { path });
            }
          }
        });

        // Phase 2: Deep check (content) - only if enabled and needed
        if (filesToDeepCheck.length > 0) {
          await this.performDeepContentCheck(filesToDeepCheck, files);
        }

        // Update snapshots with content hashes if deep check is enabled
        if (this.deepChangeDetection) {
          for (const file of files) {
            if (file.type === "file" && file.path !== "/") {
              const snapshot = currentFiles.get(file.path);
              if (snapshot) {
                snapshot.contentHash = await this.calculateHash(file.content);
                if (file.metadata) {
                  snapshot.metadataHash = await this.calculateHash(
                    file.metadata,
                  );
                }
              }
            }
          }
        }

        // Update snapshots
        this.fileSnapshots = currentFiles;
        resolve();
      };

      request.onerror = () => resolve();
    });
  }

  private async performDeepContentCheck(
    paths: string[],
    allFiles: StoredFile[],
  ): Promise<void> {
    for (const path of paths) {
      const file = allFiles.find((f) => f.path === path);
      const snapshot = this.fileSnapshots.get(path);

      if (file && snapshot) {
        const currentContentHash = await this.calculateHash(file.content);
        const currentMetadataHash = file.metadata
          ? await this.calculateHash(file.metadata)
          : undefined;

        const contentChanged =
          snapshot.contentHash && snapshot.contentHash !== currentContentHash;
        const metadataChanged =
          snapshot.metadataHash &&
          currentMetadataHash &&
          snapshot.metadataHash !== currentMetadataHash;

        if (contentChanged || metadataChanged) {
          // Content or metadata changed even though date/size didn't
          this.notifyWatchers({
            type: "updated",
            path,
            timestamp: new Date(),
          });

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

  dispose(): void {
    this.stopWatching();

    if (this.broadcastChannel) {
      this.broadcastChannel.close();
      this.broadcastChannel = null;
    }

    if (this.db) {
      this.db.close();
      this.db = null;
    }

    this.watchers = [];
    this.fileSnapshots.clear();
  }

  // Sobrescrever métodos de permissão (sempre permitido em IndexedDB)
  async canModify(path: string): Promise<boolean> {
    return true; // Sempre permitido em IndexedDB
  }

  async canCreateIn(parentPath: string): Promise<boolean> {
    try {
      const stat = await this.stat(parentPath);
      return stat.type === "directory";
    } catch {
      // Se não existe, verifica se é o root
      return normalizePath(parentPath) === "/";
    }
  }

  // Não sobrescrever writeFileAtomic - usar implementação base
  // IndexedDB não tem rename verdadeiramente atômico
}
