/* 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 {
  AUDIT,
  ENABLE,
  RESET,
  SELECT,
  HIGHLIGHT,
  UNHIGHLIGHT,
  UPDATE_CAN_BE_DISABLED,
  UPDATE_CAN_BE_ENABLED,
  UPDATE_PREF,
  UPDATE_DETAILS,
  PREF_KEYS,
  PREFS,
  UPDATE_DISPLAY_TABBING_ORDER,
} = require("resource://devtools/client/accessibility/constants.js");

const TreeView = ChromeUtils.importESModule(
  "resource://devtools/client/shared/components/tree/TreeView.mjs"
).default;

/**
 * Initial state definition
 */
function getInitialState() {
  return {
    enabled: false,
    canBeDisabled: true,
    canBeEnabled: true,
    selected: null,
    highlighted: null,
    expanded: new Set(),
    [PREFS.SCROLL_INTO_VIEW]: Services.prefs.getBoolPref(
      PREF_KEYS[PREFS.SCROLL_INTO_VIEW],
      false
    ),
    tabbingOrderDisplayed: false,
    supports: {},
  };
}

/**
 * Maintain ui components of the accessibility panel.
 */
function ui(state = getInitialState(), action) {
  switch (action.type) {
    case ENABLE:
      return onToggle(state, action, true);
    case UPDATE_CAN_BE_DISABLED:
      return onCanBeDisabledChange(state, action);
    case UPDATE_CAN_BE_ENABLED:
      return onCanBeEnabledChange(state, action);
    case UPDATE_PREF:
      return onPrefChange(state, action);
    case UPDATE_DETAILS:
      return onUpdateDetails(state, action);
    case HIGHLIGHT:
      return onHighlight(state, action);
    case AUDIT:
      return onAudit(state, action);
    case UNHIGHLIGHT:
      return onUnhighlight(state, action);
    case SELECT:
      return onSelect(state, action);
    case RESET:
      return onReset(state, action);
    case UPDATE_DISPLAY_TABBING_ORDER:
      return onUpdateDisplayTabbingOrder(state, action);
    default:
      return state;
  }
}

function onUpdateDetails(state) {
  if (!state.selected) {
    return state;
  }

  // Clear selected state that should only be set when select action is
  // performed.
  return Object.assign({}, state, { selected: null });
}

function onUnhighlight(state) {
  return Object.assign({}, state, { highlighted: null });
}

function updateExpandedNodes(expanded, ancestry) {
  expanded = new Set(expanded);
  const path = ancestry.reduceRight((accPath, { accessible }) => {
    accPath = TreeView.subPath(accPath, accessible.actorID);
    expanded.add(accPath);
    return accPath;
  }, "");

  return { path, expanded };
}

function onAudit(state, { response: ancestries, error }) {
  if (error) {
    console.warn("Error running audit", error);
    return state;
  }

  let expanded = new Set(state.expanded);
  for (const ancestry of ancestries) {
    ({ expanded } = updateExpandedNodes(expanded, ancestry));
  }

  return {
    ...state,
    expanded,
  };
}

function onHighlight(state, { accessible, response: ancestry, error }) {
  if (error) {
    console.warn("Error fetching ancestry", error);
    return state;
  }

  const { expanded } = updateExpandedNodes(state.expanded, ancestry);
  return Object.assign({}, state, { expanded, highlighted: accessible });
}

function onSelect(state, { accessible, response: ancestry, error }) {
  if (error) {
    console.warn("Error fetching ancestry", error);
    return state;
  }

  const { path, expanded } = updateExpandedNodes(state.expanded, ancestry);
  const selected = TreeView.subPath(path, accessible.actorID);

  return Object.assign({}, state, { expanded, selected });
}

/**
 * Handle "canBeDisabled" flag update for accessibility service
 *
 * @param  {object}  state   Current ui state
 * @param  {object}  action  Redux action object
 * @return {object}  updated state
 */
function onCanBeDisabledChange(state, { canBeDisabled }) {
  return Object.assign({}, state, { canBeDisabled });
}

/**
 * Handle "canBeEnabled" flag update for accessibility service
 *
 * @param  {object}  state   Current ui state.
 * @param  {object}  action  Redux action object
 * @return {object}  updated state
 */
function onCanBeEnabledChange(state, { canBeEnabled }) {
  return Object.assign({}, state, { canBeEnabled });
}

/**
 * Handle pref update for accessibility panel.
 *
 * @param  {object}  state   Current ui state.
 * @param  {object}  action  Redux action object
 * @return {object}  updated state
 */
function onPrefChange(state, { name, value }) {
  return {
    ...state,
    [name]: value,
  };
}

/**
 * Handle reset action for the accessibility panel UI.
 *
 * @param  {object}  state   Current ui state.
 * @param  {object}  action  Redux action object
 * @return {object}  updated state
 */
function onReset(state, { enabled, canBeDisabled, canBeEnabled, supports }) {
  const newState = {
    ...getInitialState(),
    enabled,
    canBeDisabled,
    canBeEnabled,
    supports,
  };

  return newState;
}

/**
 * Handle accessibilty service enabling/disabling.
 *
 * @param {object}  state   Current accessibility services enabled state.
 * @param {object}  action  Redux action object
 * @param {boolean} enabled New enabled state.
 * @return {object}  updated state
 */
function onToggle(state, { error }, enabled) {
  if (error) {
    console.warn("Error enabling accessibility service: ", error);
    return state;
  }

  return Object.assign({}, state, { enabled });
}

function onUpdateDisplayTabbingOrder(state, { error, tabbingOrderDisplayed }) {
  if (error) {
    console.warn("Error updating displaying tabbing order: ", error);
    return state;
  }

  return Object.assign({}, state, { tabbingOrderDisplayed });
}

exports.ui = ui;
