import { replaceDoubleSlash, ensurePathByPathVariables } from '@angular-ru/cdk/http/utils';
import { isNil, isNotNil } from '@angular-ru/cdk/utils';
import { RequestType } from '@angular-ru/cdk/http/typings';
import { MethodArgsRegistry } from '@angular-ru/cdk/runtime';

function validateHttpParentDecorator(name, clientClass) {
    const local = clientClass.prototype?.local;
    if (isNil(local)) {
        throw new Error(`${name} should be initialized before @RestClient('controllerPath')`);
    }
}

function BaseUrl(baseUrl = '') {
    return (clientClass) => {
        validateHttpParentDecorator('@BaseUrl', clientClass);
        const localRef = clientClass.prototype?.local;
        if (isNotNil(localRef)) {
            localRef.baseUrl = replaceDoubleSlash(baseUrl);
        }
    };
}

function getMethodArgsRegistry(method, metaKey) {
    return method?.[metaKey] ?? null;
}

function ensureMethodArgsRegistry(method, metaKey) {
    let registry = getMethodArgsRegistry(method, metaKey);
    if (isNil(registry)) {
        registry = new MethodArgsRegistry();
        Object.defineProperties(method, {
            [metaKey]: {
                enumerable: true,
                configurable: true,
                value: registry
            }
        });
    }
    return registry;
}

const META_PATH_VARIABLE = 'META_PATH_VARIABLE';
const META_REQUEST_BODY = 'META_REQUEST_BODY';
const META_REQUEST_PARAM = 'META_REQUEST_PARAM';
const KEY_REQUEST_BODY = 'KEY_REQUEST_BODY';

function ensureQueryParams(queryParams, originalMethod, args) {
    const registry = ensureMethodArgsRegistry(originalMethod, META_REQUEST_PARAM);
    if (registry.size > 0) {
        const newParams = {};
        for (const [index, value] of args.entries()) {
            const key = registry.getNameByIndex(index);
            if (isNotNil(key)) {
                newParams[key] = value;
            }
        }
        return { ...newParams, ...queryParams };
    }
    return queryParams;
}

// eslint-disable-next-line max-lines-per-function
function mutatePathByPathVariables(options) {
    const { originalMethod, args, path, pathVariables } = options;
    const registry = ensureMethodArgsRegistry(originalMethod, META_PATH_VARIABLE);
    const definedVariables = pathVariables ?? {};
    const variableMap = new Map();
    mutateMapByMethodArgsRegistry(variableMap, args, registry);
    mutateMapByDefinedVariables(variableMap, definedVariables);
    let interpolationPath = path.toString(); // copy without mutation
    if (variableMap.size > 0) {
        interpolationPath = ensurePathByPathVariables(interpolationPath, variableMap);
    }
    return interpolationPath;
}
function mutateMapByMethodArgsRegistry(variableMap, args, registry) {
    if (registry.size > 0) {
        for (const [index, value] of args.entries()) {
            const key = registry.getNameByIndex(index);
            if (isNotNil(key)) {
                variableMap.set(key, value);
            }
        }
    }
}
function mutateMapByDefinedVariables(variableMap, definedVariables) {
    if (Object.keys(definedVariables).length > 0) {
        for (const key in definedVariables) {
            if (definedVariables.hasOwnProperty(key)) {
                const value = definedVariables[key];
                variableMap.set(key, value);
            }
        }
    }
}

function validateMethod(target, descriptor) {
    const isStaticMethod = target.hasOwnProperty('prototype');
    if (isStaticMethod) {
        throw new Error(`Cannot support static methods with current decorator`);
    }
    if (descriptor === undefined) {
        throw new Error(`Current decorator can only decorate a method implementation`);
    }
}

