/**
 * An {@link AdapterHost} implementation that forwards a player adapter's outward
 * lifecycle events into the Rust `PlayerController` via the `player_report_*`
 * commands. The controller re-emits the same `PlayerStatusEvent`s the native
 * backends emit, so the frontend `player` store is fed from ONE pipeline
 * (playerEvents.ts) in both native and HTML5 modes — keeping Rust the single
 * source of truth.
 *
 * This is the sole place that talks to the report commands; adapters depend only
 * on the {@link AdapterHost} interface, never on `commands` directly, which keeps
 * them unit-testable with a mock host.
 *
 * TRACES: UR-003, UR-005 | DR-001, DR-028
 */

import { commands } from "$lib/api/bindings";
import type { AdapterHost } from "./types";

const POSITION_REPORT_INTERVAL_MS = 250;

/** Report options that let a caller bypass throttling for discrete events. */
export interface ReportPositionOptions {
  force?: boolean;
}

/**
 * Low-level report helpers, exported so the legacy `$lib/player/html5Adapter`
 * shim can keep its function-style API while there are still direct callers.
 * Prefer {@link createRustReportHost} for new adapter code.
 */
let lastPositionReport = 0;

export async function reportState(
  state: "playing" | "paused" | "loading" | "stopped" | "idle",
  mediaId: string | null
): Promise<void> {
  try {
    await commands.playerReportState(state, mediaId);
  } catch (err) {
    console.warn("[rustReportHost] Failed to report state:", err);
  }
}

export async function reportPosition(
  position: number,
  duration: number,
  { force = false }: ReportPositionOptions = {}
): Promise<void> {
  const now = Date.now();
  if (!force && now - lastPositionReport < POSITION_REPORT_INTERVAL_MS) {
    return;
  }
  lastPositionReport = now;
  try {
    await commands.playerReportPosition(position, Number.isFinite(duration) ? duration : 0);
  } catch (err) {
    console.warn("[rustReportHost] Failed to report position:", err);
  }
}

export async function reportMediaLoaded(duration: number): Promise<void> {
  try {
    await commands.playerReportMediaLoaded(Number.isFinite(duration) ? duration : 0);
  } catch (err) {
    console.warn("[rustReportHost] Failed to report media loaded:", err);
  }
}

export function resetReporting(): void {
  lastPositionReport = 0;
}

/**
 * Build an {@link AdapterHost} bound to a specific media id that forwards adapter
 * events to Rust. `onStreamUrlChanged`, `onBuffering`, and `onReady` are wired by
 * the owning view (they affect the `<video src>` / spinner), so this host accepts
 * optional view callbacks and defaults them to no-ops.
 */
export function createRustReportHost(
  mediaId: string,
  view: Partial<Pick<AdapterHost, "onStreamUrlChanged" | "onBuffering" | "onReady" | "onEnded" | "onError">> = {}
): AdapterHost {
  return {
    onState: (state) => void reportState(state, mediaId),
    onPosition: (position, duration) => void reportPosition(position, duration),
    onMediaLoaded: (duration) => void reportMediaLoaded(duration),
    onEnded: view.onEnded ?? (() => {}),
    onError: view.onError ?? ((message) => console.warn("[rustReportHost] adapter error:", message)),
    onStreamUrlChanged: view.onStreamUrlChanged ?? (() => {}),
    onBuffering: view.onBuffering ?? (() => {}),
    onReady: view.onReady ?? (() => {}),
  };
}
