/**
 * API client for the orchestrator backend.
 */

import type { UpdateSettingsInput } from "@shared/settings-schema";
import type {
  ApiResponse,
  ApplicationStage,
  ApplicationTask,
  AppSettings,
  BackupInfo,
  BranchInfo,
  DemoInfoResponse,
  Job,
  JobActionRequest,
  JobActionResponse,
  JobActionStreamEvent,
  JobChatMessage,
  JobChatStreamEvent,
  JobChatThread,
  JobListItem,
  JobOutcome,
  JobSearchProfile,
  JobSource,
  JobsListResponse,
  JobsRevisionResponse,
  JobTracerLinksResponse,
  KeywordSet,
  ManualJobDraft,
  ManualJobFetchResponse,
  ManualJobInferenceResponse,
  PipelineStatusResponse,
  PostApplicationAction,
  PostApplicationActionResponse,
  PostApplicationInboxItem,
  PostApplicationProvider,
  PostApplicationProviderActionResponse,
  PostApplicationRouterStageTarget,
  PostApplicationSyncRun,
  ProfileStatusResponse,
  ResumeProfile,
  ResumeProjectCatalogItem,
  SearchProfile,
  StageEvent,
  StageEventMetadata,
  StageTransitionTarget,
  TracerAnalyticsResponse,
  TracerReadinessResponse,
  ValidationResult,
  VisaSponsor,
  VisaSponsorSearchResponse,
  VisaSponsorStatusResponse,
} from "@shared/types";
import { bucketQueryLength, trackProductEvent } from "@/lib/analytics";
import { showDemoBlockedToast, showDemoSimulatedToast } from "@/lib/demo-toast";

const API_BASE = "/api";

class ApiClientError extends Error {
  requestId?: string;
  status?: number;
  code?: string;

  constructor(
    message: string,
    options?: { requestId?: string; status?: number; code?: string },
  ) {
    const requestId = options?.requestId;
    super(requestId ? `${message} (requestId: ${requestId})` : message);
    this.name = "ApiClientError";
    this.requestId = requestId;
    this.status = options?.status;
    this.code = options?.code;
  }
}

type LegacyApiResponse<T> =
  | {
      success: true;
      data?: T;
      message?: string;
    }
  | {
      success: false;
      error?: string;
      message?: string;
      details?: unknown;
    };

type StreamSseInput =
  | JobActionRequest
  | { content: string; stream: true }
  | { stream: true };

export type BasicAuthCredentials = {
  username: string;
  password: string;
};

export type BasicAuthPromptRequest = {
  endpoint: string;
  method: string;
  attempt: number;
  usernameHint?: string;
  errorMessage?: string;
};

type BasicAuthPromptHandler = (
  request: BasicAuthPromptRequest,
) => Promise<BasicAuthCredentials | null>;

let basicAuthPromptHandler: BasicAuthPromptHandler | null = null;
let basicAuthPromptInFlight: Promise<BasicAuthCredentials | null> | null = null;
let cachedBasicAuthCredentials: BasicAuthCredentials | null = null;

export function setBasicAuthPromptHandler(
  handler: BasicAuthPromptHandler | null,
): void {
  basicAuthPromptHandler = handler;
}

export function clearBasicAuthCredentials(): void {
  cachedBasicAuthCredentials = null;
  clearBasicAuthSessionStorage();
}

/** Clears stored Basic Auth and reloads so you can sign in as a different user (separate job data). */
export function signOutBasicAuthAndReload(): void {
  clearBasicAuthCredentials();
  window.location.reload();
}

export function setCachedBasicAuthCredentials(
  credentials: BasicAuthCredentials | null,
): void {
  cachedBasicAuthCredentials = credentials;
  if (credentials) persistBasicAuthToSessionStorage(credentials);
  else clearBasicAuthSessionStorage();
}

export function __resetApiClientAuthForTests(): void {
  basicAuthPromptHandler = null;
  basicAuthPromptInFlight = null;
  cachedBasicAuthCredentials = null;
  clearBasicAuthSessionStorage();
}

function normalizeApiResponse<T>(
  payload: unknown,
): ApiResponse<T> | LegacyApiResponse<T> {
  if (!payload || typeof payload !== "object") {
    throw new ApiClientError("API request failed: malformed JSON response");
  }
  const response = payload as Record<string, unknown>;
  if (typeof response.ok === "boolean") {
    return payload as ApiResponse<T>;
  }
  if (typeof response.success === "boolean") {
    return payload as LegacyApiResponse<T>;
  }
  throw new ApiClientError("API request failed: unexpected response shape");
}

function describeAction(endpoint: string, method?: string): string {
  const verb = (method || "GET").toUpperCase();
  const normalized = endpoint.split("?")[0] || endpoint;
  if (verb === "POST" && normalized === "/pipeline/run") {
    return "Pipeline run used demo simulation.";
  }
  if (verb === "POST" && normalized.endsWith("/process")) {
    return "Job processing used demo simulation.";
  }
  if (verb === "POST" && normalized.endsWith("/summarize")) {
    return "Summary generation used demo simulation.";
  }
  if (verb === "POST" && normalized.endsWith("/generate-pdf")) {
    return "PDF generation used demo simulation.";
  }
  if (verb === "POST" && normalized.endsWith("/rescore")) {
    return "Suitability rescoring used demo simulation.";
  }
  if (verb === "POST" && normalized.endsWith("/apply")) {
    return "Apply flow used demo simulation and no external sync.";
  }
  if (normalized.startsWith("/onboarding/validate")) {
    return "Credential validation is simulated in demo mode.";
  }
  return "This action ran in demo simulation mode.";
}

export function encodeBasicAuthHeaderValue(
  credentials: BasicAuthCredentials,
): string {
  return `Basic ${btoa(`${credentials.username}:${credentials.password}`)}`;
}

const BASIC_AUTH_SESSION_STORAGE_KEY = "jobops_basic_auth_v1";

/** Read persisted credentials without updating the in-memory API client cache. */
export function peekBasicAuthSessionCredentials(): BasicAuthCredentials | null {
  return readBasicAuthFromSessionStorage();
}

/** In-memory session (after prompt) or persisted session (after reload). */
export function getActiveBasicAuthCredentials(): BasicAuthCredentials | null {
  if (cachedBasicAuthCredentials) return cachedBasicAuthCredentials;
  return readBasicAuthFromSessionStorage();
}

