import { __ } from './i18n';
import { ensureTrailingSlash, reviewSettings } from './config';
import type { BootstrapResponse, ReviewAsset, ReviewComment, ReviewMessage, ScreenshotState } from '../types';

export class ApiError extends Error {
  public readonly status: number;
  public readonly code?: string;

  constructor(message: string, status: number, code?: string) {
    super(message);
    this.name = 'ApiError';
    this.status = status;
    this.code = code;
  }
}

type ApiOptions = Omit<RequestInit, 'body' | 'headers'> & {
  body?: unknown;
  headers?: HeadersInit;
  idempotencyKey?: string;
};

let csrfToken = reviewSettings().csrfToken;

export function setCsrfToken(token?: string | null): void {
	csrfToken = token ?? undefined;
}

export async function api<T>(path: string, options: ApiOptions = {}): Promise<T> {
  const url = new URL(path.replace(/^\//, ''), ensureTrailingSlash(reviewSettings().restUrl));
  const { body: optionBody, headers: optionHeaders, idempotencyKey, ...requestOptions } = options;
  const headers = new Headers(optionHeaders);
  headers.set('Accept', 'application/json');

  const multipart = typeof FormData !== 'undefined' && optionBody instanceof FormData;
  if (optionBody !== undefined && !multipart) {
    headers.set('Content-Type', 'application/json');
  }
  if (csrfToken) {
    headers.set('X-CommentMate-CSRF', csrfToken);
  }
  const adminNonce = reviewSettings().admin?.nonce;
  if (adminNonce) {
    headers.set('X-WP-Nonce', adminNonce);
  }
  if (idempotencyKey) {
    headers.set('Idempotency-Key', idempotencyKey);
  }

  const response = await fetch(url, {
    ...requestOptions,
    credentials: 'same-origin',
    headers,
    body: optionBody === undefined ? undefined : (multipart ? optionBody : JSON.stringify(optionBody))
  });
  const contentType = response.headers.get('content-type') ?? '';
  const payload: unknown = contentType.includes('application/json')
    ? await response.json().catch(() => null)
    : await response.text().catch(() => '');

  if (!response.ok) {
    const record = payload as { message?: string; code?: string } | null;
    throw new ApiError(record?.message ?? __('The review request could not be completed.', 'commentmate'), response.status, record?.code);
  }

  return payload as T;
}

export async function exchangeShareSecret(selector: string, secret: string): Promise<unknown> {
  return api<unknown>('session/exchange', {
    method: 'POST',
    body: { selector, secret },
    idempotencyKey: crypto.randomUUID()
  });
}

export async function bootstrap(url: string): Promise<BootstrapResponse> {
  return api<BootstrapResponse>(`bootstrap?url=${encodeURIComponent(url)}`, { method: 'GET', cache: 'no-store' });
}

export async function createComment(payload: Pick<ReviewComment, 'body' | 'anchor'> & { allowEmptyBody?: boolean }, idempotencyKey: string = crypto.randomUUID()): Promise<ReviewComment> {
  return api<ReviewComment>('comments', {
    method: 'POST',
    body: {
      ...payload,
      url: payload.anchor.url,
      pageTitle: payload.anchor.title
    },
    idempotencyKey
  });
}

export async function updateCommentStatus(commentId: number, status: 'open' | 'resolved'): Promise<ReviewComment> {
	return api<ReviewComment>(`comments/${commentId}/status`, {
		method: 'PATCH',
		body: { status },
		idempotencyKey: crypto.randomUUID()
	});
}

export async function updateComment(commentId: number, body: string): Promise<ReviewComment> {
	return api<ReviewComment>(`comments/${commentId}`, {
		method: 'PATCH',
		body: { body },
		idempotencyKey: crypto.randomUUID()
	});
}

export async function deleteComment(commentId: number): Promise<{ deleted: boolean }> {
	return api<{ deleted: boolean }>(`comments/${commentId}`, {
		method: 'DELETE',
		idempotencyKey: crypto.randomUUID()
	});
}

export async function updateGuestName(name: string): Promise<{ guestName: string }> {
  return api<{ guestName: string }>('session/name', {
    method: 'PATCH',
    body: { name },
    idempotencyKey: crypto.randomUUID()
  });
}

export async function createMessage(commentId: number, body: string, idempotencyKey: string = crypto.randomUUID()): Promise<ReviewMessage> {
  return api<ReviewMessage>(`comments/${commentId}/messages`, {
    method: 'POST',
    body: { body },
    idempotencyKey
  });
}

export async function updateMessage(commentId: number, messageId: number, body: string): Promise<ReviewMessage> {
  return api<ReviewMessage>(`comments/${commentId}/messages/${messageId}`, {
    method: 'PATCH',
    body: { body },
    idempotencyKey: crypto.randomUUID()
  });
}

export async function deleteMessage(commentId: number, messageId: number): Promise<{ deleted: boolean }> {
  return api<{ deleted: boolean }>(`comments/${commentId}/messages/${messageId}`, {
    method: 'DELETE',
    idempotencyKey: crypto.randomUUID()
  });
}

const uploadKeys = new WeakMap<File, Map<string, string>>();

export async function uploadAttachment(commentId: number, file: File, messageId?: number): Promise<ReviewAsset> {
  const target = `${commentId}:${messageId ?? 0}`;
  const keys = uploadKeys.get(file) ?? new Map<string, string>();
  if (!keys.has(target)) keys.set(target, crypto.randomUUID());
  uploadKeys.set(file, keys);
  const body = new FormData();
  body.append('file', file, file.name);
  if (messageId) body.append('messageId', String(messageId));
  return api<ReviewAsset>(`comments/${commentId}/attachments`, {
    method: 'POST',
    body,
    idempotencyKey: keys.get(target)
  });
}

export async function storeScreenshot(commentId: number, payload: string, mimeType: string, state: Extract<ScreenshotState, 'captured' | 'partial'>): Promise<ReviewAsset> {
  return api<ReviewAsset>(`comments/${commentId}/screenshot`, {
    method: 'POST',
    body: { payload, mimeType, state },
    idempotencyKey: crypto.randomUUID()
  });
}

export async function markScreenshotUnavailable(commentId: number): Promise<{ commentId: number; screenshotState: 'unavailable' }> {
  return api<{ commentId: number; screenshotState: 'unavailable' }>(`comments/${commentId}/screenshot/unavailable`, {
    method: 'POST',
    body: {},
    idempotencyKey: crypto.randomUUID()
  });
}

export function attachmentUrl(attachmentId: number): string {
  return new URL(`attachments/${attachmentId}`, ensureTrailingSlash(reviewSettings().restUrl)).toString();
}

/**
 * Loads a private review attachment through fetch so the logged-in owner can
 * use the existing nonce-protected owner endpoint too.
 */
export async function attachmentBlob(attachmentId: number, ownerReviewId?: number): Promise<Blob> {
  const path = ownerReviewId
    ? `admin/reviews/${ownerReviewId}/attachments/${attachmentId}`
    : `attachments/${attachmentId}`;
  const headers = new Headers({ Accept: 'image/*,application/octet-stream;q=0.8' });
  if (csrfToken) headers.set('X-CommentMate-CSRF', csrfToken);
  const adminNonce = reviewSettings().admin?.nonce;
  if (adminNonce) headers.set('X-WP-Nonce', adminNonce);

  const response = await fetch(new URL(path, ensureTrailingSlash(reviewSettings().restUrl)), {
    credentials: 'same-origin',
    headers,
    cache: 'no-store'
  });
  if (!response.ok) {
    const payload = await response.json().catch(() => null) as { message?: string; code?: string } | null;
    throw new ApiError(payload?.message ?? __('The attachment could not be loaded.', 'commentmate'), response.status, payload?.code);
  }

  return response.blob();
}

/**
 * Object URLs are deliberately short-lived: the rendering component revokes
 * them when it unmounts.
 */
export async function attachmentObjectUrl(attachmentId: number, ownerReviewId?: number): Promise<string> {
  return URL.createObjectURL(await attachmentBlob(attachmentId, ownerReviewId));
}

export async function promoteAttachment(reviewId: number, attachmentId: number): Promise<ReviewAsset> {
  return api<ReviewAsset>(`admin/reviews/${reviewId}/attachments/${attachmentId}/promote`, {
    method: 'POST',
    body: {},
    idempotencyKey: crypto.randomUUID()
  });
}

export async function saveCompressedAttachment(reviewId: number, attachmentId: number, options: { convertToWebp: boolean; compressionPercent: number; maxWidth: number; filename: string }): Promise<{ mediaAttachmentId: number }> {
  return api(`admin/reviews/${reviewId}/attachments/${attachmentId}/compressed`, { method: 'POST', body: options, idempotencyKey: crypto.randomUUID() });
}
export async function completeReview(): Promise<{ notified: boolean }> {
  return api('review/complete', { method: 'POST', body: {}, idempotencyKey: crypto.randomUUID() });
}

export async function repairCommentPin(comment: ReviewComment, anchor: ReviewComment['anchor']): Promise<ReviewComment> {
  return api(`comments/${comment.id}/reposition`, { method: 'PATCH', body: { anchor, url: anchor.url, observedAnchor: comment.anchor, placementMismatch: true }, idempotencyKey: crypto.randomUUID() });
}
