/** Dependency-free JavaScript SDK for the anonymous, read-only v1 catalog. */
export class CatalogError extends Error {
  constructor(message, status, code, retryAfter) {
    super(message);
    this.name = 'CatalogError';
    Object.assign(this, {status, code, retryAfter});
  }
}

async function get(path, {signal, fetchImpl = globalThis.fetch} = {}) {
  const response = await fetchImpl(`https://grahamandlane.com${path}`, {
    method: 'GET', credentials: 'omit',
    headers: {'Accept': 'application/json', 'API-Version': '1'},
    signal: signal || AbortSignal.timeout(15000),
  });
  const data = await response.json();
  if (!response.ok) throw new CatalogError(data.error?.message || 'Catalog request failed', response.status, data.error?.code, response.headers.get('Retry-After'));
  return data;
}

export function getStudioInformation(options) {
  return get('/api/v1/studio.json', options);
}

export function findArrangements(query = '', {limit = 20, cursor, ...options} = {}) {
  if (typeof query !== 'string' || query.length > 200) throw new TypeError('query must be a string of at most 200 characters');
  if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new TypeError('limit must be an integer from 1 to 100');
  if (cursor !== undefined && (typeof cursor !== 'string' || !cursor || cursor.length > 2048)) throw new TypeError('cursor must be an opaque string returned by the API');
  const params = new URLSearchParams({q: query, limit: String(limit)});
  if (cursor !== undefined) params.set('cursor', cursor);
  return get(`/api/v1/arrangements.json?${params}`, options);
}
