/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const {
  bindActionCreators,
} = require("resource://devtools/client/shared/vendor/redux.js");
const {
  createFactory,
} = require("resource://devtools/client/shared/vendor/react.mjs");
const {
  render,
  unmountComponentAtNode,
} = require("resource://devtools/client/shared/vendor/react-dom.mjs");
const Provider = createFactory(
  require("resource://devtools/client/shared/vendor/react-redux.js").Provider
);
const {
  START_IGNORE_ACTION,
} = require("resource://devtools/client/shared/redux/middleware/ignore.js");

const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js");
const LocalizationProvider = createFactory(FluentReact.LocalizationProvider);

const actions = require("resource://devtools/client/aboutdebugging/src/actions/index.js");
const {
  configureStore,
} = require("resource://devtools/client/aboutdebugging/src/create-store.js");
const {
  setDebugTargetCollapsibilities,
} = require("resource://devtools/client/aboutdebugging/src/modules/debug-target-collapsibilities.js");

const {
  l10n,
} = require("resource://devtools/client/aboutdebugging/src/modules/l10n.js");

const {
  addNetworkLocationsObserver,
  getNetworkLocations,
  removeNetworkLocationsObserver,
} = require("resource://devtools/client/aboutdebugging/src/modules/network-locations.js");
const {
  addUSBRuntimesObserver,
  getUSBRuntimes,
  removeUSBRuntimesObserver,
} = require("resource://devtools/client/aboutdebugging/src/modules/usb-runtimes.js");

loader.lazyRequireGetter(
  this,
  "adb",
  "resource://devtools/client/shared/remote-debugging/adb/adb.js",
  true
);
loader.lazyRequireGetter(
  this,
  "adbAddon",
  "resource://devtools/client/shared/remote-debugging/adb/adb-addon.js",
  true
);
loader.lazyRequireGetter(
  this,
  "adbProcess",
  "resource://devtools/client/shared/remote-debugging/adb/adb-process.js",
  true
);

const Router = createFactory(
  require("resource://devtools/client/shared/vendor/react-router-dom.js")
    .HashRouter
);
const App = createFactory(
  require("resource://devtools/client/aboutdebugging/src/components/App.js")
);

const AboutDebugging = {
  async init() {
    const direction = Services.locale.isAppLocaleRTL ? "rtl" : "ltr";
    document.documentElement.setAttribute("dir", direction);

    this.onAdbAddonUpdated = this.onAdbAddonUpdated.bind(this);
    this.onAdbProcessReady = this.onAdbProcessReady.bind(this);
    this.onNetworkLocationsUpdated = this.onNetworkLocationsUpdated.bind(this);
    this.onUSBRuntimesUpdated = this.onUSBRuntimesUpdated.bind(this);

    this.store = configureStore();
    this.actions = bindActionCreators(actions, this.store.dispatch);

    const width = this.getRoundedViewportWidth();
    this.actions.recordTelemetryEvent("open_adbg", { width });

    await l10n.init([
      "branding/brand.ftl",
      "devtools/client/aboutdebugging.ftl",
    ]);

    this.actions.createThisFirefoxRuntime();

    // Listen to Network locations updates and retrieve the initial list of locations.
    addNetworkLocationsObserver(this.onNetworkLocationsUpdated);
    await this.onNetworkLocationsUpdated();

    // Listen to USB runtime updates and retrieve the initial list of runtimes.

    // If ADB is already started, wait for the initial runtime list to be able to restore
    // already connected runtimes.
    const isProcessStarted = await adb.isProcessStarted();
    const onAdbRuntimesReady = isProcessStarted
      ? adb.once("runtime-list-ready")
      : null;
    addUSBRuntimesObserver(this.onUSBRuntimesUpdated);
    await onAdbRuntimesReady;

    await this.onUSBRuntimesUpdated();

    render(
      Provider(
        {
          store: this.store,
        },
        LocalizationProvider(
          { bundles: l10n.getBundles() },
          Router({}, App({}))
        )
      ),
      this.mount
    );

    adbAddon.on("update", this.onAdbAddonUpdated);
    this.onAdbAddonUpdated();
    adbProcess.on("adb-ready", this.onAdbProcessReady);
    // get the initial status of adb process, in case it's already started
    this.onAdbProcessReady();
  },

  onAdbAddonUpdated() {
    this.actions.updateAdbAddonStatus(adbAddon.status);
  },

  onAdbProcessReady() {
    this.actions.updateAdbReady(adbProcess.ready);
  },

  onNetworkLocationsUpdated() {
    return this.actions.updateNetworkLocations(getNetworkLocations());
  },

  async onUSBRuntimesUpdated() {
    const runtimes = await getUSBRuntimes();
    return this.actions.updateUSBRuntimes(runtimes);
  },

  async destroy() {
    const width = this.getRoundedViewportWidth();
    this.actions.recordTelemetryEvent("close_adbg", { width });

    const state = this.store.getState();
    const currentRuntimeId = state.runtimes.selectedRuntimeId;
    if (currentRuntimeId) {
      await this.actions.unwatchRuntime(currentRuntimeId);
    }

    // Remove all client listeners.
    this.actions.removeRuntimeListeners();

    // Prevents any further action from being dispatched
    this.store.dispatch(START_IGNORE_ACTION);

    removeNetworkLocationsObserver(this.onNetworkLocationsUpdated);
    removeUSBRuntimesObserver(this.onUSBRuntimesUpdated);
    adbAddon.off("update", this.onAdbAddonUpdated);
    adbProcess.off("adb-ready", this.onAdbProcessReady);
    setDebugTargetCollapsibilities(state.ui.debugTargetCollapsibilities);
    unmountComponentAtNode(this.mount);
  },

  get mount() {
    return document.getElementById("mount");
  },

  /**
   * Computed viewport width, rounded at 50px. Used for telemetry events.
   */
  getRoundedViewportWidth() {
    return Math.ceil(window.outerWidth / 50) * 50;
  },
};

window.addEventListener(
  "DOMContentLoaded",
  () => {
    AboutDebugging.init();
  },
  { once: true }
);

window.addEventListener(
  "unload",
  () => {
    AboutDebugging.destroy();
  },
  { once: true }
);

// Expose AboutDebugging to tests so that they can access to the store.
window.AboutDebugging = AboutDebugging;
