/**
 * @openfang/sdk — Official JavaScript client for the OpenFang Agent OS REST API.
 *
 * Usage:
 *   const { OpenFang } = require("@openfang/sdk");
 *   const client = new OpenFang("http://localhost:3000");
 *
 *   const agent = await client.agents.create({ template: "assistant" });
 *   const reply = await client.agents.message(agent.id, "Hello!");
 *   console.log(reply);
 *
 *   // Streaming:
 *   for await (const event of client.agents.stream(agent.id, "Tell me a joke")) {
 *     process.stdout.write(event.delta || "");
 *   }
 */

"use strict";

class OpenFangError extends Error {
  constructor(message, status, body) {
    super(message);
    this.name = "OpenFangError";
    this.status = status;
    this.body = body;
  }
}

class OpenFang {
  /**
   * @param {string} baseUrl - OpenFang server URL (e.g. "http://localhost:3000")
   * @param {object} [opts]
   * @param {Record<string, string>} [opts.headers] - Extra headers for every request
   */
  constructor(baseUrl, opts) {
    this.baseUrl = baseUrl.replace(/\/+$/, "");
    this._headers = Object.assign({ "Content-Type": "application/json" }, (opts && opts.headers) || {});
    this.agents = new AgentResource(this);
    this.sessions = new SessionResource(this);
    this.workflows = new WorkflowResource(this);
    this.skills = new SkillResource(this);
    this.channels = new ChannelResource(this);
    this.tools = new ToolResource(this);
    this.models = new ModelResource(this);
    this.providers = new ProviderResource(this);
    this.memory = new MemoryResource(this);
    this.triggers = new TriggerResource(this);
    this.schedules = new ScheduleResource(this);
  }

  /** Low-level fetch wrapper. */
  async _request(method, path, body) {
    var url = this.baseUrl + path;
    var init = { method: method, headers: Object.assign({}, this._headers) };
    if (body !== undefined) {
      init.body = JSON.stringify(body);
    }
    var res = await fetch(url, init);
    if (!res.ok) {
      var text = await res.text().catch(function () { return ""; });
      throw new OpenFangError("HTTP " + res.status + ": " + text, res.status, text);
    }
    var ct = res.headers.get("content-type") || "";
    if (ct.includes("application/json")) {
      return res.json();
    }
    return res.text();
  }

  /** Low-level SSE streaming. Returns an async iterator of parsed events. */
  async *_stream(method, path, body) {
    var url = this.baseUrl + path;
    var headers = Object.assign({}, this._headers, { Accept: "text/event-stream" });
    var init = { method: method, headers: headers };
    if (body !== undefined) {
      init.body = JSON.stringify(body);
    }
    var res = await fetch(url, init);
    if (!res.ok) {
      var text = await res.text().catch(function () { return ""; });
      throw new OpenFangError("HTTP " + res.status + ": " + text, res.status, text);
    }
    var reader = res.body.getReader();
    var decoder = new TextDecoder();
    var buffer = "";
    while (true) {
      var result = await reader.read();
      if (result.done) break;
      buffer += decoder.decode(result.value, { stream: true });
      var lines = buffer.split("\n");
      buffer = lines.pop() || "";
      for (var i = 0; i < lines.length; i++) {
        var line = lines[i].trim();
        if (line.startsWith("data: ")) {
          var data = line.slice(6);
          if (data === "[DONE]") return;
          try {
            yield JSON.parse(data);
          } catch (_) {
            yield { raw: data };
          }
        }
      }
    }
  }

  /** Health check. */
  async health() {
    return this._request("GET", "/api/health");
  }

  /** Detailed health. */
  async healthDetail() {
    return this._request("GET", "/api/health/detail");
  }

  /** Server status. */
  async status() {
    return this._request("GET", "/api/status");
  }

  /** Server version. */
  async version() {
    return this._request("GET", "/api/version");
  }

  /** Prometheus metrics (text). */
  async metrics() {
    return this._request("GET", "/api/metrics");
  }

  /** Usage statistics. */
  async usage() {
    return this._request("GET", "/api/usage");
  }

  /** Config. */
  async config() {
    return this._request("GET", "/api/config");
  }
}

// ── Agent Resource ──────────────────────────────────────────────

class AgentResource {
  constructor(client) { this._c = client; }

