import EventEmitter from "events";
import { usb } from "usb";
import debounce from "lodash/debounce";
import { getDevices } from "@ledgerhq/hw-transport-node-hid-noevents";
import { log } from "@ledgerhq/logs";

export default (
  delay: number,
  listenDevicesPollingSkip: () => boolean,
): {
  events: EventEmitter;
  stop: () => void;
} => {
  const events = new EventEmitter();
  events.setMaxListeners(0);
  let listDevices = getDevices();

  const flatDevice = d => d.path;

  const getFlatDevices = () => [...new Set(getDevices().map(d => flatDevice(d)))];

  const getDeviceByPaths = paths => listDevices.find(d => paths.includes(flatDevice(d)));

  let lastDevices = getFlatDevices();

  const poll = () => {
    if (!listenDevicesPollingSkip()) {
      log("hid-listen", "Polling for added or removed devices");
      let changeFound = false;
      const currentDevices = getFlatDevices();
      const newDevices = currentDevices.filter(d => !lastDevices.includes(d));

      if (newDevices.length > 0) {
        log("hid-listen", "New device found:", newDevices);
        listDevices = getDevices();
        events.emit("add", getDeviceByPaths(newDevices));
        changeFound = true;
      } else {
        log("hid-listen", "No new device found");
      }

      const removeDevices = lastDevices.filter(d => !currentDevices.includes(d));

      if (removeDevices.length > 0) {
        log("hid-listen", "Removed device found:", removeDevices);
        events.emit("remove", getDeviceByPaths(removeDevices));
        listDevices = listDevices.filter(d => !removeDevices.includes(flatDevice(d)));
        changeFound = true;
      } else {
        log("hid-listen", "No removed device found");
      }

      if (changeFound) {
        lastDevices = currentDevices;
      }
    } else {
      log("hid-listen", "Polling skipped, re-debouncing");
      debouncedPoll();
    }
  };

  const debouncedPoll = debounce(poll, delay);

  const attachDetected = device => {
    log("hid-listen", "Device add detected:", device);
    debouncedPoll();
  };

  usb.on("attach", attachDetected);
  log("hid-listen", "attach listener added");

  const detachDetected = device => {
    log("hid-listen", "Device removal detected:", device);
    debouncedPoll();
  };

  usb.on("detach", detachDetected);
  log("hid-listen", "detach listener added");
  return {
    stop: () => {
      log("hid-listen", "Stop received, removing listeners and cancelling pending debounced polls");
      debouncedPoll.cancel();
      usb.removeListener("attach", attachDetected);
      usb.removeListener("detach", detachDetected);
    },
    events,
  };
};
