import '@testing-library/jest-dom'
import { html } from 'lit'
import { createElementTest, BaseTestConfig } from '@/tests/test-framework'
import { CustomElementFor } from '@/tests/component-registry'
import './fileupload'
import { IPktFileUpload, FileItem, TFileTransfer, TQueueItemOperation } from './fileupload-types'

interface FileUploadTestConfig extends Partial<IPktFileUpload>, BaseTestConfig {}

const createFileUploadTest = async (config: FileUploadTestConfig = {}) => {
  // Default empty light-DOM child: stray "Test content" breaks Lit render on `pkt-fileupload` (createRenderRoot = this).
  const { content = '', ...attributeConfig } = config
  const { container, element } = await createElementTest<
    CustomElementFor<'pkt-fileupload'>,
    BaseTestConfig
  >('pkt-fileupload', { content })

  Object.entries(attributeConfig).forEach(([key, value]) => {
    ;(element as any)[key] = value
  })
  await element.updateComplete

  return {
    container,
    fileupload: element,
  }
}

/**
 * Drive the component through its public input — same pattern as React's
 * `fireEvent.change(fileInput, { target: { files } })`. Tests should prefer
 * this over poking `.addFiles()` directly so the assertions stay close to
 * the user-visible flow.
 */
const selectFiles = async (
  fileupload: CustomElementFor<'pkt-fileupload'>,
  files: File[],
): Promise<void> => {
  const input = fileupload.querySelector<HTMLInputElement>('input[type="file"]')
  if (!input) throw new Error('Expected pkt-fileupload to render a native file input')
  Object.defineProperty(input, 'files', {
    configurable: true,
    value: files,
  })
  input.dispatchEvent(new Event('change', { bubbles: true }))
  await fileupload.updateComplete
}

/** Convenience to track every `files-changed` event the component emits. */
const recordFilesChanged = (fileupload: CustomElementFor<'pkt-fileupload'>) => {
  const events: Array<{ files: FileItem[]; reason: string; changedFileIds?: string[] }> = []
  fileupload.addEventListener('files-changed', (event) => {
    events.push((event as CustomEvent).detail)
  })
  return events
}

/** Returns the most recent file list as observed via `files-changed` events. */
const lastFilesFromEvents = (
  events: Array<{ files: FileItem[] }>,
): FileItem[] => (events.length === 0 ? [] : events[events.length - 1].files)

afterEach(() => {
  document.body.innerHTML = ''
})

