import { current, produce } from "immer"
import { localStorageTexts, texts } from "../_constants"
import { getFromLocalStorage, saveToLocalStorage } from "../_storage"
import { GroupModel, MedadataModel, QuestionModel, QuestionnaireModel, QuestionReferenceModel } from "../models"
import { getQuestionType } from "./_utils"
import { Modal } from "bootstrap"
import { Question } from "./questionTemplates"

import DQMNavigation from "./_navigation"
import DQMValidator from "../rules/_validator"
import DQMRules from "../rules/_rules"
import DQMOptions from "../_options"
import createJsonOutput from "./_json"
import { createButton } from "./_buttons"

import './_modal'
import './_global'
import DQMRulesActions from "../rules/_action"

interface State {
  [key: string]: string | number | boolean
}

let inputState: State = {}

export default class DQMRenderer {
  private readonly options: DQMOptions
  private readonly questionnaire: QuestionnaireModel
  private readonly pages: GroupModel[]
  private questions: QuestionModel[]
  private readonly metadata: MedadataModel

  private readonly eventListenersAttached: WeakMap<Element, boolean> = new WeakMap()
  private readonly contentContainer: any
  private readonly navigation: DQMNavigation
  
  private readonly questionInstances: Map<string, Question> = new Map()
  private readonly renderedQuestions: Set<string> = new Set()

  private currentPageIndex: number = 0
  private currentGroupIndex: number = 0

  protected pageCount: number
  protected groupsCount: number

  private cacheButtonInitialized = false
  private readonly totalCount: number = 0

  private readonly linkUrlBase: HTMLElement = document.querySelector('[data-link-url]')
  private readonly linkAssignBase: HTMLElement = document.querySelector('[data-link-assign]')

  private readonly questionElementCache: Map<string, string> = new Map()

  constructor(options: DQMOptions, questionnaire: QuestionnaireModel) {
    if (!questionnaire || typeof questionnaire !== 'object' || Object.keys(questionnaire).length === 0)
      throw new Error('QuestionnaireModel cannot be null, undefined, or empty')
    
    this.options = options
    this.questionnaire = questionnaire
    
    if (questionnaire.pages) {
      if (!Array.isArray(questionnaire.pages) || questionnaire.pages.length === 0)
        throw new Error('questionnaire.pages cannot be empty')
      
      if (!questionnaire.pages[0] || !questionnaire.pages[0].groups || questionnaire.pages[0].groups.length === 0)
        throw new Error('questionnaire.pages[0].groups cannot be empty')
      
      this.pages = questionnaire.pages
      this.pageCount = questionnaire.pages[0].groups.length
      this.questions = questionnaire.questions
      this.totalCount = questionnaire.pages[0].groups.length - 1
      this.navigation = new DQMNavigation()
      this.eventListenersAttached = new WeakMap()
    } else {
      if (!questionnaire.questions || Object.keys(questionnaire.questions).length === 0)
        throw new Error('questionnaire.questions cannot be empty when pages are not provided')
      
      this.questions = questionnaire.questions
    }
    
    if (!questionnaire.metadata || typeof questionnaire.metadata !== 'object' || Object.keys(questionnaire.metadata).length === 0)
      throw new Error('questionnaire.metadata cannot be null, undefined, or empty')
    
    this.metadata = questionnaire.metadata
    
    this.contentContainer = document.querySelector('[data-page-content]')
    
    if (!this.contentContainer)
      throw new Error('Content container with [data-page-content] attribute not found in DOM')
    
    this.initializeUrlRouting()
  }

  private clearCaches(): void {
    this.questionElementCache.clear()
    this.renderedQuestions.clear()
    this.questionInstances.clear()
  }

  private initializeUrlRouting(): void {
    window.addEventListener('popstate', (event) => {
      if (event.state) {
        this.currentPageIndex = event.state.pageIndex ?? 0
        this.currentGroupIndex = event.state.groupIndex ?? 0
        this.navigateToCurrentState(false)
      } else {
        this.parseUrlParams()
      }
    })

    this.parseUrlParams()
  }

