import type { Event as OpencodeEvent, Part } from "@opencode-ai/sdk/v2";

import { logger } from "../logger.js";
import { type QuestionAnswers } from "../runtime/opencode.js";

export type PermissionRequestPayload = {
  session_id: string;
  request_id: string;
  permission: string;
  patterns: string[];
  target?: string;
  always: string[];
  tool?: {
    messageID: string;
    callID: string;
  };
  created_at: number;
};

type QuestionOptionPayload = {
  label: string;
  description: string;
};

type QuestionInfoPayload = {
  header: string;
  question: string;
  options: QuestionOptionPayload[];
  multiple?: boolean;
  custom?: boolean;
};

export type QuestionRequestPayload = {
  session_id: string;
  request_id: string;
  questions: QuestionInfoPayload[];
  tool?: {
    messageID: string;
    callID: string;
  };
  created_at: number;
};

export type TodoItemPayload = {
  id: string;
  content: string;
  status: "pending" | "in_progress" | "completed" | "cancelled";
  priority?: "low" | "medium" | "high";
  created_at?: number;
  updated_at?: number;
};

export type TodoUpdatePayload = {
  session_id: string;
  message_id?: string;
  todos: TodoItemPayload[];
  created_at: number;
};

const isDevelopmentDebugLoggingEnabled = process.env.NODE_ENV === "development";

const toolLabels: Record<string, string> = {
  memory_manager: "记忆写入",
  geocode: "地理编码",
  session_search: "历史会话检索",
  skill_manager: "流程沉淀",
  web_search: "网页搜索",
  locate_features: "地图定位",
  zoom_to_map: "地图缩放",
  view_history: "历史数据面板",
  view_scada: "SCADA 面板",
  show_chart: "图表渲染",
  render_junctions: "节点渲染",
};

export const logDevelopmentDebug = (
  message: string,
  metadata: Record<string, unknown>,
) => {
  if (!isDevelopmentDebugLoggingEnabled) {
    return;
  }
  logger.info(metadata, message);
};

export const getErrorMessage = (error: {
  name: string;
  data?: { message?: string };
}) => error.data?.message ?? error.name;

export const getUnknownErrorMessage = (error: unknown) => {
  if (
    typeof error === "object" &&
    error !== null &&
    "name" in error &&
    typeof error.name === "string"
  ) {
    const maybeData = "data" in error ? error.data : undefined;
    return getErrorMessage({
      name: error.name,
      data:
        typeof maybeData === "object" && maybeData !== null && "message" in maybeData
          ? { message: typeof maybeData.message === "string" ? maybeData.message : undefined }
          : undefined,
    });
  }
  return error instanceof Error ? error.message : String(error);
};

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

export const normalizeToolParams = (value: unknown): Record<string, unknown> => {
  if (isObjectRecord(value)) {
    return value;
  }
  if (typeof value === "string") {
    try {
      const parsed = JSON.parse(value) as unknown;
      return isObjectRecord(parsed) ? parsed : {};
    } catch {
      return {};
    }
  }
  return {};
};

export const extractRequestReason = (params: Record<string, unknown>) => {
  const candidates = ["reason", "request_reason", "why", "purpose", "rationale"];
  for (const key of candidates) {
    const value = params[key];
    if (typeof value === "string") {
      const normalized = value.trim();
      if (normalized) {
        return normalized;
      }
    }
  }
  return "";
};

export const isSkillEvent = (event: OpencodeEvent) =>
  event.type.toLowerCase().includes("skill");

export const extractSkillAuditInfo = (event: OpencodeEvent) => {
  const payload = isObjectRecord(event.properties)
    ? (event.properties as Record<string, unknown>)
    : {};
  const candidateName =
    typeof payload.skill === "string"
      ? payload.skill
      : typeof payload.skillName === "string"
        ? payload.skillName
        : typeof payload.name === "string"
          ? payload.name
          : event.type;
  const reason = extractRequestReason(payload);
  return {
    name: candidateName,
    reason,
    payload,
  };
};

export const hasToolParams = (params: Record<string, unknown>) =>
  Object.keys(params).length > 0;

export const isSessionEvent = (event: OpencodeEvent, sessionId: string) =>
  "properties" in event &&
  typeof event.properties === "object" &&
  event.properties !== null &&
  "sessionID" in event.properties &&
  event.properties.sessionID === sessionId;

export const isPermissionAskedEvent = (
  event: OpencodeEvent,
): event is Extract<OpencodeEvent, { type: "permission.asked" }> =>
  event.type === "permission.asked";

export const isPermissionV2AskedEvent = (
  event: OpencodeEvent,
): event is Extract<OpencodeEvent, { type: "permission.v2.asked" }> =>
  event.type === "permission.v2.asked";

