import { Injectable } from '@angular/core'
import { Validators, UntypedFormGroup, UntypedFormControl } from '@angular/forms'

import { MatTableDataSource } from '@angular/material/table'

import { Observable, forkJoin, of as observableOf } from 'rxjs'
import { map, mergeMap } from 'rxjs/operators'

const fieldTypeMap = {
    Int: 'number',
    Float: 'number',
    Boolean: 'checkbox',
}

// const introspectionQuery = gql`
//     query introspect ($modelName: String!) {
//       __type(name: $modelName) {
//         name
//         fields {
//           name
//           type {
//             name
//             kind
//             ofType {
//               name
//             }
//           }
//         }
//       }
//     }
// `

// const inspectionQuery = gql`
//     query inspect ($modelName: String!) {
//       inspect(modelName: $modelName) {
//         pkFields
//         relation_fields
//         joins {
//           name
//           target
//           rel_field
//         }
//       }
//     }
// `

// const dashboardPageQuery = gql`
// query dashboard_page ($group: String!, $name: String!) {
//     dashboard_page(group: $group, name: $name) {
//         name
//         group
//         description
//         html
//         notebook
//         time
//         dfData
//         plotData
//         attachment
//         expandedPanes
//         sections {
//             name
//             plot
//         }
//     }
// }`

export class FieldIntrospection {
    constructor(
        public name: string,
        public type: string,
        public primary: boolean = false,
    ) {}

    get inputType() {
        return fieldTypeMap[this.type] || 'text'
    }

    get isTextArea() {
        return this.type == 'JSONString'
    }

    get isText() {
        return this.type == 'String' || this.type == 'Int' || this.type == 'Float'
    }

    get isCheckbox() {
        return this.type == 'Boolean'
    }

    get validator() {
        if (this.type == 'ID') {
            return Validators.required
        }
    }
}

export class ModelIntrospection {
    constructor(
        public name: string,
        public fields: FieldIntrospection[],
        public relations: object[],
    ) {}

    pkFields(): FieldIntrospection[] {
        return this.fields.filter(f => f.primary)
    }

    columns(): string[] {
        return this.fields.map(f => f.name)
    }

    columnsForGql(): string {
        return this.columns().join(' ')
    }

    get mutationFields(): string {
        return this.fields.map(t => '$' + t.name + ':' + t.type).join(',')
    }

    get mutationVars(): string {
        return this.fields.map(t => t.name + ':$' + t.name).join(',')
    }

    get mutationData(): string {
        return this.columns().join(',')
    }

    getMutationQuery() {
        console.warn('Migrate Graphql')
        return observableOf()
        // return gql`
        // mutation mutation(${this.mutationFields}) {
        //     mutation(${this.mutationVars}) {
        //         ${this.mutationData}
        //     }
        // }
        // `
    }
}

export class JoinField {
    constructor(
        public name: string,
        public target: string,
        public rel_field: string,
    ) {}
}

export class ModelInspection {
    constructor(
        public pkFields: string[],
        public relations: string[],
        public joins: JoinField[],
    ) {}

    get joinedFieldNames() {
        return this.joins.map(join => join.rel_field)
    }
}

export class DashboardPageSection {
    constructor(
        public name: string,
        public plot: string,
    ) {}
}

// export class DashboardPage {
//     constructor(
//         public name: string,
//         public group: string,
//         public errors: string = undefined,
//         public description: string = undefined,
//         public html: string = undefined,
//         public notebook: string = undefined,
//         public dfData: MatTableDataSource<object> = undefined,
//         public plotData: Object = undefined,
//         public time: Date = undefined,
//         public attachment: string = undefined,
//         public expandedPanes: string[] = [],
//         public sections: DashboardPageSection[] = []
//     ) {}
// }

export class Model {
    constructor(
        public inspection: ModelInspection,
        public introspection: ModelIntrospection,
    ) {}

    getFormFields(formGroup: UntypedFormGroup, item): FieldIntrospection[] {
        // Return the form fields and build the FormGroup controls accordingly
        // XXX: move to another class, aka admin.models.Model?
        let formFields = []
        this.introspection.fields.forEach(
            field => {
                // Don't add fields which are in inspection.relations
                if (this.inspection.joinedFieldNames.indexOf(field.name) >= 0) {
                    return
                }
                let control = new UntypedFormControl(field.name, field.validator)
                control.setValue(item[field.name])
                formGroup.addControl(field.name, control)
                formFields.push(field)
            }
        )

        this.inspection.joins.forEach(
            join => {
                let control = new UntypedFormControl()
                control.setValue(item[join.rel_field])
                formGroup.addControl(join.name, control)
            }
        )

        return formFields
    }
}

@Injectable()
export class ModelDataService {
    constructor(
        // private apollo: Apollo
    ) {}

    fullInspect(modelName): Observable<Model> {
        return forkJoin([
            this.inspect(modelName),
            this.introspect(modelName),
        ]).pipe(map(
            res => new Model(res[0], res[1])
        ))
    }

