import { logger } from "../logger.js";
import { type SessionTurnRecord } from "../sessions/transcriptStore.js";
import { MemoryStore } from "../memory/store.js";
import { type OpencodeRuntimeAdapter } from "../runtime/opencode.js";

import { collectTextContent } from "./chatStream.js";

const TITLE_PROMPT_TIMEOUT_MS = 5000;
const TITLE_CONTEXT_MESSAGE_LIMIT = 40;
const TITLE_CONTEXT_CHAR_LIMIT = 2400;
const TITLE_CONTEXT_MESSAGE_CHAR_LIMIT = 240;
const RESTORE_TURN_LIMIT = 8;
const RESTORE_MESSAGE_CHAR_LIMIT = 480;
const RESTORE_CONTEXT_CHAR_LIMIT = 3200;
const DEFAULT_SESSION_TITLE = "新对话";

const buildSessionTitle = (message: string) => {
  const normalized = message.replace(/\s+/g, " ").trim();
  if (!normalized) {
    return DEFAULT_SESSION_TITLE;
  }
  return normalized.length > 24 ? `${normalized.slice(0, 24)}...` : normalized;
};

const appendTitleContextMessage = (
  lines: string[],
  role: "用户" | "助手",
  content: string | undefined,
  maxLength = TITLE_CONTEXT_MESSAGE_CHAR_LIMIT,
) => {
  const normalized = content?.replace(/\s+/g, " ").trim();
  if (!normalized) {
    return;
  }
  lines.push(`${role}：${normalized.slice(0, maxLength)}`);
};

const buildTitleConversationContext = async (
  runtime: OpencodeRuntimeAdapter,
  sessionId: string,
) => {
  const messages = await runtime.messages(sessionId, TITLE_CONTEXT_MESSAGE_LIMIT);
  const recentMessages = messages
    .filter(
      (message) =>
        message.info.role === "user" || message.info.role === "assistant",
    )
    .map((message) => ({
      role: message.info.role,
      content: collectTextContent(message.parts)
        .replace(/\s+/g, " ")
        .trim()
        .slice(0, TITLE_CONTEXT_MESSAGE_CHAR_LIMIT),
    }))
    .filter((message) => message.content.length > 0);

  if (recentMessages.length === 0) {
    return "";
  }

  const formattedMessages = recentMessages.map(
    (message) => `${message.role === "user" ? "用户" : "助手"}：${message.content}`,
  );
  const fullConversation = formattedMessages.join("\n");
  if (fullConversation.length <= TITLE_CONTEXT_CHAR_LIMIT) {
    return fullConversation;
  }

  const headCount = Math.min(4, formattedMessages.length);
  const tailCount = Math.min(8, Math.max(0, formattedMessages.length - headCount));
  const middleOmitted = formattedMessages.length > headCount + tailCount;
  const summary = [
    ...formattedMessages.slice(0, headCount),
    ...(middleOmitted ? ["……（中间省略若干轮对话）"] : []),
    ...formattedMessages.slice(-tailCount),
  ].join("\n");

  return summary.slice(0, TITLE_CONTEXT_CHAR_LIMIT);
};