  private parseUrlParams(): void {
    const urlParams = new URLSearchParams(window.location.search)
    const pageParam = urlParams.get('pg')
    const groupParam = urlParams.get('grp')

    if (pageParam !== null) {
      const pageIndex = parseInt(pageParam, 10)
      
      if (!isNaN(pageIndex) && pageIndex >= 0 && pageIndex < this.pages[0].groups.length)
        this.currentPageIndex = pageIndex
    }

    if (groupParam !== null) {
      const groupIndex = parseInt(groupParam, 10)

      if (!isNaN(groupIndex) && groupIndex >= 0)
        this.currentGroupIndex = groupIndex
    }
  }

  private updateUrl(updateHistory: boolean = true): void {
    const url = new URL(window.location.href)
    
    url.searchParams.set('pg', this.currentPageIndex.toString())
    url.searchParams.set('grp', this.currentGroupIndex.toString())
    
    const newUrl = url.toString()
    
    if (updateHistory) {
      const state = {
        pageIndex: this.currentPageIndex,
        groupIndex: this.currentGroupIndex
      }

      window.history.pushState(state, '', newUrl)
    } else {
      window.history.replaceState({
        pageIndex: this.currentPageIndex,
        groupIndex: this.currentGroupIndex
      }, '', newUrl)
    }
  }

  private navigateToCurrentState(updateHistory: boolean = true): void {
    this.currentPageIndex = Math.max(0, Math.min(this.currentPageIndex, this.pages[0].groups.length - 1))
    
    const currentPage = this.pages[0].groups[this.currentPageIndex]
    this.pageCount = currentPage.groups.length
    this.currentGroupIndex = Math.max(0, Math.min(this.currentGroupIndex, this.pageCount - 1))

    if (updateHistory)
      this.updateUrl()

    saveToLocalStorage(this.currentGroupIndex, localStorageTexts.pageGroupIndex)
    saveToLocalStorage(this.currentPageIndex, localStorageTexts.pageIndex)

    this.updatePageDisplay()
    this.updateNavigation()
    
    document.body.scrollTop = 0
    document.documentElement.scrollTop = 0
  }

  private updatePageDisplay(): void {
    const allPages = document.querySelectorAll('[id^="dqm-page-"]')
    allPages.forEach(page => page.classList.add('d-none'))

    const currentPage = document.querySelector(`#dqm-page-${this.currentPageIndex}`)
    
    if (currentPage) {
      currentPage.classList.remove('d-none')
      DQMRulesActions.show(currentPage)
    }

    const allGroups = document.querySelectorAll(`[dqm-group^="${this.currentPageIndex}"]`)
    allGroups.forEach(group => {
      DQMRulesActions.hide(group)
    })

    const currentGroup = document.querySelector(`[dqm-group="${this.currentPageIndex}${this.currentGroupIndex}"]`)
    if (currentGroup) {
      DQMRulesActions.show(currentGroup)
      currentGroup.classList.remove('d-none')
    }
  }

  public init(): void {
    if (this.options.enableLogging)
      console.log("[DQM]: Version 1.0.0")

    this.clearCaches()

    const content = this.renderPages()

    if (this.contentContainer)
      this.contentContainer.innerHTML = content

    document.addEventListener('dqm-uistate-changed', () => {
      this.updateNavigation()
    })

    document.addEventListener('dropdownLoaded', () => {
      this.updateNavigation()
    })

    this.updateNavigation()
    this.collectInputState()
    this.setupInputListeners()

    setTimeout(() => {
      this.restoreInputState()
      this.updateNavigation()

      setTimeout(() => this.initializePageComponents(), 1)

      this.questionInstances.forEach((questionInstance: Question) => {
        questionInstance.addFunctionality()
      })

      this.navigateToCurrentState(false)
    }, 100)
  }

  public renderOnlyQuestions(): void {
    const content = this.renderQuestionsByIds()
    
    if (this.options.enableLogging)
      console.log("[DQM] Rendering Questions")

    if (this.contentContainer)
      this.contentContainer.innerHTML = content

    this.questionInstances.forEach((questionInstance: Question) => {
      questionInstance.addFunctionality()
    })
  }

