/**
 * The Vue application instance.
 * https://vuejs.org/guide/essentials/application.html
 * This is the root component
 */
const app = Vue.createApp({
  /**
   * to prevent conflict with jekyll we change the 
   * delimiters from {{ }} to [[ ]]
   */
  delimiters: ['[[', ']]'],
  /**
   * The data object of the root component.
   * These variables are available in the html template.
   */
  data() {
    return {
      // The rule object
      rule: global.rule || {
        ruleID: "",
        timestamp: "",
        icon: "",
        name: "",
        lineage: "",
        summary: "",
        creator: {
          name: "",
          url: "",
        },
        modules: []
      },
      legacy: false,
      rID: false,
      loading: false,
      publishing: false,
      view: (global.rule) ? true : false,
      preview: (global.rule) ? true : false,
      template: (global.rule) ? true : false,
      steinAPI: 'https://api.steinhq.com/v1/storages/5e8b937ab88d3d04ae0816a5',
      // TODO keep an array of past states for undo/redo
      history: [],
      // the data of the current module in the editor
      editor: {
        source: null,
        previousState: null,
        module: null,
      },
      // the module that is currently being dragged
      holding: false,
      // object of icons for easy access
      icons: {
        plus: '/assets/tabler_icons/circle-plus.svg',
        info: '/assets/tabler_icons/info-circle.svg',
        chevron: '/assets/tabler_icons/chevrons-down.svg',
        blank: '/assets/tabler_icons/circle-dotted.svg',
        view: '/assets/tabler_icons/eye.svg',
        edit: '/assets/tabler_icons/tool.svg',
        plus: '/assets/tabler_icons/plus.svg',
        minus: '/assets/tabler_icons/minus.svg',
        publish: '/assets/tabler_icons/cloud-upload.svg',
        download: '/assets/tabler_icons/download.svg',
        export: '/assets/tabler_icons/file-download.svg',
        upload: '/assets/tabler_icons/file-upload.svg',
        fork: '/assets/tabler_icons/git-fork.svg',
      },
      // icons available in the editor
      moduleIcons: {
        culture: '/assets/tabler_icons/palette.svg',
        decision: '/assets/tabler_icons/thumb-up.svg',
        process: '/assets/tabler_icons/rotate.svg',
        structure: '/assets/tabler_icons/building.svg',
        relationship: '/assets/tabler_icons/heart.svg',
        economic: '/assets/tabler_icons/coin.svg',
        legal: '/assets/tabler_icons/license.svg',
        map: '/assets/tabler_icons/map.svg',
        communications: '/assets/tabler_icons/microphone.svg',
      },
      // icons available for rules
      ruleIcons: {
        atom: '/assets/tabler_icons/atom.svg',
        bandage: '/assets/tabler_icons/bandage.svg',
        book: '/assets/tabler_icons/book.svg',
        box: '/assets/tabler_icons/box.svg',
        church: '/assets/tabler_icons/building-church.svg',
        store: '/assets/tabler_icons/building-store.svg',
        brush: '/assets/tabler_icons/brush.svg',
        car: '/assets/tabler_icons/car.svg',
        clock: '/assets/tabler_icons/clock.svg',
        cloud: '/assets/tabler_icons/cloud.svg',
        compass: '/assets/tabler_icons/compass.svg',
        game: '/assets/tabler_icons/device-gamepad.svg',
        flask: '/assets/tabler_icons/flask.svg',
        location: '/assets/tabler_icons/location.svg',
        moon: '/assets/tabler_icons/moon.svg',
        settings: '/assets/tabler_icons/settings.svg',
        shield: '/assets/tabler_icons/shield.svg',
        star: '/assets/tabler_icons/star.svg',
        tool: '/assets/tabler_icons/tool.svg',
        world: '/assets/tabler_icons/world.svg',
      },
      // the template for modules
      moduleTemplate: {
        moduleID: "",
        name: "",
        icon: "",
        summary: "",
        type: "",
        modules: []
      },
      // tracks the current module library tab that is open
      moduleLibrary: 'culture',
      moduleTypes: {
        // custom: {
        //   question: 'Modules that you\'ve created.',
        //   icon: '/assets/tabler_icons/circle-plus.svg',
        //   open: true
        // },
        culture: {
          question: 'What are the core missions, values, and norms?',
          icon: '/assets/tabler_icons/palette.svg',
          open: true
        },
        decision: {
          question: 'Who can make decisions and how?',
          icon: '/assets/tabler_icons/thumb-up.svg',
          open: false
        },
        process: {
          question: 'How are policies implemented, and how do they evolve?',
          icon: '/assets/tabler_icons/rotate.svg ',
          open: false
        },
        structure: {
          question: 'What kinds of roles and internal entities are there?',
          icon: '/assets/tabler_icons/building.svg',
          open: false
        }
      },
      // array of modules that have been created by the user
      // TODO: implement custom modules
      customModules: [],
      // library of existing modules
      modules: global.modules,
      exports: {
        markdown: null,
        json: null,
      }
    }
  },
  /**
   * Vue provide passes data to other components.
   * https://vuejs.org/guide/components/provide-inject.html#provide-inject
   */
  provide() {
    return {
      editor: this.editor,
      icons: this.icons,
    }
  },
  created() {
    this.addToEditor(this.newModule());

    var urlParams = new URLSearchParams(window.location.search);
    if (urlParams.has('r')) {
      this.rID = urlParams.get('r');
      this.preview = true;
      this.view = true;
      this.fetchRule(this.rID);
    }
  },
  computed: {
    /**
     * Exports the current rule into a normalized format
     * Cleans up all submodules so they are ready for export
     * @returns {Object} a Rule object
     */
    ruleExport() {
      //TODO: test if icon is an absolute url and only add the global if it is not
      /**
       * Takes a module and recursively cleans it and all submodules up
       * @param {Object} module a module object
       * @returns 
       */
      function cleanModules(module) {
        const newModule = {
          moduleID: module.moduleID,
          name: module.name,
          icon: (module.icon && !module.icon.includes('http')) ? global.url + module.icon : module.icon,
          summary: module.summary,
          type: module.type,
          modules: (module.modules) ? module.modules.map(cleanModules) : [],
        }
        return newModule;
      }

      return {
        ruleID: (this.rule.ruleID) ? this.rule.ruleID : this.slugify(this.rule.name),
        timestamp: this.timesString(),
        icon: (this.rule.icon && !this.rule.icon.includes('http')) ? global.url + this.rule.icon : this.rule.icon,
        name: this.rule.name,
        lineage: this.rule.lineage,
        summary: this.rule.summary,
        creator: {
          name: this.rule.creator.name,
          url: this.rule.creator.url,
        },
        modules: this.rule.modules.map(cleanModules)
      }
    },
    /**
     * @returns {String} the current rule as a JSON string
     */
    json() {
      return JSON.stringify(this.ruleExport, null, 2);
    },
    /**
     * Creates an array of all moduleIDs in use
     * @returns {Array} an array of all module's (in the library and custom modules) moduleID
     */
    listModuleIds() {
      const modules = [...this.rule.modules, ...this.customModules];
      return modules.map(module => module.moduleID)
    },
    /**
     * @returns {Object} the current module in the editor
     */
    moduleInEditor() {
      return this.moduleEditor[0]
    },
    /**
     * Tests if the current module in the editor has been modified
     * against the editor.previousState object
     * @returns {Boolean} true if the module in the editor has been modified
     */
    //TODO: find a more accurate solution than just turning the object into a string
    editorHasEdits() {
      return this.editor.module && Object.entries(this.editor.module).toString() !== Object.entries(this.editor.previousState).toString();
    },
  },
  methods: {
    // module methods ===========================================================

    /**
     * @returns {Object} a new module object from the moduleTemplate
     */
    newModule() {
      return { ...this.moduleTemplate }
    },
    /**
     * spreads a source module into a new module from the moduleTemplate
     * @param {Object} sourceModule the module to copy
     * @param {Boolean} includeSubmodules whether to copy submodules or not
     * @returns 
     */
    cloneModule(sourceModule,includeSubmodules) {
      let output = {
        ...this.moduleTemplate,
        ...sourceModule,
        //TODO: implement lineage pattern, same as the rule does
        source: sourceModule, // keep track of where this module came from
      };
      if (!includeSubmodules) output.modules = [];
      // delete unnecessary properties
      delete output.content;
      delete output.readonly;
      // TODO: give module a unique id
      // if (output.moduleID) output.moduleID = this.getUniqueId(output.moduleID);
      return output;
    },
    /**
     * Handles the click event to copy a module
     * @param {Event} ev the click event
     */
    handleClickCopyModule(ev) {
      const clickTarget = this.getClosestModule(ev.target);
      if (!clickTarget) return;

      this.copyModule(clickTarget.module);
    },
    /**
     * Handles the click event to edit a module
     * @param {Event} ev the click event
     */
    handleClickEditModule(ev) {
      const clickTarget = this.getClosestModule(ev.target);
      if (!clickTarget) return;

      this.editModule(clickTarget.module);
    },
    /**
     * Copies a module to the editor
     * @param {Object} module the module to copy
     */
    copyModule(module) {
      this.copyToEditor(module);
    },
    /**
     * moves a module to the editor
     * @param {Object} module the module to edit
     */
    editModule(module) {
      this.addToEditor(module);
    },
    /**
     * add a module to another module (or to the rule by default) as a submodule
     * @param {Object} module to add
     * @param {Object} target a module or the rule Object where the module should be added as a submodule 
     */
    addModule(module,target = this.rule) {
      target.modules.push(module);
    },
    /**
     * remove a module from another module (or from the rule)
     * recursively moves through all submodules in the target
     * removes ONLY the first instance of the module
     * @param {Object} module the module to remove from target
     * @param {Object} target the module or rule to remove the module from (defaults to rule)
     */
    removeModule(module, target = this.rule) {
      if (! this.moduleContains(module, target)) return; // if the module is not in the target, do nothing
      // 
      target.modules.forEach((m, idx) => {
        if (m === module) {
          target.modules.splice(idx, 1);
          return;
        } else {
          this.removeModule(module, m);
        }
      });
    },
    /**
     * Deletes custom module from the customModules array and clears the editor
     * @param {Object} module the module to be deleted
     */
    deleteModule(module) {
      this.removeCustomModule(module);
      // TODO: only clear the editor if the module is present in the editor
      this.clearEditor();
    },
    /**
     * Handles the start drag event for a module
     * @param {Event} ev the drag event
     */
    startDragModule(ev) {
      const targetModule = this.getClosestModule(ev.target);
      if (!targetModule) return;
      const module = targetModule.module;

      ev.dataTransfer.setDragImage(targetModule, ev.offsetX, ev.offsetY);
      
      this.holding = {module};
    },
    /**
     * Handles the start drag event for a module 
     * when the module is being rearranged within the rule
     * @param {Event} ev the drag event
     */
    rearrangeModule(ev) {
      const targetModule = this.getClosestModule(ev.target);
      if (!targetModule) return;
      const source = this.getClosestModule(targetModule.parentNode).module;
      const module = targetModule.module;

      ev.dataTransfer.setDragImage(targetModule, ev.offsetX, ev.offsetY);

      this.holding = {
        module,
        source,
      };
    },
    /**
     * Handles the dragend event for a module
     */
    endDragModule() {
      this.holding = false;
    },
    /**
     * Recursively searches modules and their submodules for a module
     * @param {Object} needle the module to search for
     * @param {Object} haystack the module to search in (defaults to rule)
     * @returns {Boolean} true if the module is in the haystack
     */
    // TODO: return the module location in the haystack (Maybe?)
    moduleContains(needle, haystack = this.rule) {
      if (! haystack.modules ) return false; // does the haystack even have modules?
      if (haystack.modules.includes(needle)) return true; // is the needle in the haystack?
      return haystack.modules.some(submodule => this.moduleContains(needle, submodule)); // is the needle in any of the submodules?
    },

    // rule methods ===========================================================

    /**
     * Handles the drop event for a module
     * adds the module to the closest submodule or the rule depending on what it is dropped onto
     * then adds the module to the editor
     * @param {Event} ev the drop event
     */
    dropOnRule(ev) {
      //TODO browser drag objects that hover over drop zone are showing a 'add' icon
      const landingNode = this.getClosestModule(ev.target);
      if (!this.holding.module || !landingNode) return; // if there is no module to drop or no landing node, do nothing

      const landingModule = landingNode.module; // module is set with the v-bind prop binding
      const holdingModule = this.holding.module;
      if (holdingModule === landingModule) return; // if the module is the same, do nothing

      // if the module being dropped is readyonly clone it, otherwise use the original
      const readonly = holdingModule.readonly;
      const module = (readonly) ? this.cloneModule(holdingModule) : holdingModule;

      if (this.holding.source) {
        // if the module has a source, remove it from that source
        this.removeModule(holdingModule, this.holding.source);
      }
      
      this.addModule(module, landingModule);
      this.editModule(module);
      this.endDragModule();
    },
    fetchRule(id) {
      this.loading = true;

      // handle legacy links
      // TODO: handle this at a global level
      let redirect = {
        'benevolent_dictator': 'benevolent-dictator',
        circles: 'circles',
        consensus: 'consensus',
        'do-ocracy': 'do-ocracy',
        'elected_board': 'elected-board',
        jury: 'jury',
        petition: 'petition',
        'self-appointed_board': 'self-appointed-board',
      }

      // if the rule is a legacy link, redirect
      if (redirect[id]) {
        location.href = `/templates/${redirect[id]}`;
        return;
      }


      const store = new SteinStore(
        this.steinAPI
      );

      (async () => {
        var rule = [];
        // read values from all sheets
        await store.read('rules', { search: { ruleID: id } }).then(data => {
          // test if there's anything in data
          if (data.length > 0) {
            rule = data[0];
          }
          console.log(rule);
        });

        // no rule found, exit
        // TODO: inform the user that the rule was not found
        if (!rule) {
          this.loading = false;
          return;
        }

        // if this is a legacy (pre-v3) set it as such
        if (rule.version < 3) {
          this.loading = false;
          this.legacy = true;
          this.rule = rule;

          Vue.nextTick(() => {
            if (rule.version == 2) displayBuilderHTML();
          });

          return;
        }

        this.rule = {
          ruleID: rule.ruleID,
          timestamp: rule.timestamp,
          icon: rule.icon,
          name: rule.name,
          lineage: rule.lineage,
          summary: rule.summary,
          creator: {
            name: rule.creatorName,
            url: rule.creatorUrl,
          },
          modules: (rule.modules) ? JSON.parse(rule.modules) : []
        }

        this.loading = false;
        
      })();
    },

    // editor methods =========================================================

    /**
     * Adds a module to the editor
     * @param {Object} module the module to add to the editor
     */
    addToEditor(module) {
      this.preventEditorLoss();
      this.setEditorSource(module);
      this.setEditorPreviousState(module);
      this.editor.module = module;
    },
    /**
     * Copies a module to the editor
     * @param {Object} module the module to copy to the editor
     */
    copyToEditor(module) {
      const moduleCopy = this.cloneModule(module);
      this.preventEditorLoss();
      this.setEditorSource(module);
      this.setEditorPreviousState(moduleCopy);
      this.editor.module = moduleCopy;
    },
    /**
     * Takes a module and clones it into the editor.previousState
     * @param {Object} module the module to add to the previous state
     */
    setEditorPreviousState(module) {
      this.editor.previousState = { ...module };
    },
    /**
     * Sets the editor.source to the module
     * @param {Object} module the module to set the editor source to
     */
    setEditorSource(module) {
      this.editor.source = module;
    },
    /**
     * Checks if the editor has edits and that the current module in the editor is not present in the rule
     * If the module in the editor would be lost, confirm with the user
     * then adds the module to the customModules array
     */
    preventEditorLoss() {
      // if the editor has changes and the module isn't in the rule, save it to the custom modules
      if (this.editorHasEdits && !this.moduleContains(this.editor.module)) {
        this.confirm('You have unsaved changes. Are you sure you want to discard them?')
        this.addCustomModule(this.editor.module);
      }
    },
    /**
     * Handles the click event for adding a module from the editor to the rule
     */
    clickAddModule() {
      const module = this.editor.module;
      this.addModule(module);
      this.addToEditor(module);
    },
    /**
     * Handles the click event for removing a module in the editor from the rule
     */
    clickRemoveModule() {
      const module = this.editor.module;
      this.removeModule(module);
    },
    /**
     * Clears the editor
     */
    clearEditor() {
      this.preventEditorLoss();
      this.editor.module = null;
      this.editor.previousState = null;
    },
    /**
     * Saves the module in the editor to customModules array
     */
    saveEditor() {
      this.addCustomModule(this.editor.module);
      this.setEditorPreviousState(this.editor.module);
    },
    
    // custom module methods ==================================================

    /**
     * Adds a module to the customModules array
     * @param {Object} module the module to add to the customModules array
     */
    addCustomModule(module) {
      // if module is not in customModules, add it
      if (!this.customModules.includes(module)) {
        this.customModules.unshift(module);
      }
    },
    /**
     * Creates a new module, set's a default name, and adds it to the editor
     */
    newCustomModule() {
      const module = this.newModule();
      module.name = 'New Module';
      this.addToEditor(module);
    },
    /**
     * Removes a module from the customModules array
     * @param {Object} module the module to remove from the customModules array
     */
    removeCustomModule(module) {
      this.confirm("are you sure you want to remove this custom module?");
      const index = this.customModules.indexOf(module);
      this.customModules.splice(index, 1);
    },
    /**
     * Handles confirmation messages for users
     * @param {String} msg the message to display in the confirm dialog
     */
    // TODO: add a confirm dialog and return boolean based on user input
    confirm(msg) {
      console.log(msg);
    },

    // export and download methods =============================================

    /**
     * Handles click event for publishing the rule
     */
    handleClickPublish() {
      // Confirm user knows what they're getting into
      if (!confirm("Publish to the public Library?")) return;

      if ( !this.rule.name ) {
        alert("Please enter a name for this rule.");
        return;
      }

      if ( this.rule.modules.length < 1 ) {
        alert("Please add at least one module to this rule.");
        return;
      }

      this.publishing = true;

      const rule = this.ruleExport;
      const ruleID = new Date().getTime(); // TODO: allow for custom named IDs, check for uniqueness

      // add to database
      const store = new SteinStore(
        this.steinAPI
      );

      store.append('rules', [{
        ruleID: ruleID,
        timestamp: rule.timestamp,
        icon: rule.icon,
        name: rule.name,
        lineage: rule.lineage,
        summary: rule.summary,
        modules: this.jsonify(rule.modules),
        creatorName: rule.creator.name,
        creatorUrl: rule.creator.url,
        version: 3
      }]).then(data => {
        this.publishing = false;
        window.open("/create/?r=" + ruleID, "_self", false);
      });
    },
    /**
     * Handles the click event for downloading the rule as a Markdown file
     * Creates a YAML string of the rule
     * Then adds it to the bottom of a markdown file
     * created from the #rule-export html element
     */
    handleClickDownload() {
      const yaml = jsyaml.dump(this.ruleExport);
      const turndown = new TurndownService();
      const html = document.getElementById('rule-export');
      if (!html) return;

      const markdown = turndown.turndown(html);
      const output = markdown + '\n\n----\n```yaml\n' + yaml + '\n```';

      this.saveFile(`${this.ruleExport.ruleID}.md`, output, 'text/markdown');
    },
    /**
     * Handles the click event for downloading the rule as a JSON file
     */
    handleClickExport() {
      const output = this.json;
      this.saveFile(`${this.ruleExport.ruleID}.json`, output, 'application/json');
    },
    /**
     * IE10+ Firefox, and Chrome method for saving a file
     * https://stackoverflow.com/a/33542499
     * @param {String} filename name of the file to save
     * @param {String} data data to save into a file
     * @param {String} type MIME type of the file 
     */
    saveFile(filename, data, type) {
      const blob = new Blob([data], { type: type });
      if (window.navigator.msSaveOrOpenBlob) {
        window.navigator.msSaveBlob(blob, filename);
      }
      else {
        const elem = window.document.createElement('a');
        elem.href = window.URL.createObjectURL(blob);
        elem.download = filename;
        document.body.appendChild(elem);
        elem.click();
        document.body.removeChild(elem);
      }
    },
    /**
     * Handles the click event for importing a rule from a JSON file
     */
    handleClickImport() {

    },

    // utility methods ===========================================================

    /**
     * Takes an html element and finds the closest node (inclusive) that has a data-module-id attribute
     * @param {Node} el the html element to check
     * @returns {Node} the closest node with a data-module-id attribute
     */
    getClosestModule(el) {
      const parent = el.closest('[data-module-id]');
      if (!parent) return false;
      if (!parent.module) return false;
      return parent;
    },
    /**
     * Handles the click event for activating the rule preview
     */
    clickPreview() {
      if(this.template) this.rule.icon = ''; // TODO: find a less hacky way to reset template icons
      this.view = false;
      this.preview = !this.preview;
    },
    /**
     * Filters module library based on the search term
     * @param {String} type the name of the type to filter
     * @returns {Array} the filtered module library
     */
    getModulesByType(type) {
      return this.modules.filter(module => module.type === type)
    },
    /**
     * Slugifies a string
     * https://gist.github.com/codeguy/6684588 (one of the comments)
     * @param {String} string the string to slugify
     * @returns 
     */
    slugify(string) {
      const separator = '-';
      return string
        .toString()
        .normalize('NFD')                   // split an accented letter in the base letter and the accent
        .replace(/[\u0300-\u036f]/g, '')    // remove all previously split accents
        .toLowerCase()
        .replace(/[^a-z0-9 -]/g, '')        // remove all chars not letters, numbers and spaces (to be replaced)
        .trim()
        .replace(/\s+/g, separator);
    },
    /**
     * Creates a human readable timestamp string
     * @param {String} date optional date to format
     * @returns {String} human readable date '2022.4.12 14:44:56 UTC'
     */
    timesString(date) {
      let now = new Date(date);
      if (isNaN(now)) now = new Date();
      return now.getUTCFullYear() + '.' + (now.getUTCMonth() + 1) + '.' + now.getUTCDate()
        + ' ' + now.getUTCHours() + ":" + now.getUTCMinutes() + ":" + now.getUTCSeconds()
        + ' UTC';
    },
    /**
     * stringify an Object
     * @param {Object} obj
     * @returns JSON string
     */
    jsonify(obj) {
      return JSON.stringify(obj, null, 2);
    },
    /**
     * Takes a moduleID and checks if that moduleID is in use
     * if so, returns the moduleID with a number appended to it
     * @param {String} test the moduleID to test
     * @returns {String} the moduleID, or if in use, with a number appended to it
     */
    getUniqueId(test) {
      let id = test;
      let i = 0;
      while (this.listModuleIds.includes(id)) {
        i++;
        id = `${test}-${i}`;
      }
      return id
    },
  },
});

