/* global process */
import { ARROW_DOWN, ARROW_NEXT, ASSET_DOWNLOAD } from '#amidala/main/shared/icons.js'
import formatTime from '#amidala/main/shared/format-time.js'
import useApi, { STATUS, LOAD_MORE_STATUS, PROMPT_STATUS } from './use-api.js'
import AdminPanel from './admin-panel.js'
import LABELS from './labels.js'
import { isEdit } from '#amidala/main/shared/wcm-mode.js'

const { h, register, matter, preactHooks } = globalThis.ami
const { Box, Button, Icon, Input, Link } = matter
const { useEffect, useMemo, useRef, useState } = preactHooks

const IMAGE_GEN_API_URL = process.env.IMAGE_GEN_API_URL || ''
const LG_COL_SPAN_4 = 'lg:col-span-4'

// Form configuration.
const PROMPT_MIN_LENGTH = 5
const PROMPT_MAX_LENGTH = 1024
const NUM_VARIATIONS = 2
const VARIATIONS = Array.from({ length: NUM_VARIATIONS }, (_, i) => ({ label: i + 1, value: i + 1 }))
const SIZES = [
  { label: '1:1 - 2048x2048 (Square)', value: '2048x2048' },
  { label: '4:3 - 2304x1792 (Landscape)', value: '2304x1792' },
  { label: '3:4 - 1792x2304 (Portrait)', value: '1792x2304' },
  { label: '16:9 - 2688x1536 (Widescreen)', value: '2688x1536' },
  { label: '9:16 - 1440x2560 (Tall portrait)', value: '1440x2560' },
]

// Common Link-like button props for accessibility.
const accessibleLinkButtonProps = {
  role: 'button',
  tabindex: 0,
  onKeyDown: e => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault()
      e.stopPropagation()
      e.currentTarget.click()
    }
  }
}

function PromptForm ({ formRef, submitPrompt, isInProgress, customModels, quotaUsage }) {
  const [state, setState] = useState({ isExpanded: false })
  const formOptionsRef = useRef(null)
  const quotaData = useMemo(() => {
    if (!quotaUsage?.total) {
      return null
    }
    const imagesLeft = Math.max(quotaUsage.total - quotaUsage.usage, 0)
    const percentageLeft = Math.min(Math.floor((imagesLeft / quotaUsage.total) * 100), 100)
    return { ...quotaUsage, imagesLeft, percentageLeft }
  }, [quotaUsage])

  useEffect(() => {
    // Set default values.
    resetOptions()
  }, [])

  const onSubmit = event => {
    event.preventDefault()

    const form = formRef.current
    const prompt = form.text.value.trim() // We internally use "text" to avoid "prompt.prompt" confusion.
    if (!prompt?.length) {
      form.text.value = ''
      form.reportValidity()
      return
    }
    const formData =
      // Filter out empty values to skip these fields entirely, remove text as it will be renamed to prompt.
      Object.fromEntries(
        [...new FormData(form)].filter(([k, v]) => k !== 'text' && Boolean(v?.trim?.()))
      )
    const numVariations = Number(formData.numVariations)
    const [width, height] = formData.size.split('x')

    form.text.value = ''
    submitPrompt({
      ...formData,
      prompt,
      numVariations,
      size: { width, height }
    }, () => {
      if (!form.text.value) {
        // Restore prompt text on error.
        form.text.value = prompt
      }
    }).catch(console.error)
  }

  const resetOptions = () => {
    const inputs = formOptionsRef.current.querySelectorAll('select, input, textarea')
    for (const input of inputs) {
      if (input.dataset.default) {
        input.value = input.dataset.default
      } else if (input.options) {
        input.selectedIndex = 0
      } else {
        input.value = ''
      }
    }
  }

  return h('form', {
    ref: formRef,
    onSubmit
  },
  h(Box, { className: 'bg-bga rounded-2xl' },
    // Main input.
    h('div', { className: 'flex gap-5' },
      h('div', { className: 'grow' },
        h(Input, {
          type: 'textarea',
          minlength: PROMPT_MIN_LENGTH,
          maxlength: PROMPT_MAX_LENGTH,
          required: true,
          name: 'text',
          placeholder: LABELS.PROMPT_PLACEHOLDER,
          'aria-label': LABELS.PROMPT_PLACEHOLDER,
          onKeyDown: e => {
            if (e.key === 'Enter' && e.ctrlKey) {
              e.preventDefault()
              formRef.current.requestSubmit()
            }
          }
        })
      ),
      h('div', { className: 'grow-0' },
        h(Button, {
          type: 'submit',
          _textless: true,
          disabled: isInProgress,
          _model: { name: LABELS.GENERATE_BUTTON, icon: ARROW_NEXT }
        })
      )
    ),
    h('div',
      h('div', { className: 'flex gap-5' },
        h('div', { className: 'w-1/2' },
          // Settings toggle.
          h(Link, {
            _variant: 'underline',
            'aria-controls': 'image-gen-prompt-settings',
            'aria-expanded': state.isExpanded,
            className: 'clickable inline-flex items-center gap-2 cursor-pointer',
            onClick: () => setState(prev => ({ ...prev, isExpanded: !prev.isExpanded })),
            ...accessibleLinkButtonProps
          },
          h('span', LABELS.NEW_PROMPT_SETTINGS),
          h('span', { className: ` transition-all ${state.isExpanded ? 'rotate-180' : ''}` }, h(Icon, { _size: 'sm' }, ARROW_DOWN)))
        ),
        // Quota/usage meter.
        Boolean(quotaData) && h('div', { className: 'w-1/2' },
          h('div', { className: 'text-right', title: `${quotaData.percentageLeft}%` }, `${LABELS.QUOTA_USAGE}: ${quotaData.imagesLeft} / ${quotaData.total}`)
        )
      ),
      h('div', { className: `grid lg:grid-cols-12 gap-5 mt-5 animate-expand ${state.isExpanded ? '' : 'hidden'}`, ref: formOptionsRef, id: 'image-gen-prompt-settings' },
        // Settings.
        h('div', { className: LG_COL_SPAN_4 },
          h(PromptFormInput, {
            label: LABELS.VARIATIONS_LABEL,
            name: 'numVariations',
            'data-default': '1',
            children: VARIATIONS.map((option, index) => h('option', { key: index, value: option.value }, option.label))
          })
        ),
        h('div', { className: LG_COL_SPAN_4 },
          h(PromptFormInput, {
            label: LABELS.SIZE_LABEL,
            name: 'size',
            children: SIZES.map((option, index) => h('option', { key: index, value: option.value }, option.label))
          })
        ),
        (customModels?.length > 0) && h('div', { className: LG_COL_SPAN_4 },
          h(PromptFormInput, {
            label: LABELS.CUSTOM_MODEL_LABEL,
            name: 'customModelId',
            children: customModels.map(({ name, id }, index) => h('option', { key: index, value: id }, name))
          })
        ),
        h('div', { className: 'lg:row-start-2 lg:col-span-12' },
          h(PromptFormInput, {
            label: LABELS.NEGATIVE_PROMPT_LABEL,
            type: 'textarea',
            maxlength: PROMPT_MAX_LENGTH,
            name: 'negativePrompt',
            placeholder: LABELS.NEGATIVE_PROMPT_PLACEHOLDER
          })
        ),
        h('div', { className: 'lg:row-start-3 lg:col-span-12' },
          h(Link, {
            _model: { name: LABELS.RESTORE_DEFAULTS, value: '#' },
            _variant: 'underline',
            onClick: e => {
              e.preventDefault()
              resetOptions()
            },
            ...accessibleLinkButtonProps
          })
        )
      )
    )
  ))
}