const normalizeGeneratedTitle = (rawTitle: string, fallback: string) => {
  const normalized = rawTitle
    .replace(/\s+/g, " ")
    .replace(/^标题[:：]\s*/i, "")
    .replace(/["'“”‘’`]/g, "")
    .replace(/[。！？!?,，、；;：:]+$/g, "")
    .trim();
  if (!normalized) {
    return fallback;
  }
  return normalized.length > 24 ? `${normalized.slice(0, 24)}...` : normalized;
};

export const shouldGenerateSessionTitle = (options: {
  recentTurnCount: number;
  isTitleManuallyEdited: boolean;
}) => options.recentTurnCount <= 1 && !options.isTitleManuallyEdited;

export const generateSessionTitle = async (
  runtime: OpencodeRuntimeAdapter,
  options: {
    sessionId: string;
    latestUserMessage: string;
    latestAssistantMessage?: string;
    fallbackTitle?: string;
  },
) => {
  const fallbackTitle = options.fallbackTitle?.trim();
  const fallback =
    fallbackTitle && fallbackTitle !== DEFAULT_SESSION_TITLE
      ? fallbackTitle
      : buildSessionTitle(options.latestUserMessage);
  let titleSessionId: string | undefined;
  try {
    const scopedContext: string[] = [];
    appendTitleContextMessage(scopedContext, "用户", options.latestUserMessage, 480);
    appendTitleContextMessage(scopedContext, "助手", options.latestAssistantMessage, 960);
    const conversation =
      scopedContext.length > 0
        ? scopedContext.join("\n")
        : await buildTitleConversationContext(runtime, options.sessionId);
    if (!conversation) {
      return fallback;
    }

    const titleSession = await runtime.createSession(`title-${Date.now().toString(36)}`);
    titleSessionId = titleSession.id;
    const request = runtime
      .prompt(
        titleSession.id,
        [
          "你是会话标题生成器。",
          "请根据下面整段多轮对话生成一个 8-16 字中文标题。",
          "要求：简洁、具体、可读、避免标点、不要引号、不要解释。",
          "优先概括用户当前真实需求和助手最终结论。",
          "忽略系统提示、历史记忆、学习上下文、工具日志等元信息。",
          "不要直接照抄用户任一条消息原文。",
          "只输出标题本身。",
          "",
          conversation,
        ].join("\n"),
      )
      .then(async () => {
        await runtime.waitForSessionIdle(titleSession.id, TITLE_PROMPT_TIMEOUT_MS);
        const messages = await runtime.messages(titleSession.id, 20);
        const assistantMessage = [...messages]
          .reverse()
          .find((message) => message.info.role === "assistant");
        const title = collectTextContent(assistantMessage?.parts ?? []);
        return normalizeGeneratedTitle(title, fallback);
      });

    const timeout = new Promise<string>((resolve) => {
      setTimeout(() => resolve(fallback), TITLE_PROMPT_TIMEOUT_MS);
    });

    return await Promise.race([request, timeout]);
  } catch (error) {
    logger.warn({ err: error }, "failed to generate session title, using fallback");
    return fallback;
  } finally {
    if (titleSessionId) {
      await runtime.abortSession(titleSessionId).catch((error) => {
        logger.debug({ sessionId: titleSessionId, err: error }, "failed to cleanup title session");
      });
    }
  }
};

export const getConversationTurnStats = async (
  runtime: OpencodeRuntimeAdapter,
  sessionId: string,
) => {
  const messages = await runtime.messages(sessionId, 12);
  return messages.reduce(
    (stats, message) => {
      if (message.info.role === "user") {
        stats.userMessageCount += 1;
      } else if (message.info.role === "assistant") {
        stats.assistantMessageCount += 1;
      }
      return stats;
    },
    {
      userMessageCount: 0,
      assistantMessageCount: 0,
    },
  );
};

export const buildPromptWithLearningContext = async (
  memoryStore: MemoryStore,
  actorKey: string,
  projectKey: string,
  options: {
    recentTurns: SessionTurnRecord[];
    persistedMessages?: unknown[];
    message: string;
    restoreConversation?: boolean;
  },
) => {
  const snapshot = await memoryStore.buildPromptSnapshot({ actorKey, projectKey });
  const restoredConversation = options.restoreConversation === false
    ? ""
    : buildRestoredConversationFromMessages(options.persistedMessages) ||
      buildRestoredConversationContext(options.recentTurns);
  if (!snapshot && !restoredConversation) {
    return options.message;
  }
  return [snapshot, restoredConversation, `[Current user request]\n${options.message}`]
    .filter(Boolean)
    .join("\n\n");
};

export const shouldRestoreConversationForRuntime = (options: {
  hadExistingSessionRecord: boolean;
  runtimeHasConversation: boolean;
}) => !options.hadExistingSessionRecord || !options.runtimeHasConversation;

const buildRestoredConversationContext = (recentTurns: SessionTurnRecord[]) => {
  const formattedTurns = recentTurns
    .slice(-RESTORE_TURN_LIMIT)
    .flatMap((turn) => [
      `用户：${compactMessage(turn.userMessage)}`,
      `助手：${compactMessage(turn.assistantMessage)}`,
    ])
    .filter((entry) => entry.length > 0);

  if (formattedTurns.length === 0) {
    return "";
  }

  const conversation = formattedTurns.join("\n");
  const trimmedConversation =
    conversation.length > RESTORE_CONTEXT_CHAR_LIMIT
      ? `${conversation.slice(0, RESTORE_CONTEXT_CHAR_LIMIT - 3)}...`
      : conversation;

  return [
    "[Previous conversation context]",
    "以下为当前前端对话线程中最近的历史对话，请延续其中已确认的目标、约束、结论与引用结果。",
    trimmedConversation,
  ].join("\n");
};

const compactMessage = (value: string) => {
  const normalized = value.replace(/\s+/g, " ").trim();
  if (!normalized) {
    return "";
  }
  return normalized.length > RESTORE_MESSAGE_CHAR_LIMIT
    ? `${normalized.slice(0, RESTORE_MESSAGE_CHAR_LIMIT - 3)}...`
    : normalized;
};

const isObjectRecord = (value: unknown): value is Record<string, unknown> =>
  typeof value === "object" && value !== null && !Array.isArray(value);

const isSyntheticAssistantError = (content: string) =>
  /^⚠️\s*\*\*(请求已中断|错误[:：]?)/.test(content);

export const extractLatestFrontendTurn = (messages: unknown[] | undefined) => {
  if (!Array.isArray(messages) || messages.length === 0) {
    return null;
  }

  for (let index = messages.length - 1; index >= 0; index -= 1) {
    const assistant = messages[index];
    if (!isObjectRecord(assistant) || assistant.role !== "assistant") {
      continue;
    }
    const assistantMessage =
      typeof assistant.content === "string"
        ? assistant.content.replace(/\s+/g, " ").trim()
        : "";
    if (!assistantMessage || isSyntheticAssistantError(assistantMessage)) {
      continue;
    }

    const user = messages
      .slice(0, index)
      .reverse()
      .find((message) => isObjectRecord(message) && message.role === "user");
    if (!isObjectRecord(user) || typeof user.content !== "string") {
      continue;
    }
    const userMessage = user.content.replace(/\s+/g, " ").trim();
    if (!userMessage) {
      continue;
    }

    return {
      assistantMessage,
      toolCallCount: estimateFrontendToolCallCount(assistant),
      userMessage,
    };
  }

  return null;
};

const buildRestoredConversationFromMessages = (messages: unknown[] | undefined) => {
  if (!Array.isArray(messages) || messages.length === 0) {
    return "";
  }

  const formattedMessages = messages
    .slice(-(RESTORE_TURN_LIMIT * 2 + 2))
    .flatMap((message) => {
      if (!isObjectRecord(message)) {
        return [];
      }

      const role = message.role;
      const content = message.content;
      if ((role !== "user" && role !== "assistant") || typeof content !== "string") {
        return [];
      }

      const normalizedContent = compactMessage(content);
      if (!normalizedContent) {
        return [];
      }

      if (role === "assistant" && isSyntheticAssistantError(normalizedContent)) {
        return [];
      }

      return [`${role === "user" ? "用户" : "助手"}：${normalizedContent}`];
    });

  if (formattedMessages.length === 0) {
    return "";
  }

  const conversation = formattedMessages.join("\n");
  const trimmedConversation =
    conversation.length > RESTORE_CONTEXT_CHAR_LIMIT
      ? `${conversation.slice(0, RESTORE_CONTEXT_CHAR_LIMIT - 3)}...`
      : conversation;

  return [
    "[Previous conversation context]",
    "以下为当前前端对话线程中最近的历史对话，请延续其中已确认的目标、约束、结论与引用结果。",
    trimmedConversation,
  ].join("\n");
};

const estimateFrontendToolCallCount = (assistant: Record<string, unknown>) => {
  const progress = Array.isArray(assistant.progress) ? assistant.progress : [];
  const artifacts = Array.isArray(assistant.artifacts) ? assistant.artifacts : [];
  const toolProgressCount = progress.filter(
    (item) =>
      isObjectRecord(item) &&
      (item.phase === "tool" ||
        (typeof item.id === "string" && item.id.startsWith("tool-"))),
  ).length;
  return Math.max(toolProgressCount, artifacts.length);
};
