/**
 * FactToDoorbellBridge — WP-08 sole formal Doorbell generation entry.
 *
 * Rule:
 *   governance admits work → WorkAdmissionPolicy allow → enqueue unique pending Doorbell
 *
 * dedupeKey shared across all entry points prevents double wake of the same work.
 * mode=off refuses formal enqueue (legacy path remains authoritative).
 */

import { randomUUID } from "node:crypto";

import type { WorkAdmissionDecision } from "./policy/WorkAdmissionPolicy.ts";
import type { DoorbellMessage } from "./contracts/DoorbellMessage.ts";
import {
  DoorbellMailbox,
  type EnqueueResult,
} from "./store/DoorbellMailbox.ts";
import { SwitchboardPaths } from "./paths/SwitchboardPaths.ts";
import type { SwitchboardDispatchMode } from "./SwitchboardDispatchMode.ts";
import { SwitchboardModeError } from "./SwitchboardDispatchMode.ts";

export interface AdmissibleWorkFact {
  workerId: string;
  summary: string;
  dedupeKey: string;
  sourceFactRefs: string[];
  priority?: DoorbellMessage["priority"];
  riskLevel?: DoorbellMessage["riskLevel"];
  topic?: string;
  taskId?: string;
  requestedBy?: string;
  /** Must be allow to create a formal doorbell. */
  admission: WorkAdmissionDecision;
  deliveryHint?: DoorbellMessage["deliveryHint"];
  extension?: Record<string, unknown>;
}

export interface FactToDoorbellBridgeOptions {
  paths: SwitchboardPaths;
  mode: SwitchboardDispatchMode;
  mailbox?: DoorbellMailbox;
  now?: () => Date;
  idFactory?: () => string;
}

export type BridgeEnqueueResult =
  | (EnqueueResult & {
      doorbellId: string;
      mode: SwitchboardDispatchMode;
      refused?: undefined;
    })
  | {
      status: "refused";
      reason: string;
      mode: SwitchboardDispatchMode;
      doorbellId?: undefined;
    };

export class FactToDoorbellBridge {
  readonly #paths: SwitchboardPaths;
  readonly #mailbox: DoorbellMailbox;
  #mode: SwitchboardDispatchMode;
  readonly #now: () => Date;
  readonly #idFactory: () => string;

  constructor(opts: FactToDoorbellBridgeOptions) {
    this.#paths = opts.paths;
    this.#mailbox = opts.mailbox ?? new DoorbellMailbox(opts.paths);
    this.#mode = opts.mode;
    this.#now = opts.now ?? (() => new Date());
    this.#idFactory = opts.idFactory ?? (() => randomUUID());
  }

  get mode(): SwitchboardDispatchMode {
    return this.#mode;
  }

  get paths(): SwitchboardPaths {
    return this.#paths;
  }

  get mailbox(): DoorbellMailbox {
    return this.#mailbox;
  }

  setMode(mode: SwitchboardDispatchMode): void {
    this.#mode = mode;
  }

  /**
   * Sole formal Doorbell writer. Same dedupeKey → duplicate (no second wake).
   */
  async enqueueFromAdmittedWork(
    fact: AdmissibleWorkFact,
  ): Promise<BridgeEnqueueResult> {
    if (this.#mode === "off") {
      return {
        status: "refused",
        reason: "mode=off — formal doorbell disabled; use legacy dispatcher",
        mode: this.#mode,
      };
    }
    if (fact.admission.decision !== "allow") {
      return {
        status: "refused",
        reason: `admission=${fact.admission.decision}:${fact.admission.reason}`,
        mode: this.#mode,
      };
    }
    const workerId = String(fact.workerId ?? "").trim();
    const dedupeKey = String(fact.dedupeKey ?? "").trim();
    if (!workerId || !dedupeKey) {
      throw new SwitchboardModeError(
        "FactToDoorbellBridge requires workerId and dedupeKey",
      );
    }
    const doorbellId = `db-${this.#idFactory()}`;
    const message: DoorbellMessage = {
      schema: "codeflowmu.doorbell/v1",
      doorbellId,
      recipientWorkerId: workerId,
      topic: fact.topic?.trim() || "work.dispatch",
      sourceFactRefs: [...fact.sourceFactRefs],
      summary: fact.summary,
      priority: fact.priority ?? "P1",
      dedupeKey,
      riskLevel: fact.riskLevel,
      requestedBy: fact.requestedBy,
      createdAt: this.#now().toISOString(),
      deliveryHint: fact.deliveryHint,
      extension: {
        ...(fact.extension ?? {}),
        taskId: fact.taskId,
        formal: true,
        bridge: "FactToDoorbellBridge",
        switchboard_mode: this.#mode,
      },
    };
    const result = await this.#mailbox.enqueue(message);
    if (result.status === "duplicate") {
      return {
        ...result,
        doorbellId: result.path.includes(doorbellId)
          ? doorbellId
          : (await this.#mailbox.findByDedupeKey(workerId, dedupeKey))?.message
              .doorbellId ?? doorbellId,
        mode: this.#mode,
      };
    }
    return { ...result, doorbellId, mode: this.#mode };
  }
}
