/* eslint-disable no-inline-comments */
// @ts-check
import { isDeepStrictEqual } from "node:util";
import { join } from "node:path/posix";
import findContentChanges from "./find-content-changes.mjs";
import {
  getFileHashSync,
  isInterestingChangeType,
  moduleIsInterestingForDiff,
} from "./helpers.mjs";

/**
 * @import { IModule, IRevisionChange, IRevisionData, ICruiseResult } from "../../types/dependency-cruiser.mjs"
 * @import { IStrictCruiseOptions } from "../../types/strict-options.mjs"
 * @import { changeType, getSHA } from "watskeburt"
 */

/**
 * @param {string} pBaseDirectory
 * @returns {(IModule) => IModule}
 */
function addCheckSumToModule(pBaseDirectory) {
  return (pModule) => {
    if (moduleIsInterestingForDiff(pModule)) {
      return {
        ...pModule,
        checksum: getFileHashSync(join(pBaseDirectory, pModule.source)),
      };
    }
    return pModule;
  };
}

/**
 * @param {IRevisionChange[]} pChanges
 * @param {IModule[]} pModules
 * @returns {IRevisionChange[]}
 */
function refreshChanges(pChanges, pModules) {
  return pChanges.filter(
    (pChange) =>
      !pModules.some(
        (pModule) =>
          pModule.source === pChange.name &&
          pModule.checksum === pChange.checksum,
      ),
  );
}

export default class ContentStrategy {
  /**
   * @param {string} pDirectory
   * @param {ICruiseResult} pCachedCruiseResult
   * @param {IStrictCruiseOptions} pCruiseOptions
   * @param {Object} pOptions
   * @param {Set<string>} pOptions.extensions
   * @param {Set<changeType>=} pOptions.interestingChangeTypes?
   * @param {string=} pOptions.baseDir
   * @param {typeof findContentChanges=} pOptions.diffListFn
   * @param {typeof getSHA=} pOptions.checksumFn
   * @returns {IRevisionData}
   */
  getRevisionData(pDirectory, pCachedCruiseResult, pCruiseOptions, pOptions) {
    const lOptions = {
      diffListFn: findContentChanges,
      baseDir: process.cwd(),
      ...pOptions,
    };
    return {
      SHA1: "unknown-in-content-cache-strategy",
      changes: /** @type {IRevisionChange[]} */ (
        lOptions.diffListFn(pDirectory, pCachedCruiseResult, {
          baseDir: lOptions.baseDir,
          extensions: lOptions.extensions,
          includeOnly: pCruiseOptions.includeOnly,
          exclude: pCruiseOptions.exclude,
        })
      ).filter(isInterestingChangeType(lOptions.interestingChangeTypes)),
    };
  }

  /**
   * @param {IRevisionData=} pExistingRevisionData
   * @param {IRevisionData=} pNewRevisionData
   * @returns {boolean}
   */
  revisionDataEqual(pExistingRevisionData, pNewRevisionData) {
    return Boolean(
      pExistingRevisionData &&
        pNewRevisionData &&
        // Even though we don't really have a SHA1, it might be the previous version
        // of the cache did, e.g. because it was rendered with the metadata cache
        // strategy. In that case the SHA1 comparison is a reliable, fast bailout.
        pExistingRevisionData.SHA1 === pNewRevisionData.SHA1 &&
        isDeepStrictEqual(
          pExistingRevisionData.changes,
          pNewRevisionData.changes,
        ),
    );
  }

  /**
   * @param {ICruiseResult} pCruiseResult
   * @param {IRevisionData=} pRevisionData
   * @returns {ICruiseResult}
   */
  prepareRevisionDataForSaving(pCruiseResult, pRevisionData) {
    const lModulesWithCheckSum = pCruiseResult.modules.map(
      addCheckSumToModule(pCruiseResult.summary.optionsUsed.baseDir || "."),
    );
    const lRevisionData = {
      ...pRevisionData,
      changes: refreshChanges(pRevisionData.changes, lModulesWithCheckSum),
    };

    return {
      ...pCruiseResult,
      modules: lModulesWithCheckSum,
      revisionData: lRevisionData,
    };
  }
}
