/**
 * Typed REST client for the Bookline admin SPA.
 *
 * Prefers `wp.apiFetch` (always enqueued by wp-admin). Falls back to a
 * `fetch` wrapper so unit tests do not need a full wp environment.
 *
 * Locale-aware money and date helpers live here so screens never format
 * numbers inline (docs/22 §9, docs/14 §4).
 *
 * @package
 */
import { ApiError } from './api-types';
import type { AdminConfig } from './api-types';
import {
	formatAppointmentWhen,
	formatDateValue,
	formatFriendlyDate,
	formatFriendlyDateTime,
	formatFriendlyTime,
	formatRelativeWhen,
	type DateFormatContext,
	type DateDisplayMode,
} from '../../lib/dates';

export { ApiError };
export type { AdminConfig };
export type {
	ServicePayload,
	ServiceDurationPayload,
	ServiceExtraPayload,
	AgentPayload,
	LocationPayload,
	CustomerPayload,
	MutationResponse,
	DeleteResponse,
	ServiceDurationsResponse,
	ServiceDuration,
	ServiceExtra,
	ServiceExtrasResponse,
} from '../types/api';

declare global {
	interface Window {
		wp?: {
			apiFetch?: ( options: {
				path: string;
				method?: string;
				data?: Record< string, unknown >;
				headers?: Record< string, string >;
			} ) => Promise< unknown >;
		};
		booklineAdmin?: AdminConfig;
	}
}

export interface ApiOptions {
	readonly path: string;
	readonly method?: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT';
	readonly data?: unknown;
	readonly query?: Record< string, string | number | boolean | undefined >;
	readonly signal?: AbortSignal;
	readonly headers?: Record< string, string >;
}

function getConfig(): AdminConfig {
	return window.booklineAdmin ?? {};
}

function ns(): string {
	return getConfig().restNamespace ?? '/bookline/v1';
}

function buildPath( path: string, query: ApiOptions[ 'query' ] ): string {
	if ( ! query ) {
		return path;
	}
	const search = new URLSearchParams();
	for ( const [ key, value ] of Object.entries( query ) ) {
		if ( value === undefined ) {
			continue;
		}
		search.set( key, String( value ) );
	}
	const qs = search.toString();
	if ( qs.length === 0 ) {
		return path;
	}
	return path + ( path.includes( '?' ) ? '&' : '?' ) + qs;
}

function normaliseError( err: unknown ): ApiError {
	if ( err instanceof ApiError ) {
		return err;
	}
	const e = err as
		| {
				status?: number;
				body?: { code?: string; message?: string; data?: unknown };
		  }
		| undefined;
	const status = typeof e?.status === 'number' ? e.status : 0;
	const code = e?.body?.code ?? 'bookline_unknown';
	const message = e?.body?.message ?? 'That request failed. Try again.';
	return new ApiError( status, code, message, e?.body?.data );
}

export async function request< T >( opts: ApiOptions ): Promise< T > {
	const cfg = getConfig();
	const restRoot = cfg.restRoot ?? '/wp-json';
	const path = opts.path.startsWith( '/' ) ? opts.path : '/' + opts.path;
	const url = restRoot + ns() + path;

	const wp = window.wp?.apiFetch;
	if ( typeof wp === 'function' ) {
		const relativePath = buildPath(
			url.startsWith( restRoot ) ? url.slice( restRoot.length ) : url,
			opts.query
		);
		try {
			const response = await wp( {
				path: relativePath,
				method: opts.method ?? 'GET',
				data: opts.data as Record< string, unknown > | undefined,
				headers: {
					...( cfg.nonce ? { 'X-WP-Nonce': cfg.nonce } : {} ),
					...( opts.headers ?? {} ),
				},
			} );
			return response as T;
		} catch ( err ) {
			throw normaliseError( err );
		}
	}

	const init: RequestInit = {
		method: opts.method ?? 'GET',
		headers: {
			'Content-Type': 'application/json',
			...( cfg.nonce ? { 'X-WP-Nonce': cfg.nonce } : {} ),
			...( opts.headers ?? {} ),
		},
		signal: opts.signal ?? null,
		credentials: 'same-origin',
	};
	if ( opts.data !== undefined && opts.method !== 'GET' ) {
		init.body = JSON.stringify( opts.data );
	}

	const finalUrl = buildPath( url, opts.query );
	const res = await fetch( finalUrl, init );
	if ( ! res.ok ) {
		let body: unknown;
		try {
			body = await res.json();
		} catch {
			body = undefined;
		}
		throw normaliseError( { status: res.status, body } );
	}
	if ( res.status === 204 ) {
		return undefined as T;
	}
	return ( await res.json() ) as T;
}