  /** List all agents. */
  async list() {
    return this._c._request("GET", "/api/agents");
  }

  /** Get agent by ID. */
  async get(id) {
    return this._c._request("GET", "/api/agents/" + id);
  }

  /** Create (spawn) a new agent.
   * @param {object} opts - e.g. { template: "assistant", name: "My Agent" }
   */
  async create(opts) {
    return this._c._request("POST", "/api/agents", opts);
  }

  /** Delete (kill) an agent. */
  async delete(id) {
    return this._c._request("DELETE", "/api/agents/" + id);
  }

  /** Stop an agent. */
  async stop(id) {
    return this._c._request("POST", "/api/agents/" + id + "/stop");
  }

  /** Clone an agent. */
  async clone(id) {
    return this._c._request("POST", "/api/agents/" + id + "/clone");
  }

  /** Update agent. */
  async update(id, data) {
    return this._c._request("PUT", "/api/agents/" + id + "/update", data);
  }

  /** Set agent mode. */
  async setMode(id, mode) {
    return this._c._request("PUT", "/api/agents/" + id + "/mode", { mode: mode });
  }

  /** Set agent model. */
  async setModel(id, model) {
    return this._c._request("PUT", "/api/agents/" + id + "/model", { model: model });
  }

  /** Send a message and get the full response. */
  async message(id, text, opts) {
    var body = Object.assign({ message: text }, opts || {});
    return this._c._request("POST", "/api/agents/" + id + "/message", body);
  }

  /** Send a message and stream the response (async iterator of SSE events).
   * @example
   *   for await (const evt of client.agents.stream(id, "Hello")) {
   *     if (evt.type === "text_delta") process.stdout.write(evt.delta);
   *   }
   */
  async *stream(id, text, opts) {
    var body = Object.assign({ message: text }, opts || {});
    yield* this._c._stream("POST", "/api/agents/" + id + "/message/stream", body);
  }

  /** Get agent session. */
  async session(id) {
    return this._c._request("GET", "/api/agents/" + id + "/session");
  }

  /** Reset agent session. */
  async resetSession(id) {
    return this._c._request("POST", "/api/agents/" + id + "/session/reset");
  }

  /** Compact session. */
  async compactSession(id) {
    return this._c._request("POST", "/api/agents/" + id + "/session/compact");
  }

  /** List sessions for an agent. */
  async listSessions(id) {
    return this._c._request("GET", "/api/agents/" + id + "/sessions");
  }

  /** Create a new session. */
  async createSession(id, label) {
    return this._c._request("POST", "/api/agents/" + id + "/sessions", { label: label });
  }

  /** Switch to a session. */
  async switchSession(id, sessionId) {
    return this._c._request("POST", "/api/agents/" + id + "/sessions/" + sessionId + "/switch");
  }

  /** Get agent skills. */
  async getSkills(id) {
    return this._c._request("GET", "/api/agents/" + id + "/skills");
  }

  /** Set agent skills. */
  async setSkills(id, skills) {
    return this._c._request("PUT", "/api/agents/" + id + "/skills", skills);
  }

  /** Upload a file to agent. */
  async upload(id, file, filename) {
    var url = this._c.baseUrl + "/api/agents/" + id + "/upload";
    var form = new FormData();
    form.append("file", file, filename);
    var res = await fetch(url, { method: "POST", body: form });
    if (!res.ok) throw new OpenFangError("Upload failed: " + res.status, res.status);
    return res.json();
  }

  /** Update agent identity. */
  async setIdentity(id, identity) {
    return this._c._request("PATCH", "/api/agents/" + id + "/identity", identity);
  }

  /** Patch agent config. */
  async patchConfig(id, config) {
    return this._c._request("PATCH", "/api/agents/" + id + "/config", config);
  }
}

// ── Session Resource ────────────────────────────────────────────

class SessionResource {
  constructor(client) { this._c = client; }

  async list() {
    return this._c._request("GET", "/api/sessions");
  }

  async delete(id) {
    return this._c._request("DELETE", "/api/sessions/" + id);
  }

  async setLabel(id, label) {
    return this._c._request("PUT", "/api/sessions/" + id + "/label", { label: label });
  }
}

// ── Workflow Resource ───────────────────────────────────────────

class WorkflowResource {
  constructor(client) { this._c = client; }

  async list() {
    return this._c._request("GET", "/api/workflows");
  }

