<template>
  <main
    class="invoice-page"
    data-testid="manual-invoice-prototype-page"
  >
    <header class="invoice-page__header">
      <div class="invoice-page__title">
        <h1>{{ t('manualInvoice.createTitle') }}</h1>
      </div>
      <div class="source-summary">
        <span>当前来源</span>
        <strong>{{ sourceGroupName }}</strong>
        <small>Manual Invoice Mode</small>
      </div>
    </header>

    <form
      class="invoice-form"
      @submit.prevent="submitInvoice"
    >
      <input
        v-model="form.document.invoice_date"
        data-testid="document-invoice-date"
        type="hidden"
        @input="markDocumentDatesEdited"
      >

      <div class="invoice-form-column">
        <section
          class="section-card"
          aria-labelledby="basic-information-title"
        >
          <header class="section-header">
            <div class="section-title">
              <span class="section-index">01</span>
              <h2 id="basic-information-title">
                Basic Information
              </h2>
            </div>
          </header>

          <div class="form-grid">
            <div class="field">
              <div class="field__heading">
                <label
                  for="invoice-company"
                  class="field-label field-label--required"
                >Company</label>
              </div>
              <div class="directory-control directory-control--manual">
                <select
                  id="invoice-company"
                  v-model="form.recipient.company_code"
                  data-testid="recipient-company-code"
                >
                  <option :value="MANUAL_SELECTION">
                    Manual
                  </option>
                  <option
                    v-for="company in recipientCompanies"
                    :key="company.company_code"
                    :value="company.company_code"
                  >
                    {{ company.company }}
                  </option>
                </select>
                <input
                  v-model.trim="form.recipient.company"
                  data-testid="recipient-company-input"
                  type="text"
                  autocomplete="off"
                  :placeholder="t('manualInvoice.companyInputPlaceholder')"
                  :class="validationClass('company')"
                  v-bind="validationAria('company')"
                  required
                >
              </div>
              <p
                v-if="fieldError('company')"
                :id="fieldErrorId('company')"
                class="field-error"
                :data-testid="fieldErrorTestId('company')"
              >
                {{ fieldError('company') }}
              </p>
            </div>

            <div class="field">
              <div class="field__heading">
                <label
                  for="invoice-attention"
                  class="field-label field-label--required"
                >Attention</label>
              </div>
              <div class="directory-control directory-control--manual">
                <select
                  id="invoice-attention"
                  v-model="form.recipient.contact_id"
                  data-testid="recipient-contact-id"
                >
                  <option :value="MANUAL_SELECTION">
                    Manual
                  </option>
                  <option
                    v-for="contact in recipientContacts"
                    :key="contact.contact_id"
                    :value="contact.contact_id"
                  >
                    {{ contact.attention }}
                  </option>
                </select>
                <input
                  v-model.trim="form.recipient.attention"
                  data-testid="recipient-attention-input"
                  type="text"
                  autocomplete="off"
                  :placeholder="t('manualInvoice.attentionInputPlaceholder')"
                  :class="validationClass('attention')"
                  v-bind="validationAria('attention')"
                  required
                >
              </div>
              <p
                v-if="fieldError('attention')"
                :id="fieldErrorId('attention')"
                class="field-error"
                :data-testid="fieldErrorTestId('attention')"
              >
                {{ fieldError('attention') }}
              </p>
            </div>

            <label class="field field--wide">
              <span class="field-label field-label--required">Address</span>
              <textarea
                v-model.trim="form.recipient.address"
                data-testid="recipient-address"
                rows="3"
                :placeholder="t('manualInvoice.addressInputPlaceholder')"
                :class="validationClass('address')"
                v-bind="validationAria('address')"
                required
              />
              <span
                v-if="fieldError('address')"
                :id="fieldErrorId('address')"
                class="field-error"
                :data-testid="fieldErrorTestId('address')"
              >
                {{ fieldError('address') }}
              </span>
            </label>

            <label class="field">
              <span class="field-label field-label--required">Tel</span>
              <input
                v-model.trim="form.recipient.telephone"
                data-testid="recipient-telephone"
                type="tel"
                autocomplete="off"
                :class="validationClass('telephone')"
                v-bind="validationAria('telephone')"
                required
              >
              <span
                v-if="fieldError('telephone')"
                :id="fieldErrorId('telephone')"
                class="field-error"
                :data-testid="fieldErrorTestId('telephone')"
              >
                {{ fieldError('telephone') }}
              </span>
            </label>

            <label class="field">
              <span class="field-label field-label--required">Email</span>
              <input
                v-model.trim="form.recipient.email"
                data-testid="recipient-email"
                type="email"
                autocomplete="off"
                :class="validationClass('email')"
                v-bind="validationAria('email')"
                required
              >
              <span
                v-if="fieldError('email')"
                :id="fieldErrorId('email')"
                class="field-error"
                :data-testid="fieldErrorTestId('email')"
              >
                {{ fieldError('email') }}
              </span>
            </label>

            <div class="field">
              <div class="field__heading">
                <label
                  for="invoice-booking-date"
                  class="field-label field-label--required"
                >Booking Date</label>
              </div>
              <div class="date-control">
                <DatePicker
                  v-model="documentBookingDateModel"
                  input-id="invoice-booking-date"
                  class="localized-date-picker"
                  data-testid="document-booking-date-picker"
                  date-format="yy-mm-dd"
                  :placeholder="t('manualInvoice.datePlaceholder')"
                  :input-class="validationClass('booking-date')"
                  :pt="datePickerPt('document-booking-date', 'booking-date')"
                  :invalid="Boolean(fieldError('booking-date'))"
                  show-icon
                  icon-display="input"
                  append-to="body"
                  panel-class="manual-invoice-datepicker-panel"
                  required
                  @update:model-value="markDocumentDatesEdited"
                />
              </div>
              <p
                v-if="fieldError('booking-date')"
                :id="fieldErrorId('booking-date')"
                class="field-error"
                :data-testid="fieldErrorTestId('booking-date')"
              >
                {{ fieldError('booking-date') }}
              </p>
            </div>
          </div>
        </section>

        <section
          class="section-card"
          aria-labelledby="reservation-title"
        >
          <header class="section-header">
            <div class="section-title">
              <span class="section-index">02</span>
              <h2 id="reservation-title">
                Please refer to the following reservation
              </h2>
            </div>
          </header>

          <div class="form-grid">
            <label class="field field--wide">
              <span class="field-label field-label--required">Group Name</span>
              <input
                v-model.trim="form.booking.group_name"
                data-testid="booking-group-name"
                type="text"
                autocomplete="off"
                :class="validationClass('group-name')"
                v-bind="validationAria('group-name')"
                required
              >
              <span
                v-if="fieldError('group-name')"
                :id="fieldErrorId('group-name')"
                class="field-error"
                :data-testid="fieldErrorTestId('group-name')"
              >
                {{ fieldError('group-name') }}
              </span>
            </label>

            <div class="field">
              <div class="field__heading">
                <label
                  for="invoice-arrival-date"
                  class="field-label field-label--required"
                >Arrival Date</label>
              </div>
              <div class="date-control">
                <DatePicker
                  v-model="bookingArrivalDateModel"
                  input-id="invoice-arrival-date"
                  class="localized-date-picker"
                  data-testid="booking-arrival-date-picker"
                  date-format="yy-mm-dd"
                  :placeholder="t('manualInvoice.datePlaceholder')"
                  :input-class="validationClass('arrival-date')"
                  :pt="datePickerPt('booking-arrival-date', 'arrival-date')"
                  :invalid="Boolean(fieldError('arrival-date'))"
                  show-icon
                  icon-display="input"
                  append-to="body"
                  panel-class="manual-invoice-datepicker-panel"
                  required
                />
              </div>
              <p
                v-if="fieldError('arrival-date')"
                :id="fieldErrorId('arrival-date')"
                class="field-error"
                :data-testid="fieldErrorTestId('arrival-date')"
              >
                {{ fieldError('arrival-date') }}
              </p>
            </div>

            <div class="field">
              <div class="field__heading">
                <label
                  for="invoice-departure-date"
                  class="field-label field-label--required"
                >Departure Date</label>
              </div>
              <div class="date-control">
                <DatePicker
                  v-model="bookingDepartureDateModel"
                  input-id="invoice-departure-date"
                  class="localized-date-picker"
                  data-testid="booking-departure-date-picker"
                  date-format="yy-mm-dd"
                  :placeholder="t('manualInvoice.datePlaceholder')"
                  :input-class="validationClass('departure-date')"
                  :pt="datePickerPt('booking-departure-date', 'departure-date')"
                  :invalid="Boolean(fieldError('departure-date'))"
                  show-icon
                  icon-display="input"
                  append-to="body"
                  panel-class="manual-invoice-datepicker-panel"
                  required
                />
              </div>
              <p
                v-if="fieldError('departure-date')"
                :id="fieldErrorId('departure-date')"
                class="field-error"
                :data-testid="fieldErrorTestId('departure-date')"
              >
                {{ fieldError('departure-date') }}
              </p>
            </div>

            <div class="field">
              <div class="field__heading">
                <label
                  for="invoice-due-date"
                  class="field-label field-label--required"
                >Due Date</label>
              </div>
              <div class="date-control">
                <DatePicker
                  v-model="documentDueDateModel"
                  input-id="invoice-due-date"
                  class="localized-date-picker"
                  data-testid="document-due-date-picker"
                  date-format="yy-mm-dd"
                  :placeholder="t('manualInvoice.datePlaceholder')"
                  :input-class="validationClass('due-date')"
                  :pt="datePickerPt('document-due-date', 'due-date')"
                  :invalid="Boolean(fieldError('due-date'))"
                  show-icon
                  icon-display="input"
                  append-to="body"
                  panel-class="manual-invoice-datepicker-panel"
                  required
                  @update:model-value="markDocumentDatesEdited"
                />
              </div>
              <p
                v-if="fieldError('due-date')"
                :id="fieldErrorId('due-date')"
                class="field-error"
                :data-testid="fieldErrorTestId('due-date')"
              >
                {{ fieldError('due-date') }}
              </p>
            </div>

            <div class="field">
              <div class="field__heading">
                <span class="field-label">Room Rate</span>
              </div>
              <div class="room-rate-control">
                <label>
                  <div class="number-control">
                    <input
                      v-model="primaryCharge.rate"
                      data-testid="primary-room-rate"
                      type="text"
                      inputmode="decimal"
                      autocomplete="off"
                      :class="validationClass('primary-rate')"
                      v-bind="validationAria('primary-rate')"
                      required
                    >
                    <span
                      class="number-suffix"
                      aria-hidden="true"
                    >rm/n</span>
                  </div>
                  <span
                    v-if="fieldError('primary-rate')"
                    :id="fieldErrorId('primary-rate')"
                    class="field-error"
                    :data-testid="fieldErrorTestId('primary-rate')"
                  >
                    {{ fieldError('primary-rate') }}
                  </span>
                </label>
                <label>
                  <input
                    v-model.trim="form.booking.room_rate_note"
                    data-testid="booking-room-rate-note"
                    type="text"
                    :placeholder="t('manualInvoice.roomRateNotePlaceholder')"
                    autocomplete="off"
                  >
                </label>
              </div>
            </div>

            <label class="field">
              <span class="field-label">Extra bed</span>
              <input
                v-model="form.booking.extra_bed_rate"
                data-testid="booking-extra-bed-rate"
                type="text"
                inputmode="decimal"
                autocomplete="off"
                :class="validationClass('extra-bed-rate')"
                v-bind="validationAria('extra-bed-rate')"
              >
              <span
                v-if="fieldError('extra-bed-rate')"
                :id="fieldErrorId('extra-bed-rate')"
                class="field-error"
                :data-testid="fieldErrorTestId('extra-bed-rate')"
              >
                {{ fieldError('extra-bed-rate') }}
              </span>
            </label>

            <div class="field">
              <div class="field__heading">
                <label
                  for="invoice-room-count"
                  class="field-label field-label--required"
                >No.of Room (s)</label>
              </div>
              <div class="number-control">
                <input
                  id="invoice-room-count"
                  v-model="primaryCharge.quantity"
                  data-testid="primary-room-count"
                  type="text"
                  inputmode="numeric"
                  autocomplete="off"
                  :class="validationClass('primary-quantity')"
                  v-bind="validationAria('primary-quantity')"
                  required
                >
                <span
                  class="number-suffix"
                  aria-hidden="true"
                >Rooms</span>
              </div>
              <p
                v-if="fieldError('primary-quantity')"
                :id="fieldErrorId('primary-quantity')"
                class="field-error"
                :data-testid="fieldErrorTestId('primary-quantity')"
              >
                {{ fieldError('primary-quantity') }}
              </p>
            </div>

            <div class="field">
              <div class="field__heading">
                <label
                  for="invoice-night-count"
                  class="field-label field-label--required"
                >No.of Night (s)</label>
              </div>
              <div class="number-control">
                <input
                  id="invoice-night-count"
                  v-model="primaryCharge.nights"
                  data-testid="primary-night-count"
                  type="text"
                  inputmode="numeric"
                  autocomplete="off"
                  :class="validationClass('primary-nights')"
                  v-bind="validationAria('primary-nights')"
                  required
                >
                <span
                  class="number-suffix"
                  aria-hidden="true"
                >Nights</span>
              </div>
              <p
                v-if="fieldError('primary-nights')"
                :id="fieldErrorId('primary-nights')"
                class="field-error"
                :data-testid="fieldErrorTestId('primary-nights')"
              >
                {{ fieldError('primary-nights') }}
              </p>
            </div>
          </div>
        </section>

        <section
          class="section-card"
          aria-labelledby="description-amount-title"
        >
          <header class="section-header">
            <div class="section-title">
              <span class="section-index">03</span>
              <h2 id="description-amount-title">
                Description&amp;Amount
              </h2>
            </div>
            <div class="section-actions">
              <span class="currency-badge">BHT</span>
              <button
                id="add-charge"
                type="button"
                class="add-button"
                data-testid="manual-invoice-add-charge"
                :disabled="form.charges.length >= MAX_CHARGE_LINES"
                @click="addCharge"
              >
                <i
                  class="pi pi-plus"
                  aria-hidden="true"
                />
                {{ t('manualInvoice.addCharge') }}
              </button>
            </div>
          </header>

          <div class="charge-lines">
            <article
              v-for="(charge, index) in form.charges"
              :key="charge.id"
              class="charge-line"
              :data-testid="`charge-row-${index}`"
            >
              <header class="charge-line__header">
                <strong>{{ t('manualInvoice.chargeLineLabel', { index: index + 1 }) }}</strong>
                <button
                  type="button"
                  class="remove-button"
                  :disabled="form.charges.length === 1"
                  :aria-label="t('manualInvoice.removeChargeLineAria', { index: index + 1 })"
                  @click="removeCharge(index)"
                >
                  <i
                    class="pi pi-trash"
                    aria-hidden="true"
                  />
                  {{ t('manualInvoice.removeCharge') }}
                </button>
              </header>

              <div class="charge-grid">
                <label class="field charge-field--description">
                  <span class="field-label field-label--required">Description</span>
                  <input
                    v-model.trim="charge.description"
                    :data-testid="`charge-description-${index}`"
                    type="text"
                    autocomplete="off"
                    :class="validationClass(chargeFieldKey(index, 'description'))"
                    v-bind="validationAria(chargeFieldKey(index, 'description'))"
                    required
                  >
                  <span
                    v-if="fieldError(chargeFieldKey(index, 'description'))"
                    :id="fieldErrorId(chargeFieldKey(index, 'description'))"
                    class="field-error"
                    :data-testid="fieldErrorTestId(chargeFieldKey(index, 'description'))"
                  >
                    {{ fieldError(chargeFieldKey(index, 'description')) }}
                  </span>
                </label>
                <label class="field charge-field--room-type">
                  <span class="field-label field-label--required">Room Types</span>
                  <input
                    v-model.trim="charge.room_type"
                    :data-testid="`charge-room-type-${index}`"
                    type="text"
                    autocomplete="off"
                    :class="validationClass(chargeFieldKey(index, 'room-type'))"
                    v-bind="validationAria(chargeFieldKey(index, 'room-type'))"
                    required
                  >
                  <span
                    v-if="fieldError(chargeFieldKey(index, 'room-type'))"
                    :id="fieldErrorId(chargeFieldKey(index, 'room-type'))"
                    class="field-error"
                    :data-testid="fieldErrorTestId(chargeFieldKey(index, 'room-type'))"
                  >
                    {{ fieldError(chargeFieldKey(index, 'room-type')) }}
                  </span>
                </label>
                <label class="field">
                  <span class="field-label field-label--required">Quantity (s)</span>
                  <input
                    v-model="charge.quantity"
                    :data-testid="`charge-quantity-${index}`"
                    type="text"
                    inputmode="numeric"
                    autocomplete="off"
                    :class="validationClass(chargeFieldKey(index, 'quantity'))"
                    v-bind="validationAria(chargeFieldKey(index, 'quantity'))"
                    required
                  >
                  <span
                    v-if="fieldError(chargeFieldKey(index, 'quantity'))"
                    :id="fieldErrorId(chargeFieldKey(index, 'quantity'))"
                    class="field-error"
                    :data-testid="fieldErrorTestId(chargeFieldKey(index, 'quantity'))"
                  >
                    {{ fieldError(chargeFieldKey(index, 'quantity')) }}
                  </span>
                </label>
                <label class="field">
                  <span class="field-label field-label--required">Rate</span>
                  <input
                    v-model="charge.rate"
                    :data-testid="`charge-rate-${index}`"
                    type="text"
                    inputmode="decimal"
                    autocomplete="off"
                    :class="validationClass(chargeFieldKey(index, 'rate'))"
                    v-bind="validationAria(chargeFieldKey(index, 'rate'))"
                    required
                  >
                  <span
                    v-if="fieldError(chargeFieldKey(index, 'rate'))"
                    :id="fieldErrorId(chargeFieldKey(index, 'rate'))"
                    class="field-error"
                    :data-testid="fieldErrorTestId(chargeFieldKey(index, 'rate'))"
                  >
                    {{ fieldError(chargeFieldKey(index, 'rate')) }}
                  </span>
                </label>
                <label class="field">
                  <span class="field-label field-label--required">Night (s)</span>
                  <input
                    v-model="charge.nights"
                    :data-testid="`charge-nights-${index}`"
                    type="text"
                    inputmode="numeric"
                    autocomplete="off"
                    :class="validationClass(chargeFieldKey(index, 'nights'))"
                    v-bind="validationAria(chargeFieldKey(index, 'nights'))"
                    required
                  >
                  <span
                    v-if="fieldError(chargeFieldKey(index, 'nights'))"
                    :id="fieldErrorId(chargeFieldKey(index, 'nights'))"
                    class="field-error"
                    :data-testid="fieldErrorTestId(chargeFieldKey(index, 'nights'))"
                  >
                    {{ fieldError(chargeFieldKey(index, 'nights')) }}
                  </span>
                </label>
                <div class="charge-amount">
                  <span>Amount (Bht)</span>
                  <output :data-testid="`charge-amount-${index}`">
                    {{ formatMoney(lineAmount(charge), previewCurrency) }}
                  </output>
                </div>
              </div>
            </article>
          </div>

          <footer class="results">
            <dl>
              <div>
                <dt>Sub-Total</dt>
                <dd>{{ formatMoney(previewTotals.subtotal, previewCurrency) }}</dd>
              </div>
              <div>
                <dt>VAT 7%</dt>
                <dd>{{ formatMoney(previewTotals.vat, previewCurrency) }}</dd>
              </div>
              <div class="total-row">
                <dt>Total amount</dt>
                <dd>{{ formatMoney(previewTotals.total, previewCurrency) }}</dd>
              </div>
            </dl>
          </footer>
        </section>
      </div>

      <footer
        class="action-bar"
        data-testid="manual-invoice-action-bar"
      >
        <button
          type="submit"
          class="generate-button"
          data-testid="manual-invoice-submit"
          :disabled="submitting"
          @click.prevent="submitInvoice"
        >
          <i
            class="pi pi-file-pdf"
            aria-hidden="true"
          />
          {{ submitting ? t('manualInvoice.generating') : t('manualInvoice.generate') }}
        </button>
      </footer>

      <section
        v-if="messages.length > 0"
        class="section-card invoice-alert"
        :class="`invoice-alert--${alertTone}`"
      >
        <strong>{{ alertTitle }}</strong>
        <ul data-testid="manual-invoice-user-messages">
          <li
            v-for="message in messages"
            :key="message"
          >
            {{ message }}
          </li>
        </ul>
        <details
          v-if="technicalDetails.length > 0"
          class="technical-details"
          data-testid="manual-invoice-technical-details"
        >
          <summary>{{ t('manualInvoice.technicalDetails') }}</summary>
          <ul>
            <li
              v-for="detail in technicalDetails"
              :key="detail"
            >
              {{ detail }}
            </li>
          </ul>
        </details>
      </section>

      <section
        v-if="generationResult"
        class="section-card result-card"
      >
        <header class="section-header">
          <div class="section-title">
            <span class="section-index">PDF</span>
            <h2>{{ t('manualInvoice.resultSection') }}</h2>
          </div>
        </header>
        <dl class="result-list">
          <div>
            <dt>{{ t('manualInvoice.generationId') }}</dt>
            <dd>{{ generationResult.invoice_generation_id }}</dd>
          </div>
          <div>
            <dt>{{ t('manualInvoice.generationStatus') }}</dt>
            <dd>{{ formatGenerationStatus(generationResult.generation_status) }}</dd>
          </div>
          <div>
            <dt>{{ t('manualInvoice.createdAt') }}</dt>
            <dd>{{ formatReservationDateTime(generationResult.created_at) }}</dd>
          </div>
          <div>
            <dt>{{ t('manualInvoice.total') }}</dt>
            <dd>{{ formatMoney(generationResult.totals.total, generationResult.totals.currency) }}</dd>
          </div>
        </dl>
        <div
          v-if="generationResult.pdf_url"
          class="result-actions"
        >
          <a
            class="primary-link"
            :href="generationResult.pdf_url"
            target="_blank"
            rel="noreferrer"
          >
            {{ t('manualInvoice.openPdf') }}
          </a>
          <button
            type="button"
            class="secondary-link"
            data-testid="manual-invoice-download-pdf"
            :disabled="downloadingPdf"
            @click="downloadPdf"
          >
            {{ downloadingPdf ? t('manualInvoice.downloadingPdf') : t('manualInvoice.downloadPdf') }}
          </button>
        </div>
        <p
          v-if="downloadMessage"
          class="muted-text"
          data-testid="manual-invoice-download-message"
        >
          {{ downloadMessage }}
        </p>
        <p
          v-if="!generationResult.pdf_url"
          class="muted-text"
        >
          {{ t('manualInvoice.noPdfUrl') }}
        </p>
      </section>
    </form>
  </main>
