'use strict';

/*!
 * ignore
 */

const get = require('../get');
const utils = require('../../utils');

module.exports = applyTimestampsToUpdate;

/*!
 * ignore
 */

function applyTimestampsToUpdate(now, createdAt, updatedAt, currentUpdate, options, isReplace) {
  const updates = currentUpdate;
  let _updates = updates;
  const timestamps = get(options, 'timestamps', true);

  // Support skipping timestamps at the query level, see gh-6980
  if (!timestamps || updates == null) {
    return currentUpdate;
  }

  const skipCreatedAt = timestamps?.createdAt === false;
  const skipUpdatedAt = timestamps?.updatedAt === false;

  if (isReplace) {
    if (currentUpdate?.$set) {
      currentUpdate = currentUpdate.$set;
      updates.$set = {};
      _updates = updates.$set;
    }
    if (!skipUpdatedAt && updatedAt && !currentUpdate[updatedAt]) {
      _updates[updatedAt] = now;
    }
    if (!skipCreatedAt && createdAt && !currentUpdate[createdAt]) {
      _updates[createdAt] = now;
    }
    return updates;
  }
  currentUpdate = currentUpdate || {};

  if (Array.isArray(updates)) {
    // Update with aggregation pipeline
    if (updatedAt == null) {
      return updates;
    }
    updates.push({ $set: { [updatedAt]: now } });
    return updates;
  }
  updates.$set = updates.$set || {};
  if (!skipUpdatedAt && updatedAt &&
      !currentUpdate.$currentDate?.[updatedAt]) {
    let timestampSet = false;
    if (updatedAt.indexOf('.') !== -1) {
      const pieces = updatedAt.split('.');
      for (let i = 1; i < pieces.length; ++i) {
        const remnant = pieces.slice(-i).join('.');
        const start = pieces.slice(0, -i).join('.');
        if (currentUpdate[start] != null) {
          currentUpdate[start][remnant] = now;
          timestampSet = true;
          break;
        } else if (currentUpdate.$set?.[start]) {
          currentUpdate.$set[start][remnant] = now;
          timestampSet = true;
          break;
        }
      }
    }

    if (!timestampSet) {
      updates.$set[updatedAt] = now;
    }

    if (Object.hasOwn(updates, updatedAt)) {
      delete updates[updatedAt];
    }
  }

  if (!skipCreatedAt && createdAt) {
    const overwriteImmutable = get(options, 'overwriteImmutable', false);
    const hasUserCreatedAt = currentUpdate[createdAt] != null
      || currentUpdate.$set?.[createdAt] != null
      || currentUpdate.$setOnInsert?.[createdAt] != null;

    // If overwriteImmutable is true and user provided createdAt, keep their value
    if (overwriteImmutable && hasUserCreatedAt) {
      // Move createdAt from top-level to $set if needed
      if (currentUpdate[createdAt] != null) {
        updates.$set[createdAt] = currentUpdate[createdAt];
        delete currentUpdate[createdAt];
      }
      // User's value is already in $set or $setOnInsert, nothing more to do
    } else {
      if (currentUpdate[createdAt]) {
        delete currentUpdate[createdAt];
      }
      if (currentUpdate.$set?.[createdAt]) {
        delete currentUpdate.$set[createdAt];
      }
      let timestampSet = false;
      if (createdAt.indexOf('.') !== -1) {
        const pieces = createdAt.split('.');
        for (let i = 1; i < pieces.length; ++i) {
          const remnant = pieces.slice(-i).join('.');
          const start = pieces.slice(0, -i).join('.');
          if (currentUpdate[start] != null) {
            currentUpdate[start][remnant] = now;
            timestampSet = true;
            break;
          } else if (currentUpdate.$set?.[start]) {
            currentUpdate.$set[start][remnant] = now;
            timestampSet = true;
            break;
          }
        }
      }

      if (!timestampSet) {
        updates.$setOnInsert = updates.$setOnInsert || {};
        updates.$setOnInsert[createdAt] = now;
      }
    }
  }

  if (utils.hasOwnKeys(updates.$set) === false) {
    delete updates.$set;
  }
  return updates;
}
