/**
 * Guard logic for /sign/v3-launch (Vaultfire V3 launch console).
 *
 * Plain CommonJS with JSDoc types so the guards can be unit-tested by
 * `node --test` (named ESM imports interoperate) and by Jest, without a build
 * step, and imported directly by the page.
 *
 * Design rules (all fail-closed):
 *   - Exactly one wallet may operate the console: the already-evidenced public
 *     Base deployer ghostkey.base.eth. Comparison is case-insensitive.
 *   - Two operator-declared addresses are explicitly rejected for V3 authority.
 *   - Base actions require chain id 8453.
 *   - No private key, seed phrase, keystore, or signing material is ever
 *     accepted, stored, or displayed.
 *   - Signing/sending/broadcast is unavailable until BOTH a verified immutable
 *     transaction package AND an explicit approval artifact exist. Neither
 *     exists, so `canSign` is false for every track and the reason strings say
 *     exactly what is missing.
 */

/** Authorized Base deployer (public address only). */
const AUTHORIZED_BASE_DEPLOYER = '0xfA15Ee28939B222B0448261A22156070f0A7813C';
const AUTHORIZED_BASE_DEPLOYER_LABEL = 'ghostkey.base.eth';

/** Base mainnet. */
const BASE_CHAIN_ID = 8453;
const BASE_CHAIN_ID_HEX = '0x2105';

/**
 * Addresses that must never hold V3 authority, per operator disposition.
 * These are rejected even if a future allowlist change adds them.
 */
const REJECTED_V3_AUTHORITY_ADDRESSES = [
  {
    address: '0xA054f831B562e729F8D268291EBde1B2EDcFb84F',
    reason: 'operator-declared compromised wallet',
  },
  {
    address: '0x5a03C962227Dd478B5CD2B9f09E81105096154ad',
    reason: 'operator-declared audit-only burner',
  },
];

/**
 * Solana public payee that appears in V2 manifest evidence. It is not V3
 * authority and not settlement evidence.
 */
const SOLANA_V2_MANIFEST_PAYEE = '6KpXaFmaR8WPWr79qzsMBvwDbDzYFErwNoWxfwqDr8JU';

/** Canonical connection surface for Base V3 operations. */
const CANONICAL_WALLETS = ['base', 'coinbase'];
/** Accepted for connection and read-only state, but not canonical. */
const NON_CANONICAL_WALLETS = ['metamask', 'walletconnect'];

/** Truthful state vocabulary. Nothing outside this set may be rendered. */
const STATE_LABELS = {
  prepared: 'Prepared',
  blocked: 'Blocked',
  deployed: 'Deployed',
  activated: 'Activated',
};

const EVM_ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;

/**
 * @param {unknown} value
 * @returns {string|null} lowercase 0x address, or null when not a valid EVM address
 */
function normalizeAddress(value) {
  if (typeof value !== 'string') return null;
  const trimmed = value.trim();
  if (!EVM_ADDRESS_RE.test(trimmed)) return null;
  return trimmed.toLowerCase();
}

/**
 * Case-insensitive allowlist check. Fails closed for null, undefined, empty
 * strings, malformed addresses, and every address that is not the deployer.
 * @param {unknown} address
 * @returns {boolean}
 */
function isAuthorizedDeployer(address) {
  const normalized = normalizeAddress(address);
  if (!normalized) return false;
  if (isRejectedForV3Authority(normalized)) return false;
  return normalized === AUTHORIZED_BASE_DEPLOYER.toLowerCase();
}

/**
 * @param {unknown} address
 * @returns {boolean}
 */
function isRejectedForV3Authority(address) {
  const normalized = normalizeAddress(address);
  if (!normalized) return false;
  return REJECTED_V3_AUTHORITY_ADDRESSES.some((entry) => entry.address.toLowerCase() === normalized);
}

/**
 * @param {unknown} address
 * @returns {string|null} rejection reason, when the address is explicitly rejected
 */
