
import { Component, OnInit } from '@angular/core';
import { Observable, OperatorFunction, concatAll } from 'rxjs';
import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators';
import { FieldType, FieldTypeConfig, FormlyFieldConfig } from '@ngx-formly/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { CrudService, Filters } from "../crud.service";
import { CrudFormlyJsonschemaService } from "../crud-formly-jsonschema.service";
import { FormGroup } from "@angular/forms";
import {DictionaryService} from "./dictionary.service";

@Component({
    selector: 'formly-foreignkey-type',
    template: `
        <div class="mb-3">
            <label *ngIf="props.label && props['hideLabel'] !== true" [attr.for]="id"
                   class="form-label">{{ props.label }}
                <span *ngIf="props.required && props['hideRequiredMarker'] !== true" aria-hidden="true">*</span>
            </label>

            <div class="alert alert-danger" role="alert" *ngIf="showError && formControl.errors">
                <formly-validation-message [field]="field"></formly-validation-message>
            </div>
            <div class="input-group" *ngIf="! this.hasValue()">
                <input type="text"
                       class="form-control"
                       placeholder="Type the first letters..."
                       (selectItem)="selectedItem($event)"
                       [(ngModel)]="foreignModel"
                       [ngbTypeahead]="search"
                       [inputFormatter]="formatter"
                       [resultFormatter]="formatter"
                       [editable]="false" />
                    <button class="btn btn-success" type="button" (click)="add(foreignModal)"><i-bs name="file-earmark-plus-fill"></i-bs></button>
            </div>
            <div class="input-group" *ngIf="this.hasValue()">
                <input type="text" 
                       class="form-control"
                       value="{{this.foreignLabel}}"
                       disabled=""
                       readonly="readonly"
                />
                <button class="btn btn-primary" type="button" (click)="see(foreignModal)"><i-bs name="eye-fill"></i-bs></button>
                <button class="btn btn-danger" type="button" (click)="remove()"><i-bs name="eraser-fill"></i-bs></button>
            </div>
            <formly-form *ngIf="this.hasValue() && this.displayedFields" [form]="this.foreignForm" [fields]="this.foreignFields" [model]="this.foreignModel"></formly-form>
        </div>
        <ng-template #foreignModal let-modal>
            <div class="modal-header">
                <button type="button" class="btn-close" aria-label="Close"
                        (click)="modal.dismiss('Cross click')"></button>
            </div>
            <div class="modal-body">
                <crud-card *ngIf="this.hasValue()" 
                           [resource]="this.foreignResource" 
                           [resource_id]="this.foreignModel._id" 
                           [schema]="this.foreignSchema" 
                           [is_modal]="true" 
                           (resourceDeleted)="onResourceDeleted($event)"
                           (resourceUpdated)="onResourceModified($event)">
                </crud-card>
                <crud-card *ngIf="! this.hasValue()" 
                           [resource]="this.foreignResource"
                           [schema]="this.foreignSchema"
                           [is_modal]="true"
                           (resourceCreated)="onResourceModified($event)">
                </crud-card>
            </div>
            <div class="modal-footer">
            </div>
        </ng-template>
    `,
})
export class ForeignkeyTypeComponent extends FieldType<FieldTypeConfig> implements OnInit
{
    public foreignForm = new FormGroup({});
    public foreignFields: FormlyFieldConfig[] = [];
    public foreignModel: any = {}
    public foreignLabel: string = ""
    public foreignResource : string = "";
    public foreignSchema : string = "";
    public displayedFields: string[] = [];
    public errorMsg: string = "";

    constructor(private crudService: CrudService,
                private formlyJsonschema: CrudFormlyJsonschemaService,
                private modalService: NgbModal,
                protected service: DictionaryService
    ) {
        super();
    }

    formatter = (foreignModel: any) => foreignModel.label;


    ngOnInit() {
        // @ts-ignore
        const reference: {resource: string, schema: string, displayedFields: string[] } = this.field.foreignKey.reference;
        this.foreignResource = reference.resource;
        this.foreignSchema = reference.schema;
        this.displayedFields = reference.displayedFields;


        this.formControl.valueChanges.pipe(
            map( v => {
                if (!v) {
                    this.foreignModel = {};
                    this.foreignLabel = "";
                } else if (v != this.foreignModel._id) {
                    this.loadModel(v);
                }
            })
        ).subscribe();
        if (this.hasValue()) {
            this.loadModel(this.formControl.value);
        }
    }

    getResource(id: string) {
        return this.crudService.get(this.foreignResource, id);
    }

    loadModel(id:string) {
        this.getResource(id).pipe(
            map(v => { this.setModel(v); return v; }),
            map( v => this.formControl.patchValue(v._id))
        ).subscribe()
    }

	search: OperatorFunction<string, readonly string[]> = (text$: Observable<string>) => {
        return text$.pipe(
            debounceTime(200),
			distinctUntilChanged(),
			map((term) => this.getList(term)),
            concatAll(),
        )
    }

    getList(term: string) : Observable<readonly string[]> {
        return this.crudService.getList(
            this.foreignResource,
            1,
            10,
            [],
            [new Filters('fulltext', 'eq', term)]
        ).pipe(
            map((result: any) => result["items"]),
        );
    }

    selectedItem(event: any) {
        this.setModel(event.item);
        this.formControl.setValue(event.item._id);
    }

    setModel(model: any) {
        this.foreignModel = model;
        this.foreignLabel = model.label;
        this.loadDisplayedFields();
        this.service.updateParameters(this.field.id!, this.getParameters())
    }

    hasValue() {
        return this.formControl.value !== undefined && this.formControl.value !== null && this.formControl.value !== "";
    }

    add(modal: any) {
		this.modalService.open(modal, { size: 'xl' });
    }

    onResourceModified(resource_id: string) {
        this.modalService.dismissAll();
        this.loadModel(resource_id);
    }

    see(modal: any) {
		this.modalService.open(modal, { size: 'xl' });
    }

    onResourceDeleted(resource_id: string) {
        this.modalService.dismissAll();
        this.remove();
    }

    remove() {
        this.formControl.setValue("");
    }

    loadDisplayedFields() {
        if (this.displayedFields) {
            this.formlyJsonschema.getUpdateFields(this.foreignSchema).subscribe( (form) =>
        {
            for (let i in form.fieldGroup) {
                // @ts-ignore
                let f = form["fieldGroup"][i]
                if (this.displayedFields.indexOf(f.key) == -1) {
                    // @ts-ignore
                    delete form["fieldGroup"][i]
                } else {
                    f.props.disabled = true;
                    f.props.readonly = true;
                    f.props.hideRequiredMarker = true;
                }
            }
            delete form.props
            delete form.templateOptions
            this.foreignFields = [form];
        });
        }
    }

    getParameters() {
        return this.extractParameters(this.foreignModel);
    }

    extractParameters(obj: any) {
        let result: string[] = [];
        for (const [k, v] of Object.entries(obj)) {
            if (typeof(obj[k]) == "string") {
                const matches = obj[k].match(/%[^\s.]+%/g);
                if (matches) {
                    result = result.concat(matches.map((p: string | any[]) => p.slice(1,-1)) || []);
                }

                result = result.concat();
            } else if (typeof(obj[k]) == "object") {
                if (obj[k]) {
                    result = result.concat(this.extractParameters(obj[k]));
                }
            }
        }

        return result
    }
}