function readBasicAuthFromSessionStorage(): BasicAuthCredentials | null {
  try {
    const raw = sessionStorage.getItem(BASIC_AUTH_SESSION_STORAGE_KEY);
    if (!raw) return null;
    const parsed = JSON.parse(raw) as unknown;
    if (!parsed || typeof parsed !== "object") return null;
    const rec = parsed as Record<string, unknown>;
    const username =
      typeof rec.username === "string" ? rec.username.trim() : "";
    const password = typeof rec.password === "string" ? rec.password : "";
    if (!username || !password) return null;
    return { username, password };
  } catch {
    return null;
  }
}

function persistBasicAuthToSessionStorage(
  credentials: BasicAuthCredentials,
): void {
  try {
    sessionStorage.setItem(
      BASIC_AUTH_SESSION_STORAGE_KEY,
      JSON.stringify(credentials),
    );
  } catch {
    // Ignore quota / private mode.
  }
}

function clearBasicAuthSessionStorage(): void {
  try {
    sessionStorage.removeItem(BASIC_AUTH_SESSION_STORAGE_KEY);
  } catch {
    // Ignore.
  }
}

function normalizeHeaders(headers?: HeadersInit): Record<string, string> {
  if (!headers) return {};
  if (headers instanceof Headers) {
    const next: Record<string, string> = {};
    headers.forEach((value, key) => {
      next[key] = value;
    });
    return next;
  }
  if (Array.isArray(headers)) {
    return Object.fromEntries(headers);
  }
  return { ...headers };
}

function isWriteMethod(method: string): boolean {
  return !["GET", "HEAD", "OPTIONS"].includes(method.toUpperCase());
}

function isUnauthorizedResponse<T>(
  response: Response,
  parsed: ApiResponse<T> | LegacyApiResponse<T>,
): boolean {
  if (response.status !== 401) return false;
  if ("ok" in parsed) {
    return parsed.ok ? false : parsed.error.code === "UNAUTHORIZED";
  }
  return !parsed.success;
}

function toApiError<T>(
  response: Response,
  parsed: ApiResponse<T> | LegacyApiResponse<T>,
): ApiClientError {
  if ("ok" in parsed) {
    if (!parsed.ok) {
      return new ApiClientError(parsed.error.message || "API request failed", {
        requestId: parsed.meta?.requestId,
        status: response.status,
        code: parsed.error.code,
      });
    }
    return new ApiClientError("API request failed", {
      requestId: parsed.meta?.requestId,
      status: response.status,
    });
  }
  if (parsed.success) {
    return new ApiClientError(parsed.message || "API request failed", {
      status: response.status,
    });
  }
  return new ApiClientError(
    parsed.error || parsed.message || "API request failed",
    {
      status: response.status,
    },
  );
}

async function requestBasicAuthCredentials(
  request: BasicAuthPromptRequest,
): Promise<BasicAuthCredentials | null> {
  if (!basicAuthPromptHandler) return null;
  if (!basicAuthPromptInFlight) {
    basicAuthPromptInFlight = basicAuthPromptHandler(request).finally(() => {
      basicAuthPromptInFlight = null;
    });
  }
  return basicAuthPromptInFlight;
}

/** Opens the Basic Auth dialog (when mounted) without waiting for a write 401 first. */
export async function promptBasicAuthDialog(
  request?: Partial<Omit<BasicAuthPromptRequest, "attempt">>,
): Promise<BasicAuthCredentials | null> {
  return requestBasicAuthCredentials({
    endpoint: request?.endpoint ?? "/auth/verify",
    method: request?.method ?? "POST",
    attempt: 1,
    usernameHint: request?.usernameHint,
    errorMessage: request?.errorMessage,
  });
}

export type BasicAuthBasicStatusResponse = {
  basicAuthEnabled: boolean;
};

export async function getBasicAuthBasicStatus(): Promise<BasicAuthBasicStatusResponse> {
  const response = await fetch(`${API_BASE}/auth/basic-status`);
  const text = await response.text();
  let payload: unknown;
  try {
    payload = JSON.parse(text);
  } catch {
    throw new ApiClientError(
      `Server error (${response.status}): Expected JSON.`,
      { status: response.status },
    );
  }
  const parsed = normalizeApiResponse<BasicAuthBasicStatusResponse>(payload);
  if ("ok" in parsed && parsed.ok) {
    return parsed.data as BasicAuthBasicStatusResponse;
  }
  throw new ApiClientError("Failed to read Basic Auth status", {
    status: response.status,
  });
}

async function verifyBasicAuthCredentials(
  credentials: BasicAuthCredentials,
): Promise<boolean> {
  const response = await fetch(`${API_BASE}/auth/verify`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: encodeBasicAuthHeaderValue(credentials),
    },
    body: "{}",
  });
  if (response.status === 401) return false;
  const text = await response.text();
  try {
    const payload = JSON.parse(text) as { ok?: boolean };
    return payload.ok === true;
  } catch {
    return false;
  }
}

export async function activateSearchProfileForBasicAuthUser(
  username: string,
  credentials: BasicAuthCredentials,
): Promise<void> {
  const auth = encodeBasicAuthHeaderValue(credentials);
  const listResponse = await fetch(`${API_BASE}/profiles`);
  const listText = await listResponse.text();
  let listPayload: {
    ok?: boolean;
    data?: Array<{ id: string; data: { basicAuthUser?: string | null } }>;
  };
  try {
    listPayload = JSON.parse(listText) as typeof listPayload;
  } catch {
    return;
  }
  if (!listPayload.ok || !Array.isArray(listPayload.data)) return;
  const normalizedUsername = username.trim().toLowerCase();
  const matches = listPayload.data.filter((row) => {
    const aliases = (row.data?.basicAuthUser ?? "")
      .split(",")
      .map((part) => part.trim().toLowerCase())
      .filter(Boolean);
    return aliases.includes(normalizedUsername);
  });
  if (matches.length !== 1) return;
  await fetch(`${API_BASE}/profiles/${matches[0].id}/activate`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: auth,
    },
    body: "{}",
  });
}

/**
 * When Basic Auth is enabled: restore session credentials, verify, optionally activate
 * the search profile tied to `basicAuthUser`. Returns true when no interactive prompt is needed.
 */
export async function tryBootstrapBasicAuthFromSessionOnly(
  preloaded?: BasicAuthBasicStatusResponse,
): Promise<boolean> {
  const status = preloaded ?? (await getBasicAuthBasicStatus());
  if (!status.basicAuthEnabled) return true;

  const fromSession = readBasicAuthFromSessionStorage();
  if (!fromSession) return false;

  const ok = await verifyBasicAuthCredentials(fromSession);
  if (!ok) {
    clearBasicAuthSessionStorage();
    return false;
  }
  setCachedBasicAuthCredentials(fromSession);
  await activateSearchProfileForBasicAuthUser(
    fromSession.username,
    fromSession,
  );
  return true;
}

