/**
 * In-memory `DirectoryHandleLike` implementation.
 *
 * Useful for tests and Node/Bun environments without the browser File
 * System Access API. The shape is intentionally compatible with browser
 * `FileSystemDirectoryHandle` so consumers can swap implementations.
 */
import type {
  DirectoryHandleLike,
  FileHandleLike,
  FileLike,
  WritableStreamLike,
} from './directory-types.js';

interface MemoryNode {
  bytes: Uint8Array;
  type: string;
}

class MemoryFileLike implements FileLike {
  constructor(
    public readonly name: string,
    private readonly node: MemoryNode,
  ) {}
  get size(): number { return this.node.bytes.byteLength; }
  get type(): string { return this.node.type; }
  stream(): ReadableStream<Uint8Array> {
    const bytes = this.node.bytes;
    return new ReadableStream<Uint8Array>({
      start(controller) {
        if (bytes.byteLength > 0) controller.enqueue(bytes);
        controller.close();
      },
    });
  }
  async arrayBuffer(): Promise<ArrayBuffer> {
    const out = new ArrayBuffer(this.node.bytes.byteLength);
    new Uint8Array(out).set(this.node.bytes);
    return out;
  }
}

class MemoryWritable implements WritableStreamLike {
  private chunks: Uint8Array[] = [];
  constructor(private readonly file: MemoryFile) {}
  async write(chunk: Uint8Array): Promise<void> {
    this.chunks.push(chunk);
  }
  async close(): Promise<void> {
    let total = 0;
    for (const c of this.chunks) total += c.byteLength;
    const out = new Uint8Array(total);
    let offset = 0;
    for (const c of this.chunks) {
      out.set(c, offset);
      offset += c.byteLength;
    }
    this.file.commit(out);
  }
  async abort(): Promise<void> {
    this.chunks = [];
  }
}

class MemoryFile implements FileHandleLike {
  readonly kind: 'file' = 'file';
  constructor(
    public readonly name: string,
    public readonly node: MemoryNode,
  ) {}
  async getFile(): Promise<FileLike> {
    return new MemoryFileLike(this.name, this.node);
  }
  async createWritable(): Promise<WritableStreamLike> {
    return new MemoryWritable(this);
  }
  /** Internal — used by `MemoryWritable.close()`. */
  commit(bytes: Uint8Array): void {
    this.node.bytes = bytes;
  }
}

class MemoryDirectory implements DirectoryHandleLike {
  readonly kind: 'directory' = 'directory';
  private readonly children = new Map<string, MemoryDirectory | MemoryFile>();
  constructor(public readonly name: string) {}

  async *entries(): AsyncIterable<[string, DirectoryHandleLike | FileHandleLike]> {
    for (const [name, child] of this.children) {
      yield [name, child];
    }
  }

  async getDirectoryHandle(name: string, opts?: { create?: boolean }): Promise<DirectoryHandleLike> {
    const existing = this.children.get(name);
    if (existing !== undefined) {
      if (existing.kind !== 'directory') {
        throw new Error(`'${name}' exists but is a file`);
      }
      return existing;
    }
    if (opts?.create !== true) throw new Error(`directory not found: ${name}`);
    const dir = new MemoryDirectory(name);
    this.children.set(name, dir);
    return dir;
  }

  async getFileHandle(name: string, opts?: { create?: boolean }): Promise<FileHandleLike> {
    const existing = this.children.get(name);
    if (existing !== undefined) {
      if (existing.kind !== 'file') {
        throw new Error(`'${name}' exists but is a directory`);
      }
      return existing;
    }
    if (opts?.create !== true) throw new Error(`file not found: ${name}`);
    const node: MemoryNode = { bytes: new Uint8Array(0), type: '' };
    const file = new MemoryFile(name, node);
    this.children.set(name, file);
    return file;
  }

  /** Test helper: synchronously add a file with given bytes. */
  addFile(name: string, bytes: Uint8Array, type: string = ''): MemoryFile {
    const node: MemoryNode = { bytes, type };
    const file = new MemoryFile(name, node);
    this.children.set(name, file);
    return file;
  }

  /** Test helper: synchronously add a subdirectory. */
  addDir(name: string): MemoryDirectory {
    const dir = new MemoryDirectory(name);
    this.children.set(name, dir);
    return dir;
  }
}

/** Construct an empty in-memory directory tree. */
export function createMemoryDirectory(name: string = ''): DirectoryHandleLike & {
  addFile(name: string, bytes: Uint8Array, type?: string): FileHandleLike;
  addDir(name: string): DirectoryHandleLike;
} {
  return new MemoryDirectory(name);
}