</template>

<script setup lang="ts">
import DatePicker from 'primevue/datepicker'
import { usePrimeVue, type PrimeVueLocaleOptions } from 'primevue/config'
import { computed, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'

import { reservationTimeZone } from '@/config/reservationConfig'
import { ApiError } from '@/services/httpClient'
import { generateManualReservationInvoice } from '@/services/reservationService'
import { useAuthStore } from '@/stores/authStore'
import type {
  ManualInvoiceChargePayload,
  ManualInvoiceGenerationRequest,
  ManualInvoiceGenerationResult,
} from '@/types/manualInvoice'
import { formatReservationDateTime } from '@/utils/reservationFormat'

const MAX_CHARGE_LINES = 10
const MANUAL_SELECTION = 'MANUAL'
const VAT_RATE = 0.07
const previewCurrency = 'THB'

type ManualInvoiceDateLocale = 'zh-CN' | 'en-US' | 'th-TH'
type ManualInvoiceDatePickerLocale = Pick<
  PrimeVueLocaleOptions,
  'fileSizeTypes' | 'dayNames' | 'dayNamesShort' | 'dayNamesMin' | 'monthNames' | 'monthNamesShort'
> &
  Partial<PrimeVueLocaleOptions>

interface RecipientContactSeed {
  contact_id: string
  attention: string
  address: string
  telephone: string
  email: string
}

interface RecipientCompanySeed {
  company_code: string
  company: string
  contacts: RecipientContactSeed[]
}

interface ChargeForm {
  id: number
  description: string
  room_type: string
  quantity: string
  rate: string
  nights: string
}

interface GenerationErrorDescription {
  message: string
  technicalDetails: string[]
}

type FieldErrorMap = Record<string, string>
type ChargeFieldName = 'description' | 'room-type' | 'quantity' | 'rate' | 'nights'

const recipientCompanies: RecipientCompanySeed[] = [
  {
    company_code: 'LIAN_TAI',
    company: 'LIAN TAI TRAVEL (THAILAND) CO., LTD.',
    contacts: [
      {
        contact_id: 'LIAN_TAI_KHUN_ANN',
        attention: 'Khun Ann',
        address: '2/86 Rajpattana Road, Rajpattana, Sapansoong, Bangkok, TH, 10240',
        telephone: '061-397-2675',
        email: 'op.liantaitravel@gmail.com',
      },
    ],
  },
  {
    company_code: 'QBD',
    company: 'Q.B.D. TRAVEL GROUP CO., LTD',
    contacts: [
      {
        contact_id: 'QBD_JITDANUN_PANAPHUCHONG',
        attention: 'Jitdanun Panaphuchong',
        address: '2/90 Rajpattana,Rajpattana,Sapansoong, Bangkok, TH, 10240',
        telephone: '089-032 0176',
        email: 'op.qbdtravel@gmail.com',
      },
    ],
  },
  {
    company_code: 'HANATOUR',
    company: 'HANATOUR TD CO., LTD.',
    contacts: [
      {
        contact_id: 'HANATOUR_WICHIENPRAKARN',
        attention: 'Wichienprakarn, Nuanphae, Khun.',
        address: 'HanaTour Bldg, 41,Insadong 5-gil, Jongno-gu, KR',
        telephone: '066 124 - 7297',
        email: 'HI219@hanatour.com',
      },
      {
        contact_id: 'HANATOUR_PHONGBUPPA',
        attention: 'Phongbuppa, Buppachat',
        address: 'HanaTour Bldg, 41,Insadong 5-gil, Jongno-gu, KR',
        telephone: '096 051 3587',
        email: 'HI223@hanatour.com',
      },
      {
        contact_id: 'HANATOUR_KANG_SUNG_HWA',
        attention: 'Kang,Sung Hwa',
        address: 'HanaTour Bldg, 41,Insadong 5-gil, Jongno-gu, KR',
        telephone: '82 051 804 0707',
        email: 'k8040707@nave.com',
      },
      {
        contact_id: 'HANATOUR_MINYOUNG_KIM',
        attention: 'Minyoung Kim',
        address: 'HanaTour Bldg, 41,Insadong 5-gil, Jongno-gu, KR',
        telephone: '82 010 6638 3345',
        email: 'mykim1220@hanayour.com',
      },
      {
        contact_id: 'HANATOUR_BOLAM_JO',
        attention: 'Bolam Jo',
        address: 'HanaTour Bldg, 41,Insadong 5-gil, Jongno-gu, KR',
        telephone: '82 010 4182 4615',
        email: 'melissa0609@hanatour.com',
      },
      {
        contact_id: 'HANATOUR_HWANG_SEONGSEOP',
        attention: 'Hwang Seongseop',
        address: 'HanaTour Bldg, 41,Insadong 5-gil, Jongno-gu, KR',
        telephone: '82 010 3167 8648',
        email: 'hwangpilot59@naver.com',
      },
      {
        contact_id: 'HANATOUR_SONG_SAE_HWA',
        attention: 'Song Sae Hwa',
        address: 'HanaTour Bldg, 41,Insadong 5-gil, Jongno-gu, KR',
        telephone: '82 010 2400 6801',
        email: 'sh-tour2016@naver.com',
      },
    ],
  },
]

const { t, te, locale } = useI18n()
const primeVue = usePrimeVue()
const authStore = useAuthStore()
let nextChargeId = 1
const defaultDocumentDates = createDefaultDocumentDates(resolveHotelTimeZone())

const form = reactive({
  document: {
    invoice_date: defaultDocumentDates.invoiceDate,
    booking_date: defaultDocumentDates.bookingDate,
    due_date: defaultDocumentDates.dueDate,
  },
  recipient: {
    company_code: '',
    contact_id: '',
    company: '',
    attention: '',
    address: '',
    telephone: '',
    email: '',
  },
  booking: {
    group_name: '',
    arrival_date: '',
    departure_date: '',
    room_rate_note: '',
    extra_bed_rate: '',
  },
  charges: [createCharge()],
})

const submitting = ref(false)
const downloadingPdf = ref(false)
const messages = ref<string[]>([])
const technicalDetails = ref<string[]>([])
const downloadMessageKey = ref('')
const alertTone = ref<'error' | 'success'>('error')
const generationResult = ref<ManualInvoiceGenerationResult | null>(null)
const documentDatesEdited = ref(false)
const validationAttempted = ref(false)

const recipientContacts = computed(() => {
  return recipientCompanies.find((company) => company.company_code === form.recipient.company_code)?.contacts ?? []
})

const primaryCharge = computed<ChargeForm>(() => form.charges[0] as ChargeForm)

const sourceGroupName = computed(() => form.booking.group_name.trim() || '未填写')
const downloadMessage = computed(() => (downloadMessageKey.value ? t(downloadMessageKey.value) : ''))
const fieldErrors = computed<FieldErrorMap>(() => (validationAttempted.value ? collectFieldErrors() : {}))
const documentBookingDateModel = createDatePickerModel(
  () => form.document.booking_date,
  (value) => {
    form.document.booking_date = value
    markDocumentDatesEdited()
  },
)
const documentDueDateModel = createDatePickerModel(
  () => form.document.due_date,
  (value) => {
    form.document.due_date = value
    markDocumentDatesEdited()
  },
)
const bookingArrivalDateModel = createDatePickerModel(
  () => form.booking.arrival_date,
  (value) => {
    form.booking.arrival_date = value
  },
)
const bookingDepartureDateModel = createDatePickerModel(
  () => form.booking.departure_date,
  (value) => {
    form.booking.departure_date = value
  },
)

const primeVueDatePickerLocales: Record<ManualInvoiceDateLocale, ManualInvoiceDatePickerLocale> = {
  'zh-CN': {
    fileSizeTypes: ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
    dayNames: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
    dayNamesShort: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
    dayNamesMin: ['日', '一', '二', '三', '四', '五', '六'],
    monthNames: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
    monthNamesShort: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
    chooseDate: '选择日期',
    chooseMonth: '选择月份',
    chooseYear: '选择年份',
    prevMonth: '上个月',
    nextMonth: '下个月',
    prevYear: '上一年',
    nextYear: '下一年',
    today: '今天',
    clear: '清除',
    firstDayOfWeek: 1,
    showMonthAfterYear: true,
    dateFormat: 'yy-mm-dd',
  },
  'en-US': {
    fileSizeTypes: ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
    dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
    dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
    dayNamesMin: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
    monthNames: [
      'January',
      'February',
      'March',
      'April',
      'May',
      'June',
      'July',
      'August',
      'September',
      'October',
      'November',
      'December',
    ],
    monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
    chooseDate: 'Choose date',
    chooseMonth: 'Choose month',
    chooseYear: 'Choose year',
    prevMonth: 'Previous month',
    nextMonth: 'Next month',
    prevYear: 'Previous year',
    nextYear: 'Next year',
    today: 'Today',
    clear: 'Clear',
    firstDayOfWeek: 0,
    showMonthAfterYear: false,
    dateFormat: 'yy-mm-dd',
  },
  'th-TH': {
    fileSizeTypes: ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
    dayNames: ['วันอาทิตย์', 'วันจันทร์', 'วันอังคาร', 'วันพุธ', 'วันพฤหัสบดี', 'วันศุกร์', 'วันเสาร์'],
    dayNamesShort: ['อา.', 'จ.', 'อ.', 'พ.', 'พฤ.', 'ศ.', 'ส.'],
    dayNamesMin: ['อา.', 'จ.', 'อ.', 'พ.', 'พฤ.', 'ศ.', 'ส.'],
    monthNames: [
      'มกราคม',
      'กุมภาพันธ์',
      'มีนาคม',
      'เมษายน',
      'พฤษภาคม',
      'มิถุนายน',
      'กรกฎาคม',
      'สิงหาคม',
      'กันยายน',
      'ตุลาคม',
      'พฤศจิกายน',
      'ธันวาคม',
    ],
    monthNamesShort: ['ม.ค.', 'ก.พ.', 'มี.ค.', 'เม.ย.', 'พ.ค.', 'มิ.ย.', 'ก.ค.', 'ส.ค.', 'ก.ย.', 'ต.ค.', 'พ.ย.', 'ธ.ค.'],
    chooseDate: 'เลือกวันที่',
    chooseMonth: 'เลือกเดือน',
    chooseYear: 'เลือกปี',
    prevMonth: 'เดือนก่อนหน้า',
    nextMonth: 'เดือนถัดไป',
    prevYear: 'ปีก่อนหน้า',
    nextYear: 'ปีถัดไป',
    today: 'วันนี้',
    clear: 'ล้าง',
    firstDayOfWeek: 1,
    showMonthAfterYear: false,
    dateFormat: 'yy-mm-dd',
  },
}

const previewTotals = computed(() => {
  const total = form.charges.reduce((sum, charge) => sum + lineAmount(charge), 0)
  const subtotal = total / (1 + VAT_RATE)
  const vat = total - subtotal
  return {
    subtotal,
    vat,
    total,
  }
})

const alertTitle = computed(() => {
  if (alertTone.value === 'success') {
    return t('manualInvoice.resultSection')
  }
  return t('manualInvoice.validationTitle')
})

watch(
  locale,
  (nextLocale) => {
    syncPrimeVueDatePickerLocale(nextLocale)
  },
  { immediate: true },
)

watch(
  () => form.recipient.company_code,
  (companyCode) => {
    if (companyCode === MANUAL_SELECTION) {
      applyManualCompanySelection()
      return
    }
    const company = recipientCompanies.find((item) => item.company_code === companyCode)
    if (!company) {
      form.recipient.contact_id = ''
      return
    }
    form.recipient.company = company.company
    if (!company.contacts.some((contact) => contact.contact_id === form.recipient.contact_id)) {
      form.recipient.contact_id = company.contacts[0]?.contact_id ?? ''
    }
    applyContactSeed(form.recipient.contact_id)
  },
)

watch(
  () => form.recipient.contact_id,
  (contactId) => {
    if (contactId === MANUAL_SELECTION) {
      clearRecipientContactProfile()
      return
    }
    applyContactSeed(contactId)
  },
)

watch(
  () => authStore.selectedHotel?.time_zone,
  (timeZone, previousTimeZone) => {
    if (!timeZone || timeZone === previousTimeZone || documentDatesEdited.value) {
      return
    }
    applyDefaultDocumentDates(timeZone)
  },
)

function createCharge(): ChargeForm {
  const id = nextChargeId
  nextChargeId += 1
  return {
    id,
    description: '',
    room_type: '',
    quantity: '',
    rate: '',
    nights: '',
  }
}

function addCharge(): void {
  if (form.charges.length >= MAX_CHARGE_LINES) {
    return
  }
  form.charges.push(createCharge())
}

function removeCharge(index: number): void {
  if (form.charges.length === 1) {
    return
  }
  form.charges.splice(index, 1)
}

function lineAmount(charge: ChargeForm): number {
  const quantity = numberValue(charge.quantity)
  const rate = numberValue(charge.rate)
  const nights = numberValue(charge.nights)
  if (quantity <= 0 || rate <= 0 || nights <= 0) {
    return 0
  }
  return quantity * rate * nights
}

async function submitInvoice(): Promise<void> {
  technicalDetails.value = []
  downloadMessageKey.value = ''
  validationAttempted.value = true
  messages.value = validateForm()
  alertTone.value = 'error'
  if (messages.value.length > 0) {
    return
  }

  submitting.value = true
  generationResult.value = null
  try {
    generationResult.value = await generateManualReservationInvoice(buildRequest())
    messages.value = []
  } catch (error) {
    const errorDescription = describeGenerationError(error)
    messages.value = [errorDescription.message]
    technicalDetails.value = errorDescription.technicalDetails
  } finally {
    submitting.value = false
  }
}

async function downloadPdf(): Promise<void> {
  const result = generationResult.value
  const pdfUrl = result?.pdf_url
  if (!pdfUrl || downloadingPdf.value) {
    return
  }

  downloadingPdf.value = true
  downloadMessageKey.value = ''
  try {
    const response = await fetch(pdfUrl, { credentials: 'omit' })
    if (!response.ok) {
      throw new Error(`PDF download failed with status ${response.status}`)
    }
    const blob = await response.blob()
    const objectUrl = URL.createObjectURL(blob)
    triggerBrowserDownload(objectUrl, createPdfFileName(result.invoice_generation_id))
    window.setTimeout(() => URL.revokeObjectURL(objectUrl), 0)
  } catch {
    downloadMessageKey.value = 'manualInvoice.downloadFallback'
    window.open(pdfUrl, '_blank', 'noopener,noreferrer')
  } finally {
    downloadingPdf.value = false
  }
}

function buildRequest(): ManualInvoiceGenerationRequest {
  return {
    hotel_id: authStore.selectedHotelId ?? undefined,
    source_type: 'MANUAL',
    task_id: null,
    order_id: null,
    template_code: 'PROFORMA_INVOICE_V1',
    invoice_payload: {
      document: {
        invoice_date: form.document.invoice_date,
        booking_date: form.document.booking_date,
        due_date: form.document.due_date,
      },
      recipient: {
        company_code: directoryCodeOrNull(form.recipient.company_code),
        contact_id: directoryCodeOrNull(form.recipient.contact_id),
        company: form.recipient.company.trim(),
        attention: form.recipient.attention.trim(),
        address: form.recipient.address.trim(),
        telephone: form.recipient.telephone.trim(),
        email: form.recipient.email.trim(),
      },
      booking: {
        group_name: form.booking.group_name.trim(),
        arrival_date: form.booking.arrival_date,
        departure_date: form.booking.departure_date,
        room_rate_note: trimToNull(form.booking.room_rate_note),
        extra_bed_rate: optionalNumberValue(form.booking.extra_bed_rate),
      },
      charges: buildCharges(),
    },
  }
}

function buildCharges(): ManualInvoiceChargePayload[] {
  return form.charges.map((charge) => ({
    description: charge.description.trim(),
    room_type: charge.room_type.trim(),
    quantity: numberValue(charge.quantity),
    rate: numberValue(charge.rate),
    nights: numberValue(charge.nights),
  }))
}

function validateForm(): string[] {
  const errors: string[] = []
  const hasRequiredDocument =
    Boolean(form.document.invoice_date) && Boolean(form.document.booking_date) && Boolean(form.document.due_date)
  const hasRequiredRecipient =
    Boolean(form.recipient.company.trim()) &&
    Boolean(form.recipient.attention.trim()) &&
    Boolean(form.recipient.address.trim()) &&
    Boolean(form.recipient.telephone.trim()) &&
    Boolean(form.recipient.email.trim())
  const hasRequiredBooking =
    Boolean(form.booking.group_name.trim()) &&
    Boolean(form.booking.arrival_date) &&
    Boolean(form.booking.departure_date)
  const charges = buildCharges()
  const extraBedRate = optionalNumberValue(form.booking.extra_bed_rate)
  const hasRequiredCharges =
    charges.length > 0 &&
    charges.every((charge) => charge.description && charge.room_type && charge.quantity > 0 && charge.rate > 0 && charge.nights > 0)

  if (!hasRequiredDocument || !hasRequiredRecipient || !hasRequiredBooking || !hasRequiredCharges) {
    errors.push(t('manualInvoice.errors.requiredFields'))
  }
  if (form.recipient.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.recipient.email)) {
    errors.push(t('manualInvoice.errors.invalidEmail'))
  }
  if (form.document.booking_date && form.document.due_date && form.document.due_date < form.document.booking_date) {
    errors.push(t('manualInvoice.errors.dueDateBeforeBookingDate'))
  }
  if (
    form.booking.arrival_date &&
    form.booking.departure_date &&
    form.booking.departure_date <= form.booking.arrival_date
  ) {
    errors.push(t('manualInvoice.errors.departureBeforeArrival'))
  }
  if (form.booking.extra_bed_rate.trim() && (extraBedRate === null || extraBedRate < 0)) {
    errors.push(t('manualInvoice.errors.extraBedNegative'))
  }
  if (charges.some((charge) => charge.quantity <= 0 || charge.rate <= 0 || charge.nights <= 0)) {
    errors.push(t('manualInvoice.errors.chargePositive'))
  }
  return [...new Set(errors)]
}