export function formatMoney( amountMinor: number, currency?: string ): string {
	const cfg = getConfig();
	const ccy = ( currency ?? cfg.currency ?? 'GBP' ).toUpperCase();
	const locale = cfg.locale ?? 'en-GB';
	try {
		return new Intl.NumberFormat( locale, {
			style: 'currency',
			currency: ccy,
			minimumFractionDigits: 2,
		} ).format( amountMinor / 100 );
	} catch {
		return new Intl.NumberFormat( 'en-GB', {
			style: 'currency',
			currency: 'GBP',
			minimumFractionDigits: 2,
		} ).format( amountMinor / 100 );
	}
}

export function formatDateTime(
	iso: string,
	opts: Intl.DateTimeFormatOptions = {}
): string {
	const cfg = getConfig();
	if ( Object.keys( opts ).length > 0 ) {
		const locale = cfg.locale ?? 'en-GB';
		const tz = cfg.siteTimezone ?? 'UTC';
		try {
			return new Intl.DateTimeFormat( locale, {
				timeZone: tz,
				year: 'numeric',
				month: 'short',
				day: '2-digit',
				hour: '2-digit',
				minute: '2-digit',
				...opts,
			} ).format( new Date( iso ) );
		} catch {
			return iso;
		}
	}
	return formatFriendlyDateTime( iso, cfg, 'instant' );
}

export function formatDate( iso: string ): string {
	return formatFriendlyDate( iso, getConfig(), 'instant' );
}

export function formatTime( iso: string ): string {
	return formatFriendlyTime( iso, getConfig(), 'instant' );
}

export function formatAppointment( iso: string ): string {
	return formatAppointmentWhen( iso, getConfig(), 'instant' );
}

export function formatRelative( iso: string ): string {
	return formatRelativeWhen( iso, getConfig() );
}

export function formatAdminDate(
	iso: string,
	mode: DateDisplayMode = 'datetime'
): string {
	return formatDateValue( iso, getConfig(), mode, 'instant' );
}

export type { DateFormatContext, DateDisplayMode };

export function can(
	capability:
		| 'manage_settings'
		| 'manage_bookings'
		| 'manage_agents'
		| 'manage_services'
		| 'manage_locations'
		| 'manage_payments'
		| 'manage_customers'
		| 'view_reports'
): boolean {
	const cfg = getConfig();
	const caps = cfg.capabilities ?? [];
	switch ( capability ) {
		case 'manage_settings':
			return (
				caps.includes( 'bookline_manage_settings' ) ||
				caps.includes( 'manage_options' )
			);
		case 'manage_bookings':
			return (
				caps.includes( 'bookline_manage_bookings' ) ||
				caps.includes( 'manage_options' )
			);
		case 'manage_agents':
			return (
				caps.includes( 'bookline_manage_agents' ) ||
				caps.includes( 'manage_options' )
			);
		case 'manage_services':
			return (
				caps.includes( 'bookline_manage_services' ) ||
				caps.includes( 'manage_options' )
			);
		case 'manage_locations':
			return (
				caps.includes( 'bookline_manage_locations' ) ||
				caps.includes( 'manage_options' )
			);
		case 'manage_payments':
			return (
				caps.includes( 'bookline_manage_payments' ) ||
				caps.includes( 'manage_options' )
			);
		case 'manage_customers':
			return (
				caps.includes( 'bookline_manage_customers' ) ||
				caps.includes( 'manage_options' )
			);
		case 'view_reports':
			return (
				caps.includes( 'bookline_view_reports' ) ||
				caps.includes( 'manage_options' )
			);
	}
}

export function siteConfig(): AdminConfig {
	return getConfig();
}

// ---------------------------------------------------------------------------
// Mutation helpers
// ---------------------------------------------------------------------------

import type {
	ServicePayload,
	ServiceDurationPayload,
	ServiceExtraPayload,
	AgentPayload,
	LocationPayload,
	CustomerPayload,
	MutationResponse,
	DeleteResponse,
	ServiceDurationsResponse,
	ServiceExtra,
	ServiceExtrasResponse,
	BookingFormDetail,
} from '../types/api';

/** Create a new service. */
export async function createService(
	data: ServicePayload
): Promise< MutationResponse > {
	return request< MutationResponse >( {
		path: '/admin/services',
		method: 'POST',
		data,
	} );
}

/** Update an existing service. */
export async function updateService(
	id: number,
	data: ServicePayload
): Promise< MutationResponse > {
	return request< MutationResponse >( {
		path: `/admin/services/${ id }`,
		method: 'PATCH',
		data,
	} );
}

/** Delete a service (soft delete). */
export async function deleteService(
	id: number
): Promise< DeleteResponse > {
	return request< DeleteResponse >( {
		path: `/admin/services/${ id }`,
		method: 'DELETE',
	} );
}