/** Prompt, verify, persist credentials, and activate matching search profile. */
export async function bootstrapBasicAuthWithPromptOnly(): Promise<void> {
  const credentials = await promptBasicAuthDialog();
  if (!credentials) return;
  const verified = await verifyBasicAuthCredentials(credentials);
  if (!verified) {
    const retry = await promptBasicAuthDialog({
      errorMessage: "Invalid credentials. Please try again.",
    });
    if (!retry) return;
    const ok2 = await verifyBasicAuthCredentials(retry);
    if (!ok2) return;
    setCachedBasicAuthCredentials(retry);
    await activateSearchProfileForBasicAuthUser(retry.username, retry);
    return;
  }
  setCachedBasicAuthCredentials(credentials);
  await activateSearchProfileForBasicAuthUser(
    credentials.username,
    credentials,
  );
}

export async function bootstrapBasicAuthOnLoad(
  preloaded?: BasicAuthBasicStatusResponse,
): Promise<void> {
  if (await tryBootstrapBasicAuthFromSessionOnly(preloaded)) return;
  await bootstrapBasicAuthWithPromptOnly();
}

async function fetchAndParse<T>(
  endpoint: string,
  options: RequestInit | undefined,
  authHeader?: string,
): Promise<{
  response: Response;
  parsed: ApiResponse<T> | LegacyApiResponse<T>;
}> {
  const headers: Record<string, string> = {
    "Content-Type": "application/json",
    ...normalizeHeaders(options?.headers),
  };
  if (authHeader) headers.Authorization = authHeader;
  const response = await fetch(`${API_BASE}${endpoint}`, {
    ...options,
    headers,
  });

  const text = await response.text();

  let payload: unknown;
  try {
    payload = JSON.parse(text);
  } catch {
    // If the response is not JSON, it's likely an HTML error page.
    throw new ApiClientError(
      `Server error (${response.status}): Expected JSON but received HTML. Is the backend server running?`,
      { status: response.status },
    );
  }
  const parsed = normalizeApiResponse<T>(payload);
  return { response, parsed };
}

async function fetchApi<T>(
  endpoint: string,
  options?: RequestInit,
): Promise<T> {
  const method = (options?.method || "GET").toUpperCase();
  const activeCreds = getActiveBasicAuthCredentials();
  let authHeader = activeCreds
    ? encodeBasicAuthHeaderValue(activeCreds)
    : undefined;
  let authAttempt = 0;
  let usernameHint = activeCreds?.username;

  const shouldPromptForAuth = (args: {
    method: string;
    endpoint: string;
    response: Response;
    parsed: ApiResponse<unknown> | LegacyApiResponse<unknown>;
  }): boolean => {
    if (!basicAuthPromptHandler) return false;
    if (authAttempt >= 2) return false;
    if (!isUnauthorizedResponse(args.response, args.parsed)) return false;

    // By default we only prompt for write methods. However, some parts of the UI
    // (Settings/Profile) require auth even for reads. Without prompting, those
    // screens appear to "forget" saved data after refresh.
    if (isWriteMethod(args.method)) return true;

    const readAuthEndpoints = new Set<string>([
      "/settings",
      "/profile",
      "/profiles",
    ]);
    return readAuthEndpoints.has(args.endpoint);
  };

  while (true) {
    const { response, parsed } = await fetchAndParse(
      endpoint,
      options,
      authHeader,
    );

    if (shouldPromptForAuth({ method, endpoint, response, parsed })) {
      const credentials = await requestBasicAuthCredentials({
        endpoint,
        method,
        attempt: authAttempt + 1,
        usernameHint,
        errorMessage:
          authAttempt > 0
            ? "Invalid credentials. Please try again."
            : undefined,
      });
      if (!credentials) {
        throw toApiError(response, parsed);
      }
      setCachedBasicAuthCredentials(credentials);
      usernameHint = credentials.username;
      authHeader = encodeBasicAuthHeaderValue(credentials);
      authAttempt += 1;
      continue;
    }

    if ("ok" in parsed) {
      if (!parsed.ok) {
        if (parsed.error.code === "UNAUTHORIZED") {
          clearBasicAuthCredentials();
        }
        if (parsed.meta?.blockedReason) {
          showDemoBlockedToast(parsed.meta.blockedReason);
        }
        throw toApiError(response, parsed);
      }
      if (parsed.meta?.simulated) {
        showDemoSimulatedToast(describeAction(endpoint, options?.method));
      }
      return parsed.data as T;
    }

    if (!parsed.success) {
      if (response.status === 401) {
        clearBasicAuthCredentials();
      }
      throw toApiError(response, parsed);
    }

    const data = parsed.data;
    if (data !== undefined) return data as T;
    if (parsed.message !== undefined) return { message: parsed.message } as T;
    return null as T;
  }
}

// Jobs API
export function getJobs(): Promise<JobsListResponse<JobListItem>>;
export function getJobs(options: {
  statuses?: string[];
  view?: "list";
}): Promise<JobsListResponse<JobListItem>>;
export function getJobs(options?: {
  statuses?: string[];
  view: "full";
}): Promise<JobsListResponse<Job>>;
export async function getJobs(options?: {
  statuses?: string[];
  view?: "full" | "list";
}): Promise<JobsListResponse<Job> | JobsListResponse<JobListItem>> {
  const params = new URLSearchParams();
  if (options?.statuses?.length)
    params.set("status", options.statuses.join(","));
  if (options?.view) params.set("view", options.view);
  const query = params.toString();
  return fetchApi<JobsListResponse<Job> | JobsListResponse<JobListItem>>(
    `/jobs${query ? `?${query}` : ""}`,
  );
}

export async function getJobsRevision(options?: {
  statuses?: string[];
}): Promise<JobsRevisionResponse> {
  const params = new URLSearchParams();
  if (options?.statuses?.length)
    params.set("status", options.statuses.join(","));
  const query = params.toString();
  return fetchApi<JobsRevisionResponse>(
    `/jobs/revision${query ? `?${query}` : ""}`,
  );
}

export async function getJob(id: string): Promise<Job> {
  return fetchApi<Job>(`/jobs/${id}?t=${Date.now()}`);
}

export async function updateJob(
  id: string,
  update: Partial<Job>,
): Promise<Job> {
  return fetchApi<Job>(`/jobs/${id}`, {
    method: "PATCH",
    body: JSON.stringify(update),
  });
}