function collectFieldErrors(): FieldErrorMap {
  const errors: FieldErrorMap = {}
  const positiveNumber = t('manualInvoice.fieldErrors.positiveNumber')

  setRequiredError(errors, 'company', form.recipient.company)
  setRequiredError(errors, 'attention', form.recipient.attention)
  setRequiredError(errors, 'address', form.recipient.address)
  setRequiredError(errors, 'telephone', form.recipient.telephone)
  setRequiredError(errors, 'email', form.recipient.email)
  if (form.recipient.email.trim() && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.recipient.email)) {
    errors.email = t('manualInvoice.fieldErrors.invalidEmail')
  }

  setRequiredError(errors, 'booking-date', form.document.booking_date)
  setRequiredError(errors, 'due-date', form.document.due_date)
  if (form.document.booking_date && form.document.due_date && form.document.due_date < form.document.booking_date) {
    errors['due-date'] = t('manualInvoice.fieldErrors.dateOrder')
  }

  setRequiredError(errors, 'group-name', form.booking.group_name)
  setRequiredError(errors, 'arrival-date', form.booking.arrival_date)
  setRequiredError(errors, 'departure-date', form.booking.departure_date)
  if (
    form.booking.arrival_date &&
    form.booking.departure_date &&
    form.booking.departure_date <= form.booking.arrival_date
  ) {
    errors['departure-date'] = t('manualInvoice.fieldErrors.dateOrder')
  }

  const extraBedRate = optionalNumberValue(form.booking.extra_bed_rate)
  if (form.booking.extra_bed_rate.trim() && (extraBedRate === null || extraBedRate < 0)) {
    errors['extra-bed-rate'] = t('manualInvoice.fieldErrors.nonNegativeNumber')
  }

  form.charges.forEach((charge, index) => {
    setRequiredError(errors, chargeFieldKey(index, 'description'), charge.description)
    setRequiredError(errors, chargeFieldKey(index, 'room-type'), charge.room_type)
    if (numberValue(charge.quantity) <= 0) {
      errors[chargeFieldKey(index, 'quantity')] = positiveNumber
    }
    if (numberValue(charge.rate) <= 0) {
      errors[chargeFieldKey(index, 'rate')] = positiveNumber
    }
    if (numberValue(charge.nights) <= 0) {
      errors[chargeFieldKey(index, 'nights')] = positiveNumber
    }
  })

  const firstQuantityError = errors[chargeFieldKey(0, 'quantity')]
  if (firstQuantityError) {
    errors['primary-quantity'] = firstQuantityError
  }
  const firstRateError = errors[chargeFieldKey(0, 'rate')]
  if (firstRateError) {
    errors['primary-rate'] = firstRateError
  }
  const firstNightsError = errors[chargeFieldKey(0, 'nights')]
  if (firstNightsError) {
    errors['primary-nights'] = firstNightsError
  }

  return errors
}