  private renderQuestionsByIds(): string {
    const updatedQuestions = this.questions.map((question: QuestionModel) => {
      if (this.renderedQuestions.has(question.id) && this.questionElementCache.has(question.id))
        return { element: this.questionElementCache.get(question.id) || '' }

      const questionInstance: any = getQuestionType(question, "", "")
      const element: any = questionInstance.init()
      
      this.questionInstances.set(question.id, questionInstance)
      this.addInputChangeListener(element, question.id)
      
      const elementHtml = element.outerHTML
      this.questionElementCache.set(question.id, elementHtml)
      this.renderedQuestions.add(question.id)

      return { element: elementHtml }
    })

    return updatedQuestions
      .map((q) => q.element)
      .join("")
  }

  private collectInputState(): void {
    const elements = document.querySelectorAll<HTMLInputElement>('[data-dqm-input]')
  
    inputState = produce(inputState, (draft) => {
      elements.forEach((element) => {
        const key = element.id || element.name

        if (key) {
          if (element.type === 'checkbox') {
            draft[key] = element.checked
          } else if (element.type === 'radio') {
            if (element.checked)
              draft[key] = element.value
          } else {
            draft[key] = element.value
          }
        }
      })
    })
  }

  private restoreInputState(): void {
    const elements = document.querySelectorAll<HTMLInputElement>('[data-dqm-input]')

    elements.forEach((element) => {
      const key = element.id || element.name

      if (key && key in inputState) {
        if (element.type === 'checkbox') {
          element.checked = inputState[key] as boolean
        } else if (element.type === 'radio') {
          element.checked = (element.value === inputState[key])
        } else {
          element.value = inputState[key] as string
        }
      }
    })

    this.updateNavigation()
  }

  private setupInputListeners(): void {
    const elements = document.querySelectorAll<HTMLInputElement>('[data-dqm-input]')

    elements.forEach((element) => {
      if (!this.eventListenersAttached.has(element)) {
        element.addEventListener('input', this.collectInputState)
        this.eventListenersAttached.set(element, true)
      }
    })
  }

  private renderPages(): string {
    if (this.options.enableLogging) 
      console.time("[DQM]: Page loaded in")

    const pageGroup = this.pages[0].groups
    const currentPage = pageGroup[this.currentPageIndex]
    this.pageCount = currentPage.groups.length

    const pages: string[] = []

    for (let i = 0; i < pageGroup.length; i++) {
      const page = pageGroup[i]
      const isActive = i === this.currentPageIndex
      const hasRules = page.rules?.length > 0
      const rulesAttr = hasRules ? `data-group-rules='${JSON.stringify(page.rules)}'` : ""

      pages.push(`
        <div 
          id="dqm-page-${i}" 
          data-page-id="${page.id}" 
          class="${isActive ? 'd-block' : 'd-none'}"
          ${rulesAttr}
        >
          ${this.renderPage(page, i)}
        </div>
      `)
    }

    if (this.options.enableLogging) 
      console.timeEnd("[DQM]: Page loaded in")

    return pages.join("")
  }

  private renderPage(page: GroupModel, pageIndex: number): string {
    return `
      <section class="content-header agrinsure-header-section mb-3">
        <div class="d-flex justify-content-between align-items-center">
          <h1 class="h2 mb-0">${page.groupname ?? 'Fragebogen'}</h1>
          <h5 class="mb-0 text-muted">${this.metadata.questionnairestr}</h5>
        </div>
      </section>
      ${this.renderGroups(page, pageIndex)}`
  }

  private renderGroups(page: GroupModel, pageIndex: number): string {
    const groupsHtml: string[] = []

    for (let i = 0; i < page.groups.length; i++)
      groupsHtml.push(this.renderGroup(page.groups[i], pageIndex, i))

    return groupsHtml.join("")
  }

