/* @flow */

import OptionManager from "./file/options/option-manager"
import * as messages from "babel-messages";
import Store from "../store";
import traverse from "babel-traverse";
import assign from "lodash/object/assign";
import clone from "lodash/lang/clone";

const GLOBAL_VISITOR_PROPS = ["enter", "exit"];

export default class Plugin extends Store {
  constructor(plugin: Object) {
    super();

    this.initialized = false;
    this.raw         = assign({}, plugin);

    this.manipulateOptions = this.take("manipulateOptions");
    this.post              = this.take("post");
    this.pre               = this.take("pre");
    this.visitor           = this.normaliseVisitor(clone(this.take("visitor")) || {});
  }

  initialized: boolean;
  raw: Object;
  manipulateOptions: ?Function;
  post: ?Function;
  pre: ?Function;
  visitor: Object;

  take(key) {
    let val = this.raw[key];
    delete this.raw[key];
    return val;
  }

  chain(target, key) {
    if (!target[key]) return this[key];
    if (!this[key]) return target[key];

    let fns: Array<?Function> = [target[key], this[key]];

    return function (...args) {
      let val;
      for (let fn of fns) {
        if (fn) {
          let ret = fn.apply(this, args);
          if (ret != null) val = ret;
        }
      }
      return val;
    };
  }

  maybeInherit(loc: string) {
    let inherits = this.take("inherits");
    if (!inherits) return;

    inherits = OptionManager.normalisePlugin(inherits, loc, "inherits");

    this.manipulateOptions = this.chain(inherits, "manipulateOptions");
    this.post = this.chain(inherits, "post");
    this.pre = this.chain(inherits, "pre");
    this.visitor = traverse.visitors.merge([inherits.visitor, this.visitor]);
  }

  /**
   * We lazy initialise parts of a plugin that rely on contextual information such as
   * position on disk and how it was specified.
   */

  init(loc: string, i: number) {
    if (this.initialized) return;
    this.initialized = true;

    this.maybeInherit(loc);

    for (let key in this.raw) {
      throw new Error(messages.get("pluginInvalidProperty", loc, i, key));
    }
  }

  normaliseVisitor(visitor: Object): Object {
    for (let key of GLOBAL_VISITOR_PROPS) {
      if (visitor[key]) {
        throw new Error(`Plugins aren't allowed to specify catch-all enter/exit handlers. Please target individual nodes.`);
      }
    }

    traverse.explode(visitor);
    return visitor;
  }
}

/* @flow */

import OptionManager from "./file/options/option-manager"
import * as messages from "babel-messages";
import Store from "../store";
import traverse from "babel-traverse";
import assign from "lodash/object/assign";
import clone from "lodash/lang/clone";

const GLOBAL_VISITOR_PROPS = ["enter", "exit"];

export default class Plugin extends Store {
  constructor(plugin: Object) {
    super();

    this.initialized = false;
    this.raw         = assign({}, plugin);

    this.manipulateOptions = this.take("manipulateOptions");
    this.post              = this.take("post");
    this.pre               = this.take("pre");
    this.visitor           = this.normaliseVisitor(clone(this.take("visitor")) || {});
  }

  initialized: boolean;
  raw: Object;
  manipulateOptions: ?Function;
  post: ?Function;
  pre: ?Function;
  visitor: Object;

  take(key) {
    let val = this.raw[key];
    delete this.raw[key];
    return val;
  }

  chain(target, key) {
    if (!target[key]) return this[key];
    if (!this[key]) return target[key];

    let fns: Array<?Function> = [target[key], this[key]];

    return function (...args) {
      let val;
      for (let fn of fns) {
        if (fn) {
          let ret = fn.apply(this, args);
          if (ret != null) val = ret;
        }
      }
      return val;
    };
  }

  maybeInherit(loc: string) {
    let inherits = this.take("inherits");
    if (!inherits) return;

    inherits = OptionManager.normalisePlugin(inherits, loc, "inherits");

    this.manipulateOptions = this.chain(inherits, "manipulateOptions");
    this.post = this.chain(inherits, "post");
    this.pre = this.chain(inherits, "pre");
    this.visitor = traverse.visitors.merge([inherits.visitor, this.visitor]);
  }

  /**
   * We lazy initialise parts of a plugin that rely on contextual information such as
   * position on disk and how it was specified.
   */

  init(loc: string, i: number) {
    if (this.initialized) return;
    this.initialized = true;

    this.maybeInherit(loc);

    for (let key in this.raw) {
      throw new Error(messages.get("pluginInvalidProperty", loc, i, key));
    }
  }

  normaliseVisitor(visitor: Object): Object {
    for (let key of GLOBAL_VISITOR_PROPS) {
      if (visitor[key]) {
        throw new Error("Plugins aren't allowed to specify catch-all enter/exit handlers. Please target individual nodes.");
      }
    }

    traverse.explode(visitor);
    return visitor;
  }
}

change template literal to normal string