import createClient, { type Middleware } from 'openapi-fetch';
import type { paths } from './generated/schema';

export type BaylineClientOptions = {
  baseUrl: string;
  getToken?: () => string | null | Promise<string | null>;
  onUnauthorized?: () => void;
  propertyId?: () => number | null;
};

export type Problem = {
  type?: string;
  title: string;
  status: number;
  detail?: string;
  instance?: string;
  errors?: Record<string, string[]>;
};

export class BaylineError extends Error {
  readonly problem: Problem;

  constructor(problem: Problem) {
    super(problem.detail ?? problem.title);
    this.name = 'BaylineError';
    this.problem = problem;
  }

  fieldErrors(): Record<string, string[]> {
    return this.problem.errors ?? {};
  }
}

export function createBaylineClient(options: BaylineClientOptions) {
  const client = createClient<paths>({
    baseUrl: options.baseUrl,
    credentials: 'include',
  });

  const auth: Middleware = {
    async onRequest({ request }) {
      const token = await options.getToken?.();

      if (token) {
        request.headers.set('Authorization', `Bearer ${token}`);
      }

      const propertyId = options.propertyId?.();

      if (propertyId) {
        request.headers.set('X-Property-Id', String(propertyId));
      }

      request.headers.set('Accept', 'application/json');

      return request;
    },
    async onResponse({ response }) {
      if (response.status === 401) {
        options.onUnauthorized?.();
      }

      return response;
    },
  };

  client.use(auth);

  return client;
}

export function unwrap<T>(result: { data?: { data: T }; error?: unknown }): T {
  if (result.error) {
    throw new BaylineError(result.error as Problem);
  }

  if (!result.data) {
    throw new BaylineError({ title: 'Empty response', status: 500 });
  }

  return result.data.data;
}

export type { paths };
