import { Injectable, Component } from "@angular/core"
import { Observable, BehaviorSubject, Subject, ReplaySubject, combineLatest } from 'rxjs'
import { map, take } from 'rxjs/operators'

import { LngLat, LngLatLike } from "maplibre-gl"
import { DataService } from "./data.service"
import { DefaultService } from "./openapi/services.gen"
import { Bootstrap, Map } from "./openapi/types.gen"

export type MapPos = {
  center: LngLatLike,
  zoom: number,
  bearing: number,
  pitch: number,
}

export const dbName = 'treetrail'  // See also dbConfig in app.module
export const settingsDbName = 'settings'

export class Config {
  constructor(
    public user: string = undefined,
    public skipIntro: boolean = false,
    public vibrate: boolean = true,
    public showZones: { [zone: string]: boolean } = {},
    public alertDistance = 250,
    public map?: Map,
    public bootstrap?: Bootstrap,
    public mapPos?: MapPos,
    public background: string = undefined,
    // public server: {} = {},
    // public client: {} = {},
    // public app: {} = {},
  ) { }
}

@Injectable()
export class ConfigService {
  constructor(
    public dataService: DataService,
    private api: DefaultService,
  ) {
  }

  public conf: BehaviorSubject<Config> // = new BehaviorSubject<Config>(new Config())

  userPrefsKeyList = ['userName', 'skipIntro', 'vibrate', 'showZones', 'background']

  bootstrap(): Observable<Bootstrap> {
    return this.api.getBootstrapBootstrapGet().pipe(map(
      resp => {
        this.conf = new BehaviorSubject<Config>(new Config(
          '',
          false,
          true,
          {},
          250,
          resp.map,
          resp,
          undefined,
        ))
        // this.conf.value.bootstrap = resp
        // this.conf.value.map = resp.map
        // this.conf.next(this.conf.value)
        return resp
      }
    ))
  }

  getMapCenter(): Observable<LngLatLike> {
    return this.conf.pipe(map(
      conf => <LngLatLike>{
        lng: conf.map.lng,
        lat: conf.map.lat
      }
    ))
  }

  loadUserSettings(data: unknown[]) {
    // TODO: assert the whole idea and use of storing the config in a BahaviourSubject
    data.forEach(kv => {
      if (kv['value']) {
        this.conf.value[kv['key']] = kv['value']
      }
    })
    // Update the list of types of zones from actual data
    this.dataService.all.subscribe(
      all => {
        let zoneTypes = new Set((Object.values(all.zones).map(
          zone => zone.type
        )))
        zoneTypes.forEach(
          zoneType => {
            if (this.conf.value.showZones[zoneType] === undefined) {
              this.conf.value.showZones[zoneType] = false
            }
          }
        )
        this.conf.next(this.conf.value)
      }
    )
    // if (!this.conf.value.skipIntro) {
    //  this.router.navigate(['intro'], {relativeTo: this.route});
    // }
  }

  storeUserData(): void {
    this.userPrefsKeyList.forEach(
      key => this.dataService.dbService.update(
        settingsDbName, {
        key: key,
        value: this.conf.value[key]
      }).subscribe()
    )
  }

  setUserPrefValue(pref: string, key: string, value: any) {
    let conf = this.conf.value
    conf[pref][key] = value
    this.conf.next(conf)
    this.storeUserData()
  }

  setUserPref(pref: string, value: any) {
    let conf = this.conf.value
    // userName is special, read (and thus stored) in bootstrap
    if (pref == 'userName') {
      conf.bootstrap.user = value
      this.conf.next(this.conf.value)
    }
    else {
      conf[pref] = value
    }
    this.conf.next(conf)
    this.storeUserData()
  }

  setMapPos(mapPos: MapPos): Observable<unknown> {
    this.updateConf({mapPos: mapPos})
    return this.dataService.dbService.update(settingsDbName, {
      key: 'mapPos',
      value: {
        center: mapPos.center,
        zoom: mapPos.zoom,
        pitch: mapPos.pitch,
        bearing: mapPos.bearing,
      }
    })
  }

  updateConf(newConf: Object) {
    this.conf.pipe(take(1)).subscribe(
      conf => this.conf.next({ ...conf, ...newConf })
    )
  }
}