/**
 * The Module Vue Component
 */
app.component('module', {
  delimiters: ['[[', ']]'],
  inject: ['editor'],
  props: {
    module: {
      type: Object,
      required: true,
    },
    inEditor: {
      type: Boolean,
      default: false,
    },
    hideSubmodules: {
      type: Boolean,
      default: false,
    }
  },
  data() {
    return {
      defaultIcon: '/assets/tabler_icons/circle-dotted.svg',
      mouseOver: false,
      dragOver: false
    }
  },
  computed: {
    icon() {
      return this.module.icon ? this.module.icon : this.defaultIcon;
    },
    moduleClass() {
      return {
        'in-editor': this.editor.source == this.module,
        'mouse-over': this.mouseOver,
        // TODO: when dragging over the icon the drag-over class disappears
        'drag-over': this.dragOver
      }
    }
  },
  template: `
<div 
  class="module"
  :class="moduleClass"
  .module="module"
  @mouseover.stop="this.mouseOver = true"
  @mouseout="this.mouseOver = false"
  @dragenter.self="this.dragOver = true"
  @dragleave.self="this.dragOver = false"
  @drop.self="this.dragOver = false"
  :data-module-id="module.moduleID"
>
  <div class="module__icon-wrapper">
    <span class="module__grain"><img src="/assets/tabler_icons/grain.svg"></span>
    <span class="module__icon"><img :src="icon"></span>
  </div>
  [[module.name]]
  <div class="submodules" v-if="!hideSubmodules && module.modules && module.modules.length">
      <module v-for="submodule in module.modules" :module="submodule" draggable="true"></module>
  </div>
</div>
`
})

