import type { Passenger } from '@edr/types';

import { apiClient } from '@/lib/api-client';
import { getDeviceId } from './deviceIdentity';

type ThreadDto = Passenger.PassengerSupportThreadDto;
type MessageDto = Passenger.PassengerSupportMessageDto;

/** Messages per page when walking the thread backwards. */
export const MESSAGES_PAGE_SIZE = 30;

export interface GetThreadParams {
  /** Opaque cursor from the previous page's `nextCursor`; omit for the newest page. */
  before?: string;
  limit?: number;
}

/** What the composer hands over: text, files, or both (never neither). */
export interface SendMessageInput {
  text?: string;
  attachments?: File[];
}

/**
 * A message with files goes as multipart so the server persists them against the
 * message it creates in the same request; text-only stays JSON.
 *
 * The `Content-Type: undefined` override is load-bearing, not cargo cult.
 * `apiClient` pins `application/json` as an instance default, and axios's
 * request transform reads that header to decide what to do with a FormData body:
 * when it sees JSON it runs the form through `formDataToJSON` and posts *that*,
 * so the files would be silently dropped and the server would see an empty
 * message. Clearing the header lets axios's XHR adapter set `multipart/form-data`
 * itself, with the boundary — which is also why we never write that value by
 * hand: a hand-set Content-Type has no boundary and fails to parse.
 */
function toRequestBody(input: SendMessageInput): {
  data: FormData | { deviceId: string; text?: string };
  config?: { headers: Record<string, undefined> };
} {
  const deviceId = getDeviceId();
  if (!input.attachments?.length) {
    return { data: { deviceId, text: input.text } };
  }

  const form = new FormData();
  form.append('deviceId', deviceId);
  // Attachment-only messages send an empty string rather than omitting the
  // field — the DTO models text as "" for those, not as absent.
  form.append('text', input.text ?? '');
  for (const file of input.attachments) form.append('attachments', file);
  return { data: form, config: { headers: { 'Content-Type': undefined } } };
}

/**
 * Passenger portal support-chat calls — a single device-scoped thread. No auth,
 * no forms; everything is keyed by a localStorage device id.
 */
export const supportApi = {
  /**
   * The device's thread. Returns the *newest page* of messages plus a cursor —
   * not the whole conversation; page backwards with `before`.
   */
  getThread: (params: GetThreadParams = {}): Promise<ThreadDto> =>
    apiClient.get('/support/device/thread', {
      params: { deviceId: getDeviceId(), ...params },
    }),
  sendMessage: (input: SendMessageInput): Promise<MessageDto> => {
    const { data, config } = toRequestBody(input);
    return apiClient.post('/support/device/messages', data, config);
  },
  /**
   * Attachment bytes, fetched through this client rather than linked directly:
   * the API guard reads the bearer token from the `Authorization` header with no
   * cookie fallback, and an `<img src>` can't carry that header — it would 401.
   *
   * `relativeUrl` is the DTO's `url` (`/support/attachments/:id`); the passenger
   * API has no global prefix, so it appends to the client's baseURL as-is.
   * `apiClient.get` unwraps a `{ data }` envelope when it finds one; a Blob has
   * no such property, so the body comes back untouched.
   */
  fetchAttachment: (relativeUrl: string): Promise<Blob> =>
    apiClient.get<Blob>(relativeUrl, { responseType: 'blob' }),
  markRead: (): Promise<{ unreadCount: number }> =>
    apiClient.post('/support/device/read', { deviceId: getDeviceId() }),
  unreadCount: (): Promise<{ unreadCount: number }> =>
    apiClient.get('/support/device/unread-count', {
      params: { deviceId: getDeviceId() },
    }),
};