function setRequiredError(errors: FieldErrorMap, fieldKey: string, value: string): void {
  if (!value.trim()) {
    errors[fieldKey] = t('manualInvoice.fieldErrors.required')
  }
}

function fieldError(fieldKey: string): string {
  return fieldErrors.value[fieldKey] ?? ''
}

function fieldErrorId(fieldKey: string): string {
  return `manual-invoice-error-${fieldKey}`
}

function fieldErrorTestId(fieldKey: string): string {
  return `manual-invoice-field-error-${fieldKey}`
}

function validationClass(fieldKey: string): Record<string, boolean> {
  return {
    'is-invalid': Boolean(fieldError(fieldKey)),
  }
}

function validationAria(fieldKey: string): Record<string, string | undefined> {
  if (!fieldError(fieldKey)) {
    return {
      'aria-invalid': undefined,
      'aria-describedby': undefined,
    }
  }
  return {
    'aria-invalid': 'true',
    'aria-describedby': fieldErrorId(fieldKey),
  }
}

function datePickerPt(testId: string, fieldKey: string): Record<string, unknown> {
  return {
    pcInputText: {
      root: {
        'data-testid': testId,
        lang: locale.value,
        ...validationAria(fieldKey),
      },
    },
    panel: {
      lang: locale.value,
      'data-testid': `${testId}-panel`,
    },
  }
}