// eslint-disable-next-line max-lines-per-function
function ensureDescriptorByType({ path, type, target, descriptor, emitOptions }) {
    validateMethod(target, descriptor);
    const originalMethod = descriptor.value;
    // eslint-disable-next-line max-lines-per-function
    descriptor.value = function (...args) {
        let newPath = path.toString();
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        const httpClient = this;
        const result = originalMethod.apply(httpClient, args);
        let template = result?.restTemplateRef ?? null;
        if (isNotNil(template)) {
            newPath = mutatePathByPathVariables({
                args,
                path: newPath,
                originalMethod,
                pathVariables: template.options.pathVariables
            });
            const bodyRegistry = ensureMethodArgsRegistry(originalMethod, META_REQUEST_BODY);
            const indexBody = bodyRegistry.getIndexByKey(KEY_REQUEST_BODY);
            const body = isNil(indexBody)
                ? template?.options.body
                : template?.options.body ?? args?.[indexBody];
            const params = ensureQueryParams(template?.options.queryParams, originalMethod, args);
            template = template
                ?.setPath(newPath)
                .setMethodType(type)
                .setBody(body)
                .setParams(params)
                .setEmitOptions(emitOptions)
                .setClient(httpClient);
        }
        else {
            throw new Error('You must return observable from your method');
        }
        return template?.asObservable?.();
    };
    return descriptor;
}

function Delete(path = '/', emitOptions = { emitFailure: true, emitSuccess: true }) {
    return (target, _name, descriptor) => ensureDescriptorByType({ path, type: RequestType.DELETE, target, descriptor, emitOptions });
}

function Get(path = '/', emitOptions = { emitFailure: true, emitSuccess: false }) {
    return (target, _name, descriptor) => ensureDescriptorByType({ path, type: RequestType.GET, target, descriptor, emitOptions });
}

function HostUrl(hostUrl = '') {
    return (clientClass) => {
        validateHttpParentDecorator('@HostUrl', clientClass);
        const localRef = clientClass.prototype?.local;
        if (isNotNil(localRef)) {
            localRef.hostUrl = hostUrl;
        }
    };
}

function Patch(path = '/', emitOptions = { emitFailure: true, emitSuccess: true }) {
    return (target, _name, descriptor) => ensureDescriptorByType({ path, type: RequestType.PATCH, target, descriptor, emitOptions });
}

// eslint-disable-next-line max-lines-per-function
function PathVariable(name) {
    return (target, methodName, parameterIndex) => {
        const key = name.trim();
        if (!key) {
            throw new Error(`@PathVariable name should be initialized`);
        }
        const originalMethod = target?.[methodName];
        const registry = ensureMethodArgsRegistry(originalMethod, META_PATH_VARIABLE);
        registry.putIndexByName(key, methodName, parameterIndex);
    };
}

function Post(path = '/', emitOptions = { emitFailure: true, emitSuccess: true }) {
    return (target, _name, descriptor) => ensureDescriptorByType({ path, type: RequestType.POST, target, descriptor, emitOptions });
}

function Put(path = '/', emitOptions = { emitFailure: true, emitSuccess: true }) {
    return (target, _name, descriptor) => ensureDescriptorByType({ path, type: RequestType.PUT, target, descriptor, emitOptions });
}

// eslint-disable-next-line max-lines-per-function
function RequestBody() {
    return (target, methodName, parameterIndex) => {
        const originalMethod = target?.[methodName];
        const registry = ensureMethodArgsRegistry(originalMethod, META_REQUEST_BODY);
        registry.putIndexByName(KEY_REQUEST_BODY, methodName, parameterIndex);
    };
}

// eslint-disable-next-line max-lines-per-function
function RequestParam(name) {
    return (target, methodName, parameterIndex) => {
        const key = name.trim();
        if (!key) {
            throw new Error(`@RequestParam name should be initialized`);
        }
        const originalMethod = target?.[methodName];
        const registry = ensureMethodArgsRegistry(originalMethod, META_REQUEST_PARAM);
        registry.putIndexByName(key, methodName, parameterIndex);
    };
}

function RestClient(restUrl = '') {
    return (clientClass) => {
        Object.defineProperties(clientClass.prototype, {
            controllerUrl: {
                writable: true,
                enumerable: true,
                configurable: true,
                value: restUrl
            },
            local: {
                writable: true,
                enumerable: true,
                configurable: true,
                value: { restUrl }
            }
        });
    };
}

/**
 * Generated bundle index. Do not edit.
 */

export { BaseUrl, Delete, Get, HostUrl, Patch, PathVariable, Post, Put, RequestBody, RequestParam, RestClient };
//# sourceMappingURL=angular-ru-cdk-http-decorators.mjs.map
