module.exports = {
  // object that describes the step and its configuration
  description: {
    // the name of your step
    name: "Replace links",

    // short description of what it does
    description: "Replaces wikilinks with the linked content",

    // array. valid options are "Scene", "Manuscript", "Join". "Join" must be the only member if present.
    availableKinds: ["Scene"],

    // array of step options, or an empty array if step has no options
    options: [
      // a boolean option follows as another example
      {
        id: "removeH1",
        name: "Remove h1 of linked content",
        description: "If checked, the h1 headers of the linked content will be removed.",
        type: "Boolean",
        default: true,
      },
      {
        id: "removeFrontmatter",
        name: "Remove frontmatter of linked content",
        description: "If checked, the frontmatter of the linked content will be removed.",
        type: "Boolean",
        default: true,
      },
    ],
  },

  /**
    Function that is executed during compilation. It may be `async`.
    Errors encountered during execution should be thrown and will
    be handled by Longform.
    @param input If the step is of kind Scene or Join (see context),
    this will be *an array* containing elements of type:
      {
        path: string; // path to scene
        name: string; // file name of scene
        contents: string; // text contents of scene
        metadata: CachedMetadata; // Obsidian metadata of scene
        indentationLevel?: number; // The indent level (starting at zero) of the scene
      }
    where each element corresponds to a scene (and thus the step has access to all scenes at once in `input`).
    If the step is of kind Manuscript (see context), this will be of type:
      {
        // text contents of manuscript
        contents: string;
      }
    @param context The execution context of the step, including the step
    kind and option values:
      {
        kind: string; // "Scene" | "Join" | "Manuscript"
        optionValues: { [id: string]: unknown } // Map of option IDs to values
        projectPath: string; // path in vault to compiling project
        draft: Draft; // The Draft type describing your project
        app: App; // Obsidian app
      }
    @note For an example of using `context` to determine the shape of `input`, see
    https://github.com/kevboh/longform/blob/main/src/compile/steps/strip-frontmatter.ts
    @returns If of kind "Scene" or "Manuscript", the same shape as `input`
    with the appropriate changes made to `contents`. If of kind "Join",
    the same shape as a "Manuscript" step input.
  */
  compile
};

async function compile(input, context) {
  const files = await getAllFiles(context.app);
  console.info("All files: ", files);

  return Promise.all(
    input.map(async scene => ({
      ...scene,
      contents: await replaceLinksWithContent(scene.contents, scene.metadata, context, files, 0)
    })) 
  )
}

function getAllFiles(app) {
  return app.vault.getFiles();
}

async function replaceLinksWithContent(content, metadata, context, files, deep) {
  if (deep > 1) {
    return content;
  }

  console.info("Current file: ", metadata, "deep: ", deep);
  const allLinks = [...(metadata.links || []), ...(metadata.embeds || [])];

  return allLinks.reduce(async (accProm, item) => {
    const acc = await accProm;
    const [linkedFile, linkedContent] = await getLinkedContent(item.link, context.app, files);
    if (!linkedContent) {
      return acc;
    }
    const linkedMetadata = getMetadata(linkedFile, context.app);
    
    let contentArray = Array.from(linkedContent);
    contentArray = removeFrontmatter(contentArray, linkedMetadata.frontmatter, context.optionValues);
    contentArray = removeHeaders(contentArray, linkedMetadata.headings, context.optionValues)
    const cleanedContent = contentArray.filter(val => val !== null)
    .join("")
    .trim();

    const processedLinkedContent = await replaceLinksWithContent(cleanedContent, linkedMetadata, context, files, deep + 1)
    return acc.replaceAll(item.original, processedLinkedContent)
  }, Promise.resolve(content));
}

async function getLinkedContent(linkText, app, files) {
  const file = files.find(file => file.basename === linkText);
  return file
    ? [file, await app.vault.read(file)]
    : [];
}

function getMetadata(file, app) {
  return app.metadataCache.getFileCache(file)
}

function removeHeaders(content, headers, optionValues) {
  if (optionValues.removeH1 && headers) {
    return headers
      .filter(header => header.level === 1)
      .reduce((acc, header) => {
        return removeFromTextArray(acc, header.position)
      }, content)
  } else {
    return content;
  }
}

function removeFrontmatter(content, frontmatter, optionValues) {
  if (frontmatter && optionValues.removeFrontmatter) {
    return removeFromTextArray(content, frontmatter.position);
  } else {
    return content;
  }
}

function removeFromTextArray(text, position) {
  return text.map((val, i) => i >= position.start.offset && i < position.end.offset ? null : val)
}