export async function getTracerAnalytics(options?: {
  jobId?: string;
  from?: number;
  to?: number;
  includeBots?: boolean;
  limit?: number;
}): Promise<TracerAnalyticsResponse> {
  const params = new URLSearchParams();
  if (options?.jobId) params.set("jobId", options.jobId);
  if (typeof options?.from === "number") {
    params.set("from", String(options.from));
  }
  if (typeof options?.to === "number") {
    params.set("to", String(options.to));
  }
  if (typeof options?.includeBots === "boolean") {
    params.set("includeBots", options.includeBots ? "1" : "0");
  }
  if (typeof options?.limit === "number") {
    params.set("limit", String(options.limit));
  }

  const query = params.toString();
  return fetchApi<TracerAnalyticsResponse>(
    `/tracer-links/analytics${query ? `?${query}` : ""}`,
  );
}

export async function getTracerReadiness(options?: {
  force?: boolean;
}): Promise<TracerReadinessResponse> {
  const params = new URLSearchParams();
  if (options?.force) params.set("force", "1");
  const query = params.toString();
  return fetchApi<TracerReadinessResponse>(
    `/tracer-links/readiness${query ? `?${query}` : ""}`,
  );
}

export async function getJobTracerLinks(
  jobId: string,
  options?: {
    from?: number;
    to?: number;
    includeBots?: boolean;
  },
): Promise<JobTracerLinksResponse> {
  const params = new URLSearchParams();
  if (typeof options?.from === "number") {
    params.set("from", String(options.from));
  }
  if (typeof options?.to === "number") {
    params.set("to", String(options.to));
  }
  if (typeof options?.includeBots === "boolean") {
    params.set("includeBots", options.includeBots ? "1" : "0");
  }
  const query = params.toString();
  return fetchApi<JobTracerLinksResponse>(
    `/tracer-links/jobs/${encodeURIComponent(jobId)}${query ? `?${query}` : ""}`,
  );
}

async function streamSseEvents<TEvent>(
  endpoint: string,
  input: StreamSseInput,
  handlers: {
    onEvent: (event: TEvent) => void;
    signal?: AbortSignal;
  },
): Promise<void> {
  const headers: Record<string, string> = {
    "Content-Type": "application/json",
  };
  if (cachedBasicAuthCredentials) {
    headers.Authorization = encodeBasicAuthHeaderValue(
      cachedBasicAuthCredentials,
    );
  }

  const response = await fetch(`${API_BASE}${endpoint}`, {
    method: "POST",
    headers,
    body: JSON.stringify(input),
    signal: handlers.signal,
  });

  if (!response.ok) {
    let errorMessage = `Stream request failed with status ${response.status}`;
    try {
      const payload = await response.json();
      const parsed = normalizeApiResponse(payload);
      if ("ok" in parsed && !parsed.ok) {
        errorMessage = parsed.error.message || errorMessage;
      }
    } catch {
      // ignore parse errors; keep status-based message
    }
    throw new ApiClientError(errorMessage, {
      status: response.status,
    });
  }

  if (!response.body) {
    throw new ApiClientError("Streaming not supported by this browser");
  }

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

  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      buffer += decoder.decode(value, { stream: true });

      let separatorIndex = buffer.indexOf("\n\n");
      while (separatorIndex !== -1) {
        const frame = buffer.slice(0, separatorIndex);
        buffer = buffer.slice(separatorIndex + 2);
        const dataLines = frame
          .split("\n")
          .filter((line) => line.startsWith("data:"))
          .map((line) => line.slice(5).trim())
          .filter(Boolean);

        for (const line of dataLines) {
          let parsedEvent: TEvent;
          try {
            parsedEvent = JSON.parse(line) as TEvent;
          } catch {
            // Ignore malformed events to keep stream resilient
            continue;
          }
          handlers.onEvent(parsedEvent);
        }
        separatorIndex = buffer.indexOf("\n\n");
      }
    }
  } finally {
    try {
      await reader.cancel();
    } catch {
      // Ignore cancellation errors when stream is already closed
    }
  }
}

export async function listJobChatThreads(jobId: string): Promise<{
  threads: JobChatThread[];
}> {
  return fetchApi<{ threads: JobChatThread[] }>(`/jobs/${jobId}/chat/threads`);
}

export async function listJobGhostwriterMessages(
  jobId: string,
  options?: { limit?: number; offset?: number },
): Promise<{ messages: JobChatMessage[]; branches: BranchInfo[] }> {
  const params = new URLSearchParams();
  if (typeof options?.limit === "number") {
    params.set("limit", String(options.limit));
  }
  if (typeof options?.offset === "number") {
    params.set("offset", String(options.offset));
  }
  const query = params.toString();
  return fetchApi<{ messages: JobChatMessage[]; branches: BranchInfo[] }>(
    `/jobs/${jobId}/chat/messages${query ? `?${query}` : ""}`,
  );
}

export async function createJobChatThread(
  jobId: string,
  input?: { title?: string | null },
): Promise<{ thread: JobChatThread }> {
  return fetchApi<{ thread: JobChatThread }>(`/jobs/${jobId}/chat/threads`, {
    method: "POST",
    body: JSON.stringify({
      title: input?.title ?? null,
    }),
  });
}

export async function listJobChatMessages(
  jobId: string,
  threadId: string,
  options?: { limit?: number; offset?: number },
): Promise<{ messages: JobChatMessage[] }> {
  const params = new URLSearchParams();
  if (typeof options?.limit === "number") {
    params.set("limit", String(options.limit));
  }
  if (typeof options?.offset === "number") {
    params.set("offset", String(options.offset));
  }
  const query = params.toString();
  return fetchApi<{ messages: JobChatMessage[] }>(
    `/jobs/${jobId}/chat/threads/${threadId}/messages${query ? `?${query}` : ""}`,
  );
}

export async function sendJobChatMessage(
  jobId: string,
  threadId: string,
  input: { content: string },
): Promise<{
  userMessage: JobChatMessage;
  assistantMessage: JobChatMessage | null;
  runId: string;
}> {
  return fetchApi<{
    userMessage: JobChatMessage;
    assistantMessage: JobChatMessage | null;
    runId: string;
  }>(`/jobs/${jobId}/chat/threads/${threadId}/messages`, {
    method: "POST",
    body: JSON.stringify(input),
  });
}

export async function streamJobChatMessage(
  jobId: string,
  threadId: string,
  input: { content: string; signal?: AbortSignal },
  handlers: {
    onEvent: (event: JobChatStreamEvent) => void;
  },
): Promise<void> {
  return streamSseEvents(
    `/jobs/${jobId}/chat/threads/${threadId}/messages`,
    { content: input.content, stream: true },
    {
      onEvent: handlers.onEvent,
      signal: input.signal,
    },
  );
}

