import { defineBackendPlugin, createEndpoint } from '@btst/stack/plugins/api';
import { convertToModelMessages, streamText, stepCountIs } from 'ai';
import { aiChatSchema } from '../db.mjs';
import { chatRequestSchema, createConversationSchema, updateConversationSchema } from '../schemas.mjs';
import { getConversationById, getAllConversations } from './getters.mjs';
import { BUILT_IN_PAGE_TOOL_SCHEMAS, BUILT_IN_PAGE_TOOL_ROUTE_ALLOWLIST } from './page-tools.mjs';
import { runHookWithShim } from '../../utils.mjs';

const aiChatBackendPlugin = (config) => defineBackendPlugin({
  name: "ai-chat",
  // Always include db schema - in public mode we just don't use it
  dbPlugin: aiChatSchema,
  api: (adapter) => ({
    getAllConversations: (userId) => getAllConversations(adapter, userId),
    getConversationById: (id) => getConversationById(adapter, id)
  }),
  routes: (adapter) => {
    const mode = config.mode ?? "authenticated";
    const isPublicMode = mode === "public";
    const getMessageTextContent = (msg) => {
      if (msg.parts && Array.isArray(msg.parts)) {
        return msg.parts.filter((part) => part.type === "text").map((part) => part.text).join("");
      }
      return "";
    };
    const serializeMessageParts = (msg) => {
      if (msg.parts && Array.isArray(msg.parts)) {
        const serializableParts = msg.parts.filter(
          (part) => part.type === "text" || part.type === "file"
        );
        return JSON.stringify(serializableParts);
      }
      return JSON.stringify([]);
    };
    const resolveUserId = async (context, throwOnMissing) => {
      if (isPublicMode) {
        return null;
      }
      if (!config.getUserId) {
        return null;
      }
      const userId = await config.getUserId(context);
      if (!userId) {
        throwOnMissing();
      }
      return userId;
    };
    const chat = createEndpoint(
      "/chat",
      {
        method: "POST",
        body: chatRequestSchema
      },
      async (ctx) => {
        const {
          messages: rawMessages,
          conversationId,
          pageContext,
          availableTools,
          routeName
        } = ctx.body;
        const uiMessages = rawMessages;
        const context = {
          body: ctx.body,
          headers: ctx.headers,
          request: ctx.request
        };
        try {
          if (config.hooks?.onBeforeChat) {
            const messagesForHook = uiMessages.map((msg) => ({
              role: msg.role,
              content: getMessageTextContent(msg)
            }));
            await runHookWithShim(
              () => config.hooks.onBeforeChat(messagesForHook, context),
              ctx.error,
              "Unauthorized: Cannot start chat"
            );
          }
          const firstMessage = uiMessages[0];
          if (!firstMessage) {
            throw ctx.error(400, {
              message: "At least one message is required"
            });
          }
          const firstMessageContent = getMessageTextContent(firstMessage);
          const modelMessages = await convertToModelMessages(uiMessages);
          const pageContextContent = pageContext && pageContext.trim() ? `

Current page context:
${pageContext}` : "";
          const systemContent = config.systemPrompt ? `${config.systemPrompt}${pageContextContent}` : pageContextContent || void 0;
          const messagesWithSystem = systemContent ? [
            { role: "system", content: systemContent },
            ...modelMessages
          ] : modelMessages;
          const activePageTools = config.enablePageTools && availableTools && availableTools.length > 0 ? (() => {
            const consumerSchemas = config.clientToolSchemas ?? {};
            return Object.fromEntries(
              availableTools.filter((name) => {
                if (name in BUILT_IN_PAGE_TOOL_SCHEMAS) {
                  const allowed = BUILT_IN_PAGE_TOOL_ROUTE_ALLOWLIST[name];
                  return allowed && routeName && allowed.includes(routeName);
                }
                return name in consumerSchemas;
              }).map((name) => {
                const schema = BUILT_IN_PAGE_TOOL_SCHEMAS[name] ?? consumerSchemas[name];
                return [name, schema];
              })
            );
          })() : {};
          if (config.hooks?.onBeforeToolsActivated && Object.keys(activePageTools).length > 0) {
            try {
              const allowed = await config.hooks.onBeforeToolsActivated(
                Object.keys(activePageTools),
                routeName,
                context
              );
              const allowedSet = new Set(allowed);
              for (const key of Object.keys(activePageTools)) {
                if (!allowedSet.has(key)) {
                  delete activePageTools[key];
                }
              }
            } catch (hookError) {
              throw ctx.error(403, {
                message: hookError instanceof Error ? hookError.message : "Unauthorized: Tool activation denied"
              });
            }
          }
          const mergedTools = Object.keys(activePageTools).length > 0 ? { ...activePageTools, ...config.tools } : config.tools;
          if (isPublicMode) {
            const result2 = streamText({
              model: config.model,
              messages: messagesWithSystem,
              tools: mergedTools,
              // Enable multi-step tool calls if tools are configured
              ...mergedTools ? { stopWhen: stepCountIs(5) } : {}
            });
            return result2.toUIMessageStreamResponse({
              originalMessages: uiMessages
            });
          }
          let userId = null;
          if (config.getUserId) {
            const resolvedUserId = await config.getUserId(context);
            if (!resolvedUserId) {
              throw ctx.error(403, {
                message: "Unauthorized: User authentication required"
              });
            }
            userId = resolvedUserId;
          }
          let convId = conversationId;
          if (!convId) {
            const newConv = await adapter.create({
              model: "conversation",
              data: {
                ...userId ? { userId } : {},
                title: firstMessageContent.slice(0, 50) || "New Conversation",
                createdAt: /* @__PURE__ */ new Date(),
                updatedAt: /* @__PURE__ */ new Date()
              }
            });
            convId = newConv.id;
          } else {
            const existing = await adapter.findMany({
              model: "conversation",
              where: [{ field: "id", value: convId, operator: "eq" }],
              limit: 1
            });
            if (!existing.length) {
              const newConv = await adapter.create({
                model: "conversation",
                data: {
                  id: convId,
                  ...userId ? { userId } : {},
                  title: firstMessageContent.slice(0, 50) || "New Conversation",
                  createdAt: /* @__PURE__ */ new Date(),
                  updatedAt: /* @__PURE__ */ new Date()
                }
              });
              convId = newConv.id;
            } else {
              const conv = existing[0];
              if (userId && conv.userId && conv.userId !== userId) {
                throw ctx.error(403, {
                  message: "Unauthorized: Cannot access this conversation"
                });
              }
            }
          }
          const existingMessages = await adapter.findMany({
            model: "message",
            where: [
              {
                field: "conversationId",
                value: convId,
                operator: "eq"
              }
            ],
            sortBy: { field: "createdAt", direction: "asc" }
          });
          const lastIncomingMessage = uiMessages[uiMessages.length - 1];
          const isNewUserMessage = lastIncomingMessage?.role === "user";
          let expectedDbCount;
          let shouldAddUserMessage = false;
          if (isNewUserMessage) {
            const lastDbUserMessage = [...existingMessages].reverse().find((m) => m.role === "user");
            const incomingUserContent = serializeMessageParts(lastIncomingMessage);
            if (lastDbUserMessage && lastDbUserMessage.content === incomingUserContent) {
              expectedDbCount = uiMessages.length;
              shouldAddUserMessage = false;
            } else {
              expectedDbCount = uiMessages.length - 1;
              shouldAddUserMessage = true;
            }
          } else {
            expectedDbCount = uiMessages.length;
            shouldAddUserMessage = false;
          }
          const actualDbCount = existingMessages.length;
          const messagesToDelete = actualDbCount > expectedDbCount ? existingMessages.slice(expectedDbCount) : [];
          await adapter.transaction(async (tx) => {
            for (const msg of messagesToDelete) {
              await tx.delete({
                model: "message",
                where: [{ field: "id", value: msg.id }]
              });
            }
            if (shouldAddUserMessage && lastIncomingMessage) {
              await tx.create({
                model: "message",
                data: {
                  conversationId: convId,
                  role: "user",
                  content: serializeMessageParts(lastIncomingMessage),
                  createdAt: /* @__PURE__ */ new Date()
                }
              });
            }
          });
          const result = streamText({
            model: config.model,
            messages: messagesWithSystem,
            tools: mergedTools,
            // Enable multi-step tool calls if tools are configured
            ...mergedTools ? { stopWhen: stepCountIs(5) } : {},
            onFinish: async (completion) => {
              try {
                const assistantParts = completion.text ? [{ type: "text", text: completion.text }] : [];
                await adapter.create({
                  model: "message",
                  data: {
                    conversationId: convId,
                    role: "assistant",
                    content: JSON.stringify(assistantParts),
                    createdAt: /* @__PURE__ */ new Date()
                  }
                });
                await adapter.update({
                  model: "conversation",
                  where: [{ field: "id", value: convId }],
                  update: { updatedAt: /* @__PURE__ */ new Date() }
                });
                if (config.hooks?.onAfterChat) {
                  const messages = await adapter.findMany({
                    model: "message",
                    where: [
                      {
                        field: "conversationId",
                        value: convId,
                        operator: "eq"
                      }
                    ],
                    sortBy: { field: "createdAt", direction: "asc" }
                  });
                  await config.hooks.onAfterChat(
                    convId,
                    messages,
                    context
                  );
                }
              } catch (error) {
                console.error("[ai-chat] Error in onFinish callback:", error);
                if (config.hooks?.onChatError) {
                  try {
                    await config.hooks.onChatError(error, context);
                  } catch (hookError) {
                    console.error(
                      "[ai-chat] Error in onChatError hook:",
                      hookError
                    );
                  }
                }
              }
            }
          });
          const response = result.toUIMessageStreamResponse({
            originalMessages: uiMessages
          });
          const headers = new Headers(response.headers);
          headers.set("X-Conversation-Id", convId);
          return new Response(response.body, {
            status: response.status,
            statusText: response.statusText,
            headers
          });
        } catch (error) {
          if (config.hooks?.onChatError) {
            await config.hooks.onChatError(error, context);
          }
          throw error;
        }
      }
    );
    const createConversation = createEndpoint(
      "/chat/conversations",
      {
        method: "POST",
        body: createConversationSchema
      },
      async (ctx) => {
        if (isPublicMode) {
          throw ctx.error(404, {
            message: "Conversations not available in public mode"
          });
        }
        const { id, title } = ctx.body;
        const context = {
          body: ctx.body,
          headers: ctx.headers
        };
        try {
          const userId = await resolveUserId(context, () => {
            throw ctx.error(403, {
              message: "Unauthorized: User authentication required"
            });
          });
          if (config.hooks?.onBeforeCreateConversation) {
            await runHookWithShim(
              () => config.hooks.onBeforeCreateConversation(
                { id, title },
                context
              ),
              ctx.error,
              "Unauthorized: Cannot create conversation"
            );
          }
          const newConv = await adapter.create({
            model: "conversation",
            data: {
              ...id ? { id } : {},
              ...userId ? { userId } : {},
              title: title || "New Conversation",
              createdAt: /* @__PURE__ */ new Date(),
              updatedAt: /* @__PURE__ */ new Date()
            }
          });
          if (config.hooks?.onConversationCreated) {
            await config.hooks.onConversationCreated(newConv, context);
          }
          return newConv;
        } catch (error) {
          if (config.hooks?.onCreateConversationError) {
            await config.hooks.onCreateConversationError(
              error,
              context
            );
          }
          throw error;
        }
      }
    );
    const listConversations = createEndpoint(
      "/chat/conversations",
      {
        method: "GET"
      },
      async (ctx) => {
        if (isPublicMode) {
          return [];
        }
        const context = {
          headers: ctx.headers
        };
        try {
          const userId = await resolveUserId(context, () => {
            throw ctx.error(403, {
              message: "Unauthorized: User authentication required"
            });
          });
          if (config.hooks?.onBeforeListConversations) {
            await runHookWithShim(
              () => config.hooks.onBeforeListConversations(context),
              ctx.error,
              "Unauthorized: Cannot list conversations"
            );
          }
          const whereConditions = [];
          if (userId) {
            whereConditions.push({
              field: "userId",
              value: userId,
              operator: "eq"
            });
          }
          const conversations = await adapter.findMany({
            model: "conversation",
            where: whereConditions.length > 0 ? whereConditions : void 0,
            sortBy: { field: "updatedAt", direction: "desc" }
          });
          if (config.hooks?.onConversationsRead) {
            await config.hooks.onConversationsRead(conversations, context);
          }
          return conversations;
        } catch (error) {
          if (config.hooks?.onListConversationsError) {
            await config.hooks.onListConversationsError(
              error,
              context
            );
          }
          throw error;
        }
      }
    );
    const getConversation = createEndpoint(
      "/chat/conversations/:id",
      {
        method: "GET"
      },
      async (ctx) => {
        if (isPublicMode) {
          throw ctx.error(404, {
            message: "Conversations not available in public mode"
          });
        }
        const { id } = ctx.params;
        const context = {
          params: ctx.params,
          headers: ctx.headers
        };
        try {
          const userId = await resolveUserId(context, () => {
            throw ctx.error(403, {
              message: "Unauthorized: User authentication required"
            });
          });
          if (config.hooks?.onBeforeGetConversation) {
            await runHookWithShim(
              () => config.hooks.onBeforeGetConversation(id, context),
              ctx.error,
              "Unauthorized: Cannot get conversation"
            );
          }
          const conversations = await adapter.findMany({
            model: "conversation",
            where: [{ field: "id", value: id, operator: "eq" }],
            limit: 1,
            join: {
              message: true
            }
          });
          if (!conversations.length) {
            throw ctx.error(404, { message: "Conversation not found" });
          }
          const conversation = conversations[0];
          if (userId && conversation.userId && conversation.userId !== userId) {
            throw ctx.error(403, {
              message: "Unauthorized: Cannot access this conversation"
            });
          }
          const messages = (conversation.message || []).sort(
            (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
          );
          const { message: _, ...conversationWithoutJoin } = conversation;
          const result = {
            ...conversationWithoutJoin,
            messages
          };
          if (config.hooks?.onConversationRead) {
            await config.hooks.onConversationRead(result, context);
          }
          return result;
        } catch (error) {
          if (config.hooks?.onGetConversationError) {
            await config.hooks.onGetConversationError(
              error,
              context
            );
          }
          throw error;
        }
      }
    );
    const updateConversation = createEndpoint(
      "/chat/conversations/:id",
      {
        method: "PUT",
        body: updateConversationSchema
      },
      async (ctx) => {
        if (isPublicMode) {
          throw ctx.error(404, {
            message: "Conversations not available in public mode"
          });
        }
        const { id } = ctx.params;
        const { title } = ctx.body;
        const context = {
          params: ctx.params,
          body: ctx.body,
          headers: ctx.headers
        };
        try {
          const userId = await resolveUserId(context, () => {
            throw ctx.error(403, {
              message: "Unauthorized: User authentication required"
            });
          });
          const existing = await adapter.findMany({
            model: "conversation",
            where: [{ field: "id", value: id, operator: "eq" }],
            limit: 1
          });
          if (!existing.length) {
            throw ctx.error(404, { message: "Conversation not found" });
          }
          const conversation = existing[0];
          if (userId && conversation.userId && conversation.userId !== userId) {
            throw ctx.error(403, {
              message: "Unauthorized: Cannot update this conversation"
            });
          }
          if (config.hooks?.onBeforeUpdateConversation) {
            await runHookWithShim(
              () => config.hooks.onBeforeUpdateConversation(
                id,
                { title },
                context
              ),
              ctx.error,
              "Unauthorized: Cannot update conversation"
            );
          }
          const updated = await adapter.update({
            model: "conversation",
            where: [{ field: "id", value: id }],
            update: {
              ...title !== void 0 ? { title } : {},
              updatedAt: /* @__PURE__ */ new Date()
            }
          });
          if (!updated) {
            throw ctx.error(404, { message: "Conversation not found" });
          }
          if (config.hooks?.onConversationUpdated) {
            await config.hooks.onConversationUpdated(updated, context);
          }
          return updated;
        } catch (error) {
          if (config.hooks?.onUpdateConversationError) {
            await config.hooks.onUpdateConversationError(
              error,
              context
            );
          }
          throw error;
        }
      }
    );
    const deleteConversation = createEndpoint(
      "/chat/conversations/:id",
      {
        method: "DELETE"
      },
      async (ctx) => {
        if (isPublicMode) {
          throw ctx.error(404, {
            message: "Conversations not available in public mode"
          });
        }
        const { id } = ctx.params;
        const context = {
          params: ctx.params,
          headers: ctx.headers
        };
        try {
          const userId = await resolveUserId(context, () => {
            throw ctx.error(403, {
              message: "Unauthorized: User authentication required"
            });
          });
          const existing = await adapter.findMany({
            model: "conversation",
            where: [{ field: "id", value: id, operator: "eq" }],
            limit: 1
          });
          if (!existing.length) {
            throw ctx.error(404, { message: "Conversation not found" });
          }
          const conversation = existing[0];
          if (userId && conversation.userId && conversation.userId !== userId) {
            throw ctx.error(403, {
              message: "Unauthorized: Cannot delete this conversation"
            });
          }
          if (config.hooks?.onBeforeDeleteConversation) {
            await runHookWithShim(
              () => config.hooks.onBeforeDeleteConversation(id, context),
              ctx.error,
              "Unauthorized: Cannot delete conversation"
            );
          }
          await adapter.delete({
            model: "conversation",
            where: [{ field: "id", value: id }]
          });
          if (config.hooks?.onConversationDeleted) {
            await config.hooks.onConversationDeleted(id, context);
          }
          return { success: true };
        } catch (error) {
          if (config.hooks?.onDeleteConversationError) {
            await config.hooks.onDeleteConversationError(
              error,
              context
            );
          }
          throw error;
        }
      }
    );
    return {
      chat,
      createConversation,
      listConversations,
      getConversation,
      updateConversation,
      deleteConversation
    };
  }
});

export { aiChatBackendPlugin };
