/*
 * Copyright (c) 2023 Eclipse Foundation, Inc.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * Contributors:
 *   Olivier Goulet <olivier.goulet@eclipse-foundation.org>
 *
 * SPDX-License-Identifier: EPL-2.0
 */

import Mustache from 'mustache';
import type { Template, Partials as HoganPartials } from 'hogan.js';

type MustachePartials = Record<string, string>;
// Since we are using two different libraries to render Mustache, we need to
// satisfy each respective libraries' type requirements.
type Partials = HoganPartials | MustachePartials;

 /* @param {HTMLElement} element The element into which the template should be rendered.
 * @param {Object<string, (data?: any) => string>} templates An object containing named templates,
 * where the keys are template IDs and the values are functions that return HTML for the template.
 * @param {string} templateId Id of the template to render.
 * @param {any} data Data used by the template.
 * @returns {Promise<undefined>} A promise that resolves when the template has been rendered.
*/

interface RenderTemplateOptions {
  /** The element into which the template should be rendered.*/
  element: HTMLElement;
  /** An object containing template ids as keys and compiled templates as
    * value.*/
  templates: Record<string, typeof Template.prototype.render>
  /** Id of the template to render. It can be an id from `templates` or a
    * custom id. */
  templateId: string; // Using a generic string instead of a key of `templates`
                      // because custom template ids exist.
  /** Data used by the template. */
  data: any;
  /** Partials used by the template. */
  partials?: Partials | Record<string, string> 
}

/**
 * Renders the specified template into the given `element`
 * @returns A promise which resolves when the template has been rendered. 
 */
const renderTemplate = ({ element, templates, templateId, data, partials }: RenderTemplateOptions): Promise<void> => {
  return new Promise(resolve => {
    // Resolve once children of element have been mutated
    const observer = new MutationObserver(() => {
      observer.disconnect();
      resolve();
    });
    observer.observe(element, {
      childList: true,
    });

    // Determine which template to add to innerHTML
    const template = templates[templateId];

    const isUsingCustomTemplate = !template && document.getElementById(templateId) !== null;
    if (isUsingCustomTemplate) {
      const customTemplate = document.getElementById(templateId).innerHTML;
      element.innerHTML = Mustache.render(customTemplate, data, partials as MustachePartials);
      return;
    }

    if (!isUsingCustomTemplate && !template) {
      const msg = `Template ${templateId} has not been found.\n\nIf this is a custom template, make sure you spelled the template name correctly.\n\nIf using a custom template was not your intention, use one of the following templates:${Object.keys(
        templates
      ).map(tpl => `\n\t- ${tpl}`)}`;
      console.error(msg);
      
      observer.disconnect();
      resolve();
      return;
    }

    element.innerHTML = template(data, partials as HoganPartials);
  });
};

export default renderTemplate;
