const { ethers } = require('hardhat');

const { MAX_UINT64 } = require('./constants');
const time = require('./time');
const { upgradeableSlot } = require('./storage');

function buildBaseRoles() {
  const roles = {
    ADMIN: {
      id: 0n,
    },
    SOME_ADMIN: {
      id: 17n,
    },
    SOME_GUARDIAN: {
      id: 35n,
    },
    SOME: {
      id: 42n,
    },
    PUBLIC: {
      id: MAX_UINT64,
    },
  };

  // Names
  Object.entries(roles).forEach(([name, role]) => (role.name = name));

  // Defaults
  for (const role of Object.keys(roles)) {
    roles[role].admin = roles.ADMIN;
    roles[role].guardian = roles.ADMIN;
  }

  // Admins
  roles.SOME.admin = roles.SOME_ADMIN;

  // Guardians
  roles.SOME.guardian = roles.SOME_GUARDIAN;

  return roles;
}

const formatAccess = access => [access[0], access[1].toString()];

const MINSETBACK = time.duration.days(5);
const EXPIRATION = time.duration.weeks(1);

const EXECUTION_ID_STORAGE_SLOT = upgradeableSlot('AccessManager', 3n);
const CONSUMING_SCHEDULE_STORAGE_SLOT = upgradeableSlot('AccessManaged', 0n);

/**
 * @requires this.{manager, caller, target, calldata}
 */
async function prepareOperation(manager, { caller, target, calldata, delay }) {
  const scheduledAt = (await time.clock.timestamp()) + 1n;
  await time.increaseTo.timestamp(scheduledAt, false); // Fix next block timestamp for predictability

  return {
    schedule: () => manager.connect(caller).schedule(target, calldata, scheduledAt + delay),
    scheduledAt,
    operationId: hashOperation(caller, target, calldata),
  };
}

const lazyGetAddress = addressable => addressable.address ?? addressable.target ?? addressable;

const hashOperation = (caller, target, data) =>
  ethers.keccak256(
    ethers.AbiCoder.defaultAbiCoder().encode(
      ['address', 'address', 'bytes'],
      [lazyGetAddress(caller), lazyGetAddress(target), data],
    ),
  );

module.exports = {
  buildBaseRoles,
  formatAccess,
  MINSETBACK,
  EXPIRATION,
  EXECUTION_ID_STORAGE_SLOT,
  CONSUMING_SCHEDULE_STORAGE_SLOT,
  prepareOperation,
  hashOperation,
};
