/// Pure helpers for the setup wizard. Anything that doesn't read or mutate
/// the wizard state lives here so setup.ts can focus on flow and rendering.
///
/// `updateStrengthUi` deliberately stays in setup.ts because it walks the
/// live `state` object — keeping it here would force every caller to pass
/// the entire wizard state through.

export interface Strength {
  score: number;
  guessesLog10: number;
}

export function escapeHtml(s: string): string {
  return s
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;');
}

/// Call the SW to score a passphrase with zxcvbn. Returns score in [0, 4]
/// and guesses_log10, or -1 on both if the round-trip failed.
export function ratePassphrase(passphrase: string): Promise<Strength> {
  return new Promise((resolve) => {
    try {
      chrome.runtime.sendMessage(
        { type: 'rate_passphrase', passphrase },
        (response: { ok: boolean; data?: { score: number; guesses_log10: number }; error?: string }) => {
          if (chrome.runtime.lastError) {
            // eslint-disable-next-line no-console
            console.warn('[relicario setup] rate_passphrase lastError:', chrome.runtime.lastError);
            resolve({ score: -1, guessesLog10: -1 }); return;
          }
          if (!response?.ok) {
            // eslint-disable-next-line no-console
            console.warn('[relicario setup] rate_passphrase rejected by SW:', response);
            resolve({ score: -1, guessesLog10: -1 }); return;
          }
          resolve({
            score: response.data?.score ?? -1,
            guessesLog10: response.data?.guesses_log10 ?? -1,
          });
        },
      );
    } catch (err) {
      // eslint-disable-next-line no-console
      console.warn('[relicario setup] rate_passphrase threw:', err);
      resolve({ score: -1, guessesLog10: -1 });
    }
  });
}

/// 150ms debounce around the rate_passphrase call so we don't hammer the SW
/// on every keystroke. The last invocation wins.
let rateDebounceTimer: ReturnType<typeof setTimeout> | null = null;
export function scheduleRate(passphrase: string, onResult: (s: Strength) => void): void {
  if (rateDebounceTimer !== null) clearTimeout(rateDebounceTimer);
  rateDebounceTimer = setTimeout(async () => {
    rateDebounceTimer = null;
    if (!passphrase) { onResult({ score: -1, guessesLog10: -1 }); return; }
    onResult(await ratePassphrase(passphrase));
  }, 150);
}

export const STRENGTH_LABELS: Record<number, { text: string; cls: string }> = {
  0: { text: 'very weak', cls: 's-very-weak' },
  1: { text: 'weak',      cls: 's-weak' },
  2: { text: 'fair',      cls: 's-fair' },
  3: { text: 'good',      cls: 's-good' },
  4: { text: 'strong',    cls: 's-strong' },
};

/// Render the entropy readout as "~10^N guesses to crack" or a friendlier
/// shorthand for large values. Returns empty string when no data.
export function entropyText(guessesLog10: number): string {
  if (guessesLog10 < 0) return '';
  const rounded = Math.round(guessesLog10);
  if (rounded < 6)  return `~10^${rounded} guesses — trivially crackable`;
  if (rounded < 9)  return `~10^${rounded} guesses — minutes on a single GPU`;
  if (rounded < 12) return `~10^${rounded} guesses — hours to days on a GPU`;
  if (rounded < 15) return `~10^${rounded} guesses — years on consumer hardware`;
  if (rounded < 20) return `~10^${rounded} guesses — beyond consumer-hardware reach`;
  return `~10^${rounded} guesses — effectively uncrackable`;
}
