import type {
    SettingsMutationResponse,
    SettingsPayload,
    SettingsTabId,
    SettingsValidationErrors,
} from '@/types/settings';

const settingsHeader = 'X-KView-Investor-Settings-Modal';

export class SettingsRequestError extends Error {
    constructor(
        message: string,
        public readonly status: number,
        public readonly errors: SettingsValidationErrors = {},
    ) {
        super(message);
        this.name = 'SettingsRequestError';
    }
}

const jsonHeaders = (): HeadersInit => ({
    Accept: 'application/json',
    'X-Requested-With': 'XMLHttpRequest',
    [settingsHeader]: 'true',
});

const responseJson = async <T>(response: Response): Promise<T> => {
    const payload = await response.json().catch(() => null) as (
        T & { message?: string; errors?: SettingsValidationErrors }
    ) | null;

    if (!response.ok) {
        throw new SettingsRequestError(
            payload?.message ?? `The settings request failed (${response.status}).`,
            response.status,
            payload?.errors ?? {},
        );
    }

    if (!payload) {
        throw new SettingsRequestError('The settings response was empty.', response.status);
    }

    return payload;
};

export const settingsPayloadUrl = (
    baseUrl: string,
    tab: SettingsTabId,
    field?: string | null,
): string => {
    const url = new URL(baseUrl, window.location.origin);

    url.searchParams.set('tab', tab);

    if (field) {
        url.searchParams.set('field', field);
    } else {
        url.searchParams.delete('field');
    }

    return url.toString();
};

export const fetchSettingsPayload = async (
    baseUrl: string,
    tab: SettingsTabId,
    options: {
        field?: string | null;
        signal?: AbortSignal;
    } = {},
): Promise<SettingsPayload> => responseJson<SettingsPayload>(await fetch(
    settingsPayloadUrl(baseUrl, tab, options.field),
    {
        headers: jsonHeaders(),
        signal: options.signal,
        credentials: 'same-origin',
    },
));

export const submitSettingsForm = async (
    url: string,
    data: FormData,
    signal?: AbortSignal,
): Promise<SettingsMutationResponse> => {
    const csrf = document.querySelector<HTMLMetaElement>('meta[name="csrf-token"]')?.content;
    const headers = new Headers(jsonHeaders());

    if (csrf) {
        headers.set('X-CSRF-TOKEN', csrf);
    }

    return responseJson<SettingsMutationResponse>(await fetch(url, {
        method: 'POST',
        headers,
        body: data,
        signal,
        credentials: 'same-origin',
    }));
};

export const tabFromSettingsUrl = (
    url: string,
    fallback: SettingsTabId,
): SettingsTabId => {
    const requested = new URL(url, window.location.origin).searchParams.get('tab');

    return ['user', 'preferences', 'workspace', 'integrations', 'system', 'admin']
        .includes(requested ?? '')
        ? requested as SettingsTabId
        : fallback;
};