  private renderGroup(group: GroupModel, pageIndex: number, index: number): string {
    const groupId = `group_${group.id}`
    const groupHtmlId = `groupHtml_${groupId}`
    const hasRules = group.rules?.length > 0
    const rulesAttr = hasRules ? `data-group-rules='${JSON.stringify(group.rules)}'` : ""

    const htmlParts: string[] = [
      `<div 
        id="${groupHtmlId}"
        class="group-container ${hasRules ? "d-none" : ""}" 
        dqm-group="${pageIndex}${index}" 
        data-page-id="${pageIndex}" 
        ${rulesAttr}
      >
        <h2 class="h3 mt-2">${group.groupname}</h2>
        <hr class="mb-4 mt-2"/>
        <div class="questions-container" id="${groupId}">
          ${this.renderQuestions(group.questionreferences, groupId)}
        </div>`
    ]

    if (group.groups?.length) {
      const nestedHtml = group.groups.map((subGroup) =>
        this.renderNestedGroup(subGroup, `${groupId}_`)
      ).join('')

      htmlParts.push(`<div class="nested-groups-container">${nestedHtml}</div>`)
    }

    htmlParts.push('</div>')
    return htmlParts.join('')
  }

  private renderNestedGroup(group: GroupModel, parentId: string = ''): string {
    const groupId = `${parentId}${group.id}`
    const groupHtmlId = `groupHtml_${groupId}`
    const hasRules = group.rules?.length > 0
    const rulesAttr = hasRules ? `data-group-rules='${JSON.stringify(group.rules)}'` : ""

    const htmlParts: string[] = [
      `<div 
        class="nested-group ${hasRules ? "d-none" : ""}" 
        id="${groupHtmlId}"
        ${rulesAttr}
      >
        <h2 class="h2">${group.groupname}</h2>
        <hr class="mb-4 mt-4"/>
        <div class="questions-container" id="${groupId}">
          ${this.renderQuestions(group.questionreferences, groupId)}
        </div>`
    ]

    if (group.groups?.length) {
      const nestedHtml = group.groups.map((subGroup) =>
        this.renderNestedGroup(subGroup, `${groupId}_`)
      ).join('')
      htmlParts.push(`<div class="nested-groups-container">${nestedHtml}</div>`)
    }

    htmlParts.push('</div>')
    return htmlParts.join('')
  }

  private renderQuestions(questionRefs: QuestionReferenceModel[], groupId: string): string {
    const questionMap = new Map<string, QuestionReferenceModel>(
      questionRefs.map(ref => [ref.questionid, ref])
    )

    const reorderedQuestions = questionRefs
      .map(ref => this.questions.find(q => q.id === ref.questionid))
      .filter((q): q is QuestionModel => q !== undefined)

    const updatedQuestions = produce(reorderedQuestions, (draft: QuestionModel[]) => {
      draft.forEach((question, index) => {
        const ref = questionMap.get(question.id)
        
        if (!ref) 
          return

        if (this.renderedQuestions.has(question.id) && this.questionElementCache.has(question.id)) {
          Object.assign(draft[index], {
            element: this.questionElementCache.get(question.id)
          })
          return
        }

        question.required = ref.required
        question.disabled = this.metadata.readonly
        question.link = this.options.showQuestionLinks ? this.linkUrlBase.dataset.linkUrl + "&i1=" + question.id : ""
        question.assignment = this.options.showQuestionAssignmentLinks ? this.linkAssignBase.dataset.linkAssign + "&i1=" + ref.assignment : ""

        if (question.subquestions && Array.isArray(question.subquestions)) {
          question.subquestions.forEach(subq => {
            subq.link = this.options.showQuestionLinks
              ? this.linkUrlBase.dataset.linkUrl + "&i1=" + subq.id
              : ""
            subq.assignment = this.options.showQuestionAssignmentLinks
              ? this.linkAssignBase.dataset.linkAssign + "&i1=" + ref.assignment
              : ""
          })
        }

        const originalQuestion = current(question)
        const questionInstance = getQuestionType(originalQuestion, groupId, "")
        const element = questionInstance.init()

        this.questionInstances.set(question.id, questionInstance)
        this.addInputChangeListener(element, question.id)

        const elementHtml = element.outerHTML
        this.questionElementCache.set(question.id, elementHtml)
        this.renderedQuestions.add(question.id)

        Object.assign(draft[index], {
          answer: questionInstance.getAnswer() ?? '',
          element: elementHtml
        })
      })
    })

    return updatedQuestions.map((q: any) => q.element).join('')
  }

