/**
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ReplaceInFileConfig, replaceInFile } from "replace-in-file";
import { isEqual, template } from "lodash";

import { Context } from "semantic-release";
import diffDefault from "jest-diff";

/**
 * Replacement is simlar to the interface used by https://www.npmjs.com/package/replace-in-file
 * with the difference being the single string for `to` and `from`.
 */
export interface Replacement {
  /**
   * files to search for replacements
   */
  files: string[];
  /**
   * The RegExp pattern to use to match.
   *
   * Uses `String.replace(new RegExp(s, 'g'), to)` for implementation.
   */
  from: string;
  /**
   * The replacement value using a template of variables.
   *
   * `__VERSION__ = "${nextRelease.version}"`
   *
   * The context object is used to render the template. Additional values
   * can be found at: https://semantic-release.gitbook.io/semantic-release/developer-guide/js-api#result
   *
   * For advanced replacement (NOTE: only for use with `release.config.js` file version), pass in a function to replace non-standard variables
   * ```
   * {
   *    from: `__VERSION__ = 11`, // eslint-disable-line
   *    to: (matched) => `__VERSION: ${parseInt(matched.split('=')[1].trim()) + 1}`, // eslint-disable-line
   *  },
   * ```
   */
  to: string | ((a: string) => string);
  ignore?: string[];
  allowEmptyPaths?: boolean;
  countMatches?: boolean;
  disableGlobs?: boolean;
  encoding?: string;
  dry?: boolean;
  /**
   * The results array can be passed to ensure that the expected replacements
   * have been made, and if not, throw and exception with the diff.
   */
  results?: {
    file: string;
    hasChanged: boolean;
    numMatches?: number;
    numReplacements?: number;
  }[];
}
/**
 * PluginConfig is used to provide multiple replacement.
 *
 * ```
 * [
 *   "@google/semantic-release-replace-plugin",
 *   {
 *     "replacements": [
 *       {
 *         "files": ["foo/__init__.py"],
 *         "from": "__VERSION__ = \".*\"",
 *         "to": "__VERSION__ = \"${nextRelease.version}\"",
 *         "results": [
 *           {
 *             "file": "foo/__init__.py",
 *             "hasChanged": true,
 *             "numMatches": 1,
 *             "numReplacements": 1
 *           }
 *         ],
 *         "countMatches": true
 *       }
 *     ]
 *   }
 * ]
 * ```
 */
export interface PluginConfig {
  /** An array of replacements to be made. */
  replacements: Replacement[];
}

export async function prepare(
  PluginConfig: PluginConfig,
  context: Context
): Promise<void> {
  for (const replacement of PluginConfig.replacements) {
    let { results } = replacement;

    delete replacement.results;

    const replaceInFileConfig = replacement as ReplaceInFileConfig;

    replaceInFileConfig.to =
      typeof replacement.to === "function"
        ? replacement.to
        : template(replacement.to)({ ...context });
    replaceInFileConfig.from = new RegExp(replacement.from, "gm");

    let actual = await replaceInFile(replaceInFileConfig);

    if (results) {
      results = results.sort();
      actual = actual.sort();

      if (!isEqual(actual.sort(), results.sort())) {
        throw new Error(
          `Expected match not found!\n${diffDefault(actual, results)}`
        );
      }
    }
  }
}