describe('PktFileUpload', () => {
  const createSeedFileItem = (
    fileId: string,
    name: string,
    type: string = 'application/pdf',
  ): FileItem => ({
    fileId,
    file: new File(['seed'], name, { type }),
    attributes: { targetFilename: name },
  })

  test('adds files with stable ids and preserves order', async () => {
    const { fileupload } = await createFileUploadTest({ multiple: true })
    const events = recordFilesChanged(fileupload)

    await selectFiles(fileupload, [
      new File(['a'], 'first.pdf', { type: 'application/pdf' }),
      new File(['b'], 'second.pdf', { type: 'application/pdf' }),
    ])

    const detail = events.at(-1)!
    expect(detail.reason).toBe('add')
    expect(detail.files).toHaveLength(2)
    expect(detail.files[0].attributes.targetFilename).toBe('first.pdf')
    expect(detail.files[1].attributes.targetFilename).toBe('second.pdf')
    const ids = detail.files.map((item) => item.fileId)
    expect(new Set(ids).size).toBe(2)

    // Re-render should not change anything user-visible: queue still has both files in order
    fileupload.disabled = true
    await fileupload.updateComplete
    const titles = Array.from(
      fileupload.querySelectorAll('.pkt-fileupload__queue-display__item__title'),
    ).map((el) => {
      const head = el.querySelector('[data-pkt-truncate-part="first"]')?.textContent ?? ''
      const tail = el.querySelector('[data-pkt-truncate-part="tail"]')?.textContent ?? ''
      return (head + tail).trim()
    })
    expect(titles).toEqual(['first.pdf', 'second.pdf'])
    // No new files-changed event should fire from a property toggle
    expect(events).toHaveLength(1)
  })

  test('remove only affects selected file and keeps other items untouched', async () => {
    const { fileupload } = await createFileUploadTest({ multiple: true })
    const events = recordFilesChanged(fileupload)

    await selectFiles(fileupload, [
      new File(['a'], 'remove-me.pdf', { type: 'application/pdf' }),
      new File(['b'], 'keep-me.pdf', { type: 'application/pdf' }),
    ])
    const beforeRemove = lastFilesFromEvents(events)

    const removeButtons = fileupload.querySelectorAll<HTMLButtonElement>(
      'button[aria-label="Slett fil"]',
    )
    expect(removeButtons.length).toBe(2)
    removeButtons[0].click()
    await fileupload.updateComplete

    const detail = events.at(-1)!
    expect(detail.reason).toBe('remove')
    expect(detail.changedFileIds).toEqual([beforeRemove[0].fileId])
    expect(detail.files).toHaveLength(1)
    expect(detail.files[0].fileId).toBe(beforeRemove[1].fileId)
    expect(detail.files[0].attributes.targetFilename).toBe('keep-me.pdf')
  })

  test('update operation keeps ids stable and emits update reason', async () => {
    const { fileupload } = await createFileUploadTest({ multiple: true })
    const events = recordFilesChanged(fileupload)

    await selectFiles(fileupload, [
      new File(['a'], 'old-name.pdf', { type: 'application/pdf' }),
      new File(['b'], 'other.pdf', { type: 'application/pdf' }),
    ])
    const beforeUpdate = lastFilesFromEvents(events)

    // updateFileItem is the documented public method for programmatic updates.
    fileupload.updateFileItem(beforeUpdate[0].fileId, {
      attributes: {
        ...beforeUpdate[0].attributes,
        targetFilename: 'new-name.pdf',
      },
    })
    await fileupload.updateComplete

    const detail = events.at(-1)!
    expect(detail.reason).toBe('update')
    expect(detail.changedFileIds).toEqual([beforeUpdate[0].fileId])
    expect(detail.files).toHaveLength(2)
    expect(detail.files[0].fileId).toBe(beforeUpdate[0].fileId)
    expect(detail.files[0].attributes.targetFilename).toBe('new-name.pdf')
    expect(detail.files[1].fileId).toBe(beforeUpdate[1].fileId)
  })

  test('controlled mode does not adopt the new list until the parent re-supplies value', async () => {
    const controlledValue: FileItem[] = [
      {
        fileId: 'seed-id',
        attributes: { targetFilename: 'seed.pdf' },
      },
    ]
    const { fileupload } = await createFileUploadTest({ value: controlledValue, multiple: true })
    const events = recordFilesChanged(fileupload)

    await selectFiles(fileupload, [new File(['a'], 'new.pdf', { type: 'application/pdf' })])

    // Component emits a `files-changed` with the proposed next list…
    const detail = events.at(-1)!
    expect(detail.reason).toBe('add')
    expect(detail.files).toHaveLength(2)

    // …but the queue still reflects the controlled value (parent hasn't updated yet).
    const titles = Array.from(
      fileupload.querySelectorAll('.pkt-fileupload__queue-display__item__title'),
    ).map((el) => el.textContent?.trim())
    expect(titles).toEqual(['seed.pdf'])
  })

  test('renders remove operation for both filename and thumbnail modes', async () => {
    const { fileupload: filenameUpload } = await createFileUploadTest({ itemRenderer: 'filename' })
    await selectFiles(filenameUpload, [
      new File(['a'], 'filename-mode.pdf', { type: 'application/pdf' }),
    ])
    expect(
      filenameUpload.querySelector('button[aria-label="Slett fil"]'),
    ).toBeInTheDocument()

    const { fileupload: thumbnailUpload } = await createFileUploadTest()
    thumbnailUpload.itemRenderer = 'thumbnail'
    await thumbnailUpload.updateComplete
    await selectFiles(thumbnailUpload, [
      new File(['b'], 'thumbnail-mode.png', { type: 'image/png' }),
    ])
    expect(
      thumbnailUpload.querySelector('.pkt-fileupload__queue-display__item__thumbnail'),
    ).toBeInTheDocument()
    expect(
      thumbnailUpload.querySelector('button[aria-label="Slett fil"]'),
    ).toBeInTheDocument()
  })

  test('host flow: native input change + remove emits expected files-changed payload', async () => {
    const { fileupload } = await createFileUploadTest({ multiple: true })
    const events: Array<any> = []
    fileupload.addEventListener('files-changed', (event) =>
      events.push((event as CustomEvent).detail),
    )

    const input = fileupload.querySelector<HTMLInputElement>('input[type="file"]')
    expect(input).toBeInTheDocument()

    const selectedFiles = [
      new File(['a'], 'dom-first.pdf', { type: 'application/pdf' }),
      new File(['b'], 'dom-second.pdf', { type: 'application/pdf' }),
    ]

    Object.defineProperty(input as HTMLInputElement, 'files', {
      value: selectedFiles as unknown as FileList,
      configurable: true,
    })

    input?.dispatchEvent(new Event('change', { bubbles: true }))
    await fileupload.updateComplete

    const addDetail = events.at(-1)
    expect(addDetail.reason).toBe('add')
    expect(addDetail.files).toHaveLength(2)

    const removeButtons = fileupload.querySelectorAll<HTMLButtonElement>(
      '.pkt-fileupload__queue-display__item__operation',
    )
    expect(removeButtons).toHaveLength(2)
    removeButtons[0].click()
    await fileupload.updateComplete

    const removeDetail = events.at(-1)
    expect(removeDetail.reason).toBe('remove')
    expect(removeDetail.files).toHaveLength(1)
    expect(removeDetail.files[0].attributes.targetFilename).toBe('dom-second.pdf')
  })

  test('disabled mode blocks remove interaction from queue action', async () => {
    const { fileupload } = await createFileUploadTest({ multiple: true })
    const events = recordFilesChanged(fileupload)
    await selectFiles(fileupload, [new File(['a'], 'locked.pdf', { type: 'application/pdf' })])
    expect(events.at(-1)!.reason).toBe('add')

    fileupload.disabled = true
    await fileupload.updateComplete

    const removeButton = fileupload.querySelector<HTMLButtonElement>('button[aria-label="Slett fil"]')
    expect(removeButton?.disabled).toBe(true)
    removeButton?.click()
    await fileupload.updateComplete

    expect(events).toHaveLength(1)
  })

  test('rejects unsupported formats and shows format error placeholder', async () => {
    const { fileupload } = await createFileUploadTest({
      allowedFormats: ['pdf'],
      formatErrorMessage: 'Kun lovlige formater: {formats}',
    })
    const events = recordFilesChanged(fileupload)

    await selectFiles(fileupload, [new File(['a'], 'photo.jpg', { type: 'image/jpeg' })])

    expect(events).toHaveLength(0)
    expect(fileupload.querySelector('pkt-alert')?.textContent).toContain(
      'Kun lovlige formater: pdf',
    )
  })

  test('accepts MIME wildcards like image/* in allowedFormats', async () => {
    const { fileupload } = await createFileUploadTest({
      allowedFormats: ['image/*'],
    })
    const events = recordFilesChanged(fileupload)

    await selectFiles(fileupload, [new File(['a'], 'good.png', { type: 'image/png' })])
    expect(events.at(-1)?.reason).toBe('add')

    await selectFiles(fileupload, [new File(['b'], 'bad.pdf', { type: 'application/pdf' })])
    expect(fileupload.querySelector('pkt-alert')?.textContent).toContain('Ugyldig filtype')
    // Bad file did not trigger another `files-changed`
    expect(events).toHaveLength(1)
  })

  test('rejects oversized files and applies {maxSize} placeholder', async () => {
    const { fileupload } = await createFileUploadTest({
      maxFileSize: '500KB',
      sizeErrorMessage: 'Maks størrelse er {maxSize}',
    })
    const events = recordFilesChanged(fileupload)

    const tooLarge = new File([new Uint8Array(600 * 1024)], 'large.pdf', {
      type: 'application/pdf',
    })
    await selectFiles(fileupload, [tooLarge])

    expect(events).toHaveLength(0)
    expect(fileupload.querySelector('pkt-alert')?.textContent).toContain('Maks størrelse er 500 KB')
  })

  test('supports onFileValidation callback for custom validation', async () => {
    const { fileupload } = await createFileUploadTest({
      onFileValidation: (file) =>
        file.name.includes('callback-blocked') ? 'Blokkert av callback' : null,
    })
    const events = recordFilesChanged(fileupload)

    await selectFiles(fileupload, [
      new File(['a'], 'callback-blocked.pdf', { type: 'application/pdf' }),
    ])

    expect(events).toHaveLength(0)
    expect(fileupload.querySelector('pkt-alert')?.textContent).toContain('Blokkert av callback')
  })

  test('supports file-validate event for custom validation', async () => {
    const { fileupload } = await createFileUploadTest({
      onFileValidation: () => null,
    })
    const events = recordFilesChanged(fileupload)
    fileupload.addEventListener('file-validate', (event) => {
      const detail = (event as CustomEvent<{ file: File; errorMessage: string | null }>).detail
      if (detail.file.name.endsWith('.exe')) detail.errorMessage = 'Blokkert av event-hook'
    })

    await selectFiles(fileupload, [
      new File(['b'], 'event-blocked.exe', { type: 'application/octet-stream' }),
    ])

    expect(events).toHaveLength(0)
    expect(fileupload.querySelector('pkt-alert')?.textContent).toContain('Blokkert av event-hook')
  })

  test('dropzone format text follows allowedFormats first, then accept', async () => {
    const { fileupload: noRestrictions } = await createFileUploadTest()
    expect(
      noRestrictions.querySelector('.pkt-fileupload__drop-zone__placeholder__formats'),
    ).not.toBeInTheDocument()

    const { fileupload: withAllowed } = await createFileUploadTest({
      allowedFormats: ['pdf', 'image/*'],
    })
    const withAllowedText = withAllowed
      .querySelector('.pkt-fileupload__drop-zone__placeholder__formats')
      ?.textContent?.replace(/\s+/g, ' ')
      .trim()
    expect(withAllowedText).toContain('Format: PDF, image/*')

    const { fileupload: withAccept } = await createFileUploadTest({
      accept: '.png,application/pdf',
    })
    const withAcceptText = withAccept
      .querySelector('.pkt-fileupload__drop-zone__placeholder__formats')
      ?.textContent?.replace(/\s+/g, ' ')
      .trim()
    expect(withAcceptText).toContain('Format: PNG, application/pdf')
  })

  test('sets required only on native file input in form strategy', async () => {
    const { fileupload: formUpload } = await createFileUploadTest({
      required: true,
      uploadStrategy: 'form',
    })
    const formInput = formUpload.querySelector<HTMLInputElement>('input[type="file"]')
    expect(formInput?.required).toBe(true)
    expect(formInput?.getAttribute('aria-required')).toBe('true')

    const { fileupload: customUpload } = await createFileUploadTest({
      required: true,
      uploadStrategy: 'custom',
    })
    const customInput = customUpload.querySelector<HTMLInputElement>('input[type="file"]')
    expect(customInput?.required).toBe(false)
    expect(customInput?.getAttribute('aria-required')).toBe('true')
  })

  test('custom strategy blocks form submit when required and no files are selected', async () => {
    const { fileupload } = await createFileUploadTest({
      required: true,
      uploadStrategy: 'custom',
      name: 'attachments',
    })
    const form = document.createElement('form')
    document.body.appendChild(form)
    form.appendChild(fileupload)
    await fileupload.updateComplete

    const submitEvent = new Event('submit', { bubbles: true, cancelable: true })
    form.dispatchEvent(submitEvent)
    await fileupload.updateComplete

    expect(submitEvent.defaultPrevented).toBe(true)
    expect(fileupload.querySelector('pkt-alert')?.textContent).toContain('Du må laste opp minst én fil.')
  })

  test('custom strategy allows submit when required and at least one file exists', async () => {
    const { fileupload } = await createFileUploadTest({
      required: true,
      uploadStrategy: 'custom',
      multiple: true,
    })
    const form = document.createElement('form')
    document.body.appendChild(form)
    form.appendChild(fileupload)
    await fileupload.updateComplete

    await selectFiles(fileupload, [new File(['a'], 'ok.pdf', { type: 'application/pdf' })])

    const submitEvent = new Event('submit', { bubbles: true, cancelable: true })
    form.dispatchEvent(submitEvent)
    await fileupload.updateComplete

    expect(submitEvent.defaultPrevented).toBe(false)
    expect(fileupload.querySelector('pkt-alert')?.textContent || '').not.toContain(
      'Du må laste opp minst én fil.',
    )
  })

  test('rename operation updates target filename and emits update event', async () => {
    const { fileupload } = await createFileUploadTest({
      value: [createSeedFileItem('rename-1', 'old-name.pdf')],
      renameFilesEnabled: true,
    })
    const events = recordFilesChanged(fileupload)

    const renameButton = fileupload.querySelector<HTMLButtonElement>(
      'button[aria-label="Rediger filnavn"]',
    )
    expect(renameButton).toBeInTheDocument()
    renameButton?.click()
    await fileupload.updateComplete

    const renameInput = fileupload.querySelector<HTMLInputElement>(
      '.pkt-fileupload__queue-display__item__rename-input',
    )
    expect(renameInput).toBeInTheDocument()
    if (renameInput) {
      renameInput.value = 'new-name.pdf'
      renameInput.dispatchEvent(
        new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true }),
      )
    }
    await fileupload.updateComplete

    expect(events.at(-1)?.reason).toBe('update')
    expect(events.at(-1)?.files[0].attributes.targetFilename).toBe('new-name.pdf')
  })

  test('comment operation supports add and remove comment', async () => {
    const { fileupload } = await createFileUploadTest({ addCommentsEnabled: true })
    await selectFiles(fileupload, [new File(['seed'], 'comment.pdf', { type: 'application/pdf' })])

    const addCommentButton = Array.from(
      fileupload.querySelectorAll<HTMLButtonElement>('.pkt-fileupload__queue-display__item__operation'),
    ).find((button) => button.textContent?.includes('Legg til kommentar'))
    expect(addCommentButton).toBeInTheDocument()
    addCommentButton?.click()
    await fileupload.updateComplete

    const commentInput = fileupload.querySelector<HTMLTextAreaElement>(
      '.pkt-fileupload__queue-display__item__comment-input',
    )
    expect(commentInput).toBeInTheDocument()
    if (commentInput) {
      commentInput.value = 'Dette er en kommentar'
    }

    const saveCommentButton = Array.from(fileupload.querySelectorAll<HTMLButtonElement>('.pkt-btn')).find(
      (button) => button.textContent?.includes('Legg til kommentar'),
    )
    saveCommentButton?.click()
    await fileupload.updateComplete

    expect(
      fileupload.querySelector('.pkt-fileupload__queue-display__item__comment__text')?.textContent,
    ).toContain('Dette er en kommentar')

    const deleteCommentButton = fileupload.querySelector<HTMLButtonElement>(
      'button[aria-label="Slett kommentar"]',
    )
    expect(deleteCommentButton).toBeInTheDocument()
    deleteCommentButton?.click()
    await fileupload.updateComplete

    expect(fileupload.querySelector('.pkt-fileupload__queue-display__item__comment__text')).not.toBeInTheDocument()
  })

  test('rename input closes on escape for keyboard users', async () => {
    const { fileupload } = await createFileUploadTest({
      value: [createSeedFileItem('rename-2', 'escape.pdf')],
      renameFilesEnabled: true,
    })

    const renameButton = fileupload.querySelector<HTMLButtonElement>(
      'button[aria-label="Rediger filnavn"]',
    )
    renameButton?.click()
    await fileupload.updateComplete

    const renameInput = fileupload.querySelector<HTMLInputElement>(
      '.pkt-fileupload__queue-display__item__rename-input',
    )
    expect(renameInput).toBeInTheDocument()
    renameInput?.dispatchEvent(
      new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true }),
    )
    await fileupload.updateComplete

    expect(
      fileupload.querySelector('.pkt-fileupload__queue-display__item__rename-input'),
    ).not.toBeInTheDocument()
  })

  test('custom operation renders inline ui', async () => {
    const inlineOperation: TQueueItemOperation = {
      id: 'inline-custom',
      title: 'Inline custom',
      renderInlineUI: ({ close }) =>
        html`<span class="custom-inline-ui">Inline UI</span>
          <button type="button" @click=${close}>Lukk</button>`,
    }

    const { fileupload } = await createFileUploadTest({
      value: [createSeedFileItem('custom-inline-1', 'custom.pdf')],
      extraOperations: [inlineOperation],
    })

    const button = Array.from(
      fileupload.querySelectorAll<HTMLButtonElement>('.pkt-fileupload__queue-display__item__operation'),
    ).find((candidate) => candidate.textContent?.includes('Inline custom'))
    button?.click()
    await fileupload.updateComplete

    expect(fileupload.querySelector('.custom-inline-ui')).toBeInTheDocument()
  })

  test('custom operation renders extended ui', async () => {
    const extendedOperation: TQueueItemOperation = {
      id: 'extended-custom',
      title: 'Extended custom',
      renderExtendedUI: () => html`<div class="custom-extended-ui">Utvidet UI</div>`,
    }

    const { fileupload } = await createFileUploadTest({
      value: [createSeedFileItem('custom-extended-1', 'custom.pdf')],
      extraOperations: [extendedOperation],
    })

    const button = Array.from(
      fileupload.querySelectorAll<HTMLButtonElement>('.pkt-fileupload__queue-display__item__operation'),
    ).find((candidate) => candidate.textContent?.includes('Extended custom'))
    button?.click()
    await fileupload.updateComplete

    expect(fileupload.querySelector('.custom-extended-ui')).toBeInTheDocument()
  })

  test('custom operation onClick receives the queue context', async () => {
    const seen: Array<{ fileId: string; isActive: boolean }> = []
    const clickOperation: TQueueItemOperation = {
      id: 'click-custom',
      title: 'Klikk',
      onClick: ({ file, isActive }) => seen.push({ fileId: file.fileId, isActive }),
    }
    const { fileupload } = await createFileUploadTest({
      value: [createSeedFileItem('click-1', 'click.pdf')],
      extraOperations: [clickOperation],
    })

    const button = Array.from(
      fileupload.querySelectorAll<HTMLButtonElement>('.pkt-fileupload__queue-display__item__operation'),
    ).find((candidate) => candidate.textContent?.includes('Klikk'))
    button?.click()
    await fileupload.updateComplete

    expect(seen).toEqual([{ fileId: 'click-1', isActive: false }])
  })

  test('renders transfer progress state with progress bar for filename and thumbnail', async () => {
    const seed = createSeedFileItem('progress-1', 'progress.pdf')
    const transfers: TFileTransfer[] = [
      { fileId: 'progress-1', progress: 0.42, showProgress: true },
    ]

    const { fileupload: filenameUpload } = await createFileUploadTest({
      value: [seed],
      transfers,
      itemRenderer: 'filename',
    })
    expect(
      filenameUpload.querySelector('.pkt-fileupload__queue-display__item--in-progress'),
    ).toBeInTheDocument()
    expect(
      filenameUpload.querySelector('.pkt-fileupload__queue-display__item__progress'),
    ).toBeInTheDocument()

    const { fileupload: thumbnailUpload } = await createFileUploadTest({
      value: [seed],
      transfers,
      itemRenderer: 'thumbnail',
    })
    expect(
      thumbnailUpload.querySelector('.pkt-fileupload__queue-display__item--in-progress'),
    ).toBeInTheDocument()
    expect(
      thumbnailUpload.querySelector('.pkt-fileupload__queue-display__item__progress'),
    ).toBeInTheDocument()
  })

  test('renders uploading text state when progress bar is hidden', async () => {
    const seed = createSeedFileItem('uploading-text-1', 'uploading.pdf')
    const { fileupload } = await createFileUploadTest({
      value: [seed],
      transfers: [{ fileId: 'uploading-text-1', progress: 0, showProgress: false }],
      itemRenderer: 'filename',
    })

    expect(
      fileupload.querySelector('.pkt-fileupload__queue-display__item__loading-text')?.textContent,
    ).toContain('Laster opp')
  })

  test('renders error transfer state with expected class and message', async () => {
    const seed = createSeedFileItem('error-1', 'failed.pdf')
    const { fileupload } = await createFileUploadTest({
      value: [seed],
      transfers: [
        {
          fileId: 'error-1',
          progress: 'error',
          errorMessage: 'Opplastingen feilet',
          showProgress: true,
          lastProgress: 0.9,
        },
      ],
    })

    expect(
      fileupload.querySelector('.pkt-fileupload__queue-display__item--error'),
    ).toBeInTheDocument()
    expect(
      fileupload.querySelector('.pkt-fileupload__queue-display__item__error-message')?.textContent,
    ).toContain('Opplastingen feilet')
  })

  test('cancel button removes queue item and emits transfer-cancelled event', async () => {
    const { fileupload } = await createFileUploadTest({ multiple: true })

    const transferCancelledEvents: Array<any> = []
    fileupload.addEventListener('transfer-cancelled', (event) =>
      transferCancelledEvents.push((event as CustomEvent).detail),
    )
    const filesChangedEvents = recordFilesChanged(fileupload)

    await selectFiles(fileupload, [new File(['seed'], 'cancel.pdf', { type: 'application/pdf' })])
    const addedFileId = lastFilesFromEvents(filesChangedEvents)[0].fileId
    fileupload.transfers = [{ fileId: addedFileId, progress: 0.5, showProgress: true }]
    await fileupload.updateComplete

    const cancelButton = fileupload.querySelector<HTMLButtonElement>(
      'button[aria-label="Avbryt opplasting"]',
    )
    expect(cancelButton).toBeInTheDocument()
    cancelButton?.click()
    await fileupload.updateComplete

    expect(filesChangedEvents.at(-1)?.reason).toBe('remove')
    expect(fileupload.querySelectorAll('.pkt-fileupload__queue-display__item')).toHaveLength(0)
    expect(transferCancelledEvents).toHaveLength(1)
    expect(transferCancelledEvents[0].fileId).toBe(addedFileId)
  })

  test('renders canceled and done transfer state class names', async () => {
    const canceled = createSeedFileItem('cancelled-file', 'cancelled.pdf')
    const done = createSeedFileItem('done-file', 'done.pdf')
    const { fileupload } = await createFileUploadTest({
      value: [canceled, done],
      transfers: [
        { fileId: 'cancelled-file', progress: 'canceled' },
        { fileId: 'done-file', progress: 'done' },
      ],
      itemRenderer: 'thumbnail',
    })

    expect(
      fileupload.querySelector('.pkt-fileupload__queue-display__item--canceled'),
    ).toBeInTheDocument()
    expect(
      fileupload.querySelector('.pkt-fileupload__queue-display__item--done'),
    ).toBeInTheDocument()
  })

  test('preview modal opens only for done previewable images in thumbnail mode', async () => {
    const image = createSeedFileItem('preview-image-1', 'preview.png', 'image/png')
    const doc = createSeedFileItem('preview-doc-1', 'not-previewable.pdf', 'application/pdf')
    const { fileupload } = await createFileUploadTest({
      value: [image, doc],
      itemRenderer: 'thumbnail',
      enableImagePreview: true,
      transfers: [
        { fileId: 'preview-image-1', progress: 'done' },
        { fileId: 'preview-doc-1', progress: 'done' },
      ],
    })

    const previewButtons = fileupload.querySelectorAll<HTMLButtonElement>(
      '.pkt-fileupload__queue-display__item__thumbnail__image-wrapper',
    )
    expect(previewButtons).toHaveLength(2)
    expect(previewButtons[0].disabled).toBe(false)
    expect(previewButtons[1].disabled).toBe(true)

    previewButtons[0].click()
    await fileupload.updateComplete

    const modal = fileupload.querySelector<HTMLElement & { open?: boolean }>('pkt-modal')
    expect(modal).toBeInTheDocument()
    expect(modal?.open).toBe(true)
  })

  test('preview modal supports arrow navigation and escape', async () => {
    const imageOne = createSeedFileItem('preview-nav-1', 'first.png', 'image/png')
    const imageTwo = createSeedFileItem('preview-nav-2', 'second.png', 'image/png')
    const { fileupload } = await createFileUploadTest({
      value: [imageOne, imageTwo],
      itemRenderer: 'thumbnail',
      enableImagePreview: true,
      transfers: [
        { fileId: 'preview-nav-1', progress: 'done' },
        { fileId: 'preview-nav-2', progress: 'done' },
      ],
    })

    const previewButton = fileupload.querySelector<HTMLButtonElement>(
      '.pkt-fileupload__queue-display__item__thumbnail__image-wrapper',
    )
    previewButton?.click()
    await fileupload.updateComplete

    const modal = fileupload.querySelector<HTMLElement & { open?: boolean; headingText?: string }>(
      'pkt-modal',
    )
    expect(modal?.headingText).toBe('first.png')

    modal?.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }))
    await fileupload.updateComplete
    expect(modal?.headingText).toBe('second.png')

    modal?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))
    await fileupload.updateComplete
    expect(modal?.open).toBe(false)
  })

  test('preview modal traps tab focus inside modal controls', async () => {
    const imageOne = createSeedFileItem('preview-trap-1', 'first.png', 'image/png')
    const imageTwo = createSeedFileItem('preview-trap-2', 'second.png', 'image/png')
    const { fileupload } = await createFileUploadTest({
      value: [imageOne, imageTwo],
      itemRenderer: 'thumbnail',
      enableImagePreview: true,
      transfers: [
        { fileId: 'preview-trap-1', progress: 'done' },
        { fileId: 'preview-trap-2', progress: 'done' },
      ],
    })

    const previewButton = fileupload.querySelector<HTMLButtonElement>(
      '.pkt-fileupload__queue-display__item__thumbnail__image-wrapper',
    )
    previewButton?.click()
    await fileupload.updateComplete

    const closeButton = fileupload.querySelector<HTMLButtonElement>('pkt-modal .pkt-modal__closeButton .pkt-btn')
    const nextButton = fileupload.querySelector<HTMLButtonElement>('.pkt-fileupload__image-preview__nav--next')
    expect(closeButton).toBeInTheDocument()
    expect(nextButton).toBeInTheDocument()
    if (!closeButton || !nextButton) throw new Error('Expected preview modal controls to exist')

    nextButton.focus()
    nextButton.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true, cancelable: true }))
    expect(document.activeElement).toBe(closeButton)

    closeButton.focus()
    closeButton.dispatchEvent(
      new KeyboardEvent('keydown', { key: 'Tab', shiftKey: true, bubbles: true, cancelable: true }),
    )
    expect(document.activeElement).toBe(nextButton)
  })

  test('falls back to icon when thumbnail or preview image fails', async () => {
    const image = createSeedFileItem('preview-error-1', 'broken.png', 'image/png')
    const { fileupload } = await createFileUploadTest({
      value: [image],
      itemRenderer: 'thumbnail',
      enableImagePreview: true,
      transfers: [{ fileId: 'preview-error-1', progress: 'done' }],
    })

    const thumbnailImage = fileupload.querySelector<HTMLImageElement>(
      '.pkt-fileupload__queue-display__item__thumbnail__image-wrapper img',
    )
    expect(thumbnailImage).toBeInTheDocument()
    if (!thumbnailImage) throw new Error('Expected thumbnail image to exist')
    thumbnailImage.dispatchEvent(new Event('error'))
    await fileupload.updateComplete
    expect(
      fileupload.querySelector('.pkt-fileupload__queue-display__item__thumbnail .pkt-icon'),
    ).toBeInTheDocument()

    const previewButton = fileupload.querySelector<HTMLButtonElement>(
      '.pkt-fileupload__queue-display__item__thumbnail__image-wrapper',
    )
    previewButton?.click()
    await fileupload.updateComplete

    const previewImage = fileupload.querySelector<HTMLImageElement>('.pkt-fileupload__image-preview__image')
    previewImage?.dispatchEvent(new Event('error'))
    await fileupload.updateComplete
    expect(
      fileupload.querySelector('.pkt-fileupload__image-preview .pkt-icon'),
    ).toBeInTheDocument()
  })

  test('renders externally-supplied errorMessage in the alert and flags the input', async () => {
    const { fileupload } = await createFileUploadTest({
      hasError: true,
      errorMessage: 'Opplastingen feilet hos tjenesten',
    })

    const alert = fileupload.querySelector('pkt-alert')
    expect(alert?.textContent).toContain('Opplastingen feilet hos tjenesten')

    const input = fileupload.querySelector<HTMLInputElement>('input[type="file"]')
    expect(input?.getAttribute('aria-invalid')).toBe('true')
    expect(input?.getAttribute('aria-describedby')).toContain(`${fileupload.id}-error`)
  })

  test('internal validation error still shown when no external error is set', async () => {
    const { fileupload } = await createFileUploadTest({ allowedFormats: ['pdf'] })

    await selectFiles(fileupload, [new File(['x'], 'bad.png', { type: 'image/png' })])

    const alert = fileupload.querySelector('pkt-alert')
    expect(alert?.textContent).toContain('Ugyldig filtype')
    expect(
      fileupload.querySelector<HTMLInputElement>('input[type="file"]')?.getAttribute('aria-invalid'),
    ).toBe('true')
  })

  test('forwards optionalTag and requiredTag to pkt-input-wrapper', async () => {
    const { fileupload: optional } = await createFileUploadTest({
      label: 'Vedlegg',
      optionalTag: true,
    })
    const optionalWrapper = optional.querySelector<HTMLElement & { optionalTag?: boolean }>('pkt-input-wrapper')
    expect(optionalWrapper?.optionalTag).toBe(true)

    const { fileupload: required } = await createFileUploadTest({
      label: 'Vedlegg',
      requiredTag: true,
    })
    const requiredWrapper = required.querySelector<HTMLElement & { requiredTag?: boolean }>('pkt-input-wrapper')
    expect(requiredWrapper?.requiredTag).toBe(true)
  })

  test('default id is unique per instance', async () => {
    const { fileupload: first } = await createFileUploadTest()
    const { fileupload: second } = await createFileUploadTest()

    expect(first.id).toMatch(/^pkt-fileupload-/)
    expect(second.id).toMatch(/^pkt-fileupload-/)
    expect(first.id).not.toBe(second.id)
  })

  test('renders queue items in the order: in-progress → error → done/queued', async () => {
    const value: FileItem[] = [
      createSeedFileItem('done-1', 'done.pdf'),
      createSeedFileItem('error-1', 'failed.pdf'),
      createSeedFileItem('progress-1', 'uploading.pdf'),
      createSeedFileItem('queued-1', 'queued.pdf'),
    ]
    const transfers: TFileTransfer[] = [
      { fileId: 'done-1', progress: 'done' },
      { fileId: 'error-1', progress: 'error', errorMessage: 'failed' },
      { fileId: 'progress-1', progress: 0.5, showProgress: true },
      { fileId: 'queued-1', progress: 'queued' },
    ]
    const { fileupload } = await createFileUploadTest({
      uploadStrategy: 'custom',
      multiple: true,
      value,
      transfers,
    })

    const titles = Array.from(
      fileupload.querySelectorAll('.pkt-fileupload__queue-display__item__title'),
    ).map((el) => {
      const head = el.querySelector('[data-pkt-truncate-part="first"]')?.textContent ?? ''
      const tail = el.querySelector('[data-pkt-truncate-part="tail"]')?.textContent ?? ''
      return (head + tail).trim()
    })
    expect(titles).toEqual(['uploading.pdf', 'failed.pdf', 'done.pdf', 'queued.pdf'])
  })

  test('truncateTail splits long filenames into head + tail spans and leaves short ones whole', async () => {
    const longName = 'a-very-long-filename-that-needs-truncation.pdf'
    const longSeed = {
      fileId: 'truncate-long',
      file: new File(['x'], longName, { type: 'application/pdf' }),
      attributes: { targetFilename: longName },
    }
    const shortSeed = {
      fileId: 'truncate-short',
      file: new File(['x'], 'short.pdf', { type: 'application/pdf' }),
      attributes: { targetFilename: 'short.pdf' },
    }
    const { fileupload } = await createFileUploadTest({
      value: [longSeed, shortSeed],
      truncateTail: 6,
    })

    const titles = fileupload.querySelectorAll('.pkt-fileupload__queue-display__item__title')
    const longTitle = titles[0] as HTMLElement
    const head = longTitle.querySelector('.pkt-fileupload__queue-display__item__title__head')
    const tail = longTitle.querySelector('.pkt-fileupload__queue-display__item__title__tail')
    expect(head?.textContent).toBe(longName.slice(0, longName.length - 6))
    expect(tail?.textContent).toBe(longName.slice(-6))
    // head + tail reconstruct the full filename (screen readers read both)
    expect((head?.textContent ?? '') + (tail?.textContent ?? '')).toBe(longName)

    const shortTitle = titles[1] as HTMLElement
    expect(shortTitle.querySelector('.pkt-fileupload__queue-display__item__title__head')).toBeNull()
    expect(
      shortTitle.querySelector('[data-pkt-truncate-part="first"]')?.textContent?.trim(),
    ).toBe('short.pdf')
  })
})
