import { ApiError, isSessionExpired, refreshNonce, SESSION_EXPIRED_MESSAGE, type RestConfig } from './client';

/** Offset of a pin inside its anchor element, 0..1 on each axis. */
export interface PinPosition {
  x: number;
  y: number;
}

export type NoteStatus = 'open' | 'resolved';

/** A note as returned by `zadex/v1/notes` (`Zadex\Team\NotesController::prepare`). */
export interface Note {
  id: number;
  postId: number;
  /** Anchor element id; '' = the page itself. */
  elementId: string;
  /** 0 = thread root, else the root this replies to. */
  parentId: number;
  author: { id: number; name: string };
  content: string;
  mentions: number[];
  status: NoteStatus;
  position: PinPosition | null;
  createdAt: string;
  updatedAt: string;
  canEdit: boolean;
  canDelete: boolean;
}

/** Someone who can be @mentioned (can edit the document). `slug` is what the mention inserts. */
export interface NoteUser {
  id: number;
  name: string;
  slug: string;
}

export interface CreateNoteBody {
  postId: number;
  content: string;
  elementId?: string;
  position?: PinPosition | null;
  parentId?: number;
}

export interface NotesApi {
  list(postId: number): Promise<Note[]>;
  create(body: CreateNoteBody): Promise<Note>;
  update(id: number, patch: { content?: string; status?: NoteStatus }): Promise<Note>;
  remove(id: number): Promise<number[]>;
  users(postId: number, search: string): Promise<NoteUser[]>;
}

type Method = 'GET' | 'POST' | 'PUT' | 'DELETE';

/** REST implementation (zadex/v1, cookie auth + X-WP-Nonce), same error handling as the document client. */
export function createRestNotesApi(config: RestConfig, fetchFn: typeof fetch = (...args) => fetch(...args)): NotesApi {
  const base = config.restUrl.replace(/\/$/, '');
  const withQuery = (path: string, query: Record<string, string>): string => `${path}${base.includes('?') ? '&' : '?'}${new URLSearchParams(query).toString()}`;
  async function call<T>(method: Method, path: string, body?: unknown, retried = false): Promise<T> {
    const res = await fetchFn(`${base}${path}`, {
      method,
      credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': config.nonce },
      body: body === undefined ? undefined : JSON.stringify(body),
    });
    const text = await res.text();
    let json: any = null;
    try {
      json = text ? JSON.parse(text) : null;
    } catch {
      throw new ApiError(`Invalid JSON from ${path}`, res.status);
    }
    if (!res.ok) {
      const err = new ApiError(json?.message ?? `Request failed (${res.status})`, res.status, json?.code ?? '');
      if (isSessionExpired(err)) {
        if (!retried && (await refreshNonce(config, fetchFn))) return call<T>(method, path, body, true);
        err.message = SESSION_EXPIRED_MESSAGE;
      }
      throw err;
    }
    return json as T;
  }
  return {
    list: async (postId) => (await call<{ notes: Note[] }>('GET', withQuery('/notes', { postId: String(postId) }))).notes,
    create: async (body) => (await call<{ note: Note }>('POST', '/notes', body)).note,
    update: async (id, patch) => (await call<{ note: Note }>('PUT', `/notes/${id}`, patch)).note,
    remove: async (id) => (await call<{ ids: number[] }>('DELETE', `/notes/${id}`)).ids,
    users: async (postId, search) => (await call<{ users: NoteUser[] }>('GET', withQuery('/notes/users', { postId: String(postId), search }))).users,
  };
}

let instance: NotesApi | null = null;

export function setNotesApi(api: NotesApi | null): void {
  instance = api;
}

export async function getNotesApi(): Promise<NotesApi> {
  if (instance) return instance;
  const config = window.ZADEX;
  if (!config) throw new Error('No config');
  // The dev harness mock is development-only: the production build drops this branch and its chunk.
  if (import.meta.env.DEV && config.restUrl.startsWith('mock:')) {
    const { createMockNotesApi } = await import('@/dev/mock-notes');
    instance = createMockNotesApi({ userId: config.user?.id ?? 1, userName: config.user?.name ?? 'You' });
  } else {
    instance = createRestNotesApi(config);
  }
  return instance;
}
