import { KeyValue } from '@angular/common'
import {
  Component,
  EventEmitter,
  HostListener,
  Input,
  OnInit,
  Output,
  ViewEncapsulation
} from '@angular/core'
import { ValidateFilterSASResponse } from 'src/app/models/sas/validate-filter.model'
import { QueryClause } from 'src/app/models/TableData'
import { HelperService } from 'src/app/services/helper.service'
import { SasStoreService } from 'src/app/services/sas-store.service'
import { DcValidator } from 'src/app/shared/dc-validator/dc-validator'
import { DcValidation } from 'src/app/shared/dc-validator/models/dc-validation.model'
import { isEmpty } from 'src/app/shared/dc-validator/utils/isEmpty'
import {
  EditRecordDropdownChangeEvent,
  EditRecordInputFocusedEvent
} from '../../models/edit-record/edit-record-events'
import { EditRecordModal } from '../../models/EditRecordModal'

@Component({
  selector: 'app-edit-record',
  templateUrl: './edit-record.component.html',
  styleUrls: ['./edit-record.component.scss'],
  encapsulation: ViewEncapsulation.None,
  standalone: false
})
export class EditRecordComponent implements OnInit {
  @Input() currentRecord!: EditRecordModal
  @Input() recordAction: string | null = null
  @Input() libds: string | undefined
  @Input() queryFilter: any
  @Input() filter: boolean = false
  @Input() submitLoading: boolean = false
  @Input() headerPks: string[] = []
  @Input() cellValidation: DcValidation[] = []
  @Input() currentRecordIndex: number = -1
  @Input() currentRecordLoadings: number[] = []
  @Input() currentRecordErrors: number[] = []
  @Input() currentRecordValidator: DcValidator | undefined

  @Output() onRecordChange: EventEmitter<EditRecordModal> =
    new EventEmitter<EditRecordModal>()
  @Output() onRecordInputFocused: EventEmitter<EditRecordInputFocusedEvent> =
    new EventEmitter<EditRecordInputFocusedEvent>()
  @Output()
  onRecordDropdownChanged: EventEmitter<EditRecordDropdownChangeEvent> =
    new EventEmitter<EditRecordDropdownChangeEvent>()
  @Output() onRecordEditClose: EventEmitter<any> = new EventEmitter<any>()
  @Output() onRecordEditConfirm: EventEmitter<any> = new EventEmitter<any>()
  @Output() onNextRecord: EventEmitter<any> = new EventEmitter<any>()
  @Output() onPreviousRecord: EventEmitter<any> = new EventEmitter<any>()

  public currentRecordInvalidCols: string[] = []
  public generateEditRecordUrlLoading: boolean = false
  public generatedRecordUrl: string | null = null
  public addRecordUrl: string | null = null
  public recordNewOrPkModified: boolean = false
  public addRecordLoading: boolean = false
  public validatorTimeout: any

  constructor(
    private sasStoreService: SasStoreService,
    private helperService: HelperService
  ) {}

  ngOnInit(): void {}

  /**
   * Runs native HOT validator against cell value
   * @param cellValidation column rules
   * @param cellValue value in the cell that is beign validated
   * @returns Promise boolean - wether valid or invalid
   */
  async validateRecordCol(
    cellValidation: any,
    cellValue: any
  ): Promise<boolean> {
    return new Promise((resolve, reject) => {
      this.currentRecordValidator?.executeHotValidator(
        cellValidation,
        cellValue,
        (valid: boolean) => {
          resolve(valid)
        }
      )
    })
  }

  /**
   * Fired when the native date picker (intl-date) changes. A native
   * `<input type="date">` value is always ISO `YYYY-MM-DD`, so it is stored
   * as-is. Mirrors recordTimeChange / recordDatetimeChange.
   * @param event native <input type="date"> change event
   * @param colKey column name (key)
   */
  recordDateChange(event: Event, colKey: string) {
    const value = (event.target as HTMLInputElement).value // YYYY-MM-DD
    if (!value || !this.currentRecord) return
    this.currentRecord[colKey] = value
    this.revalidateRecordCol(colKey, value)
  }