export const isPermissionRepliedEvent = (
  event: OpencodeEvent,
): event is Extract<OpencodeEvent, { type: "permission.replied" }> =>
  event.type === "permission.replied";

export const isPermissionV2RepliedEvent = (
  event: OpencodeEvent,
): event is Extract<OpencodeEvent, { type: "permission.v2.replied" }> =>
  event.type === "permission.v2.replied";

export const isQuestionAskedEvent = (
  event: OpencodeEvent,
): event is Extract<OpencodeEvent, { type: "question.asked" }> =>
  event.type === "question.asked";

export const isQuestionV2AskedEvent = (
  event: OpencodeEvent,
): event is Extract<OpencodeEvent, { type: "question.v2.asked" }> =>
  event.type === "question.v2.asked";

export const isQuestionRepliedEvent = (
  event: OpencodeEvent,
): event is Extract<OpencodeEvent, { type: "question.replied" }> =>
  event.type === "question.replied";

export const isQuestionV2RepliedEvent = (
  event: OpencodeEvent,
): event is Extract<OpencodeEvent, { type: "question.v2.replied" }> =>
  event.type === "question.v2.replied";

export const isQuestionRejectedEvent = (
  event: OpencodeEvent,
): event is Extract<OpencodeEvent, { type: "question.rejected" }> =>
  event.type === "question.rejected";

export const isQuestionV2RejectedEvent = (
  event: OpencodeEvent,
): event is Extract<OpencodeEvent, { type: "question.v2.rejected" }> =>
  event.type === "question.v2.rejected";

export const buildPermissionDetail = (
  event: Extract<OpencodeEvent, { type: "permission.asked" }>,
) => {
  const patterns = event.properties.patterns.length
    ? event.properties.patterns.join(", ")
    : event.properties.permission;
  return `需要用户确认权限：${event.properties.permission}；匹配规则：${patterns}`;
};

export const buildPermissionV2Detail = (
  event: Extract<OpencodeEvent, { type: "permission.v2.asked" }>,
) => {
  const resources = event.properties.resources.length
    ? event.properties.resources.join(", ")
    : event.properties.action;
  return `需要用户确认权限：${event.properties.action}；资源：${resources}`;
};

export const normalizeQuestionPayload = (
  event: Extract<OpencodeEvent, { type: "question.asked" | "question.v2.asked" }>,
  clientSessionId: string,
): QuestionRequestPayload => ({
  session_id: clientSessionId,
  request_id: event.properties.id,
  questions: event.properties.questions.map((question) => ({
    header: question.header,
    question: question.question,
    options: question.options.map((option) => ({
      label: option.label,
      description: option.description,
    })),
    multiple: question.multiple,
    custom: question.custom,
  })),
  tool: event.properties.tool,
  created_at: Date.now(),
});

export const normalizeQuestionAnswers = (answers: QuestionAnswers | undefined) =>
  Array.isArray(answers)
    ? answers.map((answer) =>
        Array.isArray(answer)
          ? answer.filter((item): item is string => typeof item === "string")
          : [],
      )
    : [];

const questionToolNames = new Set(["question", "request_user_input"]);

const normalizeQuestionOptions = (value: unknown): QuestionOptionPayload[] =>
  Array.isArray(value)
    ? value.filter(isObjectRecord).map((option) => ({
        label: typeof option.label === "string" ? option.label : "",
        description:
          typeof option.description === "string" ? option.description : "",
      })).filter((option) => option.label.trim().length > 0)
    : [];

const normalizeToolQuestionInfo = (value: unknown): QuestionInfoPayload | undefined => {
  if (!isObjectRecord(value) || typeof value.question !== "string") {
    return undefined;
  }
  const question = value.question.trim();
  if (!question) {
    return undefined;
  }
  return {
    header:
      typeof value.header === "string" && value.header.trim()
        ? value.header
        : "补充信息",
    question,
    options: normalizeQuestionOptions(value.options),
    multiple: typeof value.multiple === "boolean" ? value.multiple : undefined,
    custom: typeof value.custom === "boolean" ? value.custom : undefined,
  };
};

export const normalizeQuestionToolPayload = (
  part: Extract<Part, { type: "tool" }>,
  params: Record<string, unknown>,
  clientSessionId: string,
): QuestionRequestPayload | undefined => {
  if (!questionToolNames.has(part.tool)) {
    return undefined;
  }
  const questions = Array.isArray(params.questions)
    ? params.questions
        .map(normalizeToolQuestionInfo)
        .filter((question): question is QuestionInfoPayload => Boolean(question))
    : [];
  if (questions.length === 0) {
    return undefined;
  }
  return {
    session_id: clientSessionId,
    request_id: part.callID || part.id,
    questions,
    tool: {
      messageID: part.messageID,
      callID: part.callID,
    },
    created_at: Date.now(),
  };
};