export async function streamJobGhostwriterMessage(
  jobId: string,
  input: { content: string; signal?: AbortSignal },
  handlers: {
    onEvent: (event: JobChatStreamEvent) => void;
  },
): Promise<void> {
  return streamSseEvents(
    `/jobs/${jobId}/chat/messages`,
    { content: input.content, stream: true },
    {
      onEvent: handlers.onEvent,
      signal: input.signal,
    },
  );
}

export async function cancelJobChatRun(
  jobId: string,
  threadId: string,
  runId: string,
): Promise<{ cancelled: boolean; alreadyFinished: boolean }> {
  return fetchApi<{ cancelled: boolean; alreadyFinished: boolean }>(
    `/jobs/${jobId}/chat/threads/${threadId}/runs/${runId}/cancel`,
    {
      method: "POST",
      body: JSON.stringify({}),
    },
  );
}

export async function resetJobGhostwriterConversation(
  jobId: string,
): Promise<{ deletedMessages: number; deletedRuns: number }> {
  return fetchApi<{ deletedMessages: number; deletedRuns: number }>(
    `/jobs/${jobId}/chat/reset`,
    {
      method: "POST",
      body: JSON.stringify({}),
    },
  );
}

export async function cancelJobGhostwriterRun(
  jobId: string,
  runId: string,
): Promise<{ cancelled: boolean; alreadyFinished: boolean }> {
  return fetchApi<{ cancelled: boolean; alreadyFinished: boolean }>(
    `/jobs/${jobId}/chat/runs/${runId}/cancel`,
    {
      method: "POST",
      body: JSON.stringify({}),
    },
  );
}

export async function regenerateJobChatMessage(
  jobId: string,
  threadId: string,
  assistantMessageId: string,
): Promise<{ runId: string; assistantMessage: JobChatMessage | null }> {
  return fetchApi<{ runId: string; assistantMessage: JobChatMessage | null }>(
    `/jobs/${jobId}/chat/threads/${threadId}/messages/${assistantMessageId}/regenerate`,
    {
      method: "POST",
      body: JSON.stringify({}),
    },
  );
}

export async function streamRegenerateJobChatMessage(
  jobId: string,
  threadId: string,
  assistantMessageId: string,
  input: { signal?: AbortSignal },
  handlers: {
    onEvent: (event: JobChatStreamEvent) => void;
  },
): Promise<void> {
  return streamSseEvents(
    `/jobs/${jobId}/chat/threads/${threadId}/messages/${assistantMessageId}/regenerate`,
    { stream: true },
    {
      onEvent: handlers.onEvent,
      signal: input.signal,
    },
  );
}

export async function streamRegenerateJobGhostwriterMessage(
  jobId: string,
  assistantMessageId: string,
  input: { signal?: AbortSignal },
  handlers: {
    onEvent: (event: JobChatStreamEvent) => void;
  },
): Promise<void> {
  return streamSseEvents(
    `/jobs/${jobId}/chat/messages/${assistantMessageId}/regenerate`,
    { stream: true },
    {
      onEvent: handlers.onEvent,
      signal: input.signal,
    },
  );
}

export async function editJobGhostwriterMessage(
  jobId: string,
  messageId: string,
  input: { content: string; signal?: AbortSignal },
  handlers: {
    onEvent: (event: JobChatStreamEvent) => void;
  },
): Promise<void> {
  return streamSseEvents(
    `/jobs/${jobId}/chat/messages/${messageId}/edit`,
    { content: input.content, stream: true },
    {
      onEvent: handlers.onEvent,
      signal: input.signal,
    },
  );
}

export async function switchJobGhostwriterBranch(
  jobId: string,
  messageId: string,
): Promise<{ messages: JobChatMessage[]; branches: BranchInfo[] }> {
  return fetchApi<{ messages: JobChatMessage[]; branches: BranchInfo[] }>(
    `/jobs/${jobId}/chat/messages/${messageId}/switch-branch`,
    {
      method: "POST",
      body: JSON.stringify({}),
    },
  );
}

function toJobIdList(idOrIds: string | string[]): string[] {
  return Array.isArray(idOrIds) ? idOrIds : [idOrIds];
}

export async function processJob(
  ids: string[],
  options?: { force?: boolean },
): Promise<JobActionResponse>;
export async function processJob(
  id: string,
  options?: { force?: boolean },
): Promise<Job>;
export async function processJob(
  idOrIds: string | string[],
  options?: { force?: boolean },
): Promise<Job | JobActionResponse> {
  const jobIds = toJobIdList(idOrIds);
  const result = await runJobAction({
    action: "move_to_ready",
    jobIds,
    ...(options?.force ? { options: { force: true } } : {}),
  });

  if (Array.isArray(idOrIds)) return result;
  return getSingleJobFromActionResult(result, idOrIds);
}

export async function rescoreJob(ids: string[]): Promise<JobActionResponse>;
export async function rescoreJob(id: string): Promise<Job>;
export async function rescoreJob(
  idOrIds: string | string[],
): Promise<Job | JobActionResponse> {
  const jobIds = toJobIdList(idOrIds);
  const result = await runJobAction({
    action: "rescore",
    jobIds,
  });
  if (Array.isArray(idOrIds)) return result;
  return getSingleJobFromActionResult(result, idOrIds);
}

export async function summarizeJob(
  id: string,
  options?: { force?: boolean },
): Promise<Job> {
  const query = options?.force ? "?force=1" : "";
  return fetchApi<Job>(`/jobs/${id}/summarize${query}`, {
    method: "POST",
  });
}

export async function generateJobPdf(id: string): Promise<Job> {
  return fetchApi<Job>(`/jobs/${id}/generate-pdf`, {
    method: "POST",
  });
}

export async function checkSponsor(id: string): Promise<Job> {
  return fetchApi<Job>(`/jobs/${id}/check-sponsor`, {
    method: "POST",
  });
}

export async function markAsApplied(id: string): Promise<Job> {
  return fetchApi<Job>(`/jobs/${id}/apply`, {
    method: "POST",
  });
}

export async function skipJob(ids: string[]): Promise<JobActionResponse>;
export async function skipJob(id: string): Promise<Job>;
export async function skipJob(
  idOrIds: string | string[],
): Promise<Job | JobActionResponse> {
  const jobIds = toJobIdList(idOrIds);
  const result = await runJobAction({
    action: "skip",
    jobIds,
  });
  if (Array.isArray(idOrIds)) return result;
  return getSingleJobFromActionResult(result, idOrIds);
}

export async function runJobAction(
  input: JobActionRequest,
): Promise<JobActionResponse> {
  return fetchApi<JobActionResponse>("/jobs/actions", {
    method: "POST",
    body: JSON.stringify(input),
  });
}