  private addInputChangeListener(element: HTMLElement, questionId: string) {
    if (this.eventListenersAttached.has(element))
      return

    const saveAnswer = (event: Event) => {
      const inputElement = event.target as HTMLInputElement
  
      const updatedQuestions = produce(this.questions, draft => {
        const questionToUpdate = draft.find(q => q.id === questionId)
        
        if (questionToUpdate)
          questionToUpdate.answer = inputElement.value
      })
  
      this.questions = updatedQuestions
    }
  
    element.addEventListener('input', saveAnswer)
    element.addEventListener('change', saveAnswer)
    
    this.eventListenersAttached.set(element, true)
  }

  private updateNavigation(): void {
    const pageNavigation = document.querySelector('[data-page-navigation]')
    
    if (!pageNavigation) 
      return

    const contactButton = pageNavigation.querySelector('#contact-sidebar-toggle')
    const downloadButton: HTMLAnchorElement = document.querySelector("#downloadQuestionnaireBtn")
    
    Array.from(pageNavigation.children).forEach(child => {
      if (child !== contactButton && child !== downloadButton)
        pageNavigation.removeChild(child)
    })

    this.pages[0].groups.forEach((page: GroupModel, index: number) => {
      const pageButton = this.createPageButton(page, index)
      pageNavigation.appendChild(pageButton)

      const pageContainer = document.createElement('div')
      pageContainer.classList.add('w-100', 'ps-3')
      pageNavigation.appendChild(pageContainer)

      if (this.currentPageIndex == index)
        this.createGroupButtons(page.groups, pageContainer)
    })

    if (this.options.enableLogging) {
      const text = JSON.stringify(this.questionnaire, null, 4)
      const blob = new Blob([text], { type: "application/json" })
      const url = URL.createObjectURL(blob)

      if (downloadButton) {
        downloadButton.href = url
        downloadButton.download = `${this.metadata.questionnairestr}.json`
        downloadButton.innerHTML = `<i class="fa fa-download"></i> Fragebogen herunterladen`
        downloadButton.className = "btn w-100 mb-2 text-start text-black"
        downloadButton.style = "background-color: #FFCC99"
      }
    }
  }

  private switchPage(pageId: number): void {
    if (pageId == this.currentPageIndex)
      return

    this.currentPageIndex = pageId
    this.currentGroupIndex = 0

    if (this.options.enableLogging)
      console.log("[DQM]: Switching to Page " + this.currentPageIndex + ". Current Group: " + this.currentGroupIndex)
    
    this.navigateToCurrentState()
  }

  private switchGroup(groupId: number): void {
    if (groupId == this.currentGroupIndex)
      return
      
    this.currentGroupIndex = groupId

    if (this.options.enableLogging)
      console.log("[DQM]: Switching to Group " + this.currentGroupIndex)

    this.navigateToCurrentState()
  }

  private createGroupButton(group: GroupModel, unanswered: any, navigationIndex: number): HTMLButtonElement {
    const groupButton: HTMLButtonElement = document.createElement('button')

    groupButton.type = 'button'

    const isActive = this.currentGroupIndex === navigationIndex

    groupButton.className = `btn btn-custom mb-2 w-100 ${isActive ? "border border-danger" : ""}`
    groupButton.innerHTML = `
      <div class="d-flex justify-content-between align-items-center w-100">
        <span class="text-white text-start">${group.groupname}</span>
        <span class="badge rounded-pill ${unanswered.required > 0 ? "text-bg-danger" : "text-bg-secondary"}">${unanswered.required}</span>
      </div>`

    return groupButton
  }

  private createGroupButtons(groups: GroupModel[], parentContainer: HTMLElement, parentGroupIndex?: number): void {
    groups.forEach((group: GroupModel, groupIndex: number) => {
      const unanswered = this.navigation.calculateUnanswered(group.questionreferences)
      const navigationIndex = parentGroupIndex ?? groupIndex
      const groupButton = this.createGroupButton(group, unanswered, navigationIndex)

      if (!this.eventListenersAttached.has(groupButton)) {
        groupButton.addEventListener('click', () => {
          this.switchGroup(navigationIndex)
        })
        this.eventListenersAttached.set(groupButton, true)
      }

      parentContainer.appendChild(groupButton)
      this.attachEventListeners(group.questionreferences)

      if (group.groups && group.groups.length > 0) {
        const subGroupContainer = document.createElement('div')
        subGroupContainer.classList.add('subgroup-container')
        subGroupContainer.classList.add('w-100', 'ps-3')
        parentContainer.appendChild(subGroupContainer)

        this.createGroupButtons(group.groups, subGroupContainer, navigationIndex)
      }
    })
  }

