import type { AlwaysOnConfig } from "../config/parseAlwaysOnConfig.js";
import type {
  AlwaysOnChannelLease,
  AlwaysOnDiscoveryState,
  GateResult,
} from "../protocol/types.js";

export type DiscoveryGateInput = {
  projectKey: string;
  config: AlwaysOnConfig;
  state: AlwaysOnDiscoveryState;
  leases: AlwaysOnChannelLease[];
  now: Date;
  projectExists: boolean;
  lockHeld: boolean;
  sessionInFlight?: boolean;
};

/**
 * Pure gate evaluation. Order is fixed per `02-pilotdeck-always-on-rewrite-plan.md` §11;
 * first failing gate wins. Lease list is consumed only as a *reverse* signal —
 * an empty lease list never blocks a fire; presence of a busy/recent lease
 * does. Workspace single-instance is enforced by `DiscoveryFire.ensureWorkspace`,
 * not by this gate.
 */
export function evaluateAlwaysOnDiscoveryGates(input: DiscoveryGateInput): GateResult {
  const { config, state, leases, now, projectKey } = input;

  if (!config.enabled || !config.trigger.enabled) {
    return { ok: false, reason: "disabled" };
  }

  const project = config.projects[projectKey];
  if (!project || !project.enabled) {
    return { ok: false, reason: "project_disabled" };
  }

  if (!input.projectExists) {
    return { ok: false, reason: "project_missing" };
  }

  if (config.dormancy.enabled && state.dormant) {
    return { ok: false, reason: "dormant_no_signal" };
  }

  const fresh = leases.filter((lease) => lease.projectKey === projectKey);

  if (input.sessionInFlight === true) {
    return { ok: false, reason: "agent_busy" };
  }
  if (fresh.length > 0) {
    if (fresh.some((lease) => lease.agentBusy)) {
      return { ok: false, reason: "agent_busy" };
    }

    const recentMs = config.trigger.recentUserMsgMinutes * 60_000;
    const lastUserMs = pickMostRecentLastUserMsgMs(fresh);
    if (lastUserMs !== undefined && now.getTime() - lastUserMs < recentMs) {
      return { ok: false, reason: "recent_user_msg" };
    }
  }

  const cooldownMs = config.trigger.cooldownMinutes * 60_000;
  if (state.lastFireCompletedAt) {
    const elapsed = now.getTime() - Date.parse(state.lastFireCompletedAt);
    if (elapsed < cooldownMs) {
      return { ok: false, reason: "cooldown" };
    }
  }

  if (state.todayRunCount >= config.trigger.dailyBudget) {
    return { ok: false, reason: "daily_budget" };
  }

  if (input.lockHeld) {
    return { ok: false, reason: "lock_busy" };
  }

  if (fresh.length === 0) {
    return { ok: true, lease: undefined };
  }
  const target = pickPreferredLease(fresh, config.trigger.preferChannel) ?? fresh[0];
  return { ok: true, lease: target };
}

function pickMostRecentLastUserMsgMs(leases: AlwaysOnChannelLease[]): number | undefined {
  let best: number | undefined;
  for (const lease of leases) {
    if (!lease.lastUserMsgAt) continue;
    const ms = Date.parse(lease.lastUserMsgAt);
    if (Number.isFinite(ms) && (best === undefined || ms > best)) {
      best = ms;
    }
  }
  return best;
}

function pickPreferredLease(
  leases: AlwaysOnChannelLease[],
  preferChannel: string,
): AlwaysOnChannelLease | undefined {
  return leases.find((lease) => lease.channelKey === preferChannel);
}