import type {
  AgentQuestionRequest,
  AgentTodoUpdate,
  PermissionReply,
  StreamEvent,
} from "@/lib/chatStream";
import type {
  AgentPermissionRequest,
  ChatProgress,
  Message,
} from "../GlobalChatbox.types";
import { createId } from "../globalChatboxUtils";

export const upsertProgress = (
  progress: ChatProgress[] | undefined,
  event: StreamEvent & { type: "progress" },
) => {
  const next = [...(progress ?? [])];
  const index = next.findIndex((item) => item.id === event.id);
  const existing = index >= 0 ? next[index] : undefined;
  const now = Date.now();
  const startedAt = event.startedAt ?? existing?.startedAt;
  const isRunning = event.status === "running";
  const endedAt = isRunning ? undefined : event.endedAt ?? existing?.endedAt ?? now;
  const elapsedMs = isRunning
    ? event.elapsedMs ??
      existing?.elapsedMs ??
      (startedAt !== undefined ? Math.max(0, now - startedAt) : undefined)
    : undefined;
  const elapsedSnapshotAt = isRunning
    ? event.elapsedMs !== undefined
      ? now
      : existing?.elapsedSnapshotAt ?? now
    : undefined;
  const durationMs = !isRunning
    ? event.durationMs ??
      existing?.durationMs ??
      (startedAt !== undefined && endedAt !== undefined
        ? Math.max(0, endedAt - startedAt)
        : undefined)
    : undefined;
  const nextItem: ChatProgress = {
    id: event.id,
    phase: event.phase,
    status: event.status,
    title: event.title,
    detail: event.detail,
    startedAt,
    endedAt,
    elapsedMs,
    elapsedSnapshotAt,
    durationMs,
  };
  if (index >= 0) {
    next[index] = nextItem;
  } else {
    next.push(nextItem);
  }
  return next;
};

export const completeRunningProgress = (progress: ChatProgress[] | undefined) =>
  progress?.map((item) => {
    if (item.status !== "running") {
      return item;
    }
    const endedAt = Date.now();
    return {
      ...item,
      status: "completed" as const,
      endedAt,
      elapsedMs: undefined,
      elapsedSnapshotAt: undefined,
      durationMs:
        item.durationMs ??
        (item.startedAt !== undefined
          ? Math.max(0, endedAt - item.startedAt)
          : item.elapsedMs),
    };
  });

export const cancelRunningTodos = (todoUpdate: AgentTodoUpdate | undefined) =>
  todoUpdate
    ? {
        ...todoUpdate,
        todos: todoUpdate.todos.map((todo) =>
          todo.status === "pending" || todo.status === "in_progress"
            ? {
                ...todo,
                status: "cancelled" as const,
                updatedAt: Date.now(),
              }
            : todo,
        ),
      }
    : undefined;

export const upsertPermission = (
  permissions: AgentPermissionRequest[] | undefined,
  event: StreamEvent & { type: "permission_request" },
) => {
  const next = [...(permissions ?? [])];
  const index = next.findIndex((item) => item.requestId === event.requestId);
  const nextItem: AgentPermissionRequest = {
    requestId: event.requestId,
    sessionId: event.sessionId,
    permission: event.permission,
    patterns: event.patterns,
    target: event.target,
    always: event.always,
    tool: event.tool,
    createdAt: event.createdAt,
    status: "pending",
  };
  if (index >= 0) {
    next[index] = {
      ...next[index],
      ...nextItem,
      status: next[index].status === "submitting" ? "submitting" : nextItem.status,
    };
  } else {
    next.push(nextItem);
  }
  return next;
};

export const toPermissionStatus = (reply: PermissionReply): AgentPermissionRequest["status"] => {
  if (reply === "always") return "approved_always";
  if (reply === "once") return "approved_once";
  return "rejected";
};

export const isActionableQuestionRequest = (question: {
  requestId: string;
  tool?: AgentQuestionRequest["tool"];
}) => Boolean(question.requestId && question.requestId !== question.tool?.callID);