  private createPageButton(page: GroupModel, index: number): HTMLButtonElement {
    const button = document.createElement('button')
    button.type = 'button'
    button.dataset.groupId = page.id
    button.className = `btn btn-custom bg-success text-white mb-2 w-100 ${index === this.currentPageIndex ? 'active border border-danger' : ''}`
  
    const totalCount = page.groups.reduce((total, e) => 
      total + 
      this.navigation.calculateUnanswered(e.questionreferences).required +
      e.groups.reduce((subTotal, r) => subTotal + this.navigation.calculateUnanswered(r.questionreferences).required, 0)
    , 0)
  
    const container = document.createElement('div')
    container.className = 'd-flex justify-content-between align-items-center w-100'
  
    const textSpan = document.createElement('span')
    textSpan.className = 'text-white text-start'
    textSpan.textContent = page.groupname ?? 'Fragebogen'
  
    const badgeSpan = document.createElement('span')
    badgeSpan.className = `badge rounded-pill ${totalCount.toString() > 0 ? "text-bg-danger" : "text-bg-secondary"}`
    badgeSpan.textContent = totalCount.toString()
  
    container.append(textSpan, badgeSpan)
    button.appendChild(container)
  
    if (!this.eventListenersAttached.has(button)) {
      button.addEventListener('click', () => {
        this.switchPage(index)
      })
      this.eventListenersAttached.set(button, true)
    }

    return button
  }

  private attachEventListeners(questions: QuestionReferenceModel[]): void {
    const elements = document.querySelectorAll(`[data-dqm-input-id]`)
  
    elements.forEach(el => {
      const questionId = el.getAttribute('data-dqm-input-id')
      
      if (!questionId || !questions.some(q => q.questionid === questionId)) 
        return
  
      if (!this.eventListenersAttached.has(el)) {
        const event = el instanceof HTMLInputElement || el instanceof HTMLSelectElement ? 'change' : 'input'
  
        el.addEventListener(event, this.updateNavigation.bind(this))
        this.eventListenersAttached.set(el, true)
      }
    })
  }

  private initializePageComponents(): void {
    const validator = new DQMValidator()
    const rules = new DQMRules(this.metadata)

    validator.addValidation()
    rules.init()
    
    this.setupNavigation()
  }

  private showCurrentGroup(groupContainers: NodeListOf<HTMLElement>): void {
    const activePageGroups = Array.from(groupContainers).filter((container) => {
      return container.closest(`[data-page-id="${this.currentPageIndex}"]`)
    })
  
    activePageGroups.forEach((container: HTMLElement, i: number): void => {
      const isCurrentGroup = i === this.currentGroupIndex
      isCurrentGroup ? DQMRulesActions.show(container) : DQMRulesActions.hide(container)
  
      if (isCurrentGroup) {
        const backButton: HTMLButtonElement | null = container.querySelector('#backButton')

        if (backButton) {
          const isFirstGroup = this.currentGroupIndex === 0
          backButton.style.display = isFirstGroup ? 'none' : 'inline-block'
          backButton.disabled = isFirstGroup
        }
      }
    })
  }

