import '@things-factory/barcode-ui'
import '@things-factory/grist-ui'
import '@material/mwc-icon'
import '@material/mwc-button'
import './route-label-history-dialog'

import gql from 'graphql-tag'
import { css, html, LitElement } from 'lit-element'
import { connect } from 'pwa-helpers/connect-mixin'

import { DirectPrinter } from '@things-factory/barcode-base'
import { MultiColumnFormStyles, SingleColumnFormStyles } from '@things-factory/form-ui'
import { i18next, localize } from '@things-factory/i18n-base'
import { openPopup } from '@things-factory/layout-base'
import { client, store } from '@things-factory/shell'
import { isMobileDevice } from '@things-factory/utils'

import { fetchSettingRule } from '../../fetch-setting-value'
import { LOCATION_TYPE, ROUTE_LABEL_SETTING_KEY } from '../constants'

class RouteLabelPopup extends connect(store)(localize(i18next)(LitElement)) {
  static get styles() {
    return [
      MultiColumnFormStyles,
      SingleColumnFormStyles,
      css`
        :host {
          padding: 10px;
          display: flex;
          flex-direction: column;
          overflow-x: overlay;
          background-color: var(--main-section-background-color);
        }
        .grist {
          background-color: var(--main-section-background-color);
          display: flex;
          flex-direction: column;
          flex: 1;
          overflow-y: auto;
        }
        data-grist {
          overflow-y: hidden;
          flex: 1;
        }
        .button-container {
          padding: 10px 0 12px 0;
          text-align: center;
        }

        @media (max-width: 460px) {
          .single-column-form {
            max-width: 100%;
          }
          .mobile-full-width {
            grid-column: span 12 / auto;
          }
          .multi-column-form {
            max-width: 100%;
          }
        }

        #search {
          float: right;
          padding-right: 5px;
          padding-bottom: 1px;
          color: var(--search-form-icon-color);
        }

        .grid-break {
          grid-column: span 24;
          height: 0;
        }
      `
    ]
  }

  static get properties() {
    return {
      config: Object,
      data: Object,
      partnerId: String,
      refNoFilter: String,
      worksheetType: String,
      sourceWorksheetType: String,
      _binNumberList: Array,
      _searchFields: Array,
      _priorityList: Array,
      _routeLabel: Object,
      _selectedPriority: String,
      _selectedBinNumber: String,
      _enableToteScanning: Boolean,
      _enableBinScanning: Boolean,
      orderInventories: Object,
      _viewMode: String,
      _appendSeqField: String,
      _historyTarget: Object,
      _historyLogs: Array,
      _historyReprintSeq: String
    }
  }

  get dataGrist() {
    return this.shadowRoot.querySelector('data-grist')
  }

  get searchForm() {
    return this.shadowRoot.querySelector('form')
  }

  get _withSkuCheckbox() {
    return this.shadowRoot.querySelector('input[name=withSku]')
  }

  get _withToteCheckbox() {
    return this.shadowRoot.querySelector('input[name=withTote]')
  }

  _focusOnSerialInput() {
    if (this.serialInput) {
      setTimeout(() => this.serialInput.focus(), 50)
    }
  }

  constructor() {
    super()
    this._viewMode = 'SKU_TOTE'
    this._appendSeqField = 'refNo'
  }

  async _sleep(ms = 0) {
    await new Promise(resolve => setTimeout(resolve, ms))
  }

  _formatDateTime(value) {
    try {
      if (!value) return ''
      if (value instanceof Date) return value.toLocaleString()
      const d = new Date(value)
      if (!Number.isNaN(d.getTime())) return d.toLocaleString()
      const n = typeof value === 'number' ? value : parseInt(value, 10)
      if (Number.isFinite(n)) {
        const dn = new Date(n)
        if (!Number.isNaN(dn.getTime())) return dn.toLocaleString()
      }
      return String(value)
    } catch (e) {
      return ''
    }
  }

