/* 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/. */

/* import-globals-from item-editing/calendar-item-editing.js */
/* import-globals-from item-editing/calendar-item-panel.js */
/* import-globals-from calendar-command-controller.js */
/* import-globals-from calendar-modes.js */
/* import-globals-from calendar-views-utils.js */

/* globals MozElements */

var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm");

var calendarTabMonitor = {
  monitorName: "calendarTabMonitor",

  // Unused, but needed functions
  onTabTitleChanged() {},
  onTabOpened() {},
  onTabClosing() {},
  onTabPersist() {},
  onTabRestored() {},

  onTabSwitched(aNewTab, aOldTab) {
    // Unfortunately, tabmail doesn't provide a hideTab function on the tab
    // type definitions. To make sure the commands are correctly disabled,
    // we want to update calendar/task commands when switching away from
    // those tabs.
    if (aOldTab?.mode.name == "calendar" || aOldTab?.mode.name == "task") {
      calendarController.updateCommands();
      calendarController2.updateCommands();
    }
    // we reset the save menu controls when moving away (includes closing)
    // from an event or task editor tab
    if (aNewTab.mode.name == "calendarEvent" || aNewTab.mode.name == "calendarTask") {
      sendMessage({ command: "triggerUpdateSaveControls" });
    } else if (window.calItemSaveControls) {
      // we need to reset the labels of the menu controls for saving if we
      // are not switching to an item tab and displayed an item tab before
      let saveMenu = document.getElementById("calendar-save-menuitem");
      let saveandcloseMenu = document.getElementById("calendar-save-and-close-menuitem");
      saveMenu.label = window.calItemSaveControls.saveMenu.label;
      saveandcloseMenu.label = window.calItemSaveControls.saveandcloseMenu.label;
    }

    // Change the mode (gCurrentMode) to match the new tab.
    switch (aNewTab.mode.name) {
      case "calendar":
        calSwitchToCalendarMode();
        break;
      case "tasks":
        calSwitchToTaskMode();
        break;
      case "chat":
      case "calendarEvent":
      case "calendarTask":
        calSwitchToMode(aNewTab.mode.name);
        break;
      case "addressBookTab":
      case "preferencesTab":
      case "contentTab":
        calSwitchToMode("special");
        break;
      default:
        calSwitchToMode("mail");
        break;
    }
  },
};

var calendarTabType = {
  name: "calendar",
  panelId: "calendarTabPanel",
  modes: {
    calendar: {
      type: "calendar",
      maxTabs: 1,
      openTab(tab) {
        tab.tabNode.setIcon("chrome://messenger/skin/icons/new/compact/calendar.svg");
        gLastShownCalendarView.get();
        tab.title = cal.l10n.getLtnString("tabTitleCalendar");
      },
      showTab(tab) {},
      closeTab(tab) {},

      persistTab(tab) {
        let tabmail = document.getElementById("tabmail");
        return {
          // Since we do strange tab switching logic in calSwitchToCalendarMode,
          // we should store the current tab state ourselves.
          background: tab != tabmail.currentTabInfo,
        };
      },

      restoreTab(tabmail, state) {
        tabmail.openTab("calendar", state);
      },

      onTitleChanged(tab) {
        tab.title = cal.l10n.getLtnString("tabTitleCalendar");
      },

      supportsCommand: (aCommand, aTab) => calendarController2.supportsCommand(aCommand),
      isCommandEnabled: (aCommand, aTab) => calendarController2.isCommandEnabled(aCommand),
      doCommand: (aCommand, aTab) => calendarController2.doCommand(aCommand),
      onEvent: (aEvent, aTab) => calendarController2.onEvent(aEvent),
    },

    tasks: {
      type: "tasks",
      maxTabs: 1,
      openTab(tab) {
        tab.tabNode.setIcon("chrome://messenger/skin/icons/new/compact/tasks.svg");
        tab.title = cal.l10n.getLtnString("tabTitleTasks");
      },
      showTab(tab) {},
      closeTab(tab) {},

      persistTab(tab) {
        let tabmail = document.getElementById("tabmail");
        return {
          // Since we do strange tab switching logic in calSwitchToTaskMode,
          // we should store the current tab state ourselves.
          background: tab != tabmail.currentTabInfo,
        };
      },

      restoreTab(tabmail, state) {
        tabmail.openTab("tasks", state);
      },

      onTitleChanged(tab) {
        tab.title = cal.l10n.getLtnString("tabTitleTasks");
      },

      supportsCommand: (aCommand, aTab) => calendarController2.supportsCommand(aCommand),
      isCommandEnabled: (aCommand, aTab) => calendarController2.isCommandEnabled(aCommand),
      doCommand: (aCommand, aTab) => calendarController2.doCommand(aCommand),
      onEvent: (aEvent, aTab) => calendarController2.onEvent(aEvent),
    },
  },

  saveTabState(tab) {},
};