function PromptFormInput ({ label, title, ...props }) {
  return h('label',
    h('div', { className: 'block font-bold', title }, label),
    h(Input, {
      type: 'select',
      ...props
    })
  )
}

function PromptItem ({ prompt, formRef }) {
  const layoutImageCount = prompt.images?.length || prompt.numVariations || 1
  const getMessage = messages =>
    h('div', { className: `${layoutImageCount < 2 ? LG_COL_SPAN_4 : 'lg:col-span-8'} text-center pt-5 pb-5 space-y-5 self-center` }, messages)
  const text = prompt.text?.trim()

  const handlers = {
    [PROMPT_STATUS.PENDING]: () => getMessage(LABELS.STATUS_SENDING_REQUEST),
    [PROMPT_STATUS.RUNNING]: () => getMessage(LABELS.STATUS_GENERATING_IMAGES),
    [PROMPT_STATUS.SUCCEEDED]: () =>
      prompt.images.map((image, imageIndex) =>
        image.url && h('div', { className: LG_COL_SPAN_4, key: image.seed },
          h('div', { className: 'relative' },
            // Image.
            h('a', {
              title: LABELS.VIEW_IMAGE_TOOLTIP,
              'aria-label': LABELS.VIEW_IMAGE_TOOLTIP,
              href: image.url,
              target: '_blank',
              rel: 'noopener noreferrer',
              className: 'clickable cursor-pointer block aspect-square',
            }, h('img', {
              className: 'block cursor-pointer w-full h-full object-cover object-center rounded-2xl',
              src: image.url,
              alt: `${text} (${imageIndex + 1})`
            })),
            // Tools.
            image.downloadUrl && h('div', { className: 'inline-flex absolute bottom-4 right-4' },
              h(Button, {
                _variant: 'plain',
                _size: 'xs',
                _textless: true,
                _model: {
                  name: LABELS.DOWNLOAD_IMAGE_BUTTON,
                  value: image.downloadUrl,
                  icon: ASSET_DOWNLOAD
                },
                onClick: e => {
                  e.preventDefault()
                  if (image.downloadUrl) {
                    // Bypass external disclaimer.
                    window.open(image.downloadUrl, '_blank', 'noopener')
                  }
                }
              })
            )
          )
        )
      ),
    [PROMPT_STATUS.CANCELLED]: () => getMessage(LABELS.STATUS_CANCELLED),
    [PROMPT_STATUS.FAILED]: () => getMessage(LABELS.STATUS_FAILED)
  }

  // No images means failed (service download/upload issues).
  const status = ((prompt.status === PROMPT_STATUS.SUCCEEDED) && !prompt.images?.length) ? PROMPT_STATUS.FAILED : prompt.status

  return handlers[status] &&
    h(Box, { key: prompt.id, className: 'bg-bga rounded-2xl' },
      h('article', { className: 'grid lg:grid-cols-12 grid-flow-row gap-5', tabindex: 0, 'data-prompt-id': prompt.id },
        handlers[status](),
        h('div', { className: `${layoutImageCount < 2 ? 'lg:col-span-8 lg:col-start-5' : 'lg:col-span-4 lg:col-start-9'} break-words space-y-5` },
          // Prompt text - break into lines.
          h('div', `${text || LABELS.PROMPT_TEXT_NOT_PROVIDED}`
            .split('\n')
            .map((line, index) => h('div', { className: 'whitespace-pre-wrap mb-1', key: index }, line))),
          // Timestamp (show invisible if not ready to prevent layout shift).
          h('time', {
            className: `block text-txa/80 ${prompt.initDateTime ? '' : 'invisible'}`,
            datetime: prompt.initDateTime?.toISOString()
          }, `${prompt.initDateTime ? formatTime(prompt.initDateTime, true) : '-'}`),
          // Reuse the prompt.
          (text?.length > 0) && h(Link, {
            _size: 'xs',
            _model: { name: LABELS.REUSE_BUTTON },
            _variant: 'underline',
            onClick: e => {
              e.preventDefault()
              formRef.current.text.value = text
              formRef.current.text.focus()
            },
            ...accessibleLinkButtonProps
          })
        )
      )
    )
}