  private createNavigationButtons(): HTMLElement {
    const buttonContainer = document.createElement('div')
    buttonContainer.classList.add('mb-4')
    Object.assign(buttonContainer.style, { position: 'absolute', bottom: '0', right: '0', width: '100%' })
    
    const buttonWrapper = document.createElement('div')
    buttonWrapper.id = 'buttonContainer'
    buttonWrapper.classList.add('d-flex', 'justify-content-between')
  
    const leftDiv = document.createElement('div')
    leftDiv.classList.add('d-flex')
  
    const rightDiv = document.createElement('div')
    rightDiv.classList.add('d-flex')
  
    const backButton = createButton('backButton', 'btn btn-outline-dark ms-2', texts.back, "fa fa-arrow-left")
    const cancelButton = createButton('cancelButton', 'btn btn-danger ms-2', texts.cancel, "fa fa-times")
    const nextButton = createButton('nextButton', 'btn btn-success ms-2', texts.continue, "fa fa-arrow-right", 'right')
    const chacheBtn = createButton("cache", "btn btn-secondary ms-2", texts.cache, "fa fa-save")

    const json = this.questionnaire
    
    if (this.metadata.readonly)
      chacheBtn.style.display = 'none'
    
    chacheBtn.addEventListener('click', () => {
      const modal = new Modal(document.getElementById('chache-modal'), {})
      modal.show()
    })
    
    if (!this.cacheButtonInitialized) {
      const chacheBtnSub = document.querySelector('#chache-questionnaire')

      chacheBtnSub.addEventListener('click', () => {
        const redirect: HTMLInputElement = document.querySelector('#redirect') 
        const redirectUrl: HTMLInputElement = document.querySelector('#redirectUrl') 
        redirect.value = "false"
        redirectUrl.value = redirectUrl.value + "&pg=" + this.currentPageIndex + "&grp=" + this.currentGroupIndex

        const dqmFrm: HTMLFormElement = document.querySelector('#dqmfrm') 
        const dqmJson: HTMLInputElement | null = document.querySelector('[data-dqm-json]')
        const formData = new FormData(dqmFrm)

        const formDataObject: Array<any>[] = []
        formData.forEach((value, key) => {
          formDataObject.push([key, value])
        })

        const data = createJsonOutput(JSON.stringify(json), formDataObject)

        dqmJson.value = data
        dqmFrm.submit()
      }, { once: true })

      this.cacheButtonInitialized = true
    }

    if (this.options.finishButton) {
      const btn = createButton("finish", "btn btn-warning ms-2", texts.end)
      rightDiv.appendChild(btn)

      btn.addEventListener('click', () => {
        const form: HTMLFormElement = document.querySelector('#dqmfrm')
        const dqmJson: HTMLInputElement | null = document.querySelector('[data-dqm-json]')

        const formData = new FormData(form)

        const formDataObject: Array<any>[] = []
        formData.forEach((value, key) => {
          formDataObject.push([key, value])
        })

        const data = createJsonOutput(JSON.stringify(json), formDataObject)

        dqmJson.value = data

        if (this.options.validateForm) {
          if (form.reportValidity())
            form.submit()
        } else {
          form.submit()
        }
      })
    }

    rightDiv.append(backButton, cancelButton, chacheBtn, nextButton)
    buttonWrapper.append(leftDiv, rightDiv)
    buttonContainer.appendChild(buttonWrapper)
  
    return buttonContainer
  }

