/**
 * Copyright (c) HashiCorp, Inc.
 * SPDX-License-Identifier: MPL-2.0
 */

import { inject as service } from '@ember/service';
import { alias, gt } from '@ember/object/computed';
import Component from '@ember/component';
import { computed } from '@ember/object';
import keyUtils from 'vault/lib/key-utils';
import pathToTree from 'vault/lib/path-to-tree';
import { task, timeout } from 'ember-concurrency';

const { ancestorKeysForKey } = keyUtils;
const DOT_REPLACEMENT = '☃';
const ANIMATION_DURATION = 250;

export default Component.extend({
  tagName: '',
  namespaceService: service('namespace'),
  auth: service(),
  store: service(),
  namespace: null,
  listCapability: null,
  canList: false,

  init() {
    this._super(...arguments);
    this.namespaceService?.findNamespacesForUser.perform();
  },

  didReceiveAttrs() {
    this._super(...arguments);

    const ns = this.namespace;
    const oldNS = this.oldNamespace;
    if (!oldNS || ns !== oldNS) {
      this.setForAnimation.perform();
      this.fetchListCapability.perform();
    }
    this.set('oldNamespace', ns);
  },

  fetchListCapability: task(function* () {
    try {
      const capability = yield this.store.findRecord('capabilities', 'sys/namespaces/');
      this.set('listCapability', capability);
      this.set('canList', true);
    } catch (e) {
      // If error out on findRecord call it's because you don't have permissions
      // and therefor don't have permission to manage namespaces
      this.set('canList', false);
    }
  }),
  setForAnimation: task(function* () {
    const leaves = this.menuLeaves;
    const lastLeaves = this.lastMenuLeaves;
    if (!lastLeaves) {
      this.set('lastMenuLeaves', leaves);
      yield timeout(0);
      return;
    }
    const isAdding = leaves.length > lastLeaves.length;
    const changedLeaf = (isAdding ? leaves : lastLeaves).get('lastObject');
    this.set('isAdding', isAdding);
    this.set('changedLeaf', changedLeaf);

    // if we're adding we want to render immediately an animate it in
    // if we're not adding, we need time to move the item out before
    // a rerender removes it
    if (isAdding) {
      this.set('lastMenuLeaves', leaves);
      yield timeout(0);
      return;
    }
    yield timeout(ANIMATION_DURATION);
    this.set('lastMenuLeaves', leaves);
  }).drop(),

  isAnimating: alias('setForAnimation.isRunning'),

  namespacePath: alias('namespaceService.path'),

  // this is an array of namespace paths that the current user
  // has access to
  accessibleNamespaces: alias('namespaceService.accessibleNamespaces'),
  inRootNamespace: alias('namespaceService.inRootNamespace'),

  namespaceTree: computed('accessibleNamespaces', function () {
    const nsList = this.accessibleNamespaces;

    if (!nsList) {
      return [];
    }
    return pathToTree(nsList);
  }),

  maybeAddRoot(leaves) {
    const userRoot = this.auth.authData.userRootNamespace;
    if (userRoot === '') {
      leaves.unshift('');
    }

    return leaves.uniq();
  },

  pathToLeaf(path) {
    // dots are allowed in namespace paths
    // so we need to preserve them, and replace slashes with dots
    // in order to use Ember's get function on the namespace tree
    // to pull out the correct level
    return (
      path
        // trim trailing slash
        .replace(/\/$/, '')
        // replace dots with snowman
        .replace(/\.+/g, DOT_REPLACEMENT)
        // replace slash with dots
        .replace(/\/+/g, '.')
    );
  },

  // an array that keeps track of what additional panels to render
  // on the menu stack
  // if you're in  'foo/bar/baz',
  // this array will be: ['foo', 'foo.bar', 'foo.bar.baz']
  // the template then iterates over this, and does  Ember.get(namespaceTree, leaf)
  // to render the nodes of each leaf

  // gets set as  'lastMenuLeaves' in the ember concurrency task above
  menuLeaves: computed('namespacePath', 'namespaceTree', 'pathToLeaf', function () {
    let ns = this.namespacePath;
    ns = (ns || '').replace(/^\//, '');
    let leaves = ancestorKeysForKey(ns);
    leaves.push(ns);
    leaves = this.maybeAddRoot(leaves);

    leaves = leaves.map(this.pathToLeaf);
    return leaves;
  }),

  // the nodes at the root of the namespace tree
  // these will get rendered as the bottom layer
  rootLeaves: computed('namespaceTree', function () {
    const tree = this.namespaceTree;
    const leaves = Object.keys(tree);
    return leaves;
  }),

  currentLeaf: alias('lastMenuLeaves.lastObject'),
  canAccessMultipleNamespaces: gt('accessibleNamespaces.length', 1),
  isUserRootNamespace: computed('auth.authData.userRootNamespace', 'namespacePath', function () {
    return this.auth.authData.userRootNamespace === this.namespacePath;
  }),

  namespaceDisplay: computed('namespacePath', 'accessibleNamespaces', 'accessibleNamespaces.[]', function () {
    const namespace = this.namespacePath;
    if (!namespace) {
      return 'root';
    }
    const parts = namespace?.split('/');
    return parts[parts.length - 1];
  }),

  actions: {
    refreshNamespaceList() {
      this.namespaceService.findNamespacesForUser.perform();
    },
  },
});
