export interface TotpPreviewOpts {
  sendMessage: (msg: { type: 'preview_totp_from_secret'; secret_b32: string }) =>
    Promise<{ ok: boolean; data?: { code: string; expires_at: number }; error?: string }>;
}

const VALID_B32 = /^[A-Z2-7]{16,}=*$/;

export function wireTotpPreview(form: HTMLElement, opts: TotpPreviewOpts): () => void {
  const input = form.querySelector<HTMLInputElement>('#f-totp');
  const row = form.querySelector<HTMLElement>('#totp-preview-row');
  if (!input || !row) return () => {};
  const codeEl = row.querySelector<HTMLElement>('.totp-code');
  const cdEl = row.querySelector<HTMLElement>('.totp-countdown');
  if (!codeEl || !cdEl) return () => {};

  let interval: ReturnType<typeof setInterval> | null = null;
  let lastSecret = '';

  const tick = async () => {
    const cleaned = lastSecret.toUpperCase().replace(/\s+/g, '').replace(/=+$/, '');
    if (!VALID_B32.test(cleaned)) {
      row.hidden = true;
      return;
    }
    const resp = await opts.sendMessage({ type: 'preview_totp_from_secret', secret_b32: cleaned });
    if (!resp.ok || !resp.data) {
      row.hidden = true;
      return;
    }
    row.hidden = false;
    // Format "492837" → "492 837" for legibility.
    codeEl.textContent = resp.data.code.length === 6
      ? `${resp.data.code.slice(0, 3)} ${resp.data.code.slice(3)}`
      : resp.data.code;
    const remaining = Math.max(0, resp.data.expires_at - Math.floor(Date.now() / 1000));
    cdEl.textContent = `${remaining}s`;
  };

  const onInput = () => {
    lastSecret = input.value;
    void tick();
  };
  input.addEventListener('input', onInput);
  if (interval === null) {
    interval = setInterval(() => { void tick(); }, 1000);
  }

  return () => {
    input.removeEventListener('input', onInput);
    if (interval !== null) { clearInterval(interval); interval = null; }
    row.hidden = true;
  };
}

/// Lazy-load jsqr and decode a QR from a Blob/File. Returns the decoded
/// string, or null if no QR was found.
async function defaultDecodeQrFromBlob(blob: Blob): Promise<string | null> {
  const [{ default: jsQR }] = await Promise.all([import('jsqr')]);
  const bitmap = await createImageBitmap(blob);
  const canvas = document.createElement('canvas');
  canvas.width = bitmap.width;
  canvas.height = bitmap.height;
  const ctx = canvas.getContext('2d');
  if (!ctx) return null;
  ctx.drawImage(bitmap, 0, 0);
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  const result = jsQR(imageData.data, imageData.width, imageData.height);
  return result?.data ?? null;
}

export interface TotpQrOpts {
  /// Inject a stub in tests where canvas + imports aren't available.
  decodeQrFromBlob?: (blob: Blob) => Promise<string | null>;
}

export function wireTotpQr(form: HTMLElement, opts: TotpQrOpts = {}): void {
  const btn = form.querySelector<HTMLButtonElement>('#totp-qr-btn');
  const panel = form.querySelector<HTMLElement>('#totp-qr-panel');
  const fileInput = form.querySelector<HTMLInputElement>('#totp-qr-file');
  const errEl = form.querySelector<HTMLElement>('#totp-qr-error');
  const totpInput = form.querySelector<HTMLInputElement>('#f-totp');
  if (!btn || !panel || !fileInput || !errEl || !totpInput) return;

  const decode = opts.decodeQrFromBlob ?? defaultDecodeQrFromBlob;

  btn.addEventListener('click', () => {
    panel.hidden = !panel.hidden;
    errEl.textContent = '';
  });

  const handleBlob = async (blob: Blob) => {
    errEl.textContent = '';
    let decoded: string | null;
    try {
      decoded = await decode(blob);
    } catch (e) {
      errEl.textContent = `decode failed: ${e instanceof Error ? e.message : String(e)}`;
      return;
    }
    if (!decoded) {
      errEl.textContent = 'no QR found in image';
      return;
    }
    if (!decoded.startsWith('otpauth://')) {
      errEl.textContent = 'not a TOTP URI (expected otpauth://...)';
      return;
    }
    try {
      const u = new URL(decoded);
      const secret = u.searchParams.get('secret');
      if (!secret) {
        errEl.textContent = 'TOTP URI missing secret';
        return;
      }
      totpInput.value = secret;
      totpInput.dispatchEvent(new Event('input', { bubbles: true })); // trigger preview
      panel.hidden = true;
    } catch {
      errEl.textContent = 'TOTP URI did not parse';
    }
  };

  fileInput.addEventListener('change', () => {
    const f = fileInput.files?.[0];
    if (f) void handleBlob(f);
  });

  panel.addEventListener('paste', (e) => {
    const item = Array.from((e as ClipboardEvent).clipboardData?.items ?? []).find((i) => i.type.startsWith('image/'));
    if (item) {
      const blob = item.getAsFile();
      if (blob) void handleBlob(blob);
    }
  });

  panel.addEventListener('dragover', (e) => { e.preventDefault(); });
  panel.addEventListener('drop', (e) => {
    e.preventDefault();
    const f = (e as DragEvent).dataTransfer?.files?.[0];
    if (f) void handleBlob(f);
  });
}
