import { api } from "./client";
import type {
  ChatConversation,
  ChatConversationListResponse,
  ChatFile,
  ChatMessage,
  ChatMessageListResponse,
  ChatMessageSearchResponse,
  ChatBookmarkToggleResponse,
  ChatBookmarkListResponse,
  ChatFileUploadResponse,
} from "@paperclipai/shared";

export const chatApi = {
  listConversations(companyId: string, opts?: { cursor?: string; limit?: number; search?: string; agentId?: string }) {
    const params = new URLSearchParams();
    if (opts?.cursor) params.set("cursor", opts.cursor);
    if (opts?.limit) params.set("limit", String(opts.limit));
    if (opts?.search) params.set("search", opts.search);
    if (opts?.agentId) params.set("agentId", opts.agentId);
    const qs = params.toString();
    return api.get<ChatConversationListResponse>(
      `/companies/${companyId}/conversations${qs ? `?${qs}` : ""}`,
    );
  },

  createConversation(companyId: string, data?: { title?: string; agentId?: string }) {
    return api.post<ChatConversation>(`/companies/${companyId}/conversations`, data ?? {});
  },

  getConversation(id: string) {
    return api.get<ChatConversation>(`/conversations/${id}`);
  },

  updateConversation(
    id: string,
    data: { title?: string; pinnedAt?: string | null; archivedAt?: string | null; agentId?: string | null },
  ) {
    return api.patch<ChatConversation>(`/conversations/${id}`, data);
  },

  deleteConversation(id: string) {
    return api.delete<void>(`/conversations/${id}`);
  },

  listMessages(conversationId: string, opts?: { cursor?: string; limit?: number }) {
    const params = new URLSearchParams();
    if (opts?.cursor) params.set("cursor", opts.cursor);
    if (opts?.limit) params.set("limit", String(opts.limit));
    const qs = params.toString();
    return api.get<ChatMessageListResponse>(
      `/conversations/${conversationId}/messages${qs ? `?${qs}` : ""}`,
    );
  },

  postMessage(
    conversationId: string,
    data: { role: string; content: string; agentId?: string },
  ) {
    return api.post<ChatMessage>(`/conversations/${conversationId}/messages`, data);
  },

  async postMessageAndStream(
    conversationId: string,
    data: { content: string; agentId?: string },
    callbacks: {
      onToken: (token: string) => void;
      onDone: (messageId: string, content: string) => void;
      onError: (error: string) => void;
    },
    signal?: AbortSignal,
  ): Promise<void> {
    const response = await fetch(`/api/conversations/${conversationId}/stream`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      credentials: "include",
      body: JSON.stringify(data),
      signal,
    });

    if (!response.ok) {
      callbacks.onError(`HTTP ${response.status}`);
      return;
    }

    const reader = response.body?.getReader();
    if (!reader) {
      callbacks.onError("No response body");
      return;
    }

    const decoder = new TextDecoder();
    let buffer = "";

    try {
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;

        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split("\n");
        buffer = lines.pop() ?? "";

        for (const line of lines) {
          if (line.startsWith("data: ")) {
            const raw = line.slice(6).trim();
            if (raw === "[DONE]") continue;
            try {
              const parsed = JSON.parse(raw) as { type: string; token?: string; messageId?: string; content?: string; error?: string };
              if (parsed.type === "token" && parsed.token !== undefined) {
                callbacks.onToken(parsed.token);
              } else if (parsed.type === "done" && parsed.messageId !== undefined) {
                callbacks.onDone(parsed.messageId, parsed.content ?? "");
              } else if (parsed.type === "error") {
                callbacks.onError(parsed.error ?? "Unknown error");
              }
            } catch {
              // ignore malformed SSE lines
            }
          }
        }
      }
    } catch (err) {
      if ((err as Error).name !== "AbortError") {
        callbacks.onError((err as Error).message);
      }
    } finally {
      reader.releaseLock();
    }
  },

  savePartialMessage(
    conversationId: string,
    data: { role: string; content: string },
  ) {
    return api.post<ChatMessage>(`/conversations/${conversationId}/messages`, data);
  },

  async editMessage(conversationId: string, messageId: string, content: string) {
    return api.patch<ChatMessage>(`/conversations/${conversationId}/messages/${messageId}`, { content });
  },

  async truncateMessagesAfter(conversationId: string, messageId: string) {
    await fetch(`/api/conversations/${conversationId}/messages/after/${messageId}`, {
      method: "DELETE",
      credentials: "include",
    });
  },

  async deleteMessage(conversationId: string, messageId: string) {
    await fetch(`/api/conversations/${conversationId}/messages/${messageId}`, {
      method: "DELETE",
      credentials: "include",
    });
  },

  handoffSpec(
    conversationId: string,
    spec: { what: string; why: string; constraints: string; success: string },
    targetRole: string = "pm",
  ) {
    return api.post<{ handoffMessageId: string; issues: Array<{ id: string; identifier: string; title: string }> }>(
      `/conversations/${conversationId}/handoff`,
      { spec, targetRole },
    );
  },

  postStatusUpdate(
    conversationId: string,
    data: { agentName: string; taskId: string; taskTitle?: string; taskUrl?: string },
  ) {
    return api.post<{ id: string }>(`/conversations/${conversationId}/status-update`, data);
  },

  searchMessages(companyId: string, q: string, limit?: number) {
    const params = new URLSearchParams({ q });
    if (limit) params.set("limit", String(limit));
    return api.get<ChatMessageSearchResponse>(
      `/companies/${companyId}/messages/search?${params}`,
    );
  },

  toggleBookmark(conversationId: string, messageId: string) {
    return api.post<ChatBookmarkToggleResponse>(
      `/conversations/${conversationId}/bookmarks`,
      { messageId },
    );
  },

  getBookmarks(companyId: string, conversationId?: string) {
    const params = new URLSearchParams();
    if (conversationId) params.set("conversationId", conversationId);
    const qs = params.toString();
    return api.get<ChatBookmarkListResponse>(
      `/companies/${companyId}/bookmarks${qs ? `?${qs}` : ""}`,
    );
  },

  branchConversation(conversationId: string, branchFromMessageId: string) {
    return api.post<ChatConversation>(
      `/conversations/${conversationId}/branch`,
      { branchFromMessageId },
    );
  },

  listBranches(conversationId: string) {
    return api.get<{ items: ChatConversation[] }>(
      `/conversations/${conversationId}/branches`,
    );
  },

  exportConversation(conversationId: string, format: "markdown" | "json") {
    // Returns a download URL — use window.location.href to trigger
    return `/api/conversations/${conversationId}/export?format=${format}`;
  },

  promoteFile(fileId: string, projectId: string) {
    return api.patch<ChatFile>(`/files/${fileId}/promote`, { projectId });
  },

  async attachFilesToMessage(fileIds: string[], messageId: string) {
    await Promise.all(
      fileIds.map((fileId) =>
        api.patch(`/files/${fileId}`, { messageId })
      )
    );
  },

  async uploadFile(
    conversationId: string,
    file: File,
    opts?: { source?: string; projectId?: string },
    onProgress?: (percent: number) => void,
  ): Promise<ChatFileUploadResponse> {
    const formData = new FormData();
    formData.append("file", file);
    if (opts?.source) formData.append("source", opts.source);
    if (opts?.projectId) formData.append("projectId", opts.projectId);

    // Use XMLHttpRequest for progress tracking
    return new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest();
      xhr.open("POST", `/api/conversations/${conversationId}/files`);
      xhr.withCredentials = true;

      xhr.upload.onprogress = (e) => {
        if (e.lengthComputable && onProgress) {
          onProgress(Math.round((e.loaded / e.total) * 100));
        }
      };

      xhr.onload = () => {
        if (xhr.status >= 200 && xhr.status < 300) {
          resolve(JSON.parse(xhr.responseText) as ChatFileUploadResponse);
        } else {
          reject(new Error(`Upload failed: ${xhr.status}`));
        }
      };

      xhr.onerror = () => reject(new Error("Upload failed"));
      xhr.send(formData);
    });
  },
};
