/**
 * The following MIT license only applies to this file:
 *
 * Copyright 2022 Connor Logan
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

const templates = new Map<string, string>();
let alreadyLoaded = false;

/**
 * Loads template files from a directory, and reloads them whenever there's a
 * change.
 *
 * The default directory is `"templates"`, relative to the current working
 * directory of the Deno process. This should only be called once per
 * application. Templates can be rendered using the {@linkcode render} function.
 */
export async function loadTemplates(dir = "templates") {
  if (alreadyLoaded) {
    throw new Error("loadTemplates should only be called once per application");
  }
  alreadyLoaded = true;
  templates.clear();

  const load = async () => {
    let size = 0;
    for await (const entry of Deno.readDir(dir)) {
      if (entry.name) {
        const content = await Deno.readTextFile(`${dir}/${entry.name}`);
        templates.set(entry.name, content);
        size += content.length;
      }
    }
    const now = new Date();
    console.log(now, "Templates:", size, "(bytes)");
    return now;
  };

  let lastUpdate = await load();

  (async () => {
    for await (const _ of Deno.watchFs(dir)) {
      // There's often multiple FS events for every update, don't reload if the
      // last event was less than 1 second ago
      if (new Date().getTime() - lastUpdate.getTime() > 1000) {
        lastUpdate = await load();
      }
    }
  })();
}

/**
 * Renders a template that's been loaded with {@linkcode loadTemplates}.
 *
 * Inserts will be replaced everywhere in the file, à la mustache templating; if
 * the keyname of the insert is `name`, the value that gets replaced in the
 * template should be `{{ name }}`.
 */
export function render(
  template: string,
  inserts?: Record<string, string>,
): string {
  let body = templates.get(template);
  if (!body) {
    throw new Deno.errors.NotFound(`Template not found: ${template}`);
  }
  if (inserts) {
    for (const [k, v] of Object.entries(inserts)) {
      body = body.replaceAll(`{{ ${k} }}`, v);
    }
  }
  return body;
}