function syncPrimeVueDatePickerLocale(nextLocale: string): void {
  const supportedLocale = normalizeDateLocale(nextLocale)
  primeVue.config.locale = {
    ...(primeVue.config.locale ?? primeVueDatePickerLocales['en-US']),
    ...primeVueDatePickerLocales[supportedLocale],
  }
}

function normalizeDateLocale(nextLocale: string): ManualInvoiceDateLocale {
  if (nextLocale === 'en-US' || nextLocale === 'th-TH') {
    return nextLocale
  }
  return 'zh-CN'
}

function chargeFieldKey(index: number, fieldName: ChargeFieldName): string {
  return `charge-${index}-${fieldName}`
}

function describeGenerationError(error: unknown): GenerationErrorDescription {
  if (error instanceof ApiError) {
    const authKey = error.status === 401 ? 'AUTH_401' : error.status === 403 ? 'AUTH_403' : null
    const errorCode = authKey ?? errorCodeFromDetails(error.details)
    const messageKey = `manualInvoice.errors.${errorCode}`
    const message = te(messageKey) ? t(messageKey) : t('manualInvoice.errors.UNKNOWN')
    return {
      message,
      technicalDetails: detailMessages(error.details),
    }
  }
  return {
    message: t('manualInvoice.errors.UNKNOWN'),
    technicalDetails: [],
  }
}

