import { Subscription } from 'rxjs'
import {
  Component,
  AfterViewInit,
  AfterContentInit,
  OnDestroy,
  ChangeDetectorRef,
  LOCALE_ID,
  Input,
  ViewEncapsulation
} from '@angular/core'
import { SasStoreService } from '../services/sas-store.service'
import { globals } from '../_globals'
import { EventService } from '../services/event.service'

import { registerLocaleData } from '@angular/common'
import localeEnGB from '@angular/common/locales/en-GB'
import { HelperService } from '../services/helper.service'
import { QueryClause } from '../models/TableData'
import { PublicGetcolvalsSASResponse } from '../models/sas/public-getcolvals.model'
import { QueryDateTime } from './models/QueryDateTime.model'
import { isSpecialMissing } from '@sasjs/utils/input/validators'
import { get } from 'lodash-es'
import { OnLoadingMoreEvent } from '../shared/autocomplete/autocomplete.component'

registerLocaleData(localeEnGB)
@Component({
  selector: 'app-query',
  templateUrl: './query.component.html',
  styleUrls: ['./query.component.scss'],
  providers: [{ provide: LOCALE_ID, useValue: 'en-GB' }],
  encapsulation: ViewEncapsulation.None,
  standalone: false
})
export class QueryComponent
  implements AfterViewInit, AfterContentInit, OnDestroy
{
  @Input() caching: boolean = true
  @Input() viewboxId: number | undefined

  private rows_increment: number = 100
  private initial_rows: number = 1000

  private columnsSub: Subscription | undefined
  private valuesSub: Subscription | undefined
  private _removeQry: Subscription | undefined
  public cols: Array<any> | undefined
  public libds: string | undefined
  public variable: string | undefined
  public value: string | undefined
  public operator: string | undefined
  public vals: Array<any> | undefined
  public innerWidth: any
  public noBorder: any
  public clauseAmt: Array<any> = ['']
  public numOperators: Array<any> = [
    '=',
    '<',
    '>',
    '<=',
    '>=',
    'BETWEEN',
    'IN',
    'NOT IN',
    'NE'
  ]
  public charOperators: Array<any> = [
    '=',
    '<',
    '>',
    '<=',
    '>=',
    'CONTAINS',
    'IN',
    'NOT IN',
    'NE'
    // "LIKE",
    // "BEGINS_WITH",
  ]
  public logic: string | undefined
  public notIn: boolean = false
  public notArr: boolean = true
  public wasBetween: boolean = false
  public whereClauseLoading: boolean = false
  public dynamicWhereClause: boolean = true
  public usePickers: boolean = false

  private queryObj: {
    elements: any[]
    clauseLogic?: string
    invalidClause?: boolean
  } = {
    elements: [
      {
        logic: null,
        type: null,
        ddtype: null,
        variable: null,
        operator: null,
        value: null,
        valueVariable: false,
        startrow: 0,
        rows: 0,
        nobs: 0,
        values: <
          {
            formatted: any
            unformatted: any
          }[]
        >[],
        operators: []
      }
    ],
    clauseLogic: '',
    invalidClause: false
  }

  public selVar: any = {
    index: null,
    format: null,
    type: null
  }

  public inObj: Array<any> = [
    {
      checked: false,
      value: null
    }
  ]

  public groupLogic: string = 'AND'

  public clauses: any = {
    queryObj: [this.queryObj],
    clauseLogic: [],
    groupLogic: this.groupLogic
  }

  public whereString: string | undefined
  public val: any
  public whereClause: string | undefined
  public logicOperators: Array<string> = ['AND', 'OR']

  /**
   * Temporary values array used in pickers
   * because they need particular format to work
   * before sending values to backend, values are parsed
   */
  public queryDateTime: QueryDateTime[] = []

  public currentClauseIndex: number = -1
  public currentQueryIndex: number = -1
  public pendingINValuesSet: {
    clauseIndex: number
    queryIndex: number
  } | null = null

  constructor(
    private sasStoreService: SasStoreService,
    private eventService: EventService,
    private helperService: HelperService,
    private cdf: ChangeDetectorRef
  ) {
    this.innerWidth = window.screen.width
    if (this.innerWidth > 768) {
      this.noBorder = 'border-left: 2px solid gray;'
    }
  }

  /**
   * Gets and sets temporary values selected with DATETIME or TIME picker
   * Those values are used for picker to work with format it lieks
   * Later before sending values to backend, values are parsed
   */
  getQueryDateTime(clauseIndex: number, queryIndex: number): QueryDateTime {
    let existingQueryDateTime = this.queryDateTime.find(
      (x) => x.clauseIndex === clauseIndex && x.queryIndex === queryIndex
    )

    if (!existingQueryDateTime) {
      const lastIndex =
        this.queryDateTime.push({
          clauseIndex,
          queryIndex,
          date: '',
          time: ''
        }) - 1

      existingQueryDateTime = this.queryDateTime[lastIndex]
    }

    return existingQueryDateTime
  }

  /**
   * When toggling pickers feature we reset the temp picker values array
   */
  usePickersChange() {
    this.queryDateTime = []
    if (this.usePickers) {
      this.clauses.queryObj.forEach((queryObj: any) => {
        queryObj.elements.forEach((element: any) => {
          const isDateOrTime = ['DATETIME', 'TIME', 'DATE'].includes(
            element.ddtype
          )

          if (isDateOrTime && element.valueVariable) {
            element.value = ''
            element.valueVariable = false
          }
        })
      })
    }
  }

  /**
   * Resets all variables used for filtering
   */
  public resetFilter() {
    this.whereString = undefined
    this.whereClause = undefined

    this.queryObj = {
      elements: [
        {
          logic: null,
          type: null,
          ddtype: null,
          variable: null,
          operator: null,
          value: null,
          values: [],
          operators: []
        }
      ]
    }

    this.clauses = {
      queryObj: [this.queryObj],
      clauseLogic: [],
      groupLogic: this.groupLogic
    }

    this.whereClauseFn(true)
  }

  /**
   * `Globals` are used to store filtering state (variables) as a caching feature
   * until browser reloads
   */
  public setToGlobals() {
    if (!this.caching) return

    let objPath = ''

    if (globals.rootParam === 'home' || globals.rootParam === 'editor') {
      if (this.viewboxId) {
        objPath = `viewboxes.${this.viewboxId}`
      } else {
        objPath = 'editor'
      }
    } else if (globals.rootParam === 'view') {
      objPath = 'viewer'
    }

    get(globals, objPath).filter.groupLogic = this.groupLogic
    if (typeof this.whereClause === 'string') {
      get(globals, objPath).filter.whereClause = this.whereClause
    }
    if (typeof this.libds === 'string') {
      get(globals, objPath).filter.libds = this.libds
    }
    get(globals, objPath).filter.clauses = this.clauses
  }

  /**
   * `Globals` are used to store filtering state (variables) as a caching feature
   * until browser reloads
   */
  public getFromGlobals() {
    if (!this.caching) return

    let objPath = ''

    if (globals.rootParam === 'home' || globals.rootParam === 'editor') {
      if (this.viewboxId) {
        objPath = `viewboxes.${this.viewboxId}`
      } else {
        objPath = 'editor'
      }
    } else if (globals.rootParam === 'view') {
      objPath = 'viewer'
    }

    if (get(globals, objPath).filter.cols.length > 0) {
      this.cols = JSON.parse(JSON.stringify(get(globals, objPath).filter.cols))
    }

    if (get(globals, objPath).filter.vals.length > 0) {
      this.vals = JSON.parse(JSON.stringify(get(globals, objPath).filter.vals))
    }

    if (get(globals, objPath).filter.groupLogic !== '') {
      this.groupLogic = get(globals, objPath).filter.groupLogic
      this.whereClause = get(globals, objPath).filter.whereClause
      this.libds = get(globals, objPath).filter.libds
      this.clauses = get(globals, objPath).filter.clauses
      this.whereClauseFn()
    }
  }

  /**
   * Sets filtering multiple caluses group logic (and / or)
   *
   * @param groupLogic to set
   */
  public setGroupLogic(groupLogic: any) {
    this.groupLogic = groupLogic
    this.clauses.groupLogic = groupLogic
    this.whereClauseFn()

    this.setToGlobals()
  }

  public dateChange(
    pickerValue: any,
    query: any,
    queryIndex: number,
    clauseIndex: number,
    queryValueIndex?: number
  ) {
    let valueInDays = this.helperService.convertJsDateToSasDate(pickerValue)

    if (query.operator === 'BETWEEN' && queryValueIndex !== undefined) {
      this.clauses.queryObj[clauseIndex].elements[queryIndex].value[
        queryValueIndex
      ] = valueInDays
    }

    this.setVariableValues(valueInDays, queryIndex, clauseIndex)
  }

  public dateTimeChange(
    query: any,
    queryIndex: number,
    clauseIndex: number,
    queryValueIndex?: number
  ) {
    const queryDateTime = this.getQueryDateTime(clauseIndex, queryIndex)

    if (queryDateTime.date === '') {
      if (query.value === '') return

      queryDateTime.date = query.value.split(':')[0]
    }

    const dateHours = parseInt(queryDateTime.time.split(':')[0])
    const dateMinutes = parseInt(queryDateTime.time.split(':')[1])
    const dateSeconds = parseInt(queryDateTime.time.split(':')[2]) || 0

    let dateObject = new Date(queryDateTime.date)
    let dateTimeObject = new Date(
      dateObject.getFullYear(),
      dateObject.getMonth(),
      dateObject.getDate(),
      dateHours,
      dateMinutes,
      dateSeconds
    )

    let valueInSeconds = this.helperService.convertJsDateToSasDate(
      dateTimeObject,
      'seconds'
    )

    if (query.operator === 'BETWEEN' && queryValueIndex !== undefined) {
      this.clauses.queryObj[clauseIndex].elements[queryIndex].value[
        queryValueIndex
      ] = valueInSeconds
    }

    this.setVariableValues(valueInSeconds, queryIndex, clauseIndex)
  }

  public timeChange(
    time: any,
    query: any,
    ind: number,
    clauseIndex: number,
    queryValueIndex?: number
  ) {
    time = time.target.value

    let hours = parseInt(time.split(':')[0])
    let minutes = parseInt(time.split(':')[1])
    let seconds = parseInt(time.split(':')[2]) || 0

    let hourSeconds = hours * 60 * 60
    let minuteSeconds = minutes * 60

    let inputValue = hourSeconds + minuteSeconds + seconds

    if (query.operator === 'BETWEEN' && queryValueIndex !== undefined) {
      this.clauses.queryObj[clauseIndex].elements[ind].value[queryValueIndex] =
        inputValue
    }

    this.setVariableValues(inputValue, ind, clauseIndex)
  }

  /**
   * Takes the clause object, removes current clause and makes new array (table)
   * ready to be sent to SAS
   */
  public dynamicWhereClauseCreator(groupIndex: number, clauseIndex: number) {
    let dynamicWhereClauseTable: QueryClause[] = []

    if (
      this.clauses.queryObj.length > 1 ||
      this.clauses.queryObj[groupIndex].elements.length > 1
    ) {
      for (
        let groupInd = 0;
        groupInd < this.clauses.queryObj.length;
        groupInd++
      ) {
        for (
          let clauseInd = 0;
          clauseInd < this.clauses.queryObj[groupInd].elements.length;
          clauseInd++
        ) {
          if (groupInd === groupIndex && clauseInd === clauseIndex) continue
          if (
            [null, undefined].includes(
              this.clauses.queryObj[groupInd].elements[clauseInd].variable
            ) ||
            [null, undefined].includes(
              this.clauses.queryObj[groupInd].elements[clauseInd].operator
            ) ||
            [null, undefined].includes(
              this.clauses.queryObj[groupInd].elements[clauseInd].value
            )
          )
            continue

          let rawValue = ''
          const operator =
            this.clauses.queryObj[groupInd].elements[clauseInd].operator
          const variable =
            this.clauses.queryObj[groupInd].elements[clauseInd].variable

          if (operator === 'BETWEEN') {
            rawValue = `${this.clauses.queryObj[groupInd].elements[clauseInd].value[0]} AND ${this.clauses.queryObj[groupInd][clauseInd].value[1]}`
          } else if (operator === 'IN' || operator === 'NOT IN') {
            this.clauses.queryObj[groupInd][clauseInd].value.forEach(
              (element: any) => {
                if (element.checked) {
                  let value = element.val

                  if (typeof value === 'string' && !isSpecialMissing(value)) {
                    if (rawValue.length > 0) {
                      rawValue += `,'${value}'`
                    } else {
                      rawValue = `('${value}'`
                    }
                  } else {
                    if (rawValue.length > 0) {
                      rawValue += `,${value}`
                    } else {
                      rawValue = `(${value}`
                    }
                  }
                }
              }
            )

            if (rawValue.length > 0) {
              rawValue += ')'
            }
          } else if (
            this.clauses.queryObj[groupInd].elements[clauseInd].value === ''
          ) {
            if (
              this.clauses.queryObj[groupInd].elements[clauseInd].type ===
              'char'
            ) {
              rawValue = `' '`
            } else {
              rawValue = '.'
            }
          } else {
            if (
              this.clauses.queryObj[groupInd].elements[clauseInd].type ===
              'char'
            ) {
              rawValue = `'${this.clauses.queryObj[groupInd].elements[clauseInd].value}'`
            }
          }

          dynamicWhereClauseTable.push({
            GROUP_LOGIC: this.clauses.groupLogic,
            SUBGROUP_LOGIC:
              this.clauses.queryObj[groupInd].clauseLogic || 'AND',
            SUBGROUP_ID: groupInd,
            VARIABLE_NM: variable,
            OPERATOR_NM: operator,
            RAW_VALUE:
              rawValue ||
              this.clauses.queryObj[groupInd].elements[clauseInd].value
          })
        }
      }
    }

    return dynamicWhereClauseTable
  }

  public async getValuesLoadMore(
    event: OnLoadingMoreEvent,
    variable: any,
    ind: any,
    clauseIndex: any
  ) {
    const libds = this.libds || ''

    try {
      if (this.cols !== undefined) {
        for (let index = 0; index < this.cols.length; index++) {
          let element = this.cols[index]
          if (element.NAME === variable) {
            this.selVar.index = index
          }
        }

        const DDTYPE = this.cols[this.selVar.index].DDTYPE

        const { startrow: currentStartrow, rows: currentRows } =
          this.clauses.queryObj[clauseIndex].elements[ind]

        const ROWS_INCREMENT: number = this.rows_increment
        const STARTROW = currentStartrow + currentRows

        let dynamicWhereClauseTable: any[] = []

        if (this.dynamicWhereClause) {
          dynamicWhereClauseTable = this.dynamicWhereClauseCreator(
            clauseIndex,
            ind
          )
        }

        const isDateOrTime = ['DATETIME', 'TIME', 'DATE'].includes(DDTYPE)

        this.sasStoreService
          .getQueryValues(
            variable,
            libds,
            dynamicWhereClauseTable,
            STARTROW,
            ROWS_INCREMENT
          )
          .then((res: PublicGetcolvalsSASResponse) => {
            const values = res.vals
            const { STARTROW, ROWS, NOBS } = res.meta[0]

            this.clauses.queryObj[clauseIndex].elements[ind].startrow = STARTROW
            this.clauses.queryObj[clauseIndex].elements[ind].rows = ROWS
            this.clauses.queryObj[clauseIndex].elements[ind].nobs = NOBS

            if (
              !(
                values.length === 1 &&
                values[0].FORMATTED === '' &&
                values[0].UNFORMATTED === ''
              )
            ) {
              this.clauses.queryObj[clauseIndex].elements[ind].values.push(
                ...values.map((item: any) => {
                  const numeric = res.$vals.vars.UNFORMATTED.type === 'num'
                  const specialMissing =
                    numeric && isSpecialMissing(item.UNFORMATTED)

                  let unformatted = specialMissing
                    ? '.' + item.UNFORMATTED
                    : item.UNFORMATTED

                  // If it's not special missing and is numeric and null then it is normal missing `.`
                  if (numeric && unformatted === null) unformatted = '.'

                  return {
                    formatted: isDateOrTime
                      ? item.FORMATTED.split('.')[0]
                      : item.FORMATTED, //WORKAROUND SHOULD BE FIXED PROPERLY (#413): We will remove the milliseconds inside the formatted value, if decimal exists and it is date, time or datetime ddtype
                    unformatted: unformatted
                  }
                })
              )

              const currentValues =
                this.clauses.queryObj[clauseIndex].elements[ind].values.length
              const moreExists = NOBS - currentValues > 0

              event.loadMoreFinished(moreExists)
            } else {
              event.loadMoreFinished(false)
            }

            this.whereClauseFn()
            this.setToGlobals()
            this.whereClauseLoading = false

            /**
             * If getValues request did not finish when operator was changed to IN, we need to set values after the request is finished.
             * Othervise values are not beign populated
             */
            if (this.pendingINValuesSet !== null) {
              const { clauseIndex, queryIndex } = this.pendingINValuesSet

              this.setINValues(clauseIndex, queryIndex)

              this.pendingINValuesSet = null
            }

            this.cdf.detectChanges()
          })
          .catch((err: any) => {
            this.whereClauseLoading = false
          })
      }
    } catch (error) {
      this.eventService.catchResponseError('public/getcolvals', error)
    }
  }

  public async getValues(variable: any, ind: any, clauseIndex: any) {
    this.whereClauseLoading = true

    let libds: string = ''

    if (this.libds !== undefined) {
      libds = this.libds
    }

    this.clearValues(ind, clauseIndex)
    this.clauses.queryObj[clauseIndex].elements[ind].values = []

    this.clauses.queryObj[clauseIndex].elements[ind].value = ''
    this.clauses.queryObj[clauseIndex].elements[ind].operator = '='
    this.clauses.queryObj[clauseIndex].elements[ind].variable = variable

    try {
      if (this.cols !== undefined) {
        for (let index = 0; index < this.cols.length; index++) {
          let element = this.cols[index]
          if (element.NAME === variable) {
            this.selVar.index = index
          }
        }

        const DDTYPE = this.cols[this.selVar.index].DDTYPE

        this.clauses.queryObj[clauseIndex].elements[ind].ddtype = DDTYPE

        this.clauses.queryObj[clauseIndex].elements[ind].type =
          this.cols[this.selVar.index].TYPE

        if (this.clauses.queryObj[clauseIndex].elements[ind].type === 'num') {
          this.clauses.queryObj[clauseIndex].elements[ind].operators =
            this.numOperators
        } else {
          this.clauses.queryObj[clauseIndex].elements[ind].operators =
            this.charOperators
        }

        let dynamicWhereClauseTable: any[] = []

        if (this.dynamicWhereClause) {
          dynamicWhereClauseTable = this.dynamicWhereClauseCreator(
            clauseIndex,
            ind
          )
        }

        const isDateOrTime = ['DATETIME', 'TIME', 'DATE'].includes(DDTYPE)

        this.sasStoreService
          .getQueryValues(
            variable,
            libds,
            dynamicWhereClauseTable,
            1,
            this.initial_rows
          )
          .then((res: PublicGetcolvalsSASResponse) => {
            const values = res.vals
            const { STARTROW, ROWS, NOBS } = res.meta[0]

            this.clauses.queryObj[clauseIndex].elements[ind].startrow = STARTROW
            this.clauses.queryObj[clauseIndex].elements[ind].rows = ROWS
            this.clauses.queryObj[clauseIndex].elements[ind].nobs = NOBS

            this.clauses.queryObj[clauseIndex].elements[ind].values = values
              .map((item: any) => {
                const numeric = res.$vals.vars.UNFORMATTED.type === 'num'
                const specialMissing =
                  numeric && isSpecialMissing(item.UNFORMATTED)

                let unformatted = specialMissing
                  ? '.' + item.UNFORMATTED
                  : item.UNFORMATTED

                // If it's not special missing and is numeric and null then it is normal missing `.`
                if (numeric && unformatted === null) unformatted = '.'

                return {
                  formatted: isDateOrTime
                    ? item.FORMATTED.split('.')[0]
                    : item.FORMATTED, //WORKAROUND SHOULD BE FIXED PROPERLY (#413): We will remove the milliseconds inside the formatted value, if decimal exists and it is date, time or datetime ddtype
                  unformatted: unformatted
                }
              })
              .slice(0, 2000)

            this.whereClauseFn()
            this.setToGlobals()
            this.whereClauseLoading = false

            /**
             * If getValues request did not finish when operator was changed to IN, we need to set values after the request is finished.
             * Othervise values are not beign populated
             */
            if (this.pendingINValuesSet !== null) {
              const { clauseIndex, queryIndex } = this.pendingINValuesSet

              this.setINValues(clauseIndex, queryIndex)

              this.pendingINValuesSet = null
            }

            this.cdf.detectChanges()
          })
          .catch((err: any) => {
            this.whereClauseLoading = false
          })
      }
    } catch (error) {
      this.eventService.catchResponseError('public/getcolvals', error)
    }
  }

  public getlogic(logic: any) {
    this.logic = logic
    this.whereClauseFn()
  }

  public setLogic() {
    this.whereClauseFn()
    this.setToGlobals()
  }

  public isArr(val: any) {
    return (
      val instanceof Array &&
      val.length > 0 &&
      typeof val[0].checked !== 'undefined'
    )
  }

  public selectedLabelChange(label: string, query: any) {
    query.valueVariable = label === 'Variable'
    query.value = ''
    this.whereClauseFn()
  }

  public variableInputChange(
    queryVariable: any,
    index: number,
    clauseIndex: number,
    event: any
  ) {
    this.getValues(queryVariable, index, clauseIndex)
  }

  public setVariableValues(valueObj: any, ind: any, clauseIndex: any) {
    // clauseIndex is current clause in group clauses, ind is current query in clause
    let operator = this.clauses.queryObj[clauseIndex].elements[ind].operator

    if (operator !== 'BETWEEN' && operator !== 'IN' && operator !== 'NOT IN') {
      this.clauses.queryObj[clauseIndex].elements[ind].value =
        valueObj.toString()
    }
    this.whereClauseFn()
    this.setToGlobals()
  }

  public setVariableOperator(ind: any, operator: any, clauseIndex: any) {
    let currentValue = this.clauses.queryObj[clauseIndex].elements[ind].value
    if (
      currentValue instanceof Array &&
      operator !== 'IN' &&
      operator !== 'NOT IN'
    ) {
      if (
        currentValue instanceof Array &&
        currentValue.length === 2 &&
        typeof currentValue[0] === 'string'
      ) {
        currentValue = currentValue[0]
      } else {
        currentValue = ''
      }
    }

    this.clearValuesOperator(ind, clauseIndex)
    this.clauses.queryObj[clauseIndex].elements[ind].operator = operator
    // tslint:disable-next-line:max-line-length
    if (
      operator === '=' ||
      operator === '>' ||
      operator === '<' ||
      operator === '<=' ||
      operator === '>=' ||
      operator === 'NE'
    ) {
      this.clauses.queryObj[clauseIndex].elements[ind].value = currentValue
    }

    if (operator === 'BETWEEN') {
      this.clauses.queryObj[clauseIndex].elements[ind].value = []
      this.clauses.queryObj[clauseIndex].elements[ind].value.push(currentValue)
      this.clauses.queryObj[clauseIndex].elements[ind].value.push('')
    }

    if (operator === 'IN' || operator === 'NOT IN') {
      this.setINValues(clauseIndex, ind)
    }

    this.whereClauseFn()
    this.setToGlobals()
  }

  public setINValues(clauseIndex: number, queryIndex: number) {
    const vals = []

    const inValues =
      this.clauses.queryObj[clauseIndex].elements[queryIndex].values

    if (inValues.length < 1) {
      this.pendingINValuesSet = {
        clauseIndex,
        queryIndex
      }
    }

    for (let index = 0; index < inValues.length; index++) {
      vals.push({ checked: false, val: inValues[index].formatted })
    }

    this.clauses.queryObj[clauseIndex].elements[queryIndex].value = vals
  }

  public clearValues(ind: any, clauseIndex: any) {
    this.clauses.queryObj[clauseIndex].elements[ind].value = ''
  }

  public clearValuesOperator(ind: any, clauseIndex: any) {
    let operator = this.clauses.queryObj[clauseIndex].elements[ind].operator
    if (
      operator === 'BETWEEN' ||
      operator === 'IN' ||
      operator === 'NOT IN' ||
      operator === 'CONTAINS'
    ) {
      this.clearValues(ind, clauseIndex)
    }
  }

  /**
   * Checks if clause is invalid and sets bool property accordingly
   * It is being used in html to mark invalid clauses red
   */
  public hasInvalidCluase(clauses: any): boolean {
    for (let clause of clauses) {
      clause['invalidClause'] = false

      if (
        clause.value === '' &&
        !(clause.operator === 'NE' || clause.operator === 'CONTAINS')
      ) {
        clause['invalidClause'] = true

        return true
      }

      if (
        clause.variable === null ||
        clause.operator === null ||
        clause.value === null
      ) {
        clause['invalidClause'] = true

        return true
      }
    }

    clauses.invalidClause = false

    return false
  }

  /**
   * Checks if group clause is invalid and sets bool property accordingly
   * It is being used in html to mark invalid group clauses red
   */
  public hasInvalidGroupCluase(): boolean {
    for (let i = 0; i < this.clauses.queryObj.length; i++) {
      if (this.hasInvalidCluase(this.clauses.queryObj[i].elements)) {
        this.clauses.queryObj[i].invalidClause = true

        return true
      } else {
        this.clauses.queryObj[i].invalidClause = false
      }
    }

    return false
  }

  public addClause(clauseIndex: number) {
    if (this.hasInvalidCluase(this.clauses.queryObj[clauseIndex].elements)) {
      this.eventService.showInfoModal(
        'Error',
        'Cannot add new clause, when one or more clauses are invalid or empty.'
      )
      return
    }

    this.clauseAmt.push('')
    this.clauses.queryObj[clauseIndex].elements.push({
      logic: null,
      type: null,
      variable: null,
      operator: null,
      value: null,
      values: [],
      operators: []
    })

    let clauseLogic = this.clauses.queryObj[clauseIndex].clauseLogic

    if (typeof clauseLogic === 'undefined') {
      this.clauses.queryObj[clauseIndex].clauseLogic = 'AND'
    } else if (clauseLogic === 'OR') {
      this.clauses.queryObj[clauseIndex].clauseLogic = 'OR'
    } else {
      this.clauses.queryObj[clauseIndex].clauseLogic = 'AND'
    }

    this.whereClauseFn()
    this.setToGlobals()
  }

  public addGroupClause() {
    if (this.hasInvalidGroupCluase()) {
      this.eventService.showInfoModal(
        'Error',
        'Cannot add new clause, when one or more clauses are invalid or empty.'
      )
      return
    }

    this.clauses.queryObj.push({
      elements: [
        {
          logic: null,
          type: null,
          variable: null,
          operator: null,
          value: null,
          values: [],
          operators: []
        }
      ]
    })
    this.setToGlobals()
  }

  public isInHtmlCollection(
    value: string,
    collection: HTMLCollectionOf<HTMLOptionElement>
  ): boolean {
    for (let i = 0; i < collection.length; i++) {
      if (collection[i].value === value) return true
    }

    return false
  }

  public removeGroupClause(clauseIndex: number) {
    this.clauses.queryObj.splice(clauseIndex, 1)
    this.whereClauseFn()
  }

  public removeClause(ind: number, clauseIndex: any) {
    this.clauses.queryObj[clauseIndex].elements.splice(ind, 1)
    this.whereClauseFn()
    this.setToGlobals()
  }

  public whereClauseFn(reset: boolean = false) {
    const clauses = this.helperService.deepClone(this.clauses)

    if (reset) clauses.queryObj[0].elements = []

    if (this.libds !== undefined) {
      let result = this.sasStoreService.whereClauseCreator(
        clauses,
        this.groupLogic,
        this.libds
      )

      if (!reset) {
        this.whereClause = result.whereClause
        this.whereString = result.string
      }
    }
  }

  async ngAfterViewInit() {
    this._removeQry = this.sasStoreService.removeQuery.subscribe((remove) => {
      let res = remove
      if (this.clauses.queryObj.length >= 1) {
        this.clauses.queryObj.length = 1
        if (this.clauses.queryObj[0].elements.length >= 1) {
          this.clauses.queryObj[0].elements.length = 1
          this.clauses.queryObj[0].elements = []
          this.clauses.queryObj[0].elements.push({
            logic: null,
            type: null,
            variable: null,
            operator: null,
            value: null,
            values: [],
            operators: []
          })
          this.whereClauseFn()
        }
      }
    })

    this.columnsSub = this.sasStoreService.columns.subscribe(
      (response: any) => {
        let cols = response.data.cols

        if (globals.rootParam === 'home' || globals.rootParam === 'editor') {
          this.cols = cols
          let some = cols[0].NAME
          this.libds = response.libds

          globals.editor.filter.cols = JSON.parse(JSON.stringify(cols))
        }

        if (globals.rootParam === 'view') {
          if (globals.viewer.filter.cols.length < 1) {
            this.cols = cols
            let some = cols[0].NAME
            this.libds = response.libds

            globals.viewer.filter.cols = JSON.parse(JSON.stringify(cols))
          }
        }

        if (!this.libds) {
          this.libds = response.libds
        }
      }
    )

    this.valuesSub = this.sasStoreService.values.subscribe((res: any) => {
      if (globals.rootParam === 'home' || globals.rootParam === 'editor') {
        if (globals.editor.filter.vals.length < 1) {
          this.vals = res.vals

          globals.editor.filter.vals = JSON.parse(JSON.stringify(res.vals))
        }
      }

      if (globals.rootParam === 'view') {
        if (globals.viewer.filter.vals.length < 1) {
          this.vals = res.vals

          globals.viewer.filter.vals = JSON.parse(JSON.stringify(res.vals))
        }
      }
    })
  }

  onAutocompleteLoadingMore(
    event: OnLoadingMoreEvent,
    variable: any,
    ind: any,
    clauseIndex: any
  ) {
    this.getValuesLoadMore(event, variable, ind, clauseIndex)
  }

  ngAfterContentInit(): void {
    this.getFromGlobals()

    setTimeout(() => {
      let varInput: HTMLElement | null =
        document.querySelector('#vals_var_id0_0')

      if (varInput) varInput.focus()
    }, 500)
  }

  ngOnDestroy(): void {
    if (this.columnsSub) {
      this.columnsSub.unsubscribe()
    }

    if (this.valuesSub) {
      this.valuesSub.unsubscribe()
    }
  }
}