function rejectionReason(address) {
  const normalized = normalizeAddress(address);
  if (!normalized) return null;
  const hit = REJECTED_V3_AUTHORITY_ADDRESSES.find((entry) => entry.address.toLowerCase() === normalized);
  return hit ? hit.reason : null;
}

/**
 * @param {unknown} chainId
 * @returns {boolean} true only for Base mainnet
 */
function isBaseChain(chainId) {
  if (typeof chainId === 'number') return Number.isInteger(chainId) && chainId === BASE_CHAIN_ID;
  if (typeof chainId === 'string') {
    const trimmed = chainId.trim();
    if (!/^(?:0x[0-9a-f]+|[0-9]+)$/i.test(trimmed)) return false;
    const parsed = Number(trimmed);
    return Number.isInteger(parsed) && parsed === BASE_CHAIN_ID;
  }
  return false;
}

/**
 * @param {unknown} wallet
 * @returns {boolean}
 */
function isCanonicalWallet(wallet) {
  return typeof wallet === 'string' && CANONICAL_WALLETS.includes(wallet);
}

/**
 * Access decision for the console operator.
 * @param {{address?: unknown, chainId?: unknown, activeWallet?: unknown, requiresBaseChain?: boolean}} input
 */
function evaluateOperatorAccess(input) {
  const address = input && input.address;
  const normalized = normalizeAddress(address);
  const connected = Boolean(normalized);
  const rejected = isRejectedForV3Authority(address);
  const authorized = isAuthorizedDeployer(address);
  const requiresBaseChain = input?.requiresBaseChain !== false;
  const onBaseChain = isBaseChain(input?.chainId);
  const blockers = [];

  if (!connected) {
    blockers.push(
      normalized === null && typeof address === 'string' && address.trim() !== ''
        ? 'Connected value is not a valid EVM address. Access denied.'
        : 'No wallet connected. Connect the authorized Base deployer to view operator state.',
    );
  } else if (rejected) {
    blockers.push(
      `Address ${normalized} is permanently rejected for V3 authority (${rejectionReason(address)}).`,
    );
  } else if (!authorized) {
    blockers.push(
      `Connected address is not the authorized Base deployer ${AUTHORIZED_BASE_DEPLOYER} (${AUTHORIZED_BASE_DEPLOYER_LABEL}).`,
    );
  }

  if (requiresBaseChain && !onBaseChain) {
    blockers.push(`Base actions require chain id ${BASE_CHAIN_ID}. Connected chain id: ${describeChain(input?.chainId)}.`);
  }

  return {
    connected,
    authorized,
    rejected,
    onBaseChain,
    canonicalWallet: isCanonicalWallet(input?.activeWallet),
    /** Operator view is unlocked only when authorized and on Base. */
    unlocked: authorized && (!requiresBaseChain || onBaseChain),
    blockers,
  };
}

/**
 * @param {unknown} chainId
 * @returns {string}
 */
function describeChain(chainId) {
  if (chainId === null || chainId === undefined || chainId === '') return 'none';
  if (typeof chainId === 'number') return String(chainId);
  return String(chainId);
}

/**
 * Signing availability. Requires an operator-authorized session AND both
 * release artifacts. Because neither artifact exists in the checked-in
 * evidence, this function returns `canSign: false` for the shipped dataset.
 *
 * @param {{
 *   access?: {unlocked?: boolean},
 *   transactionPackagePresent?: boolean,
 *   approvalArtifactPresent?: boolean,
 *   approvalPhraseRequired?: string|null,
 *   approvalPhraseProvided?: string|null,
 * }} input
 */
