/**
 * Dependency-free CareWay API v1 SDK for Node.js 18+.
 */
import { randomUUID } from "node:crypto";

export class CareWayError extends Error {
  constructor(code, message, status = 0) {
    super(message);
    this.name = "CareWayError";
    this.code = code;
    this.status = status;
  }
}

export class CareWay {
  constructor({
    apiKey,
    baseUrl = "https://api.carewayrussia.ru/api/v1",
    timeoutMs = 900000,
  }) {
    if (!apiKey) throw new TypeError("apiKey is required");
    this.apiKey = apiKey;
    this.baseUrl = baseUrl.replace(/\/+$/, "");
    this.timeoutMs = timeoutMs;
  }

  subscription() {
    return this.request("GET", "/subscription").then((result) => result.data);
  }

  listChats() {
    return this.request("GET", "/chats").then((result) => result.data);
  }

  createChat({ title = null } = {}) {
    return this.request("POST", "/chats", { title }).then((result) => result.data);
  }

  getChat(chatId) {
    return this.request("GET", `/chats/${chatId}`).then((result) => result.data);
  }

  deleteChat(chatId) {
    return this.request("DELETE", `/chats/${chatId}`).then(
      (result) => result.data.deleted
    );
  }

  sendMessage(chatId, { text, idempotencyKey = randomUUID() }) {
    return this.request("POST", `/chats/${chatId}/messages`, {
      text,
      idempotencyKey,
    }).then((result) => result.data);
  }

  async *streamMessage(
    chatId,
    { text, idempotencyKey = randomUUID() }
  ) {
    const response = await this.fetch(`/chats/${chatId}/messages/stream`, {
      method: "POST",
      body: JSON.stringify({ text, idempotencyKey }),
    });
    if (!response.ok) await this.throwResponse(response);

    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    let buffer = "";

    while (true) {
      const { value, done } = await reader.read();
      buffer += decoder.decode(value || new Uint8Array(), { stream: !done });
      let boundary;
      while ((boundary = buffer.indexOf("\n\n")) !== -1) {
        const block = buffer.slice(0, boundary).replace(/\r/g, "");
        buffer = buffer.slice(boundary + 2);
        let event = "message";
        const data = [];
        for (const line of block.split("\n")) {
          if (line.startsWith("event:")) event = line.slice(6).trim();
          if (line.startsWith("data:")) data.push(line.slice(5).trimStart());
        }
        if (!data.length) continue;
        const payload = JSON.parse(data.join("\n"));
        yield { event, data: payload };
        if (event === "error") {
          throw new CareWayError(
            payload.code || "request_failed",
            payload.message || "CareWay API request failed",
            response.status
          );
        }
      }
      if (done) break;
    }
  }

  feedback(chatId, messageId, { correct, actualCause = null }) {
    return this.request(
      "POST",
      `/chats/${chatId}/messages/${messageId}/feedback`,
      { rating: correct ? "correct" : "incorrect", actualCause }
    ).then((result) => result.data);
  }

  async request(method, path, body) {
    const response = await this.fetch(path, {
      method,
      body: body == null ? undefined : JSON.stringify(body),
    });
    if (!response.ok) await this.throwResponse(response);
    return response.json();
  }

  fetch(path, init) {
    const signal = AbortSignal.timeout(this.timeoutMs);
    return fetch(this.baseUrl + path, {
      ...init,
      signal,
      headers: {
        Authorization: `Bearer ${this.apiKey}`,
        "Content-Type": "application/json",
        Accept: "application/json",
        "User-Agent": "careway-node/1.0.0",
        ...(init.headers || {}),
      },
    });
  }

  async throwResponse(response) {
    let detail = {};
    try {
      const payload = await response.json();
      detail = payload.error || payload;
    } catch {}
    throw new CareWayError(
      detail.code || "request_failed",
      detail.message || `CareWay API returned HTTP ${response.status}`,
      response.status
    );
  }
}
