export const CODE_AGENT_SESSION_LIFECYCLE_STATUSES = Object.freeze([
  "creating",
  "ready",
  "busy",
  "idle",
  "interrupted",
  "expired",
  "failed",
  "thread-resume-failed"
]);

export const CODE_AGENT_SESSION_STATUS_ALIASES = Object.freeze({
  canceled: "interrupted",
  cancelled: "interrupted",
  timeout: "failed",
  error: "failed",
  blocked: "failed",
  degraded: "failed"
});

const ACTIVE_LIFECYCLE_STATUSES = new Set(["creating", "ready", "busy", "idle"]);
const TERMINAL_LIFECYCLE_STATUSES = new Set(["expired", "thread-resume-failed"]);
const RECOVERABLE_LIFECYCLE_STATUSES = new Set(["failed", "interrupted"]);

export function normalizeCodeAgentSessionLifecycleStatus(value, { fallback = null } = {}) {
  const text = String(value ?? "").trim().toLowerCase();
  if (!text) return fallback;
  if (CODE_AGENT_SESSION_LIFECYCLE_STATUSES.includes(text)) return text;
  if (CODE_AGENT_SESSION_STATUS_ALIASES[text]) return CODE_AGENT_SESSION_STATUS_ALIASES[text];
  if (text === "running" || text === "in_flight" || text === "in-flight") return "busy";
  if (text === "completed" || text === "available" || text === "feasible" || text === "pass") return "idle";
  return fallback;
}

export function codeAgentSessionLifecycleSummary(input = {}) {
  const session = objectOrNull(input.session);
  const runnerTrace = objectOrNull(input.runnerTrace);
  const sessionReuse = objectOrNull(input.sessionReuse);
  const sessionSummary = objectOrNull(input.sessionSummary);
  const evidenceSessionId = firstText(
    session?.sessionId,
    sessionReuse?.sessionId,
    runnerTrace?.sessionId
  );
  const topLevelSessionId = firstText(input.sessionId);
  const conversationId = firstText(input.conversationId);
  const hasSession = Boolean(evidenceSessionId || (topLevelSessionId && topLevelSessionId !== conversationId));
  const rawStatus = firstText(
    input.lifecycleStatus,
    input.sessionLifecycleStatus,
    session?.lifecycleStatus,
    session?.lifecycle?.status,
    sessionSummary?.status,
    session?.status,
    input.sessionStatus,
    runnerTrace?.sessionLifecycleStatus,
    runnerTrace?.sessionStatus,
    sessionReuse?.status,
    input.status
  );
  const fallback = fallbackLifecycleStatus(input);
  let status = normalizeCodeAgentSessionLifecycleStatus(rawStatus, { fallback });
  const reused = booleanOrNull(input.reused) ??
    booleanOrNull(sessionSummary?.reused) ??
    booleanOrNull(sessionReuse?.reused) ??
    booleanOrNull(session?.reused) ??
    booleanOrNull(runnerTrace?.sessionReused);
  const fallbackUsed = isFallbackPayload(input);
  const concreteLifecycleBlock = ["busy", "expired", "failed", "interrupted"].includes(status) && hasSession;
  const gateBlocked = objectOrNull(input.longLivedSessionGate)?.status === "blocked";
  const evidenceMissing = objectOrNull(input.blocker)?.code === "code_agent_session_evidence_missing" ||
    objectOrNull(input.error)?.code === "code_agent_session_evidence_missing";
  const degraded = fallbackUsed ||
    input.unsupported === true ||
    input.degraded === true ||
    evidenceMissing ||
    (gateBlocked && !concreteLifecycleBlock);
  if ((fallbackUsed || degraded) && !hasSession) status = "failed";
  const requiresNewSession = status ? TERMINAL_LIFECYCLE_STATUSES.has(status) : degraded || fallbackUsed;
  const reusable = status ? (ACTIVE_LIFECYCLE_STATUSES.has(status) || RECOVERABLE_LIFECYCLE_STATUSES.has(status)) && status !== "creating" && degraded !== true : false;
  const newSession = reused === false || (hasSession && !reused && !requiresNewSession);
  const reasonCode = firstText(
    input.degradationReasonCode,
    objectOrNull(input.error)?.code,
    objectOrNull(input.blocker)?.code,
    objectOrNull(input.longLivedSessionGate)?.blocker,
    fallbackUsed ? "text_chat_only_fallback" : null
  );
  const reason = firstText(
    input.degradationReason,
    objectOrNull(input.error)?.userMessage,
    objectOrNull(input.blocker)?.userMessage,
    objectOrNull(input.blocker)?.summary,
    objectOrNull(input.error)?.message,
    reasonCode
  );
  const presentation = lifecyclePresentation({
    status,
    rawStatus,
    reused,
    newSession,
    degraded,
    fallbackUsed,
    reason
  });
  return {
    status,
    rawStatus: rawStatus ?? null,
    label: presentation.label,
    action: presentation.action,
    reusable,
    reused,
    newSession,
    busy: status === "busy",
    requiresNewSession,
    degraded,
    unsupported: input.unsupported === true,
    reasonCode: reasonCode ?? null,
    reason: reason ?? null,
    userMessage: presentation.userMessage
  };
}