function getSingleJobFromActionResult(
  response: JobActionResponse,
  jobId: string,
): Job {
  const result = response.results.find((entry) => entry.jobId === jobId);
  if (!result) {
    throw new ApiClientError("Job action did not return a result for the job");
  }
  if (!result.ok) {
    throw new ApiClientError(result.error.message, {
      code: result.error.code,
    });
  }
  return result.job;
}

export async function streamJobAction(
  input: JobActionRequest,
  handlers: {
    onEvent: (event: JobActionStreamEvent) => void;
    signal?: AbortSignal;
  },
): Promise<void> {
  return streamSseEvents<JobActionStreamEvent>(
    "/jobs/actions/stream",
    input,
    handlers,
  );
}

export async function getJobStageEvents(id: string): Promise<StageEvent[]> {
  return fetchApi<StageEvent[]>(`/jobs/${id}/events?t=${Date.now()}`);
}

export async function getJobTasks(
  id: string,
  options?: { includeCompleted?: boolean },
): Promise<ApplicationTask[]> {
  const params = new URLSearchParams();
  if (options?.includeCompleted) params.set("includeCompleted", "1");
  params.set("t", Date.now().toString());
  const query = params.toString();
  return fetchApi<ApplicationTask[]>(`/jobs/${id}/tasks?${query}`);
}

export async function transitionJobStage(
  id: string,
  input: {
    toStage: StageTransitionTarget;
    occurredAt?: number | null;
    metadata?: StageEventMetadata | null;
    outcome?: JobOutcome | null;
  },
): Promise<StageEvent> {
  return fetchApi<StageEvent>(`/jobs/${id}/stages`, {
    method: "POST",
    body: JSON.stringify(input),
  });
}

export async function updateJobStageEvent(
  id: string,
  eventId: string,
  input: {
    toStage?: ApplicationStage;
    occurredAt?: number | null;
    metadata?: StageEventMetadata | null;
    outcome?: JobOutcome | null;
  },
): Promise<void> {
  return fetchApi<void>(`/jobs/${id}/events/${eventId}`, {
    method: "PATCH",
    body: JSON.stringify(input),
  });
}

export async function deleteJobStageEvent(
  id: string,
  eventId: string,
): Promise<void> {
  return fetchApi<void>(`/jobs/${id}/events/${eventId}`, {
    method: "DELETE",
  });
}

export async function updateJobOutcome(
  id: string,
  input: { outcome: JobOutcome | null; closedAt?: number | null },
): Promise<Job> {
  return fetchApi<Job>(`/jobs/${id}/outcome`, {
    method: "PATCH",
    body: JSON.stringify(input),
  });
}

// Pipeline API
export async function getPipelineStatus(): Promise<PipelineStatusResponse> {
  return fetchApi<PipelineStatusResponse>("/pipeline/status");
}

export async function runPipeline(config?: {
  topN?: number;
  minSuitabilityScore?: number;
  sources?: JobSource[];
}): Promise<{ message: string }> {
  return fetchApi<{ message: string }>("/pipeline/run", {
    method: "POST",
    body: JSON.stringify(config || {}),
  });
}

export async function cancelPipeline(): Promise<{
  message: string;
  pipelineRunId: string | null;
  alreadyRequested: boolean;
}> {
  return fetchApi<{
    message: string;
    pipelineRunId: string | null;
    alreadyRequested: boolean;
  }>("/pipeline/cancel", {
    method: "POST",
  });
}

// Post-Application Tracking API
export async function postApplicationProviderConnect(input: {
  provider?: PostApplicationProvider;
  accountKey?: string;
  payload?: Record<string, unknown>;
}): Promise<PostApplicationProviderActionResponse> {
  const provider = input.provider ?? "gmail";
  return fetchApi<PostApplicationProviderActionResponse>(
    `/post-application/providers/${provider}/actions/connect`,
    {
      method: "POST",
      body: JSON.stringify({
        ...(input.accountKey ? { accountKey: input.accountKey } : {}),
        ...(input.payload ? { payload: input.payload } : {}),
      }),
    },
  );
}

export async function postApplicationGmailOauthStart(input?: {
  accountKey?: string;
}): Promise<{
  provider: "gmail";
  accountKey: string;
  authorizationUrl: string;
  state: string;
}> {
  const params = new URLSearchParams();
  if (input?.accountKey) params.set("accountKey", input.accountKey);
  const query = params.toString();
  return fetchApi<{
    provider: "gmail";
    accountKey: string;
    authorizationUrl: string;
    state: string;
  }>(
    `/post-application/providers/gmail/oauth/start${query ? `?${query}` : ""}`,
  );
}

export async function postApplicationGmailOauthExchange(input: {
  accountKey?: string;
  state: string;
  code: string;
}): Promise<PostApplicationProviderActionResponse> {
  return fetchApi<PostApplicationProviderActionResponse>(
    "/post-application/providers/gmail/oauth/exchange",
    {
      method: "POST",
      body: JSON.stringify({
        ...(input.accountKey ? { accountKey: input.accountKey } : {}),
        state: input.state,
        code: input.code,
      }),
    },
  );
}

export async function postApplicationProviderStatus(input?: {
  provider?: PostApplicationProvider;
  accountKey?: string;
}): Promise<PostApplicationProviderActionResponse> {
  const provider = input?.provider ?? "gmail";
  return fetchApi<PostApplicationProviderActionResponse>(
    `/post-application/providers/${provider}/actions/status`,
    {
      method: "POST",
      body: JSON.stringify({
        ...(input?.accountKey ? { accountKey: input.accountKey } : {}),
      }),
    },
  );
}

export async function postApplicationProviderSync(input?: {
  provider?: PostApplicationProvider;
  accountKey?: string;
  maxMessages?: number;
  searchDays?: number;
}): Promise<PostApplicationProviderActionResponse> {
  const provider = input?.provider ?? "gmail";
  return fetchApi<PostApplicationProviderActionResponse>(
    `/post-application/providers/${provider}/actions/sync`,
    {
      method: "POST",
      body: JSON.stringify({
        ...(input?.accountKey ? { accountKey: input.accountKey } : {}),
        ...(typeof input?.maxMessages === "number"
          ? { maxMessages: input.maxMessages }
          : {}),
        ...(typeof input?.searchDays === "number"
          ? { searchDays: input.searchDays }
          : {}),
      }),
    },
  );
}

export async function postApplicationProviderDisconnect(input?: {
  provider?: PostApplicationProvider;
  accountKey?: string;
}): Promise<PostApplicationProviderActionResponse> {
  const provider = input?.provider ?? "gmail";
  return fetchApi<PostApplicationProviderActionResponse>(
    `/post-application/providers/${provider}/actions/disconnect`,
    {
      method: "POST",
      body: JSON.stringify({
        ...(input?.accountKey ? { accountKey: input.accountKey } : {}),
      }),
    },
  );
}

