import {
	WeBRecordInfoResponse,
	WeBRecordInfoSource,
	WeBWireSave,
	WeBWorkspaceListResponse,
	WeBWorkspaceResponse,
	WeBWorkspaceSavedResponse,
	WeBWorkspaceSaveListResponse,
	WeBWorkspaceSaveResponse,
	WeBWorkspaceSaveStatusResponse,
} from './types';

/**
 * The data controller's client half: the seven routes, and nothing about how
 * they are transported.
 *
 * It extends `WeBRecordInfoSource` rather than redeclaring `getRecordInfo`, so
 * a `WeBRoutes` can be handed straight to the info bar in place of the stub.
 *
 * Everything here answers *wire* types. Turning a workspace payload into the
 * engine's own types is the adapter's job (`src/adapter/`), which keeps the one
 * place that knows the field names apart from the one place that knows the
 * transport. The same holds in reverse: `saveWorkspace` takes the wire save
 * `SaveAdapter.ts` produces, never a `WeBWorkspace`.
 *
 * There is no `createWorkspace`: a workspace comes into being on its first
 * `saveWorkspace(null, …)`, and until then it is a draft over `getLibrary()`.
 */
export interface WeBRoutes extends WeBRecordInfoSource {
	getLibrary: () => Promise< WeBWorkspaceResponse >;
	getRecordInfo: (
		recordIds: readonly string[]
	) => Promise< WeBRecordInfoResponse >;
	/** The poll after `saveWorkspace` answered `status: 'processing'` */
	getSaveStatus: (
		saveId: string
	) => Promise< WeBWorkspaceSaveStatusResponse >;
	/** The latest save's overlays, or a named save's from `listSaves` */
	getWorkspace: (
		graphId: string,
		saveId?: string
	) => Promise< WeBWorkspaceSavedResponse >;
	/** The workspace's complete saves, newest first */
	listSaves: ( graphId: string ) => Promise< WeBWorkspaceSaveListResponse >;
	listWorkspaces: () => Promise< WeBWorkspaceListResponse >;
	saveWorkspace: (
		graphId: string | null,
		save: WeBWireSave
	) => Promise< WeBWorkspaceSaveResponse >;
}

/** The routes `ButtercupWP::api_call()` switches on. */
export type WeBRoute =
	| 'library/get'
	| 'workspace/get'
	| 'workspace/list'
	| 'workspace/save'
	| 'workspace/save/status'
	| 'workspace/saves'
	| 'workspace/library/record/get';

/**
 * A rejection the caller should offer a reload for rather than a retry: the
 * nonce minted with the page has expired, and every subsequent request will
 * fail the same way until the page is loaded again.
 */
export const WEB_FETCH_SESSION_EXPIRED =
	'Session expired — reload the page to continue.';

/** A rejection meaning the request never reached WordPress, or came back unreadable. */
export const WEB_FETCH_UNREACHABLE = 'Could not reach the server.';

/**
 * The site has no cloud token — it never connected, or the cloud answered
 * 401/419 and the token was cleared. Nothing changes until the user signs in
 * again, so a retry is pointless; the chrome re-checks `connect/status`
 * instead, so the button drops back to Register without a reload.
 */
export const WEB_FETCH_NOT_CONNECTED =
	'Not connected — sign in to we360 to save.';

/**
 * Connected, but the graph product is not registered on this site. A retry
 * cannot fix it either; the remedy is the Register flow.
 */
export const WEB_FETCH_NOT_REGISTERED =
	'This site is not registered for Graph.';

/**
 * Connected and registered, but community consent has not been given in the
 * cloud. Given once, in the cloud's graph area; until then every save fails
 * identically.
 */
export const WEB_FETCH_CONSENT_REQUIRED =
	'Consent is required before saving — give it in your we360 account.';

/**
 * The three connection-state rejections, for classification by callers.
 *
 * Each rejection is an `Error` whose `message` is the constant, so a caller
 * compares `weBFetchErrorMessage(reason)` against the constant it cares about.
 * The server's own message for these is carried on `cause`, not `message`,
 * because the chrome branches on which of the three it is and a message
 * written in PHP would break the comparison.
 */
const WEB_FETCH_NOT_RETRYABLE: readonly string[] = [
	WEB_FETCH_SESSION_EXPIRED,
	WEB_FETCH_NOT_CONNECTED,
	WEB_FETCH_NOT_REGISTERED,
	WEB_FETCH_CONSENT_REQUIRED,
];

/**
 * The two rejections the chrome can itself remedy, by registering.
 *
 * A subset of `WEB_FETCH_NOT_RETRYABLE` rather than the whole of it: the
 * expired nonce is remedied by reloading and consent in the cloud's graph
 * area, so offering Register for either would send the user somewhere that
 * cannot help. Compared by message, as every other classification here is.
 */
export const WEB_FETCH_CONNECTION_STATES: readonly string[] = [
	WEB_FETCH_NOT_CONNECTED,
	WEB_FETCH_NOT_REGISTERED,
];

/**
 * A rejection as something showable.
 *
 * Every rejection from `WeBRoutesWP` is an `Error` whose message is already
 * written for a user — the server's own `message` for the `{error, message}`
 * failures, and one of the constants above otherwise — so this is only
 * guarding against a non-`Error` reason reaching the UI, where React would
 * throw on being handed an object to render.
 *
 * @param reason - whatever a promise rejected with
 */
export function weBFetchErrorMessage( reason: unknown ): string {
	if ( reason instanceof Error && reason.message !== '' ) {
		return reason.message;
	}

	return typeof reason === 'string' && reason !== ''
		? reason
		: 'Error, unable to fetch data.';
}

/**
 * Whether offering a retry makes sense for this rejection.
 *
 * Four are hopeless: the expired nonce (the token was minted with the page and
 * every subsequent request fails identically until the page is loaded again),
 * and the three connection states, which only the connect flow or the cloud
 * can change. Everything else — unreachable, unavailable, invalid, not found —
 * is offered a retry; `invalid` will fail again, but the message says why.
 *
 * @param reason - whatever a promise rejected with
 */
export function weBFetchIsRetryable( reason: unknown ): boolean {
	return ! WEB_FETCH_NOT_RETRYABLE.includes( weBFetchErrorMessage( reason ) );
}
