import pickerUi from '../../infrastructure/view/components/picker/picker.ui'
import { DEFAULT_CONFIG } from "../settings/constants"

export default class SynDatePicker extends EventTarget {
  constructor(config: any, $element: Element | null) {
    super()
    this.config = Object.assign({}, DEFAULT_CONFIG, config)
    this.$element = $element
    if (this.$element !== null) this.init()
    else throw new Error('')
  }

  private initData() {
    this.MONTHS_MAP.set(0, { monthAbbrev: 'jan', monthLabel: 'January' })
    this.MONTHS_MAP.set(1, { monthAbbrev: 'feb', monthLabel: 'February' })
    this.MONTHS_MAP.set(2, { monthAbbrev: 'mar', monthLabel: 'March' })
    this.MONTHS_MAP.set(3, { monthAbbrev: 'apr', monthLabel: 'April' })
    this.MONTHS_MAP.set(4, { monthAbbrev: 'may', monthLabel: 'May' })
    this.MONTHS_MAP.set(5, { monthAbbrev: 'jun', monthLabel: 'Juny' })
    this.MONTHS_MAP.set(6, { monthAbbrev: 'jul', monthLabel: 'July' })
    this.MONTHS_MAP.set(7, { monthAbbrev: 'aug', monthLabel: 'August' })
    this.MONTHS_MAP.set(8, { monthAbbrev: 'sep', monthLabel: 'September' })
    this.MONTHS_MAP.set(9, { monthAbbrev: 'oct', monthLabel: 'October' })
    this.MONTHS_MAP.set(10, { monthAbbrev: 'nov', monthLabel: 'November' })
    this.MONTHS_MAP.set(11, { monthAbbrev: 'dec', monthLabel: 'December' })
    this.setSelectedDate(this.config.selectedDate)
    const currentDate = new Date()
    this.maxYear = currentDate.getFullYear()
    this.minYear = this.maxYear - 80

  }

  private init() {
    this.clearElement()
    this.createInitialDOMComponents()
    this.attachHandlers()
    this.initData()
  }

  private clearElement() {
    if (this.$element !== null) this.$element.innerHTML = ''
  }

  private createInitialDOMComponents() {
    this.$datepickerInput = document.createElement('input') as HTMLInputElement
    this.$datepickerInput.type = 'text'
    this.$datepickerInput.readOnly = true
    this.$datepickerInput.className = 'synDatepicker__input'
    if (this.$element !== null) this.$element.insertAdjacentElement('beforeend', this.$datepickerInput)
  }

  private attachHandlers() {
    this.handlerFocusInput()
  }

  private hidePicker() {
    this.setIsPickerVisible(false)
    this.$picker.remove()
  }

  private showPicker() {
    this.currentCalendarPosition = {} as any
    this.setCurrentCalendarPosition(this.getSelectedDate().getMonth(), this.getSelectedDate().getFullYear())
    this.setIsPickerVisible(true)
    this.$picker = document.createElement('div')
    this.$picker.className = 'synDatepicker__picker'
    this.$picker.innerHTML = pickerUi
    if (this.$element !== null) {
      this.$element.insertAdjacentElement('beforeend', this.$picker)
      this.renderMonthDates(this.getSelectedDate())
      this.attachPickersControlls()
    }
    this.changeMonthSelectedOption(this.getCurrentCalendarPosition().month)
  }

  private handleChangeMonth(order: number) {
    const { month, year } = this.getCurrentCalendarPosition()
    const selectedMonthYear = new Date(year, month + (1 * order))
    if(selectedMonthYear.getFullYear() >= this.minYear && selectedMonthYear.getFullYear() <= this.maxYear){
    this.renderMonthDates(selectedMonthYear)
    this.setCurrentCalendarPosition(selectedMonthYear.getMonth(), selectedMonthYear.getFullYear())
    this.changeMonthSelectedOption(this.getCurrentCalendarPosition().month)
    this.changeYearSelectedOption(this.getCurrentCalendarPosition().year)
  }
  }

  private setCurrentCalendarPosition(month: number, year: number) {
    this.getCurrentCalendarPosition().month = month
    this.getCurrentCalendarPosition().year = year
  }

  private renderYearOptionsCombo() {
    for (let index = this.minYear; index <= this.maxYear; index++) {
      this.$datepickerYearCombo?.insertAdjacentHTML('beforeend', `<option value="${index}">${index}</option>`)
    }
  }

