import { HrefFormatterProvider } from 'first-npm-package-nicule/core';
import { ReflectiveInjector, Type } from '@angular/core';

import { makePropDecorator } from '../../google-decorator-factories';
import { BindDecorator } from '../bind.decorator';
// import { HypermediaStore } from 'hypermedia/core/services/hypermedia.store';
import { TOKEN_PROVIDER } from '../../token-provider';
import { PARENT_ENTITY } from '../../parent-entity';
import { HypermediaStore } from '../../../core/services/hypermedia.store';

type TokenProviderType = { useClass: Type<any> } | { useFactory: any, deps?: Array<any> } | { useExisting: Type<any> };

interface LinkedEntityOptions<T> {
    as?: Type<T>;
    authorize?: boolean;
    tokenProvider?: TokenProviderType;
}

export interface BindLinkedEntityDecorator {
    <T>(classList: Array<string>, options?: LinkedEntityOptions<T>): any;
    new <T>(classList: Array<string>, options?: LinkedEntityOptions<T>): any;
}

function guid(): string {
    function s4(): string {
        return Math.floor((Math.random() + 1) * 0x10000)
            .toString(16)
            .substring(1);
    }

    return `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`;
}

export const LinkedEntity: BindLinkedEntityDecorator = makePropDecorator('BindLinkedEntity',
    <T>(classList: Array<string>, options: LinkedEntityOptions<T> = {}) => {
        return ({
            binding: {
                priority: 1,
                bind: (({ entities = [] }, fieldName, injector, parent) => {
                    let tokenOverride;

                    if (entities.length === 0) {
                        return undefined;
                    }

                    const { href } = entities.find(({ class: classes }) => classList.every(clazz => classes.includes(clazz))) || {} as any;

                    if (!href) {
                        return undefined;
                    }

                    if (!/^(http|https):\/\//.test(href)) {
                        return undefined;
                    }

                    if (options && options.tokenProvider) {
                        injector = ReflectiveInjector.resolveAndCreate([{ provide: PARENT_ENTITY, useValue: parent }, { provide: TOKEN_PROVIDER, ...options.tokenProvider }], injector);

                        const tokenProvider = injector.get(TOKEN_PROVIDER);
                        tokenOverride = tokenProvider.getToken();
                    }

                    const formatterProvider = injector.get(HrefFormatterProvider);
                    const formatter = formatterProvider.getHrefFormatterForHref(href);
                    const scope = (formatter === undefined) ? 'external' : formatter.getScope();
                    const path = (formatter === undefined) ? href : formatter.makePathFromHref(href);
                    const { authorize, as } = options;

                    const selector = injector
                        .get(HypermediaStore)
                        .select(`${path}?noCache=${guid()}`, scope, authorize, tokenOverride);

                    selector
                        .intercept(0, ({ class: classes }) => {
                            console.log("linked-entity-decorator: intercepted 0 response exception");
                            // prevents from redirecting to disconnected error page
                        });

                    if (!as) {
                        return selector;
                    }

                    return selector.as(as);
                })
            } as BindDecorator
        }) as any;
    });