  async create(workflow) {
    return this._c._request("POST", "/api/workflows", workflow);
  }

  async run(id, input) {
    return this._c._request("POST", "/api/workflows/" + id + "/run", input);
  }

  async runs(id) {
    return this._c._request("GET", "/api/workflows/" + id + "/runs");
  }
}

// ── Skill Resource ──────────────────────────────────────────────

class SkillResource {
  constructor(client) { this._c = client; }

  async list() {
    return this._c._request("GET", "/api/skills");
  }

  async install(skill) {
    return this._c._request("POST", "/api/skills/install", skill);
  }

  async uninstall(skill) {
    return this._c._request("POST", "/api/skills/uninstall", skill);
  }

  async search(query) {
    return this._c._request("GET", "/api/marketplace/search?q=" + encodeURIComponent(query));
  }
}

// ── Channel Resource ────────────────────────────────────────────

class ChannelResource {
  constructor(client) { this._c = client; }

  async list() {
    return this._c._request("GET", "/api/channels");
  }

  async configure(name, config) {
    return this._c._request("POST", "/api/channels/" + name + "/configure", config);
  }

  async remove(name) {
    return this._c._request("DELETE", "/api/channels/" + name + "/configure");
  }

  async test(name) {
    return this._c._request("POST", "/api/channels/" + name + "/test");
  }
}

// ── Tool Resource ───────────────────────────────────────────────

class ToolResource {
  constructor(client) { this._c = client; }

  async list() {
    return this._c._request("GET", "/api/tools");
  }
}

// ── Model Resource ──────────────────────────────────────────────

class ModelResource {
  constructor(client) { this._c = client; }

  async list() {
    return this._c._request("GET", "/api/models");
  }

  async get(id) {
    return this._c._request("GET", "/api/models/" + id);
  }

  async aliases() {
    return this._c._request("GET", "/api/models/aliases");
  }
}

// ── Provider Resource ───────────────────────────────────────────

class ProviderResource {
  constructor(client) { this._c = client; }

  async list() {
    return this._c._request("GET", "/api/providers");
  }

  async setKey(name, key) {
    return this._c._request("POST", "/api/providers/" + name + "/key", { key: key });
  }

  async deleteKey(name) {
    return this._c._request("DELETE", "/api/providers/" + name + "/key");
  }

  async test(name) {
    return this._c._request("POST", "/api/providers/" + name + "/test");
  }
}

// ── Memory Resource ─────────────────────────────────────────────

class MemoryResource {
  constructor(client) { this._c = client; }

  async getAll(agentId) {
    return this._c._request("GET", "/api/memory/agents/" + agentId + "/kv");
  }

  async get(agentId, key) {
    return this._c._request("GET", "/api/memory/agents/" + agentId + "/kv/" + key);
  }

  async set(agentId, key, value) {
    return this._c._request("PUT", "/api/memory/agents/" + agentId + "/kv/" + key, { value: value });
  }

  async delete(agentId, key) {
    return this._c._request("DELETE", "/api/memory/agents/" + agentId + "/kv/" + key);
  }
}

// ── Trigger Resource ────────────────────────────────────────────

class TriggerResource {
  constructor(client) { this._c = client; }

  async list() {
    return this._c._request("GET", "/api/triggers");
  }

  async create(trigger) {
    return this._c._request("POST", "/api/triggers", trigger);
  }

  async update(id, trigger) {
    return this._c._request("PUT", "/api/triggers/" + id, trigger);
  }

  async delete(id) {
    return this._c._request("DELETE", "/api/triggers/" + id);
  }
}

// ── Schedule Resource ───────────────────────────────────────────

class ScheduleResource {
  constructor(client) { this._c = client; }

  async list() {
    return this._c._request("GET", "/api/schedules");
  }

  async create(schedule) {
    return this._c._request("POST", "/api/schedules", schedule);
  }

  async update(id, schedule) {
    return this._c._request("PUT", "/api/schedules/" + id, schedule);
  }

  async delete(id) {
    return this._c._request("DELETE", "/api/schedules/" + id);
  }

  async run(id) {
    return this._c._request("POST", "/api/schedules/" + id + "/run");
  }
}

// ── Exports ─────────────────────────────────────────────────────

module.exports = { OpenFang: OpenFang, OpenFangError: OpenFangError };