XPCOMUtils.defineLazyGetter(calendarTabType.modes.calendar, "notificationbox", () => {
  return new MozElements.NotificationBox(element => {
    document.getElementById("calendar-deactivated-notification-location-events").append(element);
  });
});

XPCOMUtils.defineLazyGetter(calendarTabType.modes.tasks, "notificationbox", () => {
  return new MozElements.NotificationBox(element => {
    document.getElementById("calendar-deactivated-notification-location-tasks").append(element);
  });
});

/**
 * For details about tab info objects and the tabmail interface see:
 * comm/mail/base/content/mailTabs.js
 * comm/mail/base/content/tabmail.js
 */
var calendarItemTabType = {
  name: "calendarItem",
  perTabPanel: "vbox",
  idNumber: 0,
  modes: {
    calendarEvent: { type: "calendarEvent" },
    calendarTask: { type: "calendarTask" },
  },
  /**
   * Opens an event tab or a task tab.
   *
   * @param {object} aTab - A tab info object
   * @param {object} aArgs - Contains data about the event/task
   */
  openTab(aTab, aArgs) {
    // Create a clone to use for this tab. Remove the cloned toolbox
    // and move the original toolbox into its place. There is only
    // one toolbox/toolbar so its settings are the same for all item tabs.
    let original = document.getElementById("calendarItemPanel").firstElementChild;
    let clone = original.cloneNode(true);

    clone.querySelector("toolbox").remove();
    moveEventToolbox(clone);
    clone.setAttribute("id", "calendarItemTab" + this.idNumber);

    if (aTab.mode.type == "calendarTask") {
      // For task tabs, css class hides event-specific toolbar buttons.
      clone.setAttribute("class", "calendar-task-dialog-tab");
    }

    aTab.panel.setAttribute("id", "calendarItemTabWrapper" + this.idNumber);
    aTab.panel.appendChild(clone);

    // Set up the iframe and store the iframe's id.  The iframe's
    // src is set in onLoadCalendarItemPanel() that is called below.
    aTab.iframe = aTab.panel.querySelector("iframe");
    let iframeId = "calendarItemTabIframe" + this.idNumber;
    aTab.iframe.setAttribute("id", iframeId);
    gItemTabIds.push(iframeId);

    // Generate and set the tab title.
    let strName;
    if (aTab.mode.type == "calendarEvent") {
      strName = aArgs.calendarEvent.title ? "editEventDialog" : "newEventDialog";
      aTab.tabNode.setIcon("chrome://messenger/skin/icons/new/compact/calendar.svg");
    } else if (aTab.mode.type == "calendarTask") {
      strName = aArgs.calendarEvent.title ? "editTaskDialog" : "newTaskDialog";
      aTab.tabNode.setIcon("chrome://messenger/skin/icons/new/compact/tasks.svg");
    } else {
      throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
    }
    // name is "New Event", "Edit Task", etc.
    let name = cal.l10n.getCalString(strName);
    aTab.title = name + ": " + (aArgs.calendarEvent.title || name);

    // allowTabClose prevents the tab from being closed until we ask
    // the user if they want to save any unsaved changes.
    aTab.allowTabClose = false;

    // Put the arguments where they can be accessed easily
    // from the iframe. (window.arguments[0])
    aTab.iframe.contentWindow.arguments = [aArgs];

    // activate or de-activate 'Events and Tasks' menu items
    document.commandDispatcher.updateCommands("calendar_commands");

    onLoadCalendarItemPanel(iframeId, aArgs.url);

    this.idNumber += 1;
  },
  /**
   * Saves a tab's state when it is deactivated / hidden.  The opposite of showTab.
   *
   * @param {object} aTab - A tab info object
   */
  saveTabState(aTab) {
    // save state
    aTab.itemTabConfig = {};
    Object.assign(aTab.itemTabConfig, gConfig);

    // clear statusbar
    let statusbar = document.getElementById("status-bar");
    let items = statusbar.getElementsByClassName("event-dialog");
    for (let item of items) {
      item.setAttribute("collapsed", true);
    }
    // move toolbox to the place where it can be accessed later
    let to = document.getElementById("calendarItemPanel").firstElementChild;
    moveEventToolbox(to);
  },
  /**
   * Called when a tab is activated / shown.  The opposite of saveTabState.
   *
   * @param {object} aTab - A tab info object
   */
  showTab(aTab) {
    // move toolbox into place then load state
    moveEventToolbox(aTab.panel.firstElementChild);
    Object.assign(gConfig, aTab.itemTabConfig);
    updateItemTabState(gConfig);

    // activate or de-activate 'Events and Tasks' menu items
    document.commandDispatcher.updateCommands("calendar_commands");
  },
  /**
   * Called when there is a request to close a tab.  Using aTab.allowTabClose
   * we first prevent the tab from closing so we can prompt the user
   * about saving changes, then we allow the tab to close.
   *
   * @param {object} aTab - A tab info object
   */
  tryCloseTab(aTab) {
    if (aTab.allowTabClose) {
      return true;
    }
    onCancel(aTab.iframe.id);
    return false;
  },
  /**
   * Closes a tab.
   *
   * @param {object} aTab - A tab info object
   */
  closeTab(aTab) {
    // Remove the iframe id from the array where they are stored.
    let index = gItemTabIds.indexOf(aTab.iframe.id);
    if (index != -1) {
      gItemTabIds.splice(index, 1);
    }
    aTab.itemTabConfig = null;

    // If this is the last item tab that is closing, then delete
    // window.calItemSaveControls, so mochitests won't complain.
    let tabmail = document.getElementById("tabmail");
    let calendarItemTabCount =
      tabmail.tabModes.calendarEvent.tabs.length + tabmail.tabModes.calendarTask.tabs.length;
    if (calendarItemTabCount == 1) {
      delete window.calItemSaveControls;
    }
  },
  /**
   * Called when quitting the application (and/or closing the window).
   * Saves an open tab's state to be able to restore it later.
   *
   * @param {object} aTab - A tab info object
   */
  persistTab(aTab) {
    let args = aTab.iframe.contentWindow.arguments[0];
    // Serialize args, with manual handling of some properties.
    // persistTab is called even for new events/tasks in tabs that
    // were closed and never saved (for 'undo close tab'
    // functionality), thus we confirm we have the expected values.
    if (
      !args ||
      !args.calendar ||
      !args.calendar.id ||
      !args.calendarEvent ||
      !args.calendarEvent.id
    ) {
      return {};
    }

    let calendarId = args.calendar.id;
    let itemId = args.calendarEvent.id;
    // Handle null args.initialStartDateValue, just for good measure.
    // Note that this is not the start date for the event or task.
    let hasDateValue = args.initialStartDateValue && args.initialStartDateValue.icalString;
    let initialStartDate = hasDateValue ? args.initialStartDateValue.icalString : null;

    args.calendar = null;
    args.calendarEvent = null;
    args.initialStartDateValue = null;

    return {
      calendarId,
      itemId,
      initialStartDate,
      args,
      tabType: aTab.mode.type,
    };
  },
  /**
   * Called when starting the application (and/or opening the window).
   * Restores a tab that was open when the application was quit previously.
   *
   * @param {object} aTabmail - The tabmail interface
   * @param {object} aState - The state of the tab to restore
   */
  restoreTab(aTabmail, aState) {
    // Sometimes restoreTab is called for tabs that were never saved
    // and never meant to be persisted or restored. See persistTab.
    if (aState.args && aState.calendarId && aState.itemId) {
      aState.args.initialStartDateValue = aState.initialStartDate
        ? cal.createDateTime(aState.initialStartDate)
        : cal.dtz.getDefaultStartDate();

      aState.args.onOk = doTransaction.bind(null, "modify");

      aState.args.calendar = cal.manager.getCalendarById(aState.calendarId);
      if (aState.args.calendar) {
        aState.args.calendar.getItem(aState.itemId).then(item => {
          if (item) {
            aState.args.calendarEvent = item;
            aTabmail.openTab(aState.tabType, aState.args);
          }
        });
      }
    }
  },
};

