/**
 * Clean-room media continuity reference.
 *
 * This specimen names the contracts that coordinate a feed source, temporary
 * transport and fullscreen viewer. It is intentionally independent of any
 * application source.
 */

export type MediaKey = {
  feedItemId: string;
  mediaIndex: number;
  assetId: string;
};

export type Rect = {
  x: number;
  y: number;
  width: number;
  height: number;
};

export type MeasuredFrame = {
  key: MediaKey;
  rect: Rect;
  version: number;
  measuredAt: number;
};

export type ContinuityPhase =
  | "idle"
  | "measuringSource"
  | "opening"
  | "preparingViewer"
  | "settled"
  | "dragging"
  | "cancelling"
  | "resolvingReturnTarget"
  | "closing"
  | "handingBack"
  | "failed";

export type PresentationOwner =
  | "source"
  | "transport"
  | "viewer"
  | "none";

export type ContinuitySession = {
  token: number;
  key: MediaKey;
  phase: ContinuityPhase;
  owner: PresentationOwner;
  sourceFrame: MeasuredFrame | null;
  returnFrame: MeasuredFrame | null;
  expectedSurfaceId: string | null;
  firstVisibleFrame: boolean;
};

export function sameMedia(a: MediaKey | null, b: MediaKey | null) {
  return (
    a?.feedItemId === b?.feedItemId &&
    a?.mediaIndex === b?.mediaIndex &&
    a?.assetId === b?.assetId
  );
}

export function mayTransferToViewer(
  session: ContinuitySession,
  event: {
    token: number;
    key: MediaKey;
    surfaceId: string;
    type: "FIRST_VISIBLE_FRAME";
  },
) {
  return (
    event.type === "FIRST_VISIBLE_FRAME" &&
    event.token === session.token &&
    sameMedia(event.key, session.key) &&
    event.surfaceId === session.expectedSurfaceId
  );
}

export type ReleaseDecision =
  | { kind: "cancel" }
  | { kind: "commit"; projectedDistance: number };

export function decideRelease(input: {
  dragY: number;
  velocityY: number;
  dismissDistance: number;
}): ReleaseDecision {
  const progress = input.dragY / input.dismissDistance;
  const projected = input.dragY + Math.max(0, input.velocityY) * 0.18;
  const commit =
    progress >= 0.32 ||
    (progress >= 0.1 &&
      (input.velocityY >= 600 ||
        projected >= input.dismissDistance * 0.48));

  return commit
    ? { kind: "commit", projectedDistance: projected }
    : { kind: "cancel" };
}

export interface SourceRegistry {
  register(
    key: MediaKey,
    handle: {
      reveal(): Promise<void>;
      measure(): Promise<MeasuredFrame | null>;
    },
  ): () => void;
  reveal(key: MediaKey): Promise<void>;
  measure(key: MediaKey): Promise<MeasuredFrame | null>;
}

export async function resolveReturnTarget(
  registry: SourceRegistry,
  session: ContinuitySession,
) {
  await registry.reveal(session.key);
  await new Promise<void>((resolve) =>
    requestAnimationFrame(() => resolve()),
  );

  const measured = await registry.measure(session.key);
  if (!measured || !sameMedia(measured.key, session.key)) return null;
  return measured;
}

/**
 * Required coordinator invariants:
 *
 * 1. Exactly one presentation owner per phase.
 * 2. Every async callback is gated by token + media key + expected surface.
 * 3. Player readiness never removes the poster.
 * 4. The poster clears only after the destination proves a visible frame.
 * 5. Cancellation preserves the same session, player and viewer mount.
 * 6. Closing resolves a current target; opening geometry is historical data.
 * 7. Playback handback and presentation handback are separate decisions.
 */