/**
 * A non-interactive Module Vue Component
 * Used for displaying the module
 */
app.component('moduleDisplay', {
  delimiters: ['[[', ']]'],
  props: {
    module: {
      type: Object,
      required: true,
    }
  },
  data() {
    return {
      defaultIcon: '/assets/tabler_icons/circle-dotted.svg'
    }
  },
  computed: {
    icon() {
      return this.module.icon ? this.module.icon : this.defaultIcon;
    }
  },
  template: `
<div 
  class="module"
  .module="module"
>
  <div class="module__icon-wrapper">
    <span class="module__icon"><img :src="icon"></span>
  </div>
  [[module.name]]
  <div class="submodules" v-if="module.modules && module.modules.length">
      <module-display v-for="submodule in module.modules" :module="submodule"></module-display>
  </div>
</div>
`
})

/**
 * The Module list Vue Component
 */
app.component('moduleList', {
  delimiters: ['[[', ']]'],
  props: {
    module: {
      type: Object,
      required: true,
    },
    hideIcon: {
      type: Boolean,
      default: false,
    }
  },
  data() {
    return {
      defaultIcon: '/assets/tabler_icons/circle-dotted.svg'
    }
  },
  computed: {
    icon() {
      return this.module.icon ? this.module.icon : this.defaultIcon;
    }
  },
  template: `
<li class="module-list-item">
  <span class="module__icon" v-if="!hideIcon"><img :src="icon"> </span><strong>[[module.name]]</strong>: [[module.summary]]
  <ul class="submodules" v-if="module.modules && module.modules.length">
      <module-list v-for="submodule in module.modules" :module="submodule" :hide-icon="hideIcon"></module-list>
  </ul>
</li>
`
})