  private setupNavigation(): void {
    const groupContainers: NodeListOf<HTMLElement> = document.querySelectorAll('.group-container')
  
    groupContainers.forEach((container: HTMLElement): void => {
      const buttonContainer = this.createNavigationButtons()
      container.appendChild(buttonContainer)
  
      const nextButton: HTMLButtonElement = buttonContainer.querySelector('#nextButton')
      const backButton: HTMLButtonElement = buttonContainer.querySelector('#backButton')
      const cancelButton: HTMLButtonElement = buttonContainer.querySelector('#cancelButton')

      nextButton.addEventListener('click', () => this.handleNext())
      backButton.addEventListener('click', () => this.handleBack())
      cancelButton.addEventListener('click', () => this.handleCancel())
    })
  
    const storedGroup = getFromLocalStorage(localStorageTexts.pageGroupIndex)
    const storedPage = getFromLocalStorage(localStorageTexts.pageIndex)

    const urlParams = new URLSearchParams(window.location.search)

    if (!urlParams.has('pg') && storedPage)
      this.currentPageIndex = storedPage

    if (!urlParams.has('grp') && storedGroup)
      this.currentGroupIndex = storedGroup
  
    document.querySelectorAll('[data-load-group]').forEach((element: HTMLElement) => {
      const index = element.dataset.loadGroup
  
      element.addEventListener('click', () => {
        this.switchGroup(parseInt(index))
      })
    })

    this.showCurrentGroup(groupContainers)
    
    const restartBtn: HTMLButtonElement = document.querySelector('#close-modal')
    
    restartBtn.addEventListener('click', () => {
      this.currentGroupIndex = 0
      this.currentPageIndex = 0
      
      this.navigateToCurrentState()
      this.showCurrentGroup(groupContainers)
    })
    
    this.showCurrentGroup(groupContainers)
    
    const spinner = document.querySelector('#loadingSpinner')
    spinner?.classList.add('d-none')
    
    this.updateNavigation()
  }

  
  private handleNext(): void {
    if (this.currentGroupIndex < this.pageCount - 1) {
      this.currentGroupIndex++

      if (this.options.enableLogging)
        console.log("[DQM]: Switching to Group " + this.currentGroupIndex)
      
      this.navigateToCurrentState(true)
    } else if (this.currentPageIndex < this.totalCount) {
      this.currentPageIndex++
      this.currentGroupIndex = 0

      if (this.options.enableLogging)
        console.log("[DQM]: Switching to Page " + this.currentPageIndex)
      
      this.navigateToCurrentState(true)
      
    } else {
      const confirmModal = new Modal(document.getElementById('confirm-modal'), {})
      const existsModal = new Modal(document.getElementById('exists-modal'), {})
      const dangerModal = new Modal(document.getElementById('dangerModal'))

      if (this.metadata.readonly)
        existsModal.show()
      else
        confirmModal.show()
      
      const btnConfirm = document.getElementById("confirm-btn")
      const json = this.questionnaire

      btnConfirm.addEventListener('click', () => {
        const form: HTMLFormElement = document.querySelector('#dqmfrm')
        const dqmJson: HTMLInputElement | null = document.querySelector('[data-dqm-json]')
        const spinner = document.querySelector('#loadingSpinner')
        spinner?.classList.add('d-none')
        
        const formData = new FormData(form)
  
        const formDataObject: Array<any>[] = []
        formData.forEach((value, key) => {
          formDataObject.push([key, value])
        })

        const data = createJsonOutput(JSON.stringify(json), formDataObject)

        dqmJson.value = data

        if (this.options.enableLogging)
          console.log("[DQM]: From data set")
        
        if (this.metadata.readonly) {
          const redirectUrlEl: HTMLElement = document.querySelector('[data-redirect-url]')
          const redirectUrl = redirectUrlEl.dataset.redirectUrl

          window.location.href = redirectUrl
        } else if (this.options.validateForm) {
          if (form.reportValidity()) {
            spinner?.classList.remove('d-none')

            form.submit()
            existsModal.hide()
            confirmModal.hide()
            dangerModal.hide()
          } else {
            dangerModal.show()
            spinner?.classList.add('d-none')
          }
        } else {
          form.submit()
          existsModal.hide()
          confirmModal.hide()
          dangerModal.hide()
        }
      })
    }
  }

  private handleBack(): void {
    if (this.currentGroupIndex > 0) {
      this.currentGroupIndex--

      if (this.options.enableLogging)
        console.log("[DQM]: Switching to Group " + this.currentGroupIndex)
      
      this.navigateToCurrentState(true)
    } else if (this.currentPageIndex > 0) {
      this.currentPageIndex--
      
      const previousPage = this.pages[0].groups[this.currentPageIndex]
      this.currentGroupIndex = previousPage.groups.length - 1

      if (this.options.enableLogging)
        console.log("[DQM]: Switching to Page " + this.currentPageIndex + " with Group " + this.currentGroupIndex)
      
      this.navigateToCurrentState(true)
    }
  }

  private handleCancel(): void {
    const modal = new Modal(document.getElementById('confirm-cancel-modal'), {})
    modal.show()

    const btnCancelProcess = document.getElementById("cancel-process")
    const redirectUrl = btnCancelProcess.dataset.redirectUrl

    btnCancelProcess.addEventListener('click', () => {
      this.currentPageIndex = 0
      this.currentGroupIndex = 0

      window.location.href = redirectUrl
    })
  }
}

improve performance without changing functionality