export const toQuestionRequest = (
  event: StreamEvent & { type: "question_request" },
  status: AgentQuestionRequest["status"] = "pending",
): AgentQuestionRequest => ({
  requestId: event.requestId,
  sessionId: event.sessionId,
  questions: event.questions,
  tool: event.tool,
  createdAt: event.createdAt,
  status,
});

export const getQuestionContentSignature = (
  questions: AgentQuestionRequest["questions"],
) =>
  JSON.stringify(
    questions.map((question) => ({
      header: question.header,
      question: question.question,
      options: question.options.map((option) => ({
        label: option.label,
        description: option.description,
      })),
      multiple: question.multiple ?? false,
      custom: question.custom !== false,
    })),
  );

export const isSameQuestionRequest = (
  question: AgentQuestionRequest,
  event: StreamEvent & { type: "question_request" },
) => {
  if (question.requestId === event.requestId) return true;
  if (question.tool?.callID && event.tool?.callID) {
    return question.tool.callID === event.tool.callID;
  }
  return (
    question.status === "pending" &&
    question.sessionId === event.sessionId &&
    getQuestionContentSignature(question.questions) ===
      getQuestionContentSignature(event.questions)
  );
};

export const isSameQuestionPair = (
  left: AgentQuestionRequest,
  right: AgentQuestionRequest,
) => {
  if (left.requestId === right.requestId) return true;
  if (left.tool?.callID && right.tool?.callID) {
    return left.tool.callID === right.tool.callID;
  }
  return (
    left.status === "pending" &&
    right.status === "pending" &&
    left.sessionId === right.sessionId &&
    getQuestionContentSignature(left.questions) ===
      getQuestionContentSignature(right.questions)
  );
};

export const dedupeQuestionsAcrossMessages = (messages: Message[]) => {
  const seen: AgentQuestionRequest[] = [];
  let changed = false;
  const nextMessages = messages.map((message) => {
    if (!message.questions?.length) {
      return message;
    }
    const nextQuestions = message.questions.filter((question) => {
      if (seen.some((existing) => isSameQuestionPair(existing, question))) {
        changed = true;
        return false;
      }
      seen.push(question);
      return true;
    });
    if (nextQuestions.length === message.questions.length) {
      return message;
    }
    return {
      ...message,
      questions: nextQuestions.length ? nextQuestions : undefined,
    };
  });
  return changed ? nextMessages : messages;
};

export const upsertQuestionAcrossMessages = (
  messages: Message[],
  event: StreamEvent & { type: "question_request" },
  assistantMessageId: string,
) => {
  let existing: AgentQuestionRequest | undefined;
  for (const message of messages) {
    const match = message.questions?.find((question) =>
      isSameQuestionRequest(question, event),
    );
    if (match) {
      existing = match;
      break;
    }
  }

  const existingStatus: AgentQuestionRequest["status"] | undefined =
    existing?.status === "submitting" ? "submitting" : undefined;
  const nextQuestion =
    existing &&
    isActionableQuestionRequest(existing) &&
    !isActionableQuestionRequest(event)
      ? {
          ...existing,
          sessionId: event.sessionId,
          questions: event.questions,
          tool: event.tool ?? existing.tool,
          createdAt: event.createdAt,
          status: existingStatus ?? existing.status,
        }
      : toQuestionRequest(event, existingStatus ?? "pending");
  const targetMessageId = existing
    ? messages.find((message) =>
        message.questions?.some((question) => isSameQuestionRequest(question, event)),
      )?.id ?? assistantMessageId
    : assistantMessageId;

  return messages.map((message) => {
    const filteredQuestions = message.questions?.filter(
      (question) => !isSameQuestionRequest(question, event),
    );
    if (message.id !== targetMessageId) {
      return filteredQuestions?.length === message.questions?.length
        ? message
        : {
            ...message,
            questions: filteredQuestions?.length ? filteredQuestions : undefined,
      };
    }

    const nextQuestions = [...(filteredQuestions ?? []), nextQuestion];
    return {
      ...message,
      questions: nextQuestions,
    };
  });
};

