/*
# 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';

import {
  log as internalLogger,
  configs
} from '/common/common.js';
import * as Constants from '/common/constants.js';
import * as TabsStore from '/common/tabs-store.js';
import * as TreeBehavior from '/common/tree-behavior.js';

import { Tab, TreeItem } from '/common/TreeItem.js';

import * as Background from './background.js';
import * as BackgroundCache from './background-cache.js';
import * as Tree from './tree.js';
import * as TreeStructure from './tree-structure.js';

function log(...args) {
  internalLogger('background/handle-tree-changes', ...args);
}

let mInitialized = false;

function reserveDetachHiddenTab(tab) {
  reserveDetachHiddenTab.tabs.add(tab);
  if (reserveDetachHiddenTab.reserved)
    clearTimeout(reserveDetachHiddenTab.reserved);
  reserveDetachHiddenTab.reserved = setTimeout(async () => {
    delete reserveDetachHiddenTab.reserved;
    const tabs = new Set(TreeItem.sort(Array.from(reserveDetachHiddenTab.tabs)));
    reserveDetachHiddenTab.tabs.clear();
    log('try to detach hidden tabs: ', tabs);
    for (const tab of tabs) {
      if (!TabsStore.ensureLivingItem(tab))
        continue;
      for (const descendant of tab.$TST.descendants) {
        if (descendant.hidden)
          continue;
        const nearestVisibleAncestor = descendant.$TST.ancestors.find(ancestor => !ancestor.hidden && !tabs.has(ancestor));
        if (nearestVisibleAncestor &&
            nearestVisibleAncestor == descendant.$TST.parent)
          continue;
        for (const ancestor of descendant.$TST.ancestors) {
          if (!ancestor.hidden &&
              !ancestor.$TST.collapsed)
            break;
          if (!ancestor.$TST.subtreeCollapsed)
            continue;
          await Tree.collapseExpandSubtree(ancestor, {
            collapsed: false,
            broadcast: true
          });
        }
        if (nearestVisibleAncestor) {
          log(` => reattach descendant ${descendant.id} to ${nearestVisibleAncestor.id}`);
          await Tree.attachTabTo(descendant, nearestVisibleAncestor, {
            dontMove:  true,
            broadcast: true
          });
        }
        else {
          log(` => detach descendant ${descendant.id}`);
          await Tree.detachTab(descendant, {
            broadcast: true
          });
        }
      }
      if (tab.$TST.hasParent &&
          !tab.$TST.parent.hidden) {
        log(` => detach hidden tab ${tab.id}`);
        await Tree.detachTab(tab, {
          broadcast: true
        });
      }
    }
  }, 100);
}
reserveDetachHiddenTab.tabs = new Set();

Tab.onHidden.addListener(tab => {
  if (configs.fixupTreeOnTabVisibilityChanged)
    reserveDetachHiddenTab(tab);
});

function reserveAttachShownTab(tab) {
  tab.$TST.addState(Constants.kTAB_STATE_SHOWING);
  reserveAttachShownTab.tabs.add(tab);
  if (reserveAttachShownTab.reserved)
    clearTimeout(reserveAttachShownTab.reserved);
  reserveAttachShownTab.reserved = setTimeout(async () => {
    delete reserveAttachShownTab.reserved;
    const tabs = new Set(TreeItem.sort(Array.from(reserveAttachShownTab.tabs)));
    reserveAttachShownTab.tabs.clear();
    log('try to attach shown tabs: ', tabs);
    for (const tab of tabs) {
      if (!TabsStore.ensureLivingItem(tab) ||
          tab.$TST.hasParent) {
        tab.$TST.removeState(Constants.kTAB_STATE_SHOWING);
        continue;
      }
      const referenceTabs = TreeBehavior.calculateReferenceItemsFromInsertionPosition(tab, {
        context:      Constants.kINSERTION_CONTEXT_SHOWN,
        insertAfter:  tab.$TST.nearestVisiblePrecedingTab,
        // Instead of nearestFollowingForeignerTab, to avoid placing the tab
        // after hidden tabs (too far from the target)
        insertBefore: tab.$TST.unsafeNearestFollowingForeignerTab
      });
      if (referenceTabs.parent) {
        log(` => attach shown tab ${tab.id} to ${referenceTabs.parent.id}`);
        await Tree.attachTabTo(tab, referenceTabs.parent, {
          insertBefore: referenceTabs.insertBefore,
          insertAfter:  referenceTabs.insertAfter,
          broadcast:    true
        });
      }
      tab.$TST.removeState(Constants.kTAB_STATE_SHOWING);
    }
  }, 100);
}
reserveAttachShownTab.tabs = new Set();

Tab.onShown.addListener(tab => {
  if (configs.fixupTreeOnTabVisibilityChanged)
    reserveAttachShownTab(tab);
});

Background.onReady.addListener(() => {
  mInitialized = true;
});

Tree.onSubtreeCollapsedStateChanged.addListener((tab, _info) => {
  if (mInitialized)
    TreeStructure.reserveToSaveTreeStructure(tab.windowId);
  BackgroundCache.markWindowCacheDirtyFromTab(tab, Constants.kWINDOW_STATE_CACHED_SIDEBAR_COLLAPSED_DIRTY);
});