window.addEventListener("load", e => {
  let tabmail = document.getElementById("tabmail");
  tabmail.registerTabType(calendarTabType);
  tabmail.registerTabType(calendarItemTabType);
  tabmail.registerTabMonitor(calendarTabMonitor);
});

/**
 * Switch the calendar view, and optionally switch to calendar mode.
 *
 * @param aType     The type of view to select.
 * @param aShow     If true, the mode will be switched to calendar if not
 *                    already there.
 */
function switchCalendarView(aType, aShow) {
  gLastShownCalendarView.set(aType);

  if (aShow && gCurrentMode != "calendar") {
    // This function in turn calls switchToView(), so return afterwards.
    calSwitchToCalendarMode();
    return;
  }
  document
    .querySelector(`.calview-toggle-item[aria-selected="true"]`)
    ?.setAttribute("aria-selected", false);
  document
    .querySelector(`.calview-toggle-item[aria-controls="${aType}-view"]`)
    ?.setAttribute("aria-selected", true);
  switchToView(aType);
}

/**
 * Move the event toolbox, containing the toolbar, into view for a tab
 * or back to its hiding place where it is accessed again for other tabs.
 *
 * @param {Node} aDestination - Destination where the toolbox will be moved
 */
function moveEventToolbox(aDestination) {
  let toolbox = document.getElementById("event-toolbox");
  // the <toolbarpalette> has to be copied manually
  let palette = toolbox.palette;
  let iframe = aDestination.querySelector("iframe");
  aDestination.insertBefore(toolbox, iframe);
  toolbox.palette = palette;
}