function errorCodeFromDetails(details: unknown): string {
  if (isRecord(details) && typeof details.error_code === 'string') {
    return details.error_code
  }
  return 'UNKNOWN'
}

function detailMessages(details: unknown): string[] {
  if (!isRecord(details)) {
    return []
  }
  const messages: string[] = []
  if (typeof details.message === 'string') {
    messages.push(details.message)
  }
  const rawDetails = details.details
  if (Array.isArray(rawDetails)) {
    messages.push(...rawDetails.filter((item): item is string => typeof item === 'string'))
  }
  return [...new Set(messages)]
}

function applyContactSeed(contactId: string): void {
  const contact = recipientContacts.value.find((item) => item.contact_id === contactId)
  if (!contact) {
    return
  }
  form.recipient.attention = contact.attention
  form.recipient.address = contact.address
  form.recipient.telephone = contact.telephone
  form.recipient.email = contact.email
}

function applyManualCompanySelection(): void {
  form.recipient.company = ''
  if (form.recipient.contact_id !== MANUAL_SELECTION) {
    form.recipient.contact_id = MANUAL_SELECTION
  }
  clearRecipientContactProfile()
}

function clearRecipientContactProfile(): void {
  form.recipient.attention = ''
  form.recipient.address = ''
  form.recipient.telephone = ''
  form.recipient.email = ''
}

function markDocumentDatesEdited(): void {
  documentDatesEdited.value = true
}

function applyDefaultDocumentDates(timeZone: string): void {
  const dates = createDefaultDocumentDates(timeZone)
  form.document.invoice_date = dates.invoiceDate
  form.document.booking_date = dates.bookingDate
  form.document.due_date = dates.dueDate
}

function triggerBrowserDownload(objectUrl: string, fileName: string): void {
  const anchor = document.createElement('a')
  anchor.href = objectUrl
  anchor.download = fileName
  anchor.rel = 'noreferrer'
  anchor.style.display = 'none'
  document.body.appendChild(anchor)
  anchor.click()
  anchor.remove()
}

