// Text clipboard sync, both directions.
//
//   Host  -> browser: incoming Message{clipboard: {format: Text, content}}
//                     → navigator.clipboard.writeText (Async Clipboard API).
//                     Browsers gate this on a "user gesture" — silently
//                     fails the first time until the user has clicked
//                     somewhere on the page. We swallow the rejection.
//
//   Browser -> host:  global `paste` event → read clipboardData → send
//                     Message{clipboard: {format: Text, content: utf8 bytes,
//                     compress: false}}.
//
// Image / RTF / HTML formats are deferred to a follow-up; only Text in v1.
// The wire shape is documented at libs/hbb_common/protos/message.proto:
//   ClipboardFormat: Text=0 / Rtf=1 / Html=2 / Image*=21..23 / Special=31

import { hbb } from "../proto/generated.js";
import type { Session } from "../transport/session.js";

/** Track the last value we wrote so we don't echo our own paste back to the host. */
let lastWrittenText = "";

/** Receive an incoming Clipboard (single, format-typed) and apply it locally. */
export async function applyIncoming(c: hbb.IClipboard): Promise<void> {
  if (c.format !== hbb.ClipboardFormat.Text) return;
  if (!c.content) return;
  const text = new TextDecoder().decode(c.content as Uint8Array);
  if (!text) return;
  try {
    await navigator.clipboard.writeText(text);
    lastWrittenText = text;
  } catch {
    // Browsers refuse writeText() without a recent user gesture. The
    // user can press any key once to "grant" gesture state. Until then
    // we silently drop. Visible failure UX is more annoying than silent.
  }
}

/**
 * Receive an incoming MultiClipboards (newer wire format used by RustDesk
 * peers >= 1.3.0 against non-iOS controllers — see
 * /Users/sn0/Desktop/rustdesk/src/clipboard.rs:is_support_multi_clipboard).
 * Find the first Text-format entry and apply it.
 */
export async function applyIncomingMulti(mc: hbb.IMultiClipboards): Promise<void> {
  const list = mc.clipboards || [];
  for (const c of list) {
    if (c.format === hbb.ClipboardFormat.Text) {
      await applyIncoming(c);
      return;
    }
  }
}

/**
 * Read the browser's local clipboard text and send it to the host as
 * Message{clipboard: {format: Text, content: utf8 bytes}}. Used by the
 * keyboard handler on Cmd+V / Ctrl+V — `paste` events don't reliably fire
 * on `window` in Firefox without a focused input element, so we drive
 * clipboard sync from the keystroke instead.
 */
export async function pushClipboardText(session: Session): Promise<void> {
  let text = "";
  try {
    text = await navigator.clipboard.readText();
  } catch {
    // User hasn't granted clipboard permission, or no recent gesture.
    return;
  }
  if (!text) return;
  if (text === lastWrittenText) return;  // host pushed this to us already; no echo
  const content = new TextEncoder().encode(text);
  await session.send(hbb.Message.create({
    clipboard: hbb.Clipboard.create({
      compress: false,
      format: hbb.ClipboardFormat.Text,
      content,
    }),
  })).catch(() => { /* relay closed */ });
}

/** Defensive: also hook the standard `paste` event in case the browser
 *  fires it (Chrome does, Firefox often doesn't). */
export function bindClipboard(session: Session): () => void {
  const onPaste = (e: ClipboardEvent): void => {
    const text = e.clipboardData?.getData("text/plain");
    if (!text || text === lastWrittenText) return;
    const content = new TextEncoder().encode(text);
    session.send(hbb.Message.create({
      clipboard: hbb.Clipboard.create({
        compress: false,
        format: hbb.ClipboardFormat.Text,
        content,
      }),
    })).catch(() => { /* relay closed */ });
  };
  window.addEventListener("paste", onPaste);
  return () => window.removeEventListener("paste", onPaste);
}
