import {
  EVENT_OPTION_SET,
  OBJECT_MAGIC_PROP_PREFIX,
} from './config.js';

export class Options {
  sim = undefined;
  options = undefined;
  values = {};
  undefinedObj = {[OBJECT_MAGIC_PROP_PREFIX + 'undefined']: true};
  nullObj = {[OBJECT_MAGIC_PROP_PREFIX + 'null']: true};

  getStorageKey(path) {
    return `${path}:options`;
  }

  constructor(sim, options) {
    this.sim = sim;
    this.options = options;

    // Global methods to get/set current option values
    this.sim.getOption = (path) => this.getOption(path);
    this.sim.setOption = (path, value) => this.setOption(path, value);
    this.sim.onOptionSet = (path, cb) => this.onOptionSet(path, cb);

    // Initialize values from localStorage
    for (const groupName of Object.keys(options)) {
      for (const [name, [, , defaultValue]] of Object.entries(this.options[groupName])) {
        const path = [groupName, name].join('.');
        let value = this.getFromLocalStorage(path);
        if (value === undefined) {
          value = defaultValue;
        }
        this.values[path] = value;
      }
    }
  }

  toStored(value) {
    if (value === undefined) {
      // Do we want to interpret this as removing from storage?
      // Let's just treat it as a value for now;
      // Semantically it works because when retrieved, it will return undefined,
      // which is the same result you get if the key is not set
      return JSON.stringify(this.undefinedObj);
    } else if (value === null) {
      return JSON.stringify(this.nullObj);
    }
    return JSON.stringify(value);
  }

  // value: string
  fromStored(value) {
    if (value === null) {
      return undefined;
    } else if (value === JSON.stringify(this.undefinedObj)) {
      return undefined;
    } else if (value === JSON.stringify(this.nullObj)) {
      return null;
    }
    return JSON.parse(value);
  }

  getFromLocalStorage(path) {
    const storageKey = this.getStorageKey(path);
    const value = this.fromStored(window.localStorage.getItem(storageKey));
    this.values[path] = value;
    return value;
  }

  getOption(path) {
    const [groupName, name] = path.split('.');
    const group = this.options[groupName];
    const item = group[name];
    const [,type] = item;
    const value = this.values[path];
    switch (type) {
      case 'number': return Number(value);
      case 'boolean': return value === true || value === 'true';
      default: {
        console.error({
          path, groupName, name,
          group, item,
          type, value
        });
        throw new Error('unknown option type');
      }
    }
  }

  setOption(path, value) {
    this.values[path] = value;
    const storageKey = this.getStorageKey(path);
    window.localStorage.setItem(storageKey, this.toStored(value));
    const e = new CustomEvent(EVENT_OPTION_SET, {detail: {path, value}});
    this.sim.div.dispatchEvent(e);
  }

  // cb: ({path, value}) => undefined
  onOptionSet(path, cb) {
    this.sim.div.addEventListener(EVENT_OPTION_SET, (e) => {
      if (!path || path === e.detail.path) {
        cb({ path, value: e.detail.value });
      }
    });
  }

  getSection(sectionName) {
    const section = this.options[sectionName];
    const group = {
      type: 'group',
      name: sectionName,
      title: section._title,
      items: [],
    };
    for (const name in section) {
      if (name.startsWith('_')) continue;
      const [title, type, defaultValue, opts] = section[name];
      group.items.push({
        name,
        type,
        title,
        default: defaultValue,
        ...opts
      })
    }
    return group;
  }
}