function createPdfFileName(invoiceGenerationId: string): string {
  const safeId = invoiceGenerationId.replace(/[^a-zA-Z0-9_.-]/g, '-')
  return `proforma-invoice-${safeId}.pdf`
}

function numberValue(value: string | number): number {
  return parseNumericInput(value) ?? 0
}

function optionalNumberValue(value: string | number): number | null {
  return parseNumericInput(value)
}

function parseNumericInput(value: string | number): number | null {
  const trimmed = String(value).replace(/,/g, '').trim()
  if (!trimmed) {
    return null
  }
  const parsed = Number(trimmed)
  return Number.isFinite(parsed) ? parsed : null
}

function trimToNull(value: string): string | null {
  const trimmed = value.trim()
  return trimmed || null
}

function directoryCodeOrNull(value: string): string | null {
  if (!value || value === MANUAL_SELECTION) {
    return null
  }
  return value
}

function formatMoney(value: number, currency: string): string {
  return new Intl.NumberFormat(locale.value, {
    style: 'currency',
    currency,
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  }).format(value)
}

function formatGenerationStatus(status: string): string {
  const key = `manualInvoice.statuses.${status}`
  return te(key) ? t(key) : status
}

function createDefaultDocumentDates(timeZone: string): {
  invoiceDate: string
  bookingDate: string
  dueDate: string
} {
  const businessDate = dateInputValueInTimeZone(new Date(), timeZone)
  return {
    invoiceDate: businessDate,
    bookingDate: businessDate,
    dueDate: addDaysToDateInputValue(businessDate, 7),
  }
}

function resolveHotelTimeZone(): string {
  return authStore.selectedHotel?.time_zone || reservationTimeZone
}

function dateInputValueInTimeZone(date: Date, timeZone: string): string {
  try {
    return datePartsToInputValue(formatDateParts(date, timeZone))
  } catch {
    return datePartsToInputValue(formatDateParts(date, reservationTimeZone))
  }
}

function addDaysToDateInputValue(dateValue: string, days: number): string {
  const [year = 0, month = 1, day = 1] = dateValue.split('-').map((part) => Number(part))
  const nextDate = new Date(Date.UTC(year, month - 1, day + days))
  return [
    String(nextDate.getUTCFullYear()),
    String(nextDate.getUTCMonth() + 1).padStart(2, '0'),
    String(nextDate.getUTCDate()).padStart(2, '0'),
  ].join('-')
}

function formatDateParts(date: Date, timeZone: string): Intl.DateTimeFormatPart[] {
  return new Intl.DateTimeFormat('en-GB', {
    timeZone,
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
  }).formatToParts(date)
}

function datePartsToInputValue(parts: Intl.DateTimeFormatPart[]): string {
  const valueByType = Object.fromEntries(parts.map((part) => [part.type, part.value]))
  const year = valueByType.year
  const month = valueByType.month
  const day = valueByType.day
  return `${year}-${month}-${day}`
}

function createDatePickerModel(readValue: () => string, writeValue: (value: string) => void) {
  return computed<Date | null>({
    get: () => parseInputDateValue(readValue()),
    set: (value) => {
      writeValue(formatDatePickerValue(value))
    },
  })
}

function parseInputDateValue(readValue: string): Date | null {
  const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(readValue)
  if (!match) {
    return null
  }
  const year = Number(match[1])
  const month = Number(match[2])
  const day = Number(match[3])
  const date = new Date(year, month - 1, day)
  if (date.getFullYear() !== year || date.getMonth() !== month - 1 || date.getDate() !== day) {
    return null
  }
  return date
}

function formatDatePickerValue(value: Date | null | undefined): string {
  if (!(value instanceof Date) || Number.isNaN(value.getTime())) {
    return ''
  }
  const year = value.getFullYear()
  const month = String(value.getMonth() + 1).padStart(2, '0')
  const day = String(value.getDate()).padStart(2, '0')
  return `${year}-${month}-${day}`
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value)
}
</script>

<style scoped>
.invoice-page {
  width: min(100%, 1244px);
  margin: 0 auto;
  color: var(--th-color-navy-950);
  font-variant-numeric: tabular-nums;
}

.invoice-page__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 28px;
  padding: 6px 2px 4px;
}

.invoice-page__title {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

.invoice-page__title h1 {
  margin: 0;
  color: var(--th-color-navy-950);
  font-size: clamp(24px, 3vw, 34px);
  line-height: 1.15;
}

.source-summary {
  display: grid;
  justify-items: end;
  gap: 3px;
  flex: 0 0 auto;
  text-align: right;
}

.source-summary span,
.source-summary small {
  color: var(--th-color-slate-500);
  font-size: 10px;
}

.source-summary strong {
  max-width: 360px;
  overflow-wrap: anywhere;
  color: var(--th-color-navy-950);
  font-size: 14px;
}

.invoice-form,
.invoice-form-column {
  display: grid;
  gap: 18px;
  min-width: 0;
}

.invoice-form {
  margin-top: 18px;
}

.section-card {
  min-width: 0;
  overflow: hidden;
  border: 1px solid var(--th-color-border);
  border-radius: 8px;
  background: var(--th-color-white);
  box-shadow: var(--th-shadow-panel);
}

.section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding: 20px 22px 18px;
}

.section-title,
.section-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.section-index {
  color: var(--th-color-blue-600);
  font-size: 11px;
  font-weight: 900;
  letter-spacing: 0.08em;
}

.section-title h2 {
  margin: 0;
  color: var(--th-color-navy-950);
  font-size: 17px;
  line-height: 1.35;
}

.currency-badge {
  border: 1px solid var(--th-color-border);
  border-radius: 999px;
  color: var(--th-color-slate-500);
  font-size: 10px;
  font-weight: 900;
  padding: 6px 9px;
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px;
  min-width: 0;
  border-top: 1px solid var(--th-color-border);
  padding: 20px 22px 24px;
}

.field {
  display: grid;
  align-content: start;
  gap: 8px;
  min-width: 0;
}

.field--wide {
  grid-column: 1 / -1;
}

.field__heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 22px;
}

.field__heading label,
.field-label {
  color: var(--th-color-slate-600);
  font-size: 12px;
  font-weight: 800;
}

.field input,
.field select,
.field textarea,
.charge-grid input {
  width: 100%;
  min-width: 0;
  min-height: 40px;
  border: 1px solid #b9c7dc;
  border-radius: 6px;
  background: #fbfdff;
  color: var(--th-color-navy-950);
  font: inherit;
  font-weight: 700;
  padding: 10px 11px;
  outline: none;
  box-shadow: 0 1px 2px rgb(15 23 42 / 5%), inset 0 0 0 1px rgb(15 23 42 / 3%);
  transition: border-color 180ms ease, box-shadow 180ms ease, background-color 180ms ease;
}

.field textarea {
  min-height: 88px;
  resize: vertical;
  line-height: 1.45;
}

.field input:hover,
.field select:hover,
.field textarea:hover,
.charge-grid input:hover {
  border-color: #8fa4c2;
}

.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible,
.charge-grid input:focus-visible,
.add-button:focus-visible,
.remove-button:focus-visible,
.generate-button:focus-visible,
.primary-link:focus-visible,
.secondary-link:focus-visible {
  outline: 3px solid rgb(18 103 255 / 24%);
  outline-offset: 2px;
}

.field input.is-invalid,
.field select.is-invalid,
.field textarea.is-invalid,
.charge-grid input.is-invalid {
  border-color: #e11d48;
  background: #fff7f9;
  box-shadow: 0 0 0 3px rgb(225 29 72 / 12%), inset 0 0 0 1px rgb(225 29 72 / 8%);
}

.number-control input.is-invalid + .number-suffix {
  border-color: #e11d48;
  background: #fff1f3;
  color: #be123c;
}

.field-label--required::after {
  content: "*";
  margin-left: 4px;
  color: #e11d48;
  font-weight: 900;
}

.field-error {
  margin: -2px 0 0;
  color: #be123c;
  font-size: 11px;
  font-weight: 800;
  line-height: 1.45;
}

.directory-control {
  display: grid;
  min-width: 0;
}

.directory-control--manual {
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  align-items: start;
  gap: 10px;
}

.date-control {
  min-width: 0;
}