function ImageGen ({ 'api-url': apiUrl = IMAGE_GEN_API_URL, models, anonymous: isAnonymous }) {
  // Get models from the attributes, skip invalid entries.
  const customModels = useMemo(() =>
    models?.split(';')
      .map(model => model.split('|').map(s => s?.trim()))
      .map(([name, id = '']) => ({ name, id }))
  , [models])

  // The main state.
  const [state, setState] = useState({
    status: isEdit ? STATUS.ERROR : STATUS.LOADING,
    statusMessage: isEdit ? LABELS.ERROR_EDIT_MODE : null,
    loadMore: LOAD_MORE_STATUS.NONE,
    lastLoadPromptId: null,
    isAdmin: false
  })

  // Get API-related handlers.
  const [prompts, loadPrompts, statusPoll, submitPrompt, quotaUsage, adminTools] = useApi({
    apiUrl,
    isAnonymous,
    setState
  })

  // Form element reference.
  const formRef = useRef(null)

  useEffect(() => {
    // Load initial prompts.
    state.status === STATUS.LOADING && loadPrompts().then(async () => {
      // Determine if admin user.
      const userStatus = await adminTools.request('user-status')
      if (userStatus?.isAdmin) {
        setState(prev => ({ ...prev, isAdmin: true }))
      }
    }).catch(console.error)
  }, [state.status, apiUrl, adminTools])

  useEffect(() => {
    // Start status polling on prompt list update.
    prompts?.length && statusPoll().catch(console.error)

    // Focus on the newly loaded prompt after load more.
    if (state.lastLoadPromptId) {
      formRef.current.getRootNode().querySelectorAll(`[data-prompt-id="${state.lastLoadPromptId}"]`)[0]?.focus({ preventScroll: true })
      setState(prev => ({ ...prev, lastLoadPromptId: null }))
    }
  }, [prompts])

  // General component status.
  if ([STATUS.LOADING, STATUS.ERROR].includes(state.status)) {
    return h(Box, { className: 'rounded-2xl bg-bga text-txa text-center' },
      state.status === STATUS.ERROR ? state.statusMessage : LABELS.LOADING
    )
  }

  return h('div', { className: 'text-txa space-y-5' },
    state.isAdmin && h(AdminPanel, { adminTools }),
    // Prompt form.
    h(PromptForm, { formRef, submitPrompt, isInProgress: state.status === STATUS.PROCESSING, customModels, quotaUsage }),

    // Prompt list.
    (prompts?.length > 0) && h('div', { className: 'space-y-5' },
      prompts.map(
        prompt => h(PromptItem, { key: prompt.id || `new-${prompt.newItemId}`, prompt, formRef })
      ),
      // Load more button.
      ([LOAD_MORE_STATUS.ACTIVE, LOAD_MORE_STATUS.IN_PROGRESS].includes(state.loadMore)) &&
        h('div', { className: 'flex items-center justify-center min-h-12' },
          state.loadMore === LOAD_MORE_STATUS.IN_PROGRESS
            ? h('span', LABELS.LOADING)
            : h(Button, {
              onClick: () => {
                loadPrompts().catch(console.error)
              },
              _model: { name: LABELS.LOAD_MORE_BUTTON }
            })
        )
    )
  )
}

register(ImageGen, 'image-gen')