export function decorateCodeAgentSession(session, context = {}) {
  if (!session || typeof session !== "object") return session;
  const lifecycle = codeAgentSessionLifecycleSummary({ ...context, session });
  return {
    ...session,
    lifecycleStatus: lifecycle.status,
    lifecycle
  };
}

function fallbackLifecycleStatus(input) {
  if (isFallbackPayload(input) || input.degraded === true || input.unsupported === true) return "failed";
  const status = String(input.status ?? "").toLowerCase();
  if (status === "running") return "busy";
  if (["failed", "timeout", "error"].includes(status)) return "failed";
  if (status === "canceled") return "interrupted";
  return null;
}

function lifecyclePresentation({ status, rawStatus, reused, newSession, degraded, fallbackUsed, reason }) {
  if (fallbackUsed) {
    return {
      label: "会话降级",
      action: "degraded",
      userMessage: "当前响应来自 fallback/text-only/read-only 降级路径，不会按完整 Code Agent 长会话成功展示。"
    };
  }
  if (degraded) {
    return {
      label: "会话降级",
      action: "degraded",
      userMessage: `当前会话证据不完整或能力降级；输入已保留，可重新发送建立新会话。${reason ? ` 原因：${reason}` : ""}`
    };
  }
  if (status === "busy") {
    return {
      label: "会话忙",
      action: "wait",
      userMessage: "会话忙/请等待：上一轮仍在处理，稍后重试或取消当前请求。"
    };
  }
  if (status === "expired") {
    return {
      label: "会话已过期",
      action: "new_session",
      userMessage: "会话已过期，可重新发送建立新的 Code Agent session。"
    };
  }
  if (status === "failed") {
    return {
      label: "会话失败",
      action: "retry",
      userMessage: "当前 Code Agent session 上一轮失败；可继续发送以尝试恢复同一 conversation/session/thread。"
    };
  }
  if (status === "thread-resume-failed") {
    return {
      label: "会话存储已失效",
      action: "new_session",
      userMessage: "当前 Code Agent session/thread 的后端存储已失效；下一轮应从新的 session/thread 继续。"
    };
  }
  if (status === "interrupted") {
    return {
      label: rawStatus === "canceled" ? "会话已取消" : "会话已中断",
      action: "retry",
      userMessage: "当前 Code Agent session 已中断；输入和 trace 已保留，可重试或重新发送。"
    };
  }
  if (status === "creating") {
    return {
      label: "正在建立会话",
      action: "wait",
      userMessage: "正在建立 Code Agent session，请等待后端返回 session/thread。"
    };
  }
  if (status === "ready" || status === "idle") {
    if (reused === true) {
      return {
        label: "继续当前会话",
        action: "continue",
        userMessage: "可继续当前会话：后端已确认复用现有 conversation/session/thread。"
      };
    }
    if (newSession === true) {
      return {
        label: "新会话已建立",
        action: "continue",
        userMessage: "新会话已建立：后端返回了可复用的 session/thread。"
      };
    }
    return {
      label: "会话可用",
      action: "continue",
      userMessage: "Code Agent session 处于可继续状态。"
    };
  }
  return {
    label: "会话未确认",
    action: "observe",
    userMessage: "尚未观测到可复用的 Code Agent session 状态。"
  };
}

function isFallbackPayload(input) {
  const runner = objectOrNull(input.runner);
  const error = objectOrNull(input.error);
  const blocker = objectOrNull(input.blocker ?? error?.blocker);
  return input.fallback === true ||
    input.fallbackUsed === true ||
    input.capabilityLevel === "text-chat-only" ||
    input.provider === "openai-responses" ||
    runner?.kind === "openai-responses-fallback" ||
    input.implementationType === "openai-responses-fallback" ||
    blocker?.code === "text_chat_only_fallback" ||
    error?.code === "text_chat_only_fallback";
}

function objectOrNull(value) {
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
}

function firstText(...values) {
  for (const value of values) {
    const text = String(value ?? "").trim();
    if (text) return text;
  }
  return null;
}

function booleanOrNull(value) {
  return typeof value === "boolean" ? value : null;
}