  /**
   * Stored `YYYY-MM-DD HH:mm:ss` → native `<input type="datetime-local">` value
   * `YYYY-MM-DDTHH:mm:ss`, so the picker opens at the current value.
   */
  toNativeDatetime(value: any): string {
    if (typeof value !== 'string') return ''
    return value.includes(' ') ? value.replace(' ', 'T') : value
  }

  /**
   * Re-runs validation for a column after a native picker change (the picker
   * handlers set the value programmatically, which does not fire the textarea's
   * input handler). Mirrors recordInputChange's validate + state update.
   */
  private revalidateRecordCol(colName: string, value: any) {
    const colRules = this.currentRecordValidator?.getRule(colName)
    this.validateRecordCol(colRules, value).then((valid: boolean) =>
      this.updateValidationState(colName, valid)
    )
  }

  /**
   * Fired when the native time picker (intl-time) changes. Stores 24h
   * `HH:mm:ss`, padding the seconds the browser may omit. Mirrors the
   * IntlDatetimeEditor conversion used by the HOT grid.
   * @param event native <input type="time"> change event
   * @param colKey column name (key)
   */
  recordTimeChange(event: Event, colKey: string) {
    const value = (event.target as HTMLInputElement).value // HH:mm[:ss]
    if (!value || !this.currentRecord) return
    const stored = value.length === 5 ? `${value}:00` : value
    this.currentRecord[colKey] = stored
    this.revalidateRecordCol(colKey, stored)
  }

  /**
   * Fired when the native datetime picker (intl-datetime) changes. Converts the
   * native `YYYY-MM-DDTHH:mm[:ss]` into SAS' stored `YYYY-MM-DD HH:mm:ss`,
   * padding seconds. Mirrors the IntlDatetimeEditor conversion used by the grid.
   * @param event native <input type="datetime-local"> change event
   * @param colKey column name (key)
   */
  recordDatetimeChange(event: Event, colKey: string) {
    const value = (event.target as HTMLInputElement).value // YYYY-MM-DDTHH:mm[:ss]
    if (!value || !this.currentRecord) return
    const [date, time] = value.split('T')
    const stored = `${date} ${time.length === 5 ? `${time}:00` : time}`
    this.currentRecord[colKey] = stored
    this.revalidateRecordCol(colKey, stored)
  }

  /**
   * Close edit record modal and apply changes by emitting output event
   */
  confirmRecordEdit() {
    if (this.currentRecordInvalidCols.length < 1) {
      this.onRecordChange.emit(this.currentRecord)
    }
  }

  /**
   * Close edit record modal without applying the changes
   */
  closeRecordEdit() {
    this.onRecordEditClose.emit()
  }

  /**
   * Close the modal on Escape (cancel, like the Close button). A native picker
   * swallows the first Escape to dismiss itself, so the modal only closes once
   * focus is back on the form.
   */
  @HostListener('document:keydown.escape')
  onEscapeKey() {
    this.closeRecordEdit()
  }

  /**
   * Emitting output event when dropdown (autocomplete) input in any col change
   * @param colName column name (key)
   * @param col column index
   */
  onRecordDropdownChange(colName: string, col: number) {
    this.onRecordDropdownChanged.emit({ colName, col })
  }

  /**
   * Emitting output event when input is focused (clicked on) so we can run a `dynamic cell validation`
   * Since that bit must be run from the parent component (editor.component)
   * Result is then applied in the `cellValidation` variable and automatically updated in this component.
   * @param event input event
   * @param colName column name (key)
   */
  onRecordInputFocus(event: any, colName: number) {
    this.onRecordInputFocused.emit({ event, colName })
  }

  recordInputPaste(event: any) {
    setTimeout(() => {
      //Trim space at the end
      event.target.value = event.target.value.replace(/\s+$/, '')
    }, 0)
  }