export const applyQuestionResponse = (
  questions: AgentQuestionRequest[] | undefined,
  event: StreamEvent & { type: "question_response" },
) =>
  (questions ?? []).map((question) =>
    question.requestId === event.requestId
      ? {
          ...question,
          status: event.rejected ? "rejected" as const : "answered" as const,
          answers: event.answers ?? question.answers,
          repliedAt: Date.now(),
          error: undefined,
        }
      : question,
  );

export const createTodoUpdateFromEvent = (
  event: StreamEvent & { type: "todo_update" },
): AgentTodoUpdate => ({
  sessionId: event.sessionId,
  messageId: event.messageId,
  todos: event.todos,
  createdAt: event.createdAt,
});

export const normalizeSessionTodos = (
  messages: Message[],
  nextTodoUpdate?: AgentTodoUpdate,
  targetAssistantMessageId?: string,
) => {
  let latestTodoUpdate = nextTodoUpdate;
  if (!latestTodoUpdate) {
    for (const message of messages) {
      if (message.todos) {
        latestTodoUpdate = message.todos;
      }
    }
  }

  if (!latestTodoUpdate) {
    return messages;
  }

  const targetMessageId =
    targetAssistantMessageId ??
    [...messages].reverse().find((message) => message.role === "assistant")?.id;
  if (!targetMessageId) {
    return messages;
  }

  let changed = false;
  const nextMessages = messages.map((message) => {
    if (message.id === targetMessageId) {
      if (message.todos === latestTodoUpdate) {
        return message;
      }
      changed = true;
      return {
        ...message,
        todos: latestTodoUpdate,
      };
    }
    if (!message.todos) {
      return message;
    }
    changed = true;
    return {
      ...message,
      todos: undefined,
    };
  });

  return changed ? nextMessages : messages;
};

export const abortOpenPermissionsAfterAbort = (
  permissions: AgentPermissionRequest[] | undefined,
) => {
  if (!permissions?.length) return permissions;
  let changed = false;
  const nextPermissions = permissions.map((permission) => {
    if (
      permission.status !== "pending" &&
      permission.status !== "submitting" &&
      permission.status !== "error"
    ) {
      return permission;
    }
    changed = true;
    return {
      ...permission,
      status: "aborted" as const,
      repliedAt: Date.now(),
      error: undefined,
    };
  });
  return changed ? nextPermissions : permissions;
};

export const rejectOpenQuestionsAfterAbort = (
  questions: AgentQuestionRequest[] | undefined,
) => {
  if (!questions?.length) return questions;
  let changed = false;
  const nextQuestions = questions.map((question) => {
    if (
      question.status !== "pending" &&
      question.status !== "submitting" &&
      question.status !== "error"
    ) {
      return question;
    }
    changed = true;
    return {
      ...question,
      status: "rejected" as const,
      repliedAt: Date.now(),
      error: undefined,
    };
  });
  return changed ? nextQuestions : questions;
};

export const finalizeAssistantMessageAfterAbort = (message: Message): Message => {
  const completedProgress = completeRunningProgress(message.progress);
  const cancelledTodos = cancelRunningTodos(message.todos);
  const abortedPermissions = abortOpenPermissionsAfterAbort(message.permissions);
  const rejectedQuestions = rejectOpenQuestionsAfterAbort(message.questions);
  const hasVisibleOutput =
    message.content.trim().length > 0 ||
    Boolean(message.artifacts?.length) ||
    Boolean(abortedPermissions?.length) ||
    Boolean(rejectedQuestions?.length) ||
    Boolean(completedProgress?.length) ||
    Boolean(cancelledTodos);

  if (!hasVisibleOutput) {
    return message;
  }

  return {
    ...message,
    content: message.content || "⚠️ **请求已中断**",
    isError: true,
    progress: completedProgress,
    permissions: abortedPermissions,
    questions: rejectedQuestions,
    todos: cancelledTodos,
  };
};

export const createUserMessage = (content: string): Message => {
  const id = createId();
  return {
    id,
    role: "user",
    content,
  };
};

export const createAssistantMessage = (): Message => ({
  id: createId(),
  role: "assistant",
  content: "",
});