    inspect(modelName): Observable<ModelInspection> {
        console.warn('Migrate Graphql')
        return observableOf()
        // Inspection (Gisaf)
        // return this.get(inspectionQuery, {'modelName': modelName}).pipe(map(
        //     res => {
        //         let joins = res['inspect']['joins'].map(
        //             join => new JoinField(join['name'], join['target'], join['rel_field'])
        //         )
        //         return new ModelInspection(
        //             res['inspect']['pkFields'],
        //             res['inspect']['relation_fields'],
        //             joins
        //         )
        //     }
        // ))
    }

    introspect(modelName): Observable<ModelIntrospection> {
        // Introspection (Graphql)
        console.warn('Migrate Graphql')
        return observableOf()
        // return this.get(introspectionQuery, {'modelName': modelName}).pipe(map(
        //     res => {
        //         var relations = []
        //         let _fields = res['__type']['fields']
        //         // Find the primary keys
        //         var fields = _fields.filter(f => !f.type.name && f.type.kind=='NON_NULL').map(
        //             f => new FieldIntrospection(f.name, f['type']['ofType']['name'], true)
        //         )
        //         var pkFieldNames = fields.map(f => f.name)
        //         _fields.forEach(
        //             resField => {
        //                 let name = resField['name']
        //                 let type = resField['type']
        //                 // Skip primary keys
        //                 if (pkFieldNames.indexOf(name) != -1) {
        //                     return
        //                 }
        //                 if (name == 'geom') {
        //                     // Remove geom column
        //                     // XXX: Should be marked as geojson type
        //                     //      and might be a link to the map or something like that
        //                     return
        //                 }
        //                 if (type['kind'] == 'OBJECT') {
        //                     // Relation
        //                     return
        //                 }
        //                 fields.push(new FieldIntrospection(name, type['name']))
        //             }
        //         )
        //         return new ModelIntrospection(modelName, fields, relations)
        //     }
        // ))
    }

    mutate(model: ModelIntrospection, values: object): Observable<object> {
        console.warn('Migrate Graphql')
        return observableOf()
        // let mutationQuery = model.getMutationQuery()
        // return this.apollo.mutate({
        //     mutation: mutationQuery,
        //     variables: values,
        // })
    }

    resolveItemData(modelName: string, pk: string): Observable<object> {
        return this.fullInspect(modelName).pipe(
            mergeMap(
                model => {
                    // XXX: get pk type and name from model introspection
                    let pkField = model.introspection.pkFields()[0]
                    console.warn('Migrate Graphql')
                    return observableOf()
                    // let query = gql`
                    // query item ($pk: ID) {
                    //     ${modelName} (pk:$pk) {
                    //         ${model.introspection.columnsForGql()}
                    //     }
                    // }`
                    // return this.get(query, {pk: pk}).pipe(map(
                    //     item => {
                    //         if (item) {
                    //             return {
                    //                 'item': item,
                    //                 'model': model,
                    //             }
                    //         }
                    //     }
                    // ))
                }
            )
        )
    }

    all(model: ModelIntrospection, fields?: string[]): Observable<object> {
        if (!fields) {
            fields = model.columns()
        }
        const fieldList = fields.join(' ')
        console.warn('Migrate Graphql')
        return observableOf()
        // const query = gql`query model {${model.name}{${fieldList}}}`
        // return this.get(query)
    }

    get(query, vars: object = {}): Observable<any> {
        console.warn('Migrate Graphql')
        return observableOf()
        // return this.apollo.query({
        //     query: query,
        //     variables: vars,
        //     errorPolicy: 'all',
        // }).pipe(map(
        //     result => {
        //         if (result.errors) {
        //             throw result.errors.map(err => err.message).join(', ')
        //         }
        //         else {
        //             return result.data
        //         }
        //     }
        // ))
    }
}

// @Injectable()
// export class DashboardDataService {
//     constructor(
//         public dashboardService: DashboardService,
//     ) {}

//     getDashboardPage(group: string, name: string): Observable<Dashboard> {
//         return this.dashboardService.getDashboardPageApiDashboardPageGroupNameGet(
//             group, name
//         )
//         // return this.get(dashboardPageQuery, {'name': name, 'group': group}).pipe(map(
//         //     res => {
//         //         if (res['errors'] && res['errors'].length > 0) {
//         //             return new DashboardPage(
//         //                 name,
//         //                 group,
//         //                 res['errors'].map(e => e.message).join(', '),
//         //             )
//         //         }

//         //         let page = res['dashboard_page']
//         //         return new DashboardPage(
//         //             page['name'],
//         //             page['group'],
//         //             '',
//         //             page['description'],
//         //             page['html'],
//         //             page['notebook'],
//         //             JSON.parse(page['dfData']),
//         //             JSON.parse(page['plotData']),
//         //             page['time'],
//         //             page['attachment'],
//         //             page['expandedPanes'],
//         //             page['sections'] ? page['sections'].map(
//         //                 section => new DashboardPageSection(
//         //                     section['name'],
//         //                     section['plot']
//         //                 )
//         //             ) : []
//         //         )
//         //     }
//         // ))
//     }

//     get(query, vars: object = {}): Observable<any> {
//         console.warn('Migrate Graphql')
//         return observableOf()
//         // return this.apollo.query({
//         //     query: query,
//         //     variables: vars
//         // }).pipe(map(
//         //     result => {
//         //         if (result.errors && result.errors.length > 0) {
//         //             return result
//         //         }
//         //         return result.data
//         //     }
//         // ))
//     }
// }