export const normalizeTodoStatus = (status: string): TodoItemPayload["status"] => {
  if (status === "in_progress" || status === "completed" || status === "cancelled") {
    return status;
  }
  return "pending";
};

export const normalizeTodoPriority = (
  priority: string,
): TodoItemPayload["priority"] | undefined => {
  if (priority === "low" || priority === "medium" || priority === "high") {
    return priority;
  }
  return undefined;
};

export const collectTextContent = (parts: Part[]) =>
  parts
    .filter((part): part is Extract<Part, { type: "text" }> => part.type === "text")
    .map((part) => part.text)
    .join("");

export const normalizeToolStatus = (status: string) => {
  if (status === "completed") return "completed";
  if (status === "error") return "error";
  return "running";
};

const formatProgressValue = (value: unknown): string => {
  if (typeof value === "string") {
    return value.length > 120 ? `${value.slice(0, 117)}...` : value;
  }
  if (
    typeof value === "number" ||
    typeof value === "boolean" ||
    value === null ||
    value === undefined
  ) {
    return String(value);
  }
  try {
    const serialized = JSON.stringify(value);
    return serialized.length > 120 ? `${serialized.slice(0, 117)}...` : serialized;
  } catch {
    return "[unserializable]";
  }
};

const normalizeProgressText = (chunks: string[]) =>
  chunks.join("").replace(/\s+/g, " ").trim();

const truncateProgressText = (text: string, maxLength: number) =>
  text.length > maxLength ? `${text.slice(0, maxLength - 3)}...` : text;

const summarizeToolParams = (params: Record<string, unknown>) => {
  const ignoredKeys = new Set(["reason", "request_reason", "why", "purpose", "rationale"]);
  const summary = Object.entries(params)
    .filter(([key]) => !ignoredKeys.has(key))
    .slice(0, 4)
    .map(([key, value]) => `${key}=${formatProgressValue(value)}`)
    .join(", ");

  return summary || "无附加参数";
};

export const buildSessionStatusDetail = (status: { type: string; message?: string }) => {
  if (status.type === "retry") {
    return status.message
      ? `模型请求需要重试，原因：${status.message}`
      : "模型请求正在重试，等待下一次响应。";
  }
  if (status.type === "busy") {
    return status.message
      ? `Agent 正在处理中：${status.message}`
      : "Agent 正在执行推理、工具调用或结果整理。";
  }
  if (status.type === "idle") {
    return status.message
      ? `Agent 已空闲：${status.message}`
      : "当前会话暂时没有待处理任务。";
  }
  return status.message ? `会话状态更新：${status.message}` : `会话状态更新：${status.type}`;
};

export const buildReasoningProgressDetail = (
  chunks: string[],
  ended?: string | number | Date | null,
) => {
  const reasoningText = truncateProgressText(normalizeProgressText(chunks), 800);
  if (ended) {
    return reasoningText
      ? `推理过程：${reasoningText}`
      : "当前推理阶段已完成，Agent 将继续输出答案或进入工具执行。";
  }
  return reasoningText
    ? `正在推理：${reasoningText}`
    : "Agent 正在拆解问题、梳理执行步骤并判断是否需要调用工具。";
};

export const buildToolProgressDetail = (
  tool: string,
  status: string,
  params: Record<string, unknown>,
  reason: string,
  error?: string,
) => {
  const toolName = toolLabels[tool] ?? tool;
  const reasonText = reason ? `；调用原因：${reason}` : "";
  const paramsText = `；关键参数：${summarizeToolParams(params)}`;

  if (status === "error") {
    const errorText = error ? `；错误：${error}` : "";
    return `${toolName} 调用失败${reasonText}${paramsText}${errorText}`;
  }
  if (status === "completed") {
    return `${toolName} 已执行完成${reasonText}${paramsText}`;
  }
  if (status === "pending") {
    return `${toolName} 已进入待执行状态${reasonText}${paramsText}`;
  }
  return `${toolName} 正在执行${reasonText}${paramsText}`;
};

export const getToolProgressTitle = (tool: string, status: string) => {
  const toolName = toolLabels[tool] ?? tool;
  if (status === "completed") return `${toolName} 已完成`;
  if (status === "error") return `${toolName} 执行失败`;
  if (status === "pending") return `准备调用 ${toolName}`;
  return `正在调用 ${toolName}`;
};