  async recordInputChange(event: any, colName: string): Promise<void> {
    const colRules = this.currentRecordValidator?.getRule(colName)
    const value = event.target.value

    this.helperService.debounceCall(300, () => {
      this.validateRecordCol(colRules, value).then((valid: boolean) => {
        this.updateValidationState(colName, valid)

        if (!valid) {
          this.tryAutoPopulateNotNull(event, colName, colRules, value)
        }
      })
    })
  }

  /**
   * Updates the invalid columns list based on validation result
   */
  private updateValidationState(colName: string, valid: boolean): void {
    const index = this.currentRecordInvalidCols.indexOf(colName)

    if (valid && index > -1) {
      this.currentRecordInvalidCols.splice(index, 1)
    } else if (!valid && index < 0) {
      this.currentRecordInvalidCols.push(colName)
    }
  }

  /**
   * Auto-populates NOTNULL default value when the field is empty and has a default
   */
  private tryAutoPopulateNotNull(
    event: any,
    colName: string,
    colRules: DcValidation | undefined,
    value: any
  ): void {
    if (
      !isEmpty(value) ||
      !this.currentRecordValidator ||
      !this.currentRecord
    ) {
      return
    }

    const defaultValue =
      this.currentRecordValidator.getNotNullDefaultValue(colName)
    if (defaultValue === undefined) return

    this.currentRecord[colName] = defaultValue
    event.target.value = defaultValue

    this.validateRecordCol(colRules, defaultValue).then((isValid: boolean) => {
      this.updateValidationState(colName, isValid)
    })
  }

  onNextRecordClick() {
    this.onNextRecord.emit()
  }

  onPreviousRecordClick() {
    this.onPreviousRecord.emit()
  }

  public copyToClip(text: string) {
    navigator.clipboard.writeText(text)
    this.generatedRecordUrl = text
  }

  async generateEditRecordUrl() {
    if (this.generatedRecordUrl) {
      this.copyToClip(this.generatedRecordUrl)
    } else {
      this.generateEditRecordUrlLoading = true

      const filterQueryClauseTable: QueryClause[] = []

      this.headerPks.forEach((key: string) => {
        let type = 'C'
        let rawValue = ''
        for (let i = 0; i < this.cellValidation.length; i++) {
          const obj = this.cellValidation[i]
          if (obj.data === key) {
            if (
              obj.type === 'numeric' ||
              obj.type === 'intl-date' ||
              obj.type === 'intl-time' ||
              obj.type === 'intl-datetime'
            ) {
              type = 'N'
            }
            break
          }
        }

        if (type === 'C') {
          rawValue = `'${this.currentRecord[key]}'`
        } else {
          rawValue = this.currentRecord[key].toString()
        }

        filterQueryClauseTable.push({
          GROUP_LOGIC: 'AND',
          SUBGROUP_LOGIC: 'AND',
          SUBGROUP_ID: 0,
          VARIABLE_NM: key,
          OPERATOR_NM: '=',
          RAW_VALUE: rawValue
        })
      })

      if (filterQueryClauseTable.length > 0 && this.libds) {
        await this.sasStoreService
          .saveQuery(this.libds, filterQueryClauseTable)
          .then((res: ValidateFilterSASResponse) => {
            const id = res.result[0].FILTER_RK
            const table = res.result[0].FILTER_TABLE
            this.queryFilter = { id: id, table: table }

            //copy to clipboard
            const editLink =
              location.href.split('#')[0] +
              '#/editor/edit-record/' +
              this.queryFilter.table +
              '/' +
              this.queryFilter.id

            this.copyToClip(editLink)

            this.generateEditRecordUrlLoading = false
            this.filter = false
          })
          .catch((err: any) => {
            this.submitLoading = false
          })
      }
    }
  }

  isColPk(col: string) {
    return this.headerPks.indexOf(col) > -1
  }

  originalOrder = (
    a: KeyValue<string, string>,
    b: KeyValue<string, string>
  ): number => {
    return 0
  }

  trackByFn(index: number, item: any): number {
    return index
  }
}