/**
 * A simple button Vue Component
 */
app.component('vueButton', {
  delimiters: ['[[', ']]'],
  props: {
    icon: {
      type: String,
      required: false,
      default: false
    },
    loading: {
      type: Boolean,
      required: false,
      default: false
    }
  },
  computed: {
    classList() {
      return {
        'has-icon': this.icon,
        'is-loading': this.loading
      };
    },
    activeIcon() {
      return this.loading ? '/assets/tabler_icons/refresh.svg' : this.icon;
    }
  },
  template: `
<button class="btn" :class="classList"><img :src="activeIcon" v-if="icon"> <slot>Click Here</slot></button>
`
})

/**
 * A icon Vue Component
 */
app.component('icon', {
  delimiters: ['[[', ']]'],
  props: {
    icon: {
      type: String,
      required: true,
      default: '/assets/tabler_icons/circle-dotted.svg'
    }
  },
  template: `
<span class="icon"><img :src="icon"></span>
`
})

/**
 * Mounts the app to the DOM element with the id 'app'
 */
vm = app.mount('#app')

/**
 * Legacy functions for displaying old rules
 */

// Turns RuleBuilder contents into an output-ready nested array
// Returns empty array if no modules
function builderArray() {
  var modules = document.getElementById("module-input").children;
  // takes an array of children
  // returns an array with all modules in the array, recursively nested
  function iterateArray(childs) {
    var moduleArray = [];
    if (childs.length > 0) {
      for (var i = 0; i < childs.length; i++) {
        module = childs[i];
        if (module.classList[0] == "module") {
          var moduleName = module.children.item("module-name");
          var moduleData = moduleName.title;
          var moduleChilds = module.children;
          moduleArray.push(
            [stripHTML(moduleName.innerHTML),
            stripHTML(moduleData),
            iterateArray(moduleChilds)]);
        }
      }
    }
    return moduleArray;
  } // end function
  return iterateArray(modules);
}

// returns HTML version of Builder content
function displayBuilderHTML() {
  var output = "";
  var mainArray = builderArray();
  function arrayHTML(thisArray) {
    var thisOutput = "";
    if (thisArray.length > 0) {
      thisOutput += '<ul>\n';
      for (var i = 0; i < thisArray.length; i++) {
        var item = thisArray[i];
        thisOutput += '<li><strong>' + item[0] + '</strong> ';
        thisOutput += item[1] + '</li>\n';
        if (item[2].length > 0) {
          thisOutput += arrayHTML(item[2]);
        }
      }
      thisOutput += '</ul>\n';
    }
    return thisOutput
  }
  if (mainArray.length > 0) {
    output += '<div class="builder-list">\n';
    output += arrayHTML(mainArray) + '\n</div>\n';
  }
  document.getElementById("builder-field").innerHTML = output;
}

// Removes all HTML content, replacing line break tags with newlines
function stripHTML(input) {
  input = input.replace(/<br ?\/?>/ig, "\n").replace(/(<([^>]+)>)/ig, '');
  return input;
}