/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow strict
 * @format
 */

'use strict';

const {dirname, join, parse} = require('path');

module.exports = class HasteFS {
  directories: Set<string>;
  directoryEntries: Map<string, Array<string>>;
  files: Set<string>;

  constructor(files: Array<string>) {
    this.directories = buildDirectorySet(files);
    this.directoryEntries = buildDirectoryEntries(files.map(parse));
    this.files = new Set(files);
  }

  closest(path: string, fileName: string): ?string {
    const parsedPath = parse(path);
    const root = parsedPath.root;
    let dir = parsedPath.dir;
    do {
      const candidate = join(dir, fileName);
      if (this.files.has(candidate)) {
        return candidate;
      }
      dir = dirname(dir);
    } while (dir !== '.' && dir !== root);
    return null;
  }

  dirExists(path: string) {
    return this.directories.has(path);
  }

  exists(path: string) {
    return this.files.has(path);
  }

  getAllFiles() {
    /* $FlowFixMe(>=0.70.0 site=react_native_fb) This comment suppresses an
     * error found when Flow v0.70 was deployed. To see the error delete this
     * comment and run Flow. */
    return Array.from(this.files.keys());
  }

  matchFiles() {
    throw new Error('HasteFS.matchFiles is not implemented yet.');
  }

  matches(directory: string, pattern: RegExp) {
    const entries = this.directoryEntries.get(directory);
    /* $FlowFixMe(>=0.70.0 site=react_native_fb) This comment suppresses an
     * error found when Flow v0.70 was deployed. To see the error delete this
     * comment and run Flow. */
    return entries ? entries.filter(pattern.test, pattern) : [];
  }
};

function buildDirectorySet(files) {
  const directories = new Set();
  files.forEach(path => {
    const parsedPath = parse(path);
    const root = parsedPath.root;
    let dir = parsedPath.dir;
    while (dir !== '.' && dir !== root && !directories.has(dir)) {
      directories.add(dir);
      dir = dirname(dir);
    }
  });
  return directories;
}

function buildDirectoryEntries(files) {
  const directoryEntries = new Map();
  files.forEach(({base, dir}) => {
    const entries = directoryEntries.get(dir);
    if (entries) {
      entries.push(base);
    } else {
      directoryEntries.set(dir, [base]);
    }
  });
  return directoryEntries;
}