/** List service durations for a service. */
export async function listServiceDurations(
	serviceId: number
): Promise< ServiceDurationsResponse > {
	return request< ServiceDurationsResponse >( {
		path: '/admin/service-durations',
		query: { service_id: serviceId },
	} );
}

/** Create a service duration. */
export async function createServiceDuration(
	data: ServiceDurationPayload
): Promise< MutationResponse > {
	return request< MutationResponse >( {
		path: '/admin/service-durations',
		method: 'POST',
		data,
	} );
}

/** Update a service duration. */
export async function updateServiceDuration(
	id: number,
	data: ServiceDurationPayload
): Promise< MutationResponse > {
	return request< MutationResponse >( {
		path: `/admin/service-durations/${ id }`,
		method: 'PATCH',
		data,
	} );
}

/** Delete a service duration. */
export async function deleteServiceDuration(
	id: number
): Promise< DeleteResponse > {
	return request< DeleteResponse >( {
		path: `/admin/service-durations/${ id }`,
		method: 'DELETE',
	} );
}

/** List extras, optionally for one service. */
export async function listServiceExtras(
	serviceId?: number
): Promise< ServiceExtrasResponse > {
	return request< ServiceExtrasResponse >( {
		path: '/admin/service-extras',
		query: serviceId ? { service_id: serviceId } : undefined,
	} );
}

/** Create a service extra. */
export async function createServiceExtra(
	data: ServiceExtraPayload
): Promise< ServiceExtra > {
	return request< ServiceExtra >( {
		path: '/admin/service-extras',
		method: 'POST',
		data,
	} );
}

/** Update a service extra. */
export async function updateServiceExtra(
	id: number,
	data: ServiceExtraPayload
): Promise< ServiceExtra > {
	return request< ServiceExtra >( {
		path: `/admin/service-extras/${ id }`,
		method: 'PATCH',
		data,
	} );
}

/** Delete a service extra. */
export async function deleteServiceExtra(
	id: number
): Promise< DeleteResponse > {
	return request< DeleteResponse >( {
		path: `/admin/service-extras/${ id }`,
		method: 'DELETE',
	} );
}

/** Create a new agent. */
export async function createAgent(
	data: AgentPayload
): Promise< MutationResponse > {
	return request< MutationResponse >( {
		path: '/admin/agents',
		method: 'POST',
		data,
	} );
}

/** Update an existing agent. */
export async function updateAgent(
	id: number,
	data: AgentPayload
): Promise< MutationResponse > {
	return request< MutationResponse >( {
		path: `/admin/agents/${ id }`,
		method: 'PATCH',
		data,
	} );
}

/** Delete an agent (soft delete). */
export async function deleteAgent(
	id: number
): Promise< DeleteResponse > {
	return request< DeleteResponse >( {
		path: `/admin/agents/${ id }`,
		method: 'DELETE',
	} );
}

/** Create a new location. */
export async function createLocation(
	data: LocationPayload
): Promise< MutationResponse > {
	return request< MutationResponse >( {
		path: '/admin/locations',
		method: 'POST',
		data,
	} );
}

/** Update an existing location. */
export async function updateLocation(
	id: number,
	data: LocationPayload
): Promise< MutationResponse > {
	return request< MutationResponse >( {
		path: `/admin/locations/${ id }`,
		method: 'PATCH',
		data,
	} );
}

/** Delete a location (soft delete). */
export async function deleteLocation(
	id: number
): Promise< DeleteResponse > {
	return request< DeleteResponse >( {
		path: `/admin/locations/${ id }`,
		method: 'DELETE',
	} );
}

/** Create a new customer. */
export async function createCustomer(
	data: CustomerPayload
): Promise< MutationResponse > {
	return request< MutationResponse >( {
		path: '/admin/customers',
		method: 'POST',
		data,
	} );
}

/** Update an existing customer. */
export async function updateCustomer(
	id: number,
	data: CustomerPayload
): Promise< MutationResponse > {
	return request< MutationResponse >( {
		path: `/admin/customers/${ id }`,
		method: 'PATCH',
		data,
	} );
}

/** Delete a customer (soft delete). */
export async function deleteCustomer(
	id: number
): Promise< DeleteResponse > {
	return request< DeleteResponse >( {
		path: `/admin/customers/${ id }`,
		method: 'DELETE',
	} );
}

/**
 * Create a booking form.
 *
 * @param data Create payload.
 */
export async function createBookingForm(
	data: Record< string, unknown >
): Promise< BookingFormDetail > {
	return request< BookingFormDetail >( {
		path: '/admin/booking-forms',
		method: 'POST',
		data,
	} );
}

/**
 * Delete a booking form.
 *
 * @param id Form id.
 */
export async function deleteBookingForm(
	id: number
): Promise< DeleteResponse > {
	return request< DeleteResponse >( {
		path: `/admin/booking-forms/${ id }`,
		method: 'DELETE',
	} );
}