  render() {
    return html`
      <div>
        <form class="multi-column-form">
          <label>${i18next.t('field.filter_by')}</label>
          <select
            name="viewMode"
            @change="${e => {
              this._viewMode = e.currentTarget.value
              this._setupConfig()
              this.requestUpdate()
              this.dataGrist.fetch()
            }}"
          >
            <option value="SKU_TOTE" ?selected=${this._viewMode === 'SKU_TOTE'}>SKU/Tote</option>
            <option value="RO" ?selected=${this._viewMode === 'RO'}>RO</option>
          </select>
          <span class="grid-break"></span>

          ${this._viewMode === 'SKU_TOTE'
            ? html`
                ${this.worksheetType == 'merged'
                  ? html`
                      <label>${i18next.t('field.priority')}</label>
                      <select
                        name="priority"
                        @change="${e => {
                          this._selectedPriority = this._priorityList.filter(itm => itm.value == e.currentTarget.value)
                        }}"
                      >
                        ${(this._priorityList || []).map(
                          list => html` <option value=${list?.value}>${list?.name}</option> `
                        )}
                      </select>
                    `
                  : ''}
                ${this._enableBinScanning
                  ? html`
                      <label>${i18next.t('field.bin_number')}</label>
                      <select
                        name="binNumber"
                        @change="${e => {
                          this._selectedBinNumber = this._binNumberList.filter(itm => itm.id == e.currentTarget.value)
                          if (this._selectedBinNumber.length == 0) this._selectedBinNumber = e.currentTarget.value
                          this._changeCheckbox()
                        }}"
                      >
                        <option value="" selected></option>
                        <option value="all">Show All</option>
                        ${(this._binNumberList || []).map(
                          list => html` <option value=${list.id}>${list.name}</option> `
                        )}
                      </select>
                    `
                  : ''}

                <label for="refNo">${i18next.t('field.ref_no')}</label>
                <input
                  type="text"
                  name="refNo"
                  @input="${e => {
                    this.refNoFilter = e.currentTarget.value
                  }}"
                  @keypress="${e => {
                    if (e.key === 'Enter') {
                      this.dataGrist.fetch()
                      e.preventDefault()
                    }
                  }}"
                />

                <div options-wrap>
                  <input
                    type="checkbox"
                    checked
                    id="withSku"
                    name="withSku"
                    @change="${() => this._changeCheckbox()}"
                  />
                  <label for="withSku">${i18next.t('field.sku')}</label>

                  ${this._enableToteScanning
                    ? html`
                        <input
                          type="checkbox"
                          id="withTote"
                          name="withTote"
                          @change="${() => this._changeCheckbox()}"
                        />
                        <label for="withTote">${i18next.t('field.tote')}</label>
                      `
                    : ''}
                </div>
              `
            : html`
                <label>${i18next.t('field.sequence_reference')}</label>
                <select
                  name="appendSeqField"
                  @change="${async e => {
                    this._appendSeqField = e.currentTarget.value
                    await this.dataGrist.fetch()
                  }}"
                >
                  <option value="refNo" ?selected=${this._appendSeqField === 'refNo'}>
                    ${i18next.t('field.ref_no')}
                  </option>
                  <option value="refNo2" ?selected=${this._appendSeqField === 'refNo2'}>
                    ${i18next.t('field.ref_no_2')}
                  </option>
                  <option value="refNo3" ?selected=${this._appendSeqField === 'refNo3'}>
                    ${i18next.t('field.ref_no_3')}
                  </option>
                </select>

                <label for="refNo">${i18next.t('field.ref_no')}</label>
                <input
                  type="text"
                  name="refNo"
                  @input="${e => {
                    this.refNoFilter = e.currentTarget.value
                  }}"
                  @keypress="${e => {
                    if (e.key === 'Enter') {
                      this.dataGrist.fetch()
                      e.preventDefault()
                    }
                  }}"
                />
              `}
        </form>
        <mwc-icon id="search" @click=${e => this.dataGrist.fetch()} search>search</mwc-icon>
      </div>

      <div class="grist">
        <data-grist
          .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
          .config=${this.config}
          .fetchHandler=${this.fetchHandler.bind(this)}
          @field-change="${this._onFieldChange.bind(this)}"
        ></data-grist>
      </div>

      <div class="button-container">
        <mwc-button danger raised @click="${this._printRouteLabel.bind(this)}">${i18next.t('button.print')}</mwc-button>

        <mwc-button
          danger
          @click=${() => {
            history.back()
          }}
          raised
          label="${i18next.t('button.back')}"
        ></mwc-button>
      </div>
    `
  }

  stateChanged(state) {
    let routeLabelSetting = state.dashboard[ROUTE_LABEL_SETTING_KEY]
    this._routeLabel = (routeLabelSetting && routeLabelSetting.board) || {}
  }

  async firstUpdated() {
    await this.fetchLocations()
    this._enableToteScanning = parseInt((await fetchSettingRule('enable-tote-scanning')) || '0', 10) >= 1
    this._enableBinScanning = await fetchSettingRule('enable-bin-picking')

    this._priorityList = [
      { value: '' },
      {
        name: 'RO, Sku',
        value: 'roSku'
      },
      {
        name: 'Sku, RO',
        value: 'skuRo'
      }
    ]

    this._setupConfig()
  }

  _setupConfig() {
    if (this._viewMode === 'RO') {
      this.config = {
        rows: { appendable: false, selectable: { multiple: true } },
        pagination: { infinite: true },
        list: {
          fields: ['name', 'refNo', 'refNo2', 'refNo3', 'printQty', 'lastSeq', 'history']
        },
        columns: [
          { type: 'gutter', gutterName: 'sequence' },
          { type: 'gutter', gutterName: 'row-selector', multiple: true },
          {
            type: 'gutter',
            gutterName: 'button',
            icon: 'history',
            handlers: {
              click: (_columns, _data, _column, record) => this._openHistoryDialog(record)
            }
          },
          { type: 'string', name: 'name', header: i18next.t('field.ro'), sortable: true, width: 160 },
          { type: 'string', name: 'refNo', header: i18next.t('field.ref_no'), sortable: true, width: 150 },
          { type: 'string', name: 'refNo2', header: i18next.t('field.ref_no_2'), sortable: true, width: 150 },
          { type: 'string', name: 'refNo3', header: i18next.t('field.ref_no_3'), sortable: true, width: 150 },
          {
            type: 'string',
            name: 'printQty',
            header: i18next.t('field.print_qty'),
            record: { editable: true },
            width: 120
          },
          { type: 'integer', name: 'lastSeq', header: i18next.t('field.last_seq'), sortable: true, width: 110 },
          { type: 'string', name: 'history', header: i18next.t('field.last_printed_by'), width: 220 }
        ]
      }
      return
    }

    // Default: SKU/Tote mode (existing behavior)
    let configColumns = [
      { type: 'gutter', gutterName: 'sequence' },
      { type: 'gutter', gutterName: 'row-selector', multiple: true }
    ]

    if (this._enableToteScanning) {
      configColumns.push({
        type: 'string',
        name: 'toteNumber',
        header: i18next.t('field.tote_no'),
        sortable: true,
        width: 150
      })
    }

    configColumns.push(
      {
        type: 'string',
        name: 'productSku',
        header: i18next.t('field.sku'),
        sortable: true,
        width: 150
      },
      {
        type: 'select',
        name: 'packingType',
        header: i18next.t('field.packing_type'),
        record: {
          options: [''],
          editable: true
        },
        handlers: {
          click: (columns, data, column, record, rowIndex) => {
            if (
              !record ||
              JSON.stringify(this.dataGrist._config.columns.find(c => c.name === 'packingType').record.options) ===
                JSON.stringify(record.productDetails.map(item => item.packingType))
            ) {
              return
            }
            this.dataGrist._config.columns
              .filter(c => c.name === 'packingType')
              .map(items => (items.record.options = record.productDetails.map(pd => pd.packingType)))
            this.dataGrist.render()
          }
        },
        sortable: true,
        width: 150
      },
      {
        type: 'integer',
        name: 'packingSize',
        header: i18next.t('field.packing_size'),
        width: 150
      },
      {
        type: 'integer',
        name: 'releaseQty',
        header: i18next.t('field.release_qty'),
        width: 150
      },
      {
        type: 'select',
        name: 'packingType',
        header: i18next.t('field.packing_type'),
        width: 150,
        options: [],
        props: { searchOper: 'eq' }
      },
      {
        type: 'string',
        name: 'roName',
        header: i18next.t('field.ro'),
        sortable: true,
        width: 150
      },
      {
        type: 'string',
        name: 'refNo',
        header: i18next.t('field.ref_no'),
        sortable: true,
        width: 150
      },
      {
        type: 'string',
        name: 'refNo2',
        header: i18next.t('field.ref_no_2'),
        sortable: true,
        width: 150
      },
      {
        type: 'string',
        name: 'refNo3',
        header: i18next.t('field.ref_no_3'),
        sortable: true,
        width: 150
      },
      {
        type: 'string',
        name: 'locationName',
        header: i18next.t('field.location'),
        sortable: true,
        width: 150
      }
    )

    if (this._enableBinScanning) {
      configColumns.push({
        type: 'string',
        name: 'binNumber',
        header: i18next.t('field.bin_no'),
        sortable: true,
        width: 150
      })
    }

    this.config = {
      rows: {
        appendable: false,
        classifier: (record, rowIndex) => {
          return {
            emphasized: record.completed == true
          }
        }
      },
      pagination: { infinite: true },
      list: {
        fields: ['productSku', 'releaseQty', 'roName', 'binNumber']
      },
      columns: configColumns
    }
  }

  async fetchHandler({ sorters = [] }) {
    if (this._viewMode === 'RO') {
      try {
        const response = await client.query({
          query: gql`
            query routeLabelReleaseGoodsInExecutingWorksheet(
              $partnerId: String!
              $refNo: String
              $worksheetType: String!
              $seqField: String
            ) {
              routeLabelReleaseGoodsInExecutingWorksheet(
                partnerId: $partnerId
                refNo: $refNo
                worksheetType: $worksheetType
                seqField: $seqField
              ) {
                id
                name
                refNo
                refNo2
                refNo3
                routeId
                storeId
                storeName
                stopId
                createdAt
                lastSeq
                lastPrintedBy
              }
            }
          `,
          variables: {
            partnerId: this.partnerId,
            refNo: this.refNoFilter || null,
            worksheetType: this.sourceWorksheetType || 'PICKING',
            seqField: this._appendSeqField
          }
        })

        if (!response.errors) {
          const items = response.data.routeLabelReleaseGoodsInExecutingWorksheet || []
          return {
            total: items.length,
            records: items.map(rg => ({
              ...rg,
              printQty: 1,
              history: rg.lastPrintedBy || ''
            }))
          }
        }
      } catch (e) {
        this._showToast(e)
      }
      return { total: 0, records: [] }
    }

    const filters = []

    if (this.worksheetType == 'merged') {
      filters.push({ name: 'merged', operator: 'eq', value: 'merged' })
    }

    if (this._withSkuCheckbox?.checked == true) {
      filters.push({ name: 'withSku', operator: 'eq', value: true })
    }

    if (this._withToteCheckbox?.checked == true) {
      filters.push({ name: 'withTote', operator: 'eq', value: true })
    }

    if (this.refNoFilter) {
      filters.push({ name: 'refNo', operator: 'ilike', value: this.refNoFilter })
    }

    try {
      const response = await client.query({
        query: gql`
          query orderInventoriesOfMultipleReleaseOrders(
            $partnerId: String!
            $filters: [Filter!]
            $sortings: [Sorting!]
            $priority: String
            $binNumber: String
          ) {
            orderInventoriesOfMultipleReleaseOrders(
              partnerId: $partnerId
              filters: $filters
              sortings: $sortings
              priority: $priority
              binNumber: $binNumber
            ) {
              items {
                toteNumber
                binNumber
                productSKU
                productName
                releaseQty
                releaseGoodName
                releaseGood {
                  refNo
                  refNo2
                  refNo3
                }
                productDetails {
                  packingType
                  packingSize
                }
                productDetail {
                  packingType
                  packingSize
                }
                routeId
                storeId
                storeName
                stopId
                createdAt
                locationName
              }
              total
            }
          }
        `,
        variables: {
          partnerId: this.partnerId,
          filters,
          sortings: sorters,
          priority: this._selectedPriority?.length == 1 ? this._selectedPriority[0].value : this._selectedPriority,
          binNumber: this._selectedBinNumber?.length == 1 ? this._selectedBinNumber[0].name : this._selectedBinNumber
        }
      })

      if (!response.errors) {
        let records = response.data.orderInventoriesOfMultipleReleaseOrders

        return (this.orderInventories = {
          total: records.total || 0,
          records:
            records.items.map(itm => {
              let createdAt = new Date(itm.createdAt)
              createdAt = `${createdAt.getDate()}/${createdAt.getMonth() + 1}/${createdAt.getFullYear()}`
              return {
                ...itm,
                roName: itm.releaseGoodName,
                productSku: itm.productSKU,
                createdAt: createdAt,
                refNo: itm.releaseGood.refNo,
                refNo2: itm.releaseGood.refNo2,
                refNo3: itm.releaseGood.refNo3,
                packingType: itm.toteNumber ? null : itm.productDetail.packingType,
                packingSize: itm.toteNumber ? null : itm.productDetail.packingSize
              }
            }) || {}
        })
      }
      // Populate the packingType options
      const uniquePackingTypes = [...new Set(this.orderInventories.records.map(record => record.packingType))]
      const packingTypeOptions = uniquePackingTypes.map(typeValue => ({
        value: typeValue,
        name: typeValue // You can modify this to display a formatted name if needed
      }))

      this.config.columns.find(col => col.name === 'packingType').options = packingTypeOptions
    } catch (e) {
      this._showToast(e)
    }
  }

  async _onFieldChange(e) {
    const { column, row, after, before } = e.detail

    // Handle printQty validation for RO mode
    if (this._viewMode === 'RO' && column.name === 'printQty') {
      const printQtyStr = String(after || '').trim()
      const previousValue = before !== undefined && before !== null ? before : 1

      // Check if empty
      if (!printQtyStr || printQtyStr === '') {
        this.dataGrist._data.records[row].printQty = previousValue
        this.dataGrist.render()
        this._showToast({
          type: 'error',
          message: i18next.t('text.print_qty_is_required')
        })
        return
      }

      // Check if it contains decimal point
      if (printQtyStr.includes('.')) {
        this.dataGrist._data.records[row].printQty = previousValue
        this.dataGrist.render()
        this._showToast({
          type: 'error',
          message: i18next.t('text.print_qty_must_be_integer')
        })
        return
      }

      const qtyNum = parseInt(printQtyStr, 10)
      if (isNaN(qtyNum)) {
        this.dataGrist._data.records[row].printQty = previousValue
        this.dataGrist.render()
        this._showToast({
          type: 'error',
          message: i18next.t('text.print_qty_must_be_valid_number')
        })
        return
      }

      // Check if the parsed value matches the original (to catch cases like "123abc")
      if (qtyNum.toString() !== printQtyStr) {
        this.dataGrist._data.records[row].printQty = previousValue
        this.dataGrist.render()
        this._showToast({
          type: 'error',
          message: i18next.t('text.print_qty_must_be_valid_integer')
        })
        return
      }

      if (qtyNum < 1) {
        this.dataGrist._data.records[row].printQty = previousValue
        this.dataGrist.render()
        this._showToast({
          type: 'error',
          message: i18next.t('text.print_qty_must_be_at_least_one')
        })
        return
      }

      // If validation passes, convert to integer
      this.dataGrist._data.records[row].printQty = qtyNum
    }

    // Handle packingSize change for SKU_TOTE mode
    if (this._viewMode === 'SKU_TOTE' && column.name === 'packingType') {
      this.dataGrist._data.records[row].packingSize = e.detail.record.productDetails.find(
        item => item.packingType == after
      ).packingSize
    }
  }

  async _printRouteLabel() {
    const records = this.dataGrist.selected
    let labelId = this._routeLabel && this._routeLabel.id

    if (!records || records.length == 0) {
      this._showToast({ message: i18next.t('text.there_is_no_selected_items') })
      return
    }
    if (!labelId) {
      this._showToast({
        type: 'error',
        message: `${i18next.t('text.no_label_setting_was_found')}. ${i18next.t('text.please_check_your_setting')}`
      })
      return
    }

    // Validate printQty for RO mode
    if (this._viewMode === 'RO') {
      for (const record of records) {
        const printQty = record.printQty
        if (printQty === undefined || printQty === null || printQty === '') {
          this._showToast({
            type: 'error',
            message: i18next.t('text.print_qty_is_required')
          })
          return
        }

        // Check if it contains decimal point
        if (String(printQty).includes('.')) {
          this._showToast({
            type: 'error',
            message: i18next.t('text.print_qty_must_be_integer')
          })
          return
        }

        const qtyNum = parseInt(printQty, 10)
        if (isNaN(qtyNum)) {
          this._showToast({
            type: 'error',
            message: i18next.t('text.print_qty_must_be_valid_number')
          })
          return
        }

        // Check if the parsed value matches the original (to catch cases like "123abc")
        if (qtyNum.toString() !== String(printQty).trim()) {
          this._showToast({
            type: 'error',
            message: i18next.t('text.print_qty_must_be_valid_integer')
          })
          return
        }

        if (qtyNum < 1) {
          this._showToast({
            type: 'error',
            message: i18next.t('text.print_qty_must_be_at_least_one')
          })
          return
        }
      }

      await this._printRouteLabelByRo(records, labelId)
      return
    }

    let index = 1
    for (let record of records) {
      let max = record.releaseQty ? record.releaseQty / record.packingSize : 1
      let commands = ''
      for (let qty = 1; qty <= max; qty++) {
        let searchParams = new URLSearchParams()

        /* for pallet record mapping */
        searchParams.append('roName', record.roName)
        if (record?.binNumber) {
          searchParams.append('binNumber', record.binNumber)
        }
        searchParams.append('locationName', record.locationName)
        if (!record?.toteNumber) {
          searchParams.append('qty', record.qty)
          searchParams.append('releaseQty', record.releaseQty)
          searchParams.append('counter', `${qty} of ${max}`)
          searchParams.append('productSku', record.productSku)
          searchParams.append('productName', record.productName)
        } else {
          searchParams.append('productName', record.toteNumber)
          searchParams.append('counter', `${index} of ${records.length}`)
        }
        searchParams.append('stopId', record.stopId)
        searchParams.append('routeId', record.routeId)
        searchParams.append('storeId', record.storeId)
        searchParams.append('storeName', record.storeName)
        searchParams.append('str', `STR: ${record.storeId}`)
        searchParams.append('createdAt', `P/D: ${record.createdAt}`)
        searchParams.append(
          'storeInformation',
          `(Store ID) ${record.storeId}, (Store Name) ${record.storeName}, (Route ID) ${record.routeId}, (Stop ID) ${record.stopId}`
        )
        try {
          const response = await fetch(`label-command/${labelId}?${searchParams.toString()}`, {
            method: 'GET'
          })

          if (response.status !== 200) {
            throw `Error : Can't get label command from server (response: ${response.status})`
          }

          let command = await response.text()

          if (!this.printer) {
            this.printer = new DirectPrinter()
          }

          commands += command + ' '
        } catch (e) {
          this._showToast(e)

          delete this.printer
          break
        }
      }
      if (commands && this.printer) {
        this.printer.print(commands)
        await this._sleep(50)
      }
      index++
    }
  }

  async _printRouteLabelByRo(records, labelId) {
    try {
      const pad3 = n => String(n).padStart(3, '0')

      const parseSeqInput = input => {
        if (!input || typeof input !== 'string') return []
        const cleaned = input.replace(/\s/g, '')
        if (!cleaned) return []
        const parts = cleaned.split(',')
        const numbers = new Set()
        for (const part of parts) {
          if (!part) continue
          const range = part.split('-')
          if (range.length === 1) {
            const n = parseInt(range[0], 10)
            if (Number.isFinite(n) && n > 0) numbers.add(n)
          } else if (range.length === 2) {
            const a = parseInt(range[0], 10)
            const b = parseInt(range[1], 10)
            if (Number.isFinite(a) && Number.isFinite(b) && a > 0 && b > 0) {
              const from = Math.min(a, b)
              const to = Math.max(a, b)
              for (let i = from; i <= to; i++) numbers.add(i)
            }
          }
        }
        return Array.from(numbers).sort((x, y) => x - y)
      }

      for (const record of records) {
        const field = this._appendSeqField || 'refNo'
        const base = (record[field] || '').toString()

        let seqsToPrint = []

        // Check if reprintSeq is provided (for reprinting specific sequences)
        if (record.reprintSeq) {
          const reprintSeqs = parseSeqInput(record.reprintSeq)
          if (reprintSeqs.length > 0) {
            // Reprint specific sequence numbers (does not increment counter).
            seqsToPrint = reprintSeqs
          }
        }

        // If no reprint sequences, reserve a new range (increments counter).
        if (seqsToPrint.length === 0) {
          const printQtyRaw = Number(record.printQty ?? 1)
          const printQty = Number.isFinite(printQtyRaw) && printQtyRaw > 0 ? Math.floor(printQtyRaw) : 1

          const reserveResp = await client.mutate({
            mutation: gql`
              mutation reserveRouteLabelSequence($releaseGoodId: String!, $field: String!, $qty: Int!) {
                reserveRouteLabelSequence(releaseGoodId: $releaseGoodId, field: $field, qty: $qty) {
                  releaseGoodId
                  field
                  from
                  to
                }
              }
            `,
            variables: { releaseGoodId: record.id, field, qty: printQty }
          })

          if (reserveResp.errors) {
            throw reserveResp.errors[0]
          }

          const { from, to } = reserveResp.data.reserveRouteLabelSequence
          for (let n = from; n <= to; n++) seqsToPrint.push(n)

          // Update UI fields locally (best-effort) only when reserving new range
          record.lastSeq = to
          record.history = [record.history, `${from}-${to}`].filter(Boolean).join(', ')
        }

        let commands = ''

        for (let idx = 0; idx < seqsToPrint.length; idx++) {
          const seq = seqsToPrint[idx]
          const seqPadded = pad3(seq)
          const appendedValue = `${base}-${seqPadded}`
          const searchParams = new URLSearchParams()
          searchParams.append('roName', record.name)
          searchParams.append('refNo', record.refNo || '')
          searchParams.append('refNo2', record.refNo2 || '')
          searchParams.append('refNo3', record.refNo3 || '')

          // Override the selected field with appended sequence
          if (field === 'refNo') searchParams.set('refNo', appendedValue)
          if (field === 'refNo2') searchParams.set('refNo2', appendedValue)
          if (field === 'refNo3') searchParams.set('refNo3', appendedValue)

          searchParams.append('seq', String(seq))
          searchParams.append('seqPadded', seqPadded)
          searchParams.append('counter', `${idx + 1} of ${seqsToPrint.length}`)

          if (record.stopId != null) searchParams.append('stopId', record.stopId)
          if (record.routeId != null) searchParams.append('routeId', record.routeId)
          if (record.storeId != null) searchParams.append('storeId', record.storeId)
          if (record.storeName != null) searchParams.append('storeName', record.storeName)
          if (record.storeId != null) searchParams.append('str', `STR: ${record.storeId}`)
          if (record.createdAt) searchParams.append('createdAt', `P/D: ${record.createdAt}`)
          searchParams.append(
            'storeInformation',
            `(Store ID) ${record.storeId || ''}, (Store Name) ${record.storeName || ''}, (Route ID) ${
              record.routeId || ''
            }, (Stop ID) ${record.stopId || ''}`
          )

          const response = await fetch(`label-command/${labelId}?${searchParams.toString()}`, { method: 'GET' })
          if (response.status !== 200) {
            throw `Error : Can't get label command from server (response: ${response.status})`
          }
          commands += (await response.text()) + ' '
        }

        if (commands) {
          if (!this.printer) {
            this.printer = new DirectPrinter()
          }
          this.printer.print(commands)
          await this._sleep(50)
        }
      }
    } catch (e) {
      this._showToast(e)
      delete this.printer
    }
  }

  async fetchLocations() {
    const response = await client.query({
      query: gql`
        query locations($filters: [Filter!]) {
          locations(filters: $filters) {
            items {
              id
              name
              description
              row
              column
              shelf
              status
            }
            total
          }
        }
      `,
      variables: {
        filters: [
          {
            name: 'type',
            operator: 'eq',
            value: LOCATION_TYPE.BIN.value
          }
        ]
      }
    })

    if (!response.errors) {
      this._binNumberList = response.data.locations.items
    }
  }

  _compareValues(key, order = 'asc') {
    return function innerSort(a, b) {
      if (!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) {
        return 0
      }

      const varA = typeof a[key] === 'string' ? a[key].toUpperCase() : a[key]
      const varB = typeof b[key] === 'string' ? b[key].toUpperCase() : b[key]

      let comparison = 0
      if (varA > varB) {
        comparison = 1
      } else if (varA < varB) {
        comparison = -1
      }
      return order === 'desc' ? comparison * -1 : comparison
    }
  }

  _changeCheckbox(e) {
    let priorList = [{ value: '' }]

    let skuPriorList = [
      {
        name: 'RO, Sku',
        value: 'roSku'
      },
      {
        name: 'Sku, RO',
        value: 'skuRo'
      }
    ]

    let binPriorList = [
      {
        name: 'Sku, Bin',
        value: 'skuBin'
      },
      {
        name: 'RO, Bin',
        value: 'roBin'
      },
      {
        name: 'Bin, RO',
        value: 'binRo'
      },
      {
        name: 'Bin, Sku',
        value: 'binSku'
      }
    ]

    let totePriorList = [
      {
        name: 'RO, Tote',
        value: 'roTote'
      },
      {
        name: 'Tote, RO',
        value: 'toteRo'
      }
    ]

    if (this._withSkuCheckbox.checked == true) {
      priorList.push(...skuPriorList)
    }

    if (this._selectedBinNumber?.length > 0 && this._selectedBinNumber[0].value !== '') {
      priorList.push(...binPriorList)
    }

    if (this._withToteCheckbox.checked == true) {
      priorList.push(...totePriorList)
    }

    this._priorityList = priorList
  }

  _showToast({ type, message }) {
    document.dispatchEvent(
      new CustomEvent('notify', {
        detail: {
          type,
          message
        }
      })
    )
  }

  async _openHistoryDialog(record) {
    try {
      if (!record?.id) return

      const resp = await client.query({
        query: gql`
          query routeLabelSequenceLogs($releaseGoodId: String!, $field: String!) {
            routeLabelSequenceLogs(releaseGoodId: $releaseGoodId, field: $field) {
              id
              releaseGoodId
              field
              from
              to
              qty
              createdAt
              creatorName
            }
          }
        `,
        variables: { releaseGoodId: record.id, field: this._appendSeqField || 'refNo' }
      })

      if (resp.errors) throw resp.errors[0]
      const historyLogs = (resp.data.routeLabelSequenceLogs || []).map(l => {
        return {
          ...l,
          range: `${l.from}-${l.to}`,
          printedAt: this._formatDateTime(l.createdAt)
        }
      })

      openPopup(
        html`
          <route-label-history-dialog
            .target="${record}"
            .logs="${historyLogs}"
            .appendSeqField="${this._appendSeqField || 'refNo'}"
            .routeLabel="${this._routeLabel}"
            .onReprint="${async reprintSeq => {
              await this._reprintSelectedFromHistory(record, reprintSeq)
            }}"
          ></route-label-history-dialog>
        `,
        {
          backdrop: true,
          size: 'medium',
          title: i18next.t('text.print_history')
        }
      )
    } catch (e) {
      this._showToast(e)
    }
  }

  async _reprintSelectedFromHistory(target, reprintSeq) {
    try {
      if (!target) return

      if (!reprintSeq || !reprintSeq.trim()) {
        this._showToast({ message: i18next.t('text.please_enter_reprint_seq') })
        return
      }

      const labelId = this._routeLabel && this._routeLabel.id
      if (!labelId) {
        this._showToast({
          type: 'error',
          message: `${i18next.t('text.no_label_setting_was_found')}. ${i18next.t('text.please_check_your_setting')}`
        })
        return
      }

      await this._printRouteLabelByRo([{ ...target, reprintSeq: reprintSeq.trim() }], labelId)
      history.back()
    } catch (e) {
      this._showToast(e)
    }
  }
}

window.customElements.define('route-label-popup', RouteLabelPopup)