  private attachPickersControlls() {
    const $controls = this.$picker.querySelectorAll('.synDatepicker__control') ?? []
    $controls.forEach(($control: Element) => {
      $control.addEventListener('click', () => {
        const isNext = $control.closest('.synDatepicker__control--next') !== null
        this.handleChangeMonth(isNext ? 1 : -1)
      })
    })
    setTimeout(() => {
      this.$datepickerMonthCombo = this.$picker.querySelector<HTMLSelectElement>('.synDatepicker__combo--month') ?? document.createElement('select')
      this.$datepickerYearCombo = this.$picker.querySelector<HTMLSelectElement>('.synDatepicker__combo--year') ?? document.createElement('select')
      this.renderYearOptionsCombo()
      this.changeMonthSelectedOption(this.getCurrentCalendarPosition().month)
      this.changeYearSelectedOption(this.getCurrentCalendarPosition().year)
      this.$datepickerMonthCombo.addEventListener('change', () => {
        if (this.$datepickerMonthCombo !== null) {
          const selectedMonth = parseInt(this.$datepickerMonthCombo.value) ?? 0
          const year = new Date(this.getSelectedDate().getTime()).getFullYear()
          this.setCurrentCalendarPosition(selectedMonth, year)
          const monthYearSelected = new Date(year, selectedMonth)
          this.renderMonthDates(monthYearSelected)
        }
      })
      this.$datepickerYearCombo.addEventListener('change', () => {
        if (this.$datepickerYearCombo !== null) {
          const selectedYear = parseInt(this.$datepickerYearCombo.value) ?? 0
          const month = new Date(this.getSelectedDate().getTime()).getMonth()
          this.setCurrentCalendarPosition(month, selectedYear)
          const monthYearSelected = new Date(selectedYear, month)
          this.renderMonthDates(monthYearSelected)
        }
      })
    })
  }

  private handlerFocusInput() {
    this.$datepickerInput.addEventListener('focus', (e: FocusEvent) => {
      if (e.target === e.currentTarget && !(this.isPickerVisible())) this.showPicker()
    })
    document.addEventListener('mousedown', (e: MouseEvent) => {
      if (this.isPickerVisible() && this.$element !== null && e.target !== this.$element && !(this.$element.contains(e.target as Node))) this.hidePicker()
    })
  }

  private handleClickDay($cellDay: Element) {
    const timestamp = window.parseInt($cellDay.getAttribute('data-timestamp') ?? '')
    this.setSelectedDate(new Date(timestamp))
    if (this.getSelectedDate().getMonth() < this.getCurrentCalendarPosition().month) {
      this.handleChangeMonth(-1)
    } else if (this.getSelectedDate().getMonth() > this.getCurrentCalendarPosition().month) {
      this.handleChangeMonth(1)
    } else {
      if (this.$selectedDayCell !== null) this.$selectedDayCell.classList.toggle('syncDatepickerCalendar__day--selected', false)
      $cellDay.classList.toggle('syncDatepickerCalendar__day--selected')
      this.$selectedDayCell = $cellDay
    }
    this.hidePicker()
  }

  private getFormattedDates(date: Date): string {
    return date.toLocaleDateString()
  }

  private renderMonthDates(date: Date) {
    const $tableBody = this.$picker.querySelector('.synDatepicker__body') ?? document.createElement('tbody')
    const dateCalendarArray = this.getArrayMonthDates(date)
    const selectedMonth = date.getMonth()
    $tableBody.addEventListener('click', (e: Event) => {
      if ((e.target as Element).closest('.syncDatepickerCalendar__day') !== null) this.handleClickDay((e.target as Element).closest('.syncDatepickerCalendar__day') as Element)
    })
    $tableBody.innerHTML = ''
    dateCalendarArray.forEach((dateWeek: Date[]) => {
      const $rowFragment = document.createDocumentFragment()
      $rowFragment.append(...dateWeek.map((date: Date) => {
        const $tableCell = document.createElement('td')
        if (this.cellIsSelected(date)) this.$selectedDayCell = $tableCell
        $tableCell.textContent = `${date.getDate()}`
        $tableCell.setAttribute('data-timestamp', `${date.getTime()}`)
        $tableCell.className = `syncDatepicker__cell syncDatepickerCalendar__day ${this.cellIsSelected(date) ? 'syncDatepickerCalendar__day--selected' : ('')} syncDatepicker__cell--body${selectedMonth !== date.getMonth() ? ' syncDatepickerCalendar__day--diff' : ''}`
        return $tableCell
      }))
      const $tableRow = document.createElement('tr')
      $tableRow.className = 'synDatepicker__row synDatepicker__row--body'
      $tableRow.append($rowFragment)
      $tableBody.append($tableRow)
    })
  }