export async function getPostApplicationInbox(input?: {
  provider?: PostApplicationProvider;
  accountKey?: string;
  limit?: number;
}): Promise<{ items: PostApplicationInboxItem[]; total: number }> {
  const params = new URLSearchParams();
  params.set("provider", input?.provider ?? "gmail");
  params.set("accountKey", input?.accountKey ?? "default");
  if (typeof input?.limit === "number")
    params.set("limit", String(input.limit));
  const query = params.toString();
  return fetchApi<{ items: PostApplicationInboxItem[]; total: number }>(
    `/post-application/inbox?${query}`,
  );
}

export async function approvePostApplicationInboxItem(input: {
  messageId: string;
  provider?: PostApplicationProvider;
  accountKey?: string;
  jobId?: string;
  stageTarget?: PostApplicationRouterStageTarget;
  toStage?: ApplicationStage;
  note?: string;
  decidedBy?: string;
}): Promise<{
  message: PostApplicationInboxItem["message"];
  stageEventId: string | null;
}> {
  return fetchApi<{
    message: PostApplicationInboxItem["message"];
    stageEventId: string | null;
  }>(`/post-application/inbox/${encodeURIComponent(input.messageId)}/approve`, {
    method: "POST",
    body: JSON.stringify({
      provider: input.provider ?? "gmail",
      accountKey: input.accountKey ?? "default",
      ...(input.jobId ? { jobId: input.jobId } : {}),
      ...(input.stageTarget ? { stageTarget: input.stageTarget } : {}),
      ...(input.toStage ? { toStage: input.toStage } : {}),
      ...(input.note ? { note: input.note } : {}),
      ...(input.decidedBy ? { decidedBy: input.decidedBy } : {}),
    }),
  });
}

export async function denyPostApplicationInboxItem(input: {
  messageId: string;
  provider?: PostApplicationProvider;
  accountKey?: string;
  decidedBy?: string;
}): Promise<{
  message: PostApplicationInboxItem["message"];
}> {
  return fetchApi<{ message: PostApplicationInboxItem["message"] }>(
    `/post-application/inbox/${encodeURIComponent(input.messageId)}/deny`,
    {
      method: "POST",
      body: JSON.stringify({
        provider: input.provider ?? "gmail",
        accountKey: input.accountKey ?? "default",
        ...(input.decidedBy ? { decidedBy: input.decidedBy } : {}),
      }),
    },
  );
}

export async function runPostApplicationInboxAction(input: {
  action: PostApplicationAction;
  provider?: PostApplicationProvider;
  accountKey?: string;
  decidedBy?: string;
}): Promise<PostApplicationActionResponse> {
  return fetchApi<PostApplicationActionResponse>(
    "/post-application/inbox/actions",
    {
      method: "POST",
      body: JSON.stringify({
        action: input.action,
        provider: input.provider ?? "gmail",
        accountKey: input.accountKey ?? "default",
        ...(input.decidedBy ? { decidedBy: input.decidedBy } : {}),
      }),
    },
  );
}

export async function getPostApplicationRuns(input?: {
  provider?: PostApplicationProvider;
  accountKey?: string;
  limit?: number;
}): Promise<{ runs: PostApplicationSyncRun[]; total: number }> {
  const params = new URLSearchParams();
  params.set("provider", input?.provider ?? "gmail");
  params.set("accountKey", input?.accountKey ?? "default");
  if (typeof input?.limit === "number")
    params.set("limit", String(input.limit));
  const query = params.toString();
  return fetchApi<{ runs: PostApplicationSyncRun[]; total: number }>(
    `/post-application/runs?${query}`,
  );
}

export async function getPostApplicationRunMessages(input: {
  runId: string;
  provider?: PostApplicationProvider;
  accountKey?: string;
  limit?: number;
}): Promise<{
  run: PostApplicationSyncRun;
  items: PostApplicationInboxItem[];
  total: number;
}> {
  const params = new URLSearchParams();
  params.set("provider", input.provider ?? "gmail");
  params.set("accountKey", input.accountKey ?? "default");
  if (typeof input.limit === "number") params.set("limit", String(input.limit));
  const query = params.toString();
  return fetchApi<{
    run: PostApplicationSyncRun;
    items: PostApplicationInboxItem[];
    total: number;
  }>(
    `/post-application/runs/${encodeURIComponent(input.runId)}/messages?${query}`,
  );
}

export async function getDemoInfo(): Promise<DemoInfoResponse> {
  return fetchApi<DemoInfoResponse>("/demo/info");
}

// Manual Job Import API
export async function fetchJobFromUrl(input: {
  url: string;
}): Promise<ManualJobFetchResponse> {
  return fetchApi<ManualJobFetchResponse>("/manual-jobs/fetch", {
    method: "POST",
    body: JSON.stringify(input),
  });
}

export async function inferManualJob(input: {
  jobDescription: string;
}): Promise<ManualJobInferenceResponse> {
  return fetchApi<ManualJobInferenceResponse>("/manual-jobs/infer", {
    method: "POST",
    body: JSON.stringify(input),
  });
}

export async function importManualJob(input: {
  job: ManualJobDraft;
}): Promise<Job> {
  return fetchApi<Job>("/manual-jobs/import", {
    method: "POST",
    body: JSON.stringify(input),
  });
}

// Settings & Profile API
let settingsPromise: Promise<AppSettings> | null = null;

export async function getSettings(): Promise<AppSettings> {
  if (settingsPromise) return settingsPromise;

  settingsPromise = fetchApi<AppSettings>("/settings").finally(() => {
    // Clear the promise after a short delay to allow subsequent fresh fetches
    // but coalesce simultaneous requests.
    setTimeout(() => {
      settingsPromise = null;
    }, 100);
  });

  return settingsPromise;
}

export async function getProfileProjects(): Promise<
  ResumeProjectCatalogItem[]
> {
  return fetchApi<ResumeProjectCatalogItem[]>("/profile/projects");
}

export async function getResumeProjectsCatalog(): Promise<
  ResumeProjectCatalogItem[]
> {
  return getProfileProjects();
}

export async function getProfile(): Promise<ResumeProfile> {
  return fetchApi<ResumeProfile>("/profile");
}

export async function getProfileStatus(): Promise<ProfileStatusResponse> {
  return fetchApi<ProfileStatusResponse>("/profile/status");
}

export async function refreshProfile(): Promise<ResumeProfile> {
  return fetchApi<ResumeProfile>("/profile/refresh", {
    method: "POST",
  });
}

