<template>
  <div :id="name" class="component-doc">
    <div class="demo" v-show="hasDemoValues">
      <div ref="demo-component"></div>
    </div>

    <h3>{{name}}</h3>
    <p v-if="component.extends">
      Erft over van <a :href="'#' + component.extends.name"><code>{{component.extends.name}}</code></a>
    </p>
    <p>
      <code>&lt;{{name}}<span v-for="(prop, propName) in component.props">
        <span v-if="propName === 'value'"> v-model="[prop from parent which sets prop: 'value']"</span>
        <span v-else-if="prop.default === false">&nbsp;{{camel2dash(propName)}}</span>
        <span v-else>&nbsp;:{{camel2dash(propName)}}</span></span>&gt;<span v-if="hasSlot"> ... </span>&lt;/{{name}}&gt;</code>
    </p>
    <p v-html="component.docs"></p>
    <ul>
      <li v-for="(prop, propName) in component.props">
        <code>{{camel2dash(propName)}}<span v-if="prop.type">: {{prop.type.name}}</span></code>
        <span class="badge" v-if="prop.required">required</span>
        <span class="badge" v-if="prop.validator" :title="body(prop.validator)">validator</span>
        <span v-html="prop.docs"></span>
      </li>
      <li v-for="(method, methodName) in component.methods" v-if="method.docs">
        <button class="btn btn-secondary btn-sm" v-if="method.demoArgs"
          @click="demoMethod(methodName, method.demoArgs, $event.currentTarget)">demo</button>
        <code>.{{methodName}}{{header(method)}}</code> <div v-html="method.docs"></div>
      </li>
    </ul>
    <div class="using"><code>import <strong>{{snakeCaseToCamelCase(name)}}</strong> from <strong>'q42-cms-components{{path}}'</strong>;

  components: {
      {{snakeCaseToCamelCase(name)}}
  }
    </code></div>
  </div>
</template>

<style scoped>
.component-doc {
  clear: right;
}
h3 {
  margin-top: 1em;
}
p {
  margin-bottom: 0.5em;
}
.demo {
  width: 30%;
  float: right;
  padding: 0.5em;
  margin-bottom: 0.5em;
  background: #f3f3f3;
  position: relative;
}
.badge {
  display: inline-block;
  background: #ccc;
  color: white;
  font-weight: bold;
  font-size: 0.8em;
  padding: 0.1em 0.5em;
  border-radius: 1em;
}

.using code {
  display: block;
  white-space: pre-wrap;
}
</style>

<script>
import Vue from 'vue';

export default {
  docs: `Genereert documentatie voor een Vue component.`,
  props: {
    path: {
      type: String,
      required: true,
      docs: `Pad incl. bestandsnaam van component.`
    },
    component: {
      required: true,
      docs: `Het te documenteren component.`
    }
  },
  data() {
    return {
      mountedComponent: null,
      hasDemoValues: false
    }
  },
  mounted() {
    if (this.component.options) {
      this.component = this.component.options;
    }

    const demoComponent = this.getDemoComponent();

    if (demoComponent) {
      this.mountedComponent = new demoComponent().$mount(this.$refs['demo-component']); //
    }
  },
  computed: {
    name() {
      return this.path.split('/').pop().split('.')[0];
    },
    hasSlot() {
      return this.component.template && this.component.template.includes('<slot');
    }
  },
  methods: {
    getDemoComponent() {
      // We need a deep clone of the component, to prevent changing the original props
      const component = { ...this.component };
      component.props = { ...component.props };

      // Delete caching information from Vue
      delete component._Ctor;

      // hack the demo property values into the component, using default values
      Object.keys(component.props).forEach(propName => {
        const prop = component.props[propName] = { ...component.props[propName] };
        prop.required = false;
        if (prop.demoValue !== undefined) {
          this.hasDemoValues = true;
          prop.default = prop.demoValue;
        }
      })

      if (!this.hasDemoValues) {
        // Still show component without demo-props if it has demo-methods
        for (const methodName in component.methods)
          if (component.methods[methodName].demoArgs)
            this.hasDemoValues = true;
      }

      return this.hasDemoValues ? Vue.extend(component) : null;
    },
    body(fn) {
      return fn.toString().replace(/function [^{]*\{\s*return\s*/, '').replace(/;?\s*}/, '');
    },
    header(fn) {
      return fn.toString().replace(/function /, '').replace(/\s*\{[^]*/, '');
    },
    camel2dash(propName) {
      return propName.replace(/[A-Z]/g, c => '-' + c.toLowerCase());
    },
    demoMethod(methodName, methodArgs, demoButton) {
      methodArgs = methodArgs.map(arg => arg === '##demobutton##' ? demoButton : arg);
      this.mountedComponent[methodName](...methodArgs);
    },
    snakeCaseToCamelCase(string) {
      return string.replace(/(-\w)/g, function(m){return m[1].toUpperCase();});
    }
  }
}
</script>
