const DEFAULT_ATTACHMENTS_ENDPOINT = "/api/attachments";
const DEFAULT_AI_CHAT_ENTITY_ID = "ai-chat-draft";
const DEFAULT_CONCURRENCY = 3;
const DEFAULT_PER_FILE_TIMEOUT_MS = 6e4;
function mintRecordId() {
  const cryptoApi = globalThis.crypto;
  if (cryptoApi && typeof cryptoApi.randomUUID === "function") {
    return cryptoApi.randomUUID();
  }
  const random = Math.random().toString(36).slice(2, 10);
  const time = Date.now().toString(36);
  return `ai-chat-${time}-${random}`;
}
function resolveFetchImpl(explicit) {
  if (explicit) return explicit;
  const fallback = globalThis.fetch;
  if (!fallback) {
    throw new Error("No fetch implementation available for uploadAttachmentsForChat");
  }
  return fallback.bind(globalThis);
}
function normalizeServerErrorMessage(raw) {
  if (raw && typeof raw === "object") {
    const err = raw.error;
    if (typeof err === "string" && err.trim()) return err;
    const msg = raw.message;
    if (typeof msg === "string" && msg.trim()) return msg;
  }
  return "";
}
function mapStatusToReason(status, message) {
  if (status === 413) return "size_exceeded";
  if (status === 403 || status === 415) return "mime_rejected";
  if (status === 400) {
    const lower = message.toLowerCase();
    if (lower.includes("file type") || lower.includes("active content")) {
      return "mime_rejected";
    }
    if (lower.includes("size") || lower.includes("quota")) {
      return "size_exceeded";
    }
  }
  return "server";
}
function parseServerItem(payload, fallbackFile, inputIndex) {
  if (!payload || typeof payload !== "object") return null;
  const item = payload.item;
  if (!item || typeof item !== "object") return null;
  const id = item.id;
  if (typeof id !== "string" || !id.trim()) return null;
  const fileName = typeof item.fileName === "string" ? item.fileName : fallbackFile.name;
  const fileSize = item.fileSize;
  const size = typeof fileSize === "number" && Number.isFinite(fileSize) ? fileSize : fallbackFile.size;
  const mimeTypeCandidate = item;
  const mediaType = typeof mimeTypeCandidate.mimeType === "string" && mimeTypeCandidate.mimeType.trim() ? mimeTypeCandidate.mimeType : typeof mimeTypeCandidate.mediaType === "string" && mimeTypeCandidate.mediaType.trim() ? mimeTypeCandidate.mediaType : fallbackFile.type || "application/octet-stream";
  return {
    attachmentId: id,
    fileName,
    originalFileName: fallbackFile.name,
    inputIndex,
    mediaType,
    size
  };
}
async function uploadSingleFile(args) {
  const {
    file,
    fileIndex,
    endpoint,
    entityType,
    recordId,
    partitionCode,
    fetchImpl,
    signal,
    perFileTimeoutMs,
    onProgress
  } = args;
  const buildFailure = (reason, message) => ({
    fileName: file.name,
    originalFileName: file.name,
    inputIndex: fileIndex,
    reason,
    message
  });
  if (signal.aborted) {
    return {
      ok: false,
      failure: buildFailure("aborted", "Upload aborted before starting.")
    };
  }
  const form = new FormData();
  form.append("entityId", entityType);
  form.append("recordId", recordId);
  form.append("file", file);
  if (partitionCode && partitionCode.trim().length > 0) {
    form.append("partitionCode", partitionCode.trim());
  }
  const localController = new AbortController();
  const onParentAbort = () => localController.abort();
  signal.addEventListener("abort", onParentAbort, { once: true });
  let timeoutHandle = null;
  let timedOut = false;
  if (perFileTimeoutMs > 0) {
    timeoutHandle = setTimeout(() => {
      timedOut = true;
      localController.abort();
    }, perFileTimeoutMs);
  }
  const clearTimers = () => {
    if (timeoutHandle !== null) {
      clearTimeout(timeoutHandle);
      timeoutHandle = null;
    }
    signal.removeEventListener("abort", onParentAbort);
  };
  let response;
  try {
    response = await fetchImpl(endpoint, {
      method: "POST",
      body: form,
      signal: localController.signal
    });
  } catch (networkError) {
    clearTimers();
    if (timedOut) {
      return {
        ok: false,
        failure: buildFailure(
          "aborted",
          `Upload timed out after ${Math.round(perFileTimeoutMs / 1e3)}s. The server did not respond \u2014 try again, or attach the file to a record first and reference it in the chat.`
        )
      };
    }
    const aborted = signal.aborted || localController.signal.aborted || networkError?.name === "AbortError";
    if (aborted) {
      return { ok: false, failure: buildFailure("aborted", "Upload aborted.") };
    }
    const message = networkError instanceof Error ? networkError.message : "Network request failed.";
    return { ok: false, failure: buildFailure("network", message) };
  }
  clearTimers();
  let payload = null;
  try {
    const text = await response.text();
    if (text && text.trim()) {
      payload = JSON.parse(text);
    }
  } catch {
    payload = null;
  }
  if (!response.ok) {
    const rawMessage = normalizeServerErrorMessage(payload);
    const fallbackMessage = rawMessage || `Upload failed (${response.status}).`;
    const reason = mapStatusToReason(response.status, rawMessage);
    return { ok: false, failure: buildFailure(reason, fallbackMessage) };
  }
  const item = parseServerItem(payload, file, fileIndex);
  if (!item) {
    return {
      ok: false,
      failure: buildFailure(
        "server",
        "Attachment API returned an unexpected response shape."
      )
    };
  }
  if (onProgress) {
    try {
      onProgress(fileIndex, { loaded: item.size, total: item.size });
    } catch {
    }
  }
  return { ok: true, item };
}
async function uploadAttachmentsForChat(files, options = {}) {
  const items = [];
  const failed = [];
  if (!Array.isArray(files) || files.length === 0) {
    return { items, failed };
  }
  const fetchImpl = resolveFetchImpl(options.fetchImpl);
  const endpoint = options.endpoint?.trim() || DEFAULT_ATTACHMENTS_ENDPOINT;
  const entityType = options.entityType?.trim() || DEFAULT_AI_CHAT_ENTITY_ID;
  const recordId = options.recordId?.trim() || mintRecordId();
  const rawConcurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
  const concurrency = Math.max(
    1,
    Math.min(files.length, Math.floor(rawConcurrency) || DEFAULT_CONCURRENCY)
  );
  const signal = options.signal ?? new AbortController().signal;
  const perFileTimeoutMs = (() => {
    const raw = options.perFileTimeoutMs;
    if (raw === 0) return 0;
    if (typeof raw === "number" && Number.isFinite(raw) && raw > 0) return raw;
    return DEFAULT_PER_FILE_TIMEOUT_MS;
  })();
  const outcomes = new Array(files.length).fill(null);
  let nextIndex = 0;
  const worker = async () => {
    while (true) {
      if (signal.aborted) return;
      const currentIndex = nextIndex;
      if (currentIndex >= files.length) return;
      nextIndex = currentIndex + 1;
      const file = files[currentIndex];
      outcomes[currentIndex] = await uploadSingleFile({
        file,
        fileIndex: currentIndex,
        endpoint,
        entityType,
        recordId,
        partitionCode: options.partitionCode,
        fetchImpl,
        signal,
        perFileTimeoutMs,
        onProgress: options.onProgress
      });
    }
  };
  const workerCount = Math.min(concurrency, files.length);
  const workers = Array.from({ length: workerCount }, () => worker());
  await Promise.all(workers);
  for (let index = 0; index < files.length; index += 1) {
    const outcome = outcomes[index];
    if (outcome && outcome.ok) {
      items.push(outcome.item);
      continue;
    }
    if (outcome && !outcome.ok) {
      failed.push(outcome.failure);
      continue;
    }
    const file = files[index];
    const fallbackName = file?.name ?? `file-${index}`;
    failed.push({
      fileName: fallbackName,
      originalFileName: fallbackName,
      inputIndex: index,
      reason: "aborted",
      message: "Upload aborted before starting."
    });
  }
  return { items, failed };
}
const __testables = {
  DEFAULT_ATTACHMENTS_ENDPOINT,
  DEFAULT_AI_CHAT_ENTITY_ID,
  DEFAULT_CONCURRENCY,
  DEFAULT_PER_FILE_TIMEOUT_MS,
  mapStatusToReason
};
export {
  __testables,
  uploadAttachmentsForChat
};
//# sourceMappingURL=upload-adapter.js.map