function evaluateSigningAvailability(input) {
  const missing = [];
  if (!input?.access?.unlocked) {
    missing.push('An authorized operator session on Base chain 8453');
  }
  if (input?.transactionPackagePresent !== true) {
    missing.push('A verified immutable transaction package (none has been generated)');
  }
  if (input?.approvalArtifactPresent !== true) {
    missing.push('An explicit written approval artifact (none exists)');
  }
  const phrase = input?.approvalPhraseRequired;
  if (phrase) {
    const provided = typeof input?.approvalPhraseProvided === 'string' ? input.approvalPhraseProvided : '';
    if (provided !== phrase) {
      missing.push(`The exact approval phrase "${phrase}"`);
    }
  }
  return {
    canSign: missing.length === 0,
    /** Controls are not merely disabled — they are not rendered at all. */
    controlsRendered: false,
    missing,
  };
}

/**
 * Truthful label for a track, derived only from dataset flags. Never upgrades a
 * track to deployed or activated without recorded addresses/transactions.
 *
 * @param {{state?: string, flags?: Record<string, unknown>, deployedAddresses?: unknown, deploymentTransactions?: unknown}} track
 * @returns {'prepared'|'blocked'|'deployed'|'activated'}
 */
function resolveTrackState(track) {
  const flags = track?.flags || {};
  const hasAddresses = Array.isArray(track?.deployedAddresses)
    ? track.deployedAddresses.length > 0
    : Boolean(track?.deployedAddresses);
  const hasTransactions = Array.isArray(track?.deploymentTransactions)
    ? track.deploymentTransactions.length > 0
    : Boolean(track?.deploymentTransactions);

  if (flags.activated === true && hasAddresses && hasTransactions) return 'activated';
  if (flags.broadcast === true && hasAddresses && hasTransactions) return 'deployed';
  if (track?.state === 'prepared') return 'prepared';
  return 'blocked';
}

/**
 * Any field name that must never appear in a console input. Used by the page
 * (it renders no such field) and asserted by tests against the source files.
 */
const FORBIDDEN_INPUT_TERMS = [
  'privateKey',
  'private key',
  'seedPhrase',
  'seed phrase',
  'mnemonic',
  'keystore',
  'recovery phrase',
];

/**
 * Defensive filter for pasted role-input text: refuses anything that looks like
 * signing material instead of a public address.
 * @param {string} value
 * @returns {{safe: boolean, reason: string|null}}
 */
function screenPublicRoleInput(value) {
  const text = typeof value === 'string' ? value.trim() : '';
  if (text === '') return { safe: true, reason: null };
  const lower = text.toLowerCase();
  if (FORBIDDEN_INPUT_TERMS.some((term) => lower.includes(term.toLowerCase()))) {
    return { safe: false, reason: 'Input mentions signing material. Public addresses only.' };
  }
  const words = text.split(/\s+/).filter(Boolean);
  if (words.length >= 12 && words.every((w) => /^[a-z]+$/.test(w))) {
    return { safe: false, reason: 'Input looks like a seed phrase. Public addresses only.' };
  }
  if (/^0x[0-9a-fA-F]{64}$/.test(text)) {
    return { safe: false, reason: 'Input looks like a 32-byte secret. Public addresses only.' };
  }
  if (!EVM_ADDRESS_RE.test(text)) {
    return { safe: false, reason: 'Not a valid public Base EVM address.' };
  }
  return { safe: true, reason: null };
}

module.exports = {
  AUTHORIZED_BASE_DEPLOYER,
  AUTHORIZED_BASE_DEPLOYER_LABEL,
  BASE_CHAIN_ID,
  BASE_CHAIN_ID_HEX,
  REJECTED_V3_AUTHORITY_ADDRESSES,
  SOLANA_V2_MANIFEST_PAYEE,
  CANONICAL_WALLETS,
  NON_CANONICAL_WALLETS,
  STATE_LABELS,
  normalizeAddress,
  isAuthorizedDeployer,
  isRejectedForV3Authority,
  rejectionReason,
  isBaseChain,
  isCanonicalWallet,
  evaluateOperatorAccess,
  describeChain,
  evaluateSigningAvailability,
  resolveTrackState,
  FORBIDDEN_INPUT_TERMS,
  screenPublicRoleInput,
};
