import {
  TASK_PLAN_LABEL_MAX_CHARS,
  isTaskPlanToolName,
  normalizeTaskPlanToolName,
  sanitizeAgentTaskPlanInput
} from "./task-plan-labels.js";
const SSE_ENCODER = new TextEncoder();
const SSE_DECODER = new TextDecoder();
const TERMINAL_STATES = /* @__PURE__ */ new Set([
  "done",
  "failed",
  "skipped"
]);
const TASK_LABEL_MAX_CHARS = TASK_PLAN_LABEL_MAX_CHARS;
function deriveTaskLabel(toolName) {
  if (typeof toolName !== "string" || toolName.length === 0) {
    return "Tool call";
  }
  const display = toolName.replace(/__/g, ".");
  const segments = display.split(".");
  const lastSegment = segments[segments.length - 1] ?? display;
  const humanized = lastSegment.replace(/_/g, " ").trim();
  if (humanized.length === 0) return display.slice(0, TASK_LABEL_MAX_CHARS);
  const titled = humanized.charAt(0).toUpperCase() + humanized.slice(1);
  if (segments.length <= 1) {
    return titled.slice(0, TASK_LABEL_MAX_CHARS);
  }
  const moduleSegment = segments[0];
  const moduleLabel = moduleSegment.charAt(0).toUpperCase() + moduleSegment.slice(1).replace(/_/g, " ");
  const combined = `${moduleLabel} \xB7 ${titled}`;
  return combined.slice(0, TASK_LABEL_MAX_CHARS);
}
class TaskPlanAccumulator {
  constructor(planId) {
    this.planId = planId;
    this.tasks = /* @__PURE__ */ new Map();
    this.toolCallToTaskId = /* @__PURE__ */ new Map();
    this.taskToolNames = /* @__PURE__ */ new Map();
    this.internalToolCallIds = /* @__PURE__ */ new Set();
    this.snapshotEmitted = false;
    this.hasAgentAuthoredPlan = false;
  }
  upsert(id, patch) {
    const existing = this.tasks.get(id);
    if (!existing) {
      const created = {
        id,
        label: patch.label ?? "Tool call",
        state: patch.state ?? "running",
        source: patch.source ?? "runtime",
        detail: patch.detail,
        toolCallId: patch.toolCallId
      };
      this.tasks.set(id, { snapshot: created, emitted: false });
      return created;
    }
    const current = existing.snapshot;
    const nextState = TERMINAL_STATES.has(current.state) ? current.state : patch.state ?? current.state;
    const merged = {
      id: current.id,
      label: patch.label ?? current.label,
      state: nextState,
      source: patch.source ?? current.source,
      detail: patch.detail ?? current.detail,
      toolCallId: patch.toolCallId ?? current.toolCallId
    };
    this.tasks.set(id, { snapshot: merged, emitted: existing.emitted });
    return merged;
  }
  makeUniqueTaskId(baseId) {
    let candidate = baseId;
    let suffix = 2;
    while (this.tasks.has(candidate)) {
      candidate = `${baseId}-${suffix}`;
      suffix += 1;
    }
    return candidate;
  }
  emitFullSnapshot() {
    if (this.tasks.size === 0) return [];
    this.snapshotEmitted = true;
    const initialTasks = Array.from(this.tasks.values()).map((e) => e.snapshot);
    for (const e of this.tasks.values()) e.emitted = true;
    return [
      formatSseEvent({
        type: "data-agent-task-plan",
        planId: this.planId,
        tasks: initialTasks
      })
    ];
  }
  handleAgentAuthoredPlan(input) {
    const plan = sanitizeAgentTaskPlanInput(input);
    if (plan.tasks.length === 0) return [];
    this.tasks.clear();
    this.toolCallToTaskId.clear();
    this.taskToolNames.clear();
    this.snapshotEmitted = false;
    this.hasAgentAuthoredPlan = true;
    plan.tasks.forEach((task, index) => {
      const id = this.makeUniqueTaskId(task.id ?? `agent-plan-${index + 1}`);
      const snapshot = {
        id,
        label: task.label,
        state: "pending",
        source: "agent",
        detail: task.detail
      };
      this.tasks.set(id, { snapshot, emitted: false });
      if (task.toolName) {
        this.taskToolNames.set(id, task.toolName);
      }
    });
    return this.emitFullSnapshot();
  }
  resolveTaskIdForToolCall(toolCallId, toolName) {
    const existing = this.toolCallToTaskId.get(toolCallId);
    if (existing) return existing;
    const plannedTaskId = this.findPlannedTaskId(toolName);
    if (plannedTaskId) {
      this.toolCallToTaskId.set(toolCallId, plannedTaskId);
      return plannedTaskId;
    }
    this.toolCallToTaskId.set(toolCallId, toolCallId);
    return toolCallId;
  }
  findPlannedTaskId(toolName) {
    if (!this.hasAgentAuthoredPlan) return null;
    const entries = Array.from(this.tasks.entries());
    const isAvailable = (entry) => !TERMINAL_STATES.has(entry.snapshot.state);
    if (toolName) {
      const exactPending = entries.find(([id, entry]) => {
        return entry.snapshot.state === "pending" && this.taskToolNames.get(id) === toolName;
      });
      if (exactPending) return exactPending[0];
      const exactAvailable = entries.find(([id, entry]) => {
        return isAvailable(entry) && this.taskToolNames.get(id) === toolName;
      });
      if (exactAvailable) return exactAvailable[0];
    }
    const genericPending = entries.find(([id, entry]) => {
      return entry.snapshot.state === "pending" && !this.taskToolNames.has(id);
    });
    if (genericPending) return genericPending[0];
    const genericAvailable = entries.find(([id, entry]) => {
      return isAvailable(entry) && !this.taskToolNames.has(id);
    });
    return genericAvailable?.[0] ?? null;
  }
  existingSnapshot(taskId) {
    return this.tasks.get(taskId)?.snapshot;
  }
  /**
   * Apply a tool lifecycle chunk. Returns the SSE event lines (already
   * `data: ...\n\n`-formatted) that should be injected ahead of forwarding
   * the original chunk to the client.
   */
  handleToolChunk(chunk) {
    if (!chunk || typeof chunk.type !== "string") return [];
    const toolCallId = typeof chunk.toolCallId === "string" ? chunk.toolCallId : null;
    const toolName = normalizeTaskPlanToolName(chunk.toolName);
    if (isTaskPlanToolName(toolName)) {
      if (toolCallId) this.internalToolCallIds.add(toolCallId);
      if (chunk.type === "tool-input-available") {
        return this.handleAgentAuthoredPlan(chunk.input);
      }
      return [];
    }
    if (!toolCallId) return [];
    if (this.internalToolCallIds.has(toolCallId)) return [];
    const taskId = this.resolveTaskIdForToolCall(toolCallId, toolName);
    const existing = this.existingSnapshot(taskId);
    const source = existing?.source ?? "runtime";
    const runtimeLabel = deriveTaskLabel(toolName);
    const runtimeDetail = toolName;
    let nextSnapshot = null;
    switch (chunk.type) {
      case "tool-input-start":
        nextSnapshot = this.upsert(taskId, {
          label: source === "agent" ? existing?.label : runtimeLabel,
          state: "running",
          source,
          toolCallId,
          detail: source === "agent" ? existing?.detail : runtimeDetail
        });
        break;
      case "tool-input-available":
        nextSnapshot = this.upsert(taskId, {
          label: source === "agent" ? existing?.label : runtimeLabel,
          state: "running",
          source,
          toolCallId,
          detail: source === "agent" ? existing?.detail : runtimeDetail
        });
        break;
      case "tool-output-available":
        nextSnapshot = this.upsert(taskId, {
          state: "done",
          source,
          toolCallId
        });
        break;
      case "tool-output-error":
      case "tool-input-error":
        nextSnapshot = this.upsert(taskId, {
          state: "failed",
          source,
          toolCallId
        });
        break;
      default:
        return [];
    }
    if (!nextSnapshot) return [];
    return this.emitForSnapshot(taskId, nextSnapshot);
  }
  emitForSnapshot(id, snapshot) {
    const entry = this.tasks.get(id);
    if (!entry) return [];
    const lines = [];
    if (!this.snapshotEmitted) {
      return this.emitFullSnapshot();
    }
    if (!entry.emitted) {
      lines.push(
        formatSseEvent({
          type: "data-agent-task-update",
          planId: this.planId,
          task: snapshot
        })
      );
      entry.emitted = true;
      return lines;
    }
    lines.push(
      formatSseEvent({
        type: "data-agent-task-update",
        planId: this.planId,
        task: snapshot
      })
    );
    return lines;
  }
}
function formatSseEvent(payload) {
  return `data: ${JSON.stringify(payload)}

`;
}
function injectTaskPlanIntoStream(baseResponse, planId) {
  const { readable, writable } = new TransformStream();
  const writer = writable.getWriter();
  const accumulator = new TaskPlanAccumulator(planId);
  async function pump() {
    if (!baseResponse.body) {
      await writer.close();
      return;
    }
    const reader = baseResponse.body.getReader();
    let textBuffer = "";
    try {
      for (; ; ) {
        const { value, done } = await reader.read();
        if (done) break;
        if (!value) continue;
        textBuffer += SSE_DECODER.decode(value, { stream: true });
        textBuffer = await flushBuffer(textBuffer, accumulator, writer);
      }
      const tail = SSE_DECODER.decode();
      if (tail) {
        textBuffer += tail;
      }
      if (textBuffer.length > 0) {
        await writer.write(SSE_ENCODER.encode(textBuffer));
      }
    } catch {
    } finally {
      reader.releaseLock();
      await writer.close().catch(() => void 0);
    }
  }
  void pump();
  return new Response(readable, {
    status: baseResponse.status,
    headers: baseResponse.headers
  });
}
async function flushBuffer(buffer, accumulator, writer) {
  let rest = buffer;
  for (; ; ) {
    const boundary = rest.indexOf("\n\n");
    if (boundary === -1) break;
    const eventBlock = rest.slice(0, boundary + 2);
    rest = rest.slice(boundary + 2);
    const injected = inspectEventBlock(eventBlock, accumulator);
    for (const line of injected.before) {
      await writer.write(SSE_ENCODER.encode(line));
    }
    await writer.write(SSE_ENCODER.encode(eventBlock));
    for (const line of injected.after) {
      await writer.write(SSE_ENCODER.encode(line));
    }
  }
  return rest;
}
function inspectEventBlock(eventBlock, accumulator) {
  const dataPayload = extractDataPayload(eventBlock);
  if (!dataPayload || dataPayload === "[DONE]") {
    return { before: [], after: [] };
  }
  let parsed = null;
  try {
    parsed = JSON.parse(dataPayload);
  } catch {
    return { before: [], after: [] };
  }
  if (!parsed || typeof parsed.type !== "string") {
    return { before: [], after: [] };
  }
  const type = parsed.type;
  const injected = accumulator.handleToolChunk(parsed);
  if (injected.length === 0) {
    return { before: [], after: [] };
  }
  if (type === "tool-input-start" || type === "tool-input-available") {
    return { before: injected, after: [] };
  }
  return { before: [], after: injected };
}
function extractDataPayload(eventBlock) {
  const lines = eventBlock.split("\n");
  const dataLines = [];
  for (const line of lines) {
    if (line.startsWith("data: ")) {
      dataLines.push(line.slice(6));
    } else if (line.startsWith("data:")) {
      dataLines.push(line.slice(5));
    }
  }
  if (dataLines.length === 0) return null;
  return dataLines.join("\n");
}
export {
  TaskPlanAccumulator,
  deriveTaskLabel,
  formatSseEvent,
  injectTaskPlanIntoStream
};
//# sourceMappingURL=task-plan-stream.js.map