  private getArrayMonthDates(calendar: Date) {
    const selectedMonthCalendar = new Date(calendar.getFullYear(), calendar.getMonth(), 1)
    selectedMonthCalendar.setDate(1)
    const month = selectedMonthCalendar.getMonth()
    const year = selectedMonthCalendar.getFullYear()
    const nextMonthCalendar = new Date(year, month + 1, 1)
    nextMonthCalendar.setDate(nextMonthCalendar.getDate() - 1)
    const lastDayOfMonth = nextMonthCalendar.getDate()
    nextMonthCalendar.setDate(nextMonthCalendar.getDate() + 1)
    let dateArray: Date[][] = [new Array(7)]
    let firstMonthDayWeek = 0
    let lastMonthDayWeek = 6
    let week = 0

    for (let index = 1; index <= lastDayOfMonth; index++) {
      const monthDate = new Date(year, month, index)
      const weekDay = monthDate.getDay()
      if (index === 1) firstMonthDayWeek = weekDay
      dateArray[week][weekDay] = monthDate
      if (weekDay === 6 && index < lastDayOfMonth) {
        week++
        dateArray[week] = new Array(7)
      }
      if (index === lastDayOfMonth) lastMonthDayWeek = weekDay
    }

    const previousMonthCalendar = new Date(selectedMonthCalendar.setDate(selectedMonthCalendar.getDate() - firstMonthDayWeek))

    for (let index = 0; index < firstMonthDayWeek; index++) {
      dateArray[0][index] = new Date(previousMonthCalendar.getTime())
      previousMonthCalendar.setDate(previousMonthCalendar.getDate() + 1)
    }

    for (let index = lastMonthDayWeek + 1; index < 7; index++) {
      dateArray[week][index] = new Date(nextMonthCalendar.getTime())
      nextMonthCalendar.setDate(nextMonthCalendar.getDate() + 1)
    }

    return dateArray
  }

  public onChange(handler: EventListenerOrEventListenerObject) {
    this.addEventListener('datepicker-changing', handler)
  }

  private renderSelectedDates() {
    this.$datepickerInput.value = this.getFormattedDates(this.getSelectedDate())
  }

  private isSelectedDate(date: Date) {
    return this.selectedDate === undefined || (this.getSelectedDate().getDate() === date.getDate() &&
      this.getSelectedDate().getMonth() === date.getMonth() &&
      this.getSelectedDate().getFullYear() === date.getFullYear())
  }


  private cellIsSelected(date: Date) {
    return this.isSelectedDate(date)
  }

  private dispatchDatepickerEvent() {
    const datepickerChangingEvent = new CustomEvent('datepicker-changing')
    this.dispatchEvent(datepickerChangingEvent)
  }

  private changeMonthSelectedOption(selectedMonth: number) {
    if (this.$datepickerMonthCombo !== null) {
      this.$datepickerMonthCombo.value = `${selectedMonth}`
    }
  }

  private changeYearSelectedOption(selectedYear: number) {
    if (this.$datepickerYearCombo !== null) {
      this.$datepickerYearCombo.value = `${selectedYear}`
    }
  }

  public setSelectedDate(selectedDate: Date) {
    if (!this.isSelectedDate(selectedDate)) {
      this.dispatchDatepickerEvent()
    }
    this.selectedDate = selectedDate
    this.renderSelectedDates()
  }

  public getSelectedDate() {
    return this.selectedDate
  }

  public isPickerVisible() {
    return this.visible
  }

  private setIsPickerVisible(visible: boolean) {
    this.visible = visible
  }

  private getCurrentCalendarPosition() {
    return this.currentCalendarPosition
  }

  private MONTHS_MAP: Map<number, { monthLabel: string, monthAbbrev: string }> = new Map()
  private visible: boolean = false
  private config: any
  private $picker!: Element
  private minYear!: number
  private maxYear!: number
  private selectedDate!: Date
  private $datepickerMonthCombo: HTMLSelectElement | null = null
  private $datepickerYearCombo: HTMLSelectElement | null = null
  private $selectedDayCell: Element | null = null
  private currentCalendarPosition!: { month: number, year: number }
  private $datepickerInput!: HTMLInputElement
  private $element: Element | null
}