export async function validateLlm(input: {
  provider?: string;
  baseUrl?: string;
  apiKey?: string;
}): Promise<ValidationResult> {
  return fetchApi<ValidationResult>("/onboarding/validate/llm", {
    method: "POST",
    body: JSON.stringify(input),
  });
}

export async function getLlmModels(input?: {
  provider?: string;
  baseUrl?: string;
  apiKey?: string;
}): Promise<string[]> {
  const data = await fetchApi<{ models: string[] }>("/settings/llm-models", {
    method: "POST",
    body: JSON.stringify(input ?? {}),
  });
  return data.models;
}

export async function validateResumeConfig(): Promise<ValidationResult> {
  return fetchApi<ValidationResult>("/onboarding/validate/resume");
}

export async function updateSettings(
  update: Partial<UpdateSettingsInput>,
): Promise<AppSettings> {
  return fetchApi<AppSettings>("/settings", {
    method: "PATCH",
    body: JSON.stringify(update),
  });
}

// Database API
export async function clearDatabase(): Promise<{
  message: string;
  jobsDeleted: number;
  runsDeleted: number;
}> {
  return fetchApi<{
    message: string;
    jobsDeleted: number;
    runsDeleted: number;
  }>("/database", {
    method: "DELETE",
  });
}

export async function deleteJobsByStatus(status: string): Promise<{
  message: string;
  count: number;
}> {
  return fetchApi<{
    message: string;
    count: number;
  }>(`/jobs/status/${status}`, {
    method: "DELETE",
  });
}

export async function deleteDiscoveredMatchingArchived(): Promise<{
  message: string;
  count: number;
}> {
  return fetchApi<{
    message: string;
    count: number;
  }>("/jobs/duplicates/discovered", {
    method: "DELETE",
  });
}

export async function expireDeadListings(): Promise<{
  message: string;
  count: number;
}> {
  return fetchApi<{
    message: string;
    count: number;
  }>("/jobs/expire-dead-listings", {
    method: "POST",
  });
}

export async function deleteJobsBelowScore(threshold: number): Promise<{
  message: string;
  count: number;
  threshold: number;
}> {
  return fetchApi<{
    message: string;
    count: number;
    threshold: number;
  }>(`/jobs/score/${threshold}`, {
    method: "DELETE",
  });
}

// Visa Sponsors API
export async function getVisaSponsorStatus(): Promise<VisaSponsorStatusResponse> {
  return fetchApi<VisaSponsorStatusResponse>("/visa-sponsors/status");
}

export async function searchVisaSponsors(input: {
  query: string;
  limit?: number;
  minScore?: number;
  country?: string;
}): Promise<VisaSponsorSearchResponse> {
  if (input.query?.trim()) {
    trackProductEvent("visa_sponsor_search", {
      query_length_bucket: bucketQueryLength(input.query.trim()),
      limit: input.limit,
      min_score: input.minScore,
      country: input.country ?? "all",
    });
  }
  return fetchApi<VisaSponsorSearchResponse>("/visa-sponsors/search", {
    method: "POST",
    body: JSON.stringify(input),
  });
}

export async function getVisaSponsorOrganization(
  name: string,
  providerId?: string,
): Promise<VisaSponsor[]> {
  const params = new URLSearchParams();
  if (providerId) params.set("providerId", providerId);
  return fetchApi<VisaSponsor[]>(
    `/visa-sponsors/organization/${encodeURIComponent(name)}${params.size ? `?${params.toString()}` : ""}`,
  );
}

export async function updateVisaSponsorList(): Promise<{
  message: string;
  status: VisaSponsorStatusResponse;
}> {
  return fetchApi<{
    message: string;
    status: VisaSponsorStatusResponse;
  }>("/visa-sponsors/update", {
    method: "POST",
  });
}

// Multi-job operations (intentionally none - processing is manual)

// Backup API
export interface BackupListResponse {
  backups: BackupInfo[];
  nextScheduled: string | null;
}

export async function getBackups(): Promise<BackupListResponse> {
  return fetchApi<BackupListResponse>("/backups");
}

export async function createManualBackup(): Promise<BackupInfo> {
  return fetchApi<BackupInfo>("/backups", {
    method: "POST",
  });
}

export async function deleteBackup(filename: string): Promise<void> {
  await fetchApi<void>(`/backups/${encodeURIComponent(filename)}`, {
    method: "DELETE",
  });
}

// Profiles API
export async function listProfiles(): Promise<SearchProfile[]> {
  return fetchApi<SearchProfile[]>("/profiles");
}

export async function createProfile(input: {
  name: string;
  data: JobSearchProfile;
}): Promise<SearchProfile> {
  return fetchApi<SearchProfile>("/profiles", {
    method: "POST",
    body: JSON.stringify(input),
  });
}

export async function updateProfile(
  id: string,
  input: { name?: string; data?: JobSearchProfile },
): Promise<SearchProfile> {
  return fetchApi<SearchProfile>(`/profiles/${id}`, {
    method: "PATCH",
    body: JSON.stringify(input),
  });
}

export async function deleteProfile(id: string): Promise<void> {
  await fetchApi<void>(`/profiles/${id}`, {
    method: "DELETE",
  });
}

export async function activateProfile(id: string): Promise<void> {
  await fetchApi<void>(`/profiles/${id}/activate`, {
    method: "POST",
  });
}

export async function generateProfileFromResume(): Promise<JobSearchProfile> {
  return fetchApi<JobSearchProfile>("/profiles/generate-from-resume", {
    method: "POST",
  });
}

// Keyword sets API
export async function listKeywordSets(): Promise<KeywordSet[]> {
  return fetchApi<KeywordSet[]>("/keyword-sets");
}

export async function createKeywordSet(input: {
  name: string;
  terms?: string[];
}): Promise<KeywordSet> {
  return fetchApi<KeywordSet>("/keyword-sets", {
    method: "POST",
    body: JSON.stringify(input),
  });
}

export async function updateKeywordSet(
  id: string,
  input: { name?: string; terms?: string[] },
): Promise<KeywordSet> {
  return fetchApi<KeywordSet>(`/keyword-sets/${encodeURIComponent(id)}`, {
    method: "PATCH",
    body: JSON.stringify(input),
  });
}

export async function deleteKeywordSet(id: string): Promise<void> {
  await fetchApi<void>(`/keyword-sets/${encodeURIComponent(id)}`, {
    method: "DELETE",
  });
}

export async function activateKeywordSet(id: string): Promise<KeywordSet> {
  return fetchApi<KeywordSet>(
    `/keyword-sets/${encodeURIComponent(id)}/activate`,
    { method: "POST" },
  );
}