.localized-date-picker {
  display: block;
  width: 100%;
}

.localized-date-picker :deep(.p-inputtext) {
  width: 100%;
  padding-right: 40px;
}

.localized-date-picker :deep(.p-datepicker-input-icon-container) {
  right: 12px;
  color: var(--th-color-navy-950);
}

.number-control {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  min-width: 0;
}

.number-control input {
  border-radius: 6px 0 0 6px;
}

.number-suffix {
  display: grid;
  align-content: center;
  border: 1px solid var(--th-color-border);
  border-left: 0;
  border-radius: 0 6px 6px 0;
  background: var(--th-color-slate-50);
  padding: 5px 10px;
}

.number-suffix {
  place-items: center;
  min-width: 62px;
  color: var(--th-color-slate-500);
  font-size: 11px;
  font-weight: 800;
}

.room-rate-control {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  gap: 10px;
}

.room-rate-control label {
  display: grid;
  gap: 6px;
  min-width: 0;
}

.add-button,
.remove-button,
.generate-button,
.primary-link,
.secondary-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  border-radius: 6px;
  font-weight: 800;
  text-decoration: none;
  transition: border-color 180ms ease, background-color 180ms ease, color 180ms ease, box-shadow 180ms ease;
}

.add-button,
.remove-button {
  min-height: 34px;
  font-size: 11px;
  padding: 0 11px;
}

.add-button {
  border: 1px solid var(--th-color-blue-600);
  background: var(--th-color-blue-600);
  color: var(--th-color-white);
}

.add-button:hover {
  background: #0759e8;
}

.remove-button {
  border: 1px solid var(--th-color-border);
  background: var(--th-color-white);
  color: var(--th-color-slate-500);
}

.remove-button:hover:not(:disabled) {
  border-color: #ce2b3f;
  background: #ffecef;
  color: #ce2b3f;
}

.add-button:disabled,
.remove-button:disabled,
.generate-button:disabled,
.secondary-link:disabled {
  cursor: not-allowed;
  opacity: 0.42;
}

.charge-lines {
  border-top: 1px solid var(--th-color-border);
}

.charge-line {
  padding: 18px 22px 22px;
}

.charge-line + .charge-line {
  border-top: 1px solid var(--th-color-border);
  background: var(--th-color-slate-50);
}

.charge-line__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}

.charge-line__header strong {
  color: var(--th-color-slate-500);
  font-size: 11px;
}

.charge-grid {
  display: grid;
  grid-template-columns: minmax(180px, 2fr) minmax(145px, 1.4fr) repeat(3, minmax(88px, 0.8fr)) minmax(120px, 0.9fr);
  align-items: start;
  gap: 14px;
  min-width: 0;
}

.charge-amount {
  display: grid;
  align-content: start;
  gap: 8px;
  min-width: 0;
}

.charge-amount > span {
  min-height: 22px;
  color: var(--th-color-slate-600);
  font-size: 12px;
  font-weight: 800;
}

.charge-amount output {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  min-height: 40px;
  overflow-wrap: anywhere;
  border: 1px solid var(--th-color-border);
  border-radius: 6px;
  background: var(--th-color-slate-50);
  color: var(--th-color-navy-950);
  font-size: 13px;
  font-weight: 900;
  padding: 9px 11px;
}

.results {
  display: flex;
  justify-content: flex-end;
  border-top: 1px solid var(--th-color-border);
  background: var(--th-color-white);
  padding: 8px 22px 14px;
}

.results dl {
  width: min(100%, 390px);
  margin: 0;
}

.results dl > div,
.result-list div {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  border-bottom: 1px solid var(--th-color-slate-100);
  padding: 12px 0;
}

.results dl > div:last-child,
.result-list div:last-child {
  border-bottom: 0;
}

.results dt,
.result-list dt {
  color: var(--th-color-slate-500);
  font-size: 12px;
}

.results dd,
.result-list dd {
  margin: 0;
  color: var(--th-color-navy-950);
  font-size: 13px;
  font-weight: 900;
  text-align: right;
}

.results .total-row dt,
.results .total-row dd {
  color: var(--th-color-blue-600);
}

.action-bar {
  position: sticky;
  bottom: 16px;
  z-index: 8;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  margin-top: 18px;
  border: 1px solid var(--th-color-border);
  border-radius: 8px;
  background: rgb(255 255 255 / 96%);
  box-shadow: 0 18px 45px rgb(11 31 69 / 16%);
  padding: 12px 14px;
  backdrop-filter: blur(12px);
}

.generate-button {
  min-height: 44px;
  border: 0;
  background: var(--th-color-blue-600);
  color: var(--th-color-white);
  font-size: 12px;
  font-weight: 900;
  padding: 0 18px;
}

.generate-button:hover:not(:disabled) {
  background: #0759e8;
  box-shadow: 0 8px 18px rgb(18 103 255 / 24%);
}

.invoice-alert {
  padding: 18px 22px;
  box-shadow: none;
}

.invoice-alert--error {
  border-color: #fecdd3;
  background: #fff1f2;
  color: #be123c;
}

.invoice-alert ul {
  margin: 8px 0 0;
  padding-left: 18px;
}

.technical-details {
  border-top: 1px solid rgb(190 18 60 / 16%);
  margin-top: 12px;
  padding-top: 12px;
  color: var(--th-color-slate-500);
  font-size: 12px;
}

.technical-details summary {
  cursor: pointer;
  color: #9f1239;
  font-weight: 800;
}

.technical-details li {
  word-break: break-word;
}

.result-card {
  padding-bottom: 20px;
}

.result-list {
  display: grid;
  gap: 0;
  margin: 0;
  padding: 0 22px;
}

.result-actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  margin: 18px 22px 0;
}

.primary-link {
  border: 1px solid var(--th-color-blue-600);
  background: var(--th-color-blue-600);
  color: var(--th-color-white);
  padding: 10px 12px;
}

.secondary-link {
  border: 1px solid var(--th-color-border);
  background: var(--th-color-white);
  color: var(--th-color-blue-600);
  cursor: pointer;
  font: inherit;
  padding: 10px 12px;
}

.muted-text {
  margin: 12px 22px 0;
  color: var(--th-color-slate-500);
}

.visually-hidden {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  overflow: hidden !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
  clip-path: inset(50%) !important;
}

@media (max-width: 1050px) {
  .charge-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .charge-field--description,
  .charge-field--room-type {
    grid-column: span 2;
  }
}

@media (max-width: 700px) {
  .invoice-page__header,
  .action-bar {
    align-items: stretch;
    display: grid;
  }

  .invoice-page__title {
    align-items: flex-start;
    display: grid;
  }

  .source-summary {
    justify-items: start;
    text-align: left;
  }

  .form-grid {
    grid-template-columns: minmax(0, 1fr);
    padding: 18px 16px 20px;
  }

  .field--wide {
    grid-column: auto;
  }

  .section-header {
    align-items: stretch;
    display: grid;
    padding: 18px 16px 14px;
  }

  .section-actions {
    justify-content: space-between;
  }

  .room-rate-control {
    grid-template-columns: minmax(0, 1fr);
  }

  .charge-line {
    padding: 16px;
  }

  .charge-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .charge-field--description,
  .charge-field--room-type {
    grid-column: 1 / -1;
  }

  .results {
    padding: 8px 16px 14px;
  }

  .action-bar {
    position: static;
    padding: 14px;
  }

  .generate-button {
    width: 100%;
  }
}

@media (max-width: 560px) {
  .directory-control--manual {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 480px) {
  .number-control {
    grid-template-columns: minmax(0, 1fr);
  }

  .number-control input {
    border-radius: 6px 6px 0 0;
  }

  .number-suffix {
    min-height: 42px;
    border-top: 0;
    border-left: 1px solid var(--th-color-border);
    border-radius: 0 0 6px 6px;
  }

  .charge-line__header {
    align-items: stretch;
    display: grid;
  }

  .remove-button {
    width: 100%;
  }

  .charge-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .charge-field--description,
  .charge-field--room-type {
    grid-column: auto;
  }
}
</style>

<style>
.manual-invoice-datepicker-panel {
  z-index: 1200;
  color: var(--th-color-navy-950);
  font-family: inherit;
}
</style>
