/**
 * Copyright (c) HashiCorp, Inc.
 * SPDX-License-Identifier: BUSL-1.1
 */

import Service from '@ember/service';
import { getOwner } from '@ember/application';
import ENV from 'consul-ui/config/environment';

export function storageFor(key) {
  return function () {
    return {
      get() {
        const owner = getOwner(this);

        const localStorageService = owner.lookup('service:localStorage');

        return localStorageService.getBucket(key);
      },
    };
  };
}

/**
 * An in-memory stub of window.localStorage. Ideally this would
 * implement the [Storage](https://developer.mozilla.org/en-US/docs/Web/API/Storage)-interface that localStorage implements
 * as well.
 *
 * We use this implementation during testing to not pollute `window.localStorage`
 */
class MemoryStorage {
  constructor() {
    this.data = new Map();
  }

  getItem(key) {
    return this.data.get(key);
  }

  setItem(key, value) {
    return this.data.set(key, value.toString());
  }

  /**
   * A function to seed data into MemoryStorage. This expects an object to be
   * passed. The passed values will be persisted as a string - i.e. the values
   * passed will call their `toString()`-method before writing to storage. You need
   * to  take this into account when you want to persist complex values, like arrays
   * or objects:
   *
   * Example:
   *
   * ```js
   * const storage = new MemoryStorage();
   * storage.seed({ notices: ['notice-a', 'notice-b']});
   *
   * storage.getItem('notices') // => 'notice-a,notice-b'
   *
   * // won't work
   * storage.seed({
   *   user: { name: 'Tomster' }
   * })
   *
   * storage.getItem('user') // => '[object Object]'
   *
   * // this works
   * storage.seed({
   * .  user: JSON.stringify({name: 'Tomster'})
   * })
   *
   * storage.getItem('user') // => '{ "name": "Tomster" }'
   * ```
   * @param {object} data - the data to seed
   */
  seed(data) {
    const newData = new Map();

    const keys = Object.keys(data);

    keys.forEach((key) => {
      newData.set(key, data[key].toString());
    });

    this.data = newData;
  }
}

/**
 * There might be better ways to do this but this is good enough for now.
 * During testing we want to use MemoryStorage not window.localStorage.
 */
function initStorage() {
  if (ENV.environment === 'test') {
    return new MemoryStorage();
  } else {
    return window.localStorage;
  }
}

/**
 * A service that wraps access to local-storage. We wrap
 * local-storage to not pollute local-storage during testing.
 */
export default class LocalStorageService extends Service {
  constructor() {
    super(...arguments);

    this.storage = initStorage();
    this.buckets = new Map();
  }

  getBucket(key) {
    const bucket = this.buckets.get(key);

    if (bucket) {
      return bucket;
    } else {
      return this._setupBucket(key);
    }
  }

  _setupBucket(key) {
    const owner = getOwner(this);
    const Klass = owner.factoryFor(`storage:${key}`).class;
    const storage = new Klass(key, this.storage);

    this.buckets.set(key, storage);

    return storage;
  }
}
