import { RunId } from "@trigger.dev/core/v3/isomorphic";
import type { MollifierBuffer } from "@trigger.dev/redis-worker";
import { serialiseMollifierSnapshot, type MollifierSnapshot } from "./mollifierSnapshot.server";
import type { TripDecision } from "./mollifierGate.server";

export type MollifyNotice = {
  code: "mollifier.queued";
  message: string;
  docs: string;
};

export type MollifySyntheticResult = {
  // `id` is the canonical TaskRun primary key derived from `friendlyId`
  // via `RunId.fromFriendlyId`. Downstream consumers in the trigger
  // route — notably `saveRequestIdempotency` — index the request-
  // idempotency cache by this id; without it the cache stores
  // `undefined` and Prisma's `findFirst({ where: { id: undefined } })`
  // on retry strips the predicate and returns an arbitrary TaskRun
  // (potential cross-tenant leak). Always populated.
  //
  // `spanId` is the root-span id allocated at gate-accept time and
  // stored in the snapshot. Callers like the dashboard's Test action
  // use it to build a `v3RunSpanPath` URL that auto-opens the right
  // details panel — without it, the buffered run lands on the
  // run-detail page with no span selected (parity gap with PG runs).
  run: { id: string; friendlyId: string; spanId: string };
  error: undefined;
  // The race-loser path: if accept's SETNX hit an existing
  // buffered run with the same (env, task, idempotencyKey), the
  // response echoes the winner's runId with isCached=true. The
  // mollifier-queued notice is only attached for the happy accept.
  isCached: boolean;
  notice?: MollifyNotice;
};

const NOTICE: MollifyNotice = {
  code: "mollifier.queued",
  message: "Trigger accepted into burst buffer. Consider batchTrigger for fan-outs of 100+.",
  docs: "https://trigger.dev/docs/management/tasks/batch-trigger",
};

export async function mollifyTrigger(args: {
  runFriendlyId: string;
  environmentId: string;
  organizationId: string;
  engineTriggerInput: MollifierSnapshot;
  decision: Extract<TripDecision, { divert: true }>;
  buffer: MollifierBuffer;
  // Optional idempotency context. When both are passed, accept SETNXes
  // the lookup so the buffered window participates in trigger-time
  // dedup symmetrically with PG.
  idempotencyKey?: string;
  taskIdentifier?: string;
}): Promise<MollifySyntheticResult> {
  const result = await args.buffer.accept({
    runId: args.runFriendlyId,
    envId: args.environmentId,
    orgId: args.organizationId,
    payload: serialiseMollifierSnapshot(args.engineTriggerInput),
    idempotencyKey: args.idempotencyKey,
    taskIdentifier: args.taskIdentifier,
  });

  if (result.kind === "duplicate_idempotency") {
    // Race loser. Echo the winner's runId so the SDK's response shape
    // matches PG-side idempotency cache hits. The winner's spanId isn't
    // readily available without a second buffer fetch; an empty string
    // causes `v3RunSpanPath` to omit the `?span=` param, which matches
    // current behaviour for cached PG responses.
    return {
      run: {
        id: RunId.fromFriendlyId(result.existingRunId),
        friendlyId: result.existingRunId,
        spanId: "",
      },
      error: undefined,
      isCached: true,
    };
  }

  // Both "accepted" and "duplicate_run_id" produce the same customer-
  // visible response: a buffered-trigger acknowledgement. The duplicate
  // runId case is unreachable in practice (runIds are server-generated
  // and unique) but is silently idempotent at the buffer layer either way.
  const rawSpanId = args.engineTriggerInput.spanId;
  const spanId = typeof rawSpanId === "string" ? rawSpanId : "";
  return {
    run: {
      id: RunId.fromFriendlyId(args.runFriendlyId),
      friendlyId: args.runFriendlyId,
      spanId,
    },
    error: undefined,
    isCached: false,
    notice: NOTICE,
  };
}
