'use client'

import { Fragment, useEffect, useState } from 'react'
import { useTranslations, useLocale } from 'next-intl'
import { CharacterSelect, type CharOption } from '@/components/CharacterSelect'
import { SecretInput } from '@/components/SecretInput'
import { WowheadLink } from '@/components/WowheadLink'
import { wowheadIcon } from '@/lib/wowhead'
import { CARD_MIN_EUR, MAX_COPIES, lineEur, storeEuroTotal } from '@/lib/store-pricing'
import type { StoreCategory, StoreItem } from '@/lib/store'

const ICON_FALLBACK = 'inv_misc_questionmark'

/** Renders de los ítems de la tienda (public/), para el preview en hover. */
const STORE_DISPLAY_PATH = '/ns-themes/ns-ryu/ns-images/ns-displays'

/**
 * Importe en euros en el idioma de la web ("2,00 €", "0,125 €").
 *
 * Mínimo 2 decimales porque es dinero ("1,50 €", no "1,5 €"). Y hasta 3 a
 * propósito: un ítem en PV con precio impar cuesta medio céntimo (25 PV =
 * 0,125 €), así que redondear cada línea a 2 haría que las líneas no sumaran el
 * total y el carrito se contradiría. El total sí va redondeado a céntimos,
 * porque es el importe real que se cobra.
 */
function eurNumber(n: number, locale: string): string {
  return new Intl.NumberFormat(locale, { minimumFractionDigits: 2, maximumFractionDigits: 3 }).format(n)
}

function formatEur(n: number, locale: string): string {
  return `${eurNumber(n, locale)} €`
}

/**
 * Reaplica el tratamiento de wowhead (con `colorLinks` = teñir por calidad) a los
 * enlaces que React acaba de montar. `tooltips.js` solo recorre el DOM al cargar,
 * así que sin esto los ítems de una categoría recién abierta o del carrito se
 * quedan con el azul por defecto del tema. Es lo mismo que hace el store original
 * con $WowheadPower.refreshLinks() tras cada AJAX.
 */
function refreshWowheadLinks() {
  ;(window as unknown as { $WowheadPower?: { refreshLinks: () => void } }).$WowheadPower?.refreshLinks()
}

/** Nodo de categoría colapsable; renderiza sus ítems solo cuando está abierto. */
function CategoryNode({
  cat,
  depth,
  cart,
  onAdd,
}: {
  cat: StoreCategory
  depth: number
  cart: Map<number, CartLine>
  onAdd: (it: StoreItem) => void
}) {
  const t = useTranslations('Store')
  const [open, setOpen] = useState(false)
  const spanClass = depth === 0 ? 'store-cat' : depth === 1 ? 'store-subcat' : 'store-sub-subcat'
  // Los ítems de la hoja se montan al abrirla: hay que teñirlos entonces.
  useEffect(() => {
    if (open) refreshWowheadLinks()
  }, [open])
  return (
    <li>
      {/* `cat.css` y no `first-brown` fijo: el original colorea algunas
          categorías enteras por clase (p.ej. "Brujo" en color de brujo) poniendo
          esa clase EN LUGAR de first-brown. Y dentro del nombre, cada parte
          puede llevar la suya ("Guerrero, Sacerdote, Druida", "[Nivel 232 a
          245]" en otro tono). */}
      <span className={`${spanClass} ${cat.css}`} role="button" tabIndex={0} onClick={() => setOpen((o) => !o)}>
        <i className={`fas fa-angle-right green-info${open ? ' fa-rotate-90' : ''}`} />{' '}
        {/* El texto sin color va SUELTO, no envuelto: el tema tiene
            `span { color: #fff }`, así que meterlo en un <span> lo pondría
            blanco en vez de heredar el color de la cabecera. */}
        {cat.name.map((p, i) =>
          p.cls ? (
            <span key={i} className={`no-toggle ${p.cls}`}>
              {p.text}
            </span>
          ) : (
            <Fragment key={i}>{p.text}</Fragment>
          ),
        )}
      </span>
      {open && (
        <>
          {cat.children.length > 0 && (
            <ul className={depth === 0 ? 'store-subcat' : 'store-sub-subcat'}>
              {cat.children.map((c) => (
                <CategoryNode key={c.code} cat={c} depth={depth + 1} cart={cart} onAdd={onAdd} />
              ))}
            </ul>
          )}
          {cat.items.length > 0 && (
            <div className="item-list">
              {cat.items.map((it) => {
                const copies = cart.get(it.id)?.copies ?? 0
                return (
                  <div className="item-box" key={it.id}>
                    {/* Previsualización del render al pasar el ratón, como el original:
                        solo la tienen los ítems equipables (los demás no tienen
                        apariencia). El CSS (ns-st-tooltip-w, ns-img-display) ya viene
                        en el tema. */}
                    {it.preview && (
                      <div className="second-brown ns-st-tooltip-w">
                        {/* `fas`, no el `fa` suelto del original: cargamos Font Awesome 6,
                            donde solo .fas/.fa-solid activan la fuente (el `fa` de FA4/5
                            no pinta nada sin el shim de compatibilidad). */}
                        <i className="fas fa-image" />
                        <span className="ns-st-img-tooltip">
                          <ins className="ns-img-display" style={{ backgroundImage: `url(${STORE_DISPLAY_PATH}/${it.preview}.png)` }} />
                        </span>
                      </div>
                    )}
                    {/* eslint-disable-next-line @next/next/no-img-element */}
                    <img className="item-img-box" src={wowheadIcon(it.icon || ICON_FALLBACK, 'large')} alt={it.name} loading="lazy" />
                    <p className="item-name centered">
                      <WowheadLink id={it.itemId}>{it.name}</WowheadLink>
                    </p>
                    <hr />
                    <p>
                      <span className="second-brown">{t('quantity')}:</span> <span>{it.qty}</span>
                    </p>
                    <p>
                      <span className={it.currency === 'pv' ? 'vp-color' : 'dp-color'}>{it.currency === 'pv' ? 'PV' : 'PD'}:</span>{' '}
                      <span>{it.price}</span>
                    </p>
                    <br />
                    <button
                      type="button"
                      className="store-add-button"
                      onClick={() => onAdd(it)}
                      disabled={copies >= MAX_COPIES}
                      style={copies ? { color: '#d79602' } : undefined}
                    >
                      {copies ? t('addedCount', { n: copies }) : t('add')}
                    </button>
                  </div>
                )
              })}
            </div>
          )}
        </>
      )}
    </li>
  )
}

type PayMethod = 'balance' | 'stripe' | 'sumup'

/** Línea del carrito: el ítem del catálogo y cuántas copias se llevan. */
interface CartLine {
  it: StoreItem
  copies: number
}

/**
 * Árbol + carrito de la tienda. En modo `gift` es lo mismo pero regalando: el
 * original hace justo esto ("los objetos disponibles son los mismos de la
 * tienda"), en dos pasos —primero de quién a quién y el token, y solo después el
 * catálogo— y el correo lo recibe otro personaje.
 */
export function StoreBrowser({
  characters,
  dp,
  vp,
  realm,
  mode = 'store',
}: {
  characters: CharOption[]
  dp: number
  vp: number
  realm: string
  mode?: 'store' | 'gift'
}) {
  const gift = mode === 'gift'
  const [destination, setDestination] = useState('')
  const [confirmDest, setConfirmDest] = useState('')
  const [token, setToken] = useState('')
  const [shown, setShown] = useState(false)
  const [formError, setFormError] = useState('')
  const t = useTranslations('Store')
  const locale = useLocale()
  const [character, setCharacter] = useState('')
  const [catalog, setCatalog] = useState<StoreCategory[] | null>(null)
  const [loading, setLoading] = useState(false)
  const [cart, setCart] = useState<Map<number, CartLine>>(new Map())
  const [method, setMethod] = useState<PayMethod>('balance')
  const [sending, setSending] = useState(false)
  const [modal, setModal] = useState<{ ok: boolean; text: string } | null>(null)

  async function loadCatalog() {
    if (catalog) return
    setLoading(true)
    try {
      const res = await fetch(`/api/store/catalog?locale=${encodeURIComponent(locale)}`, { credentials: 'same-origin' })
      const data: { success?: boolean; catalog?: StoreCategory[] } = await res.json()
      setCatalog(data.success ? data.catalog ?? [] : [])
    } catch {
      setCatalog([])
    } finally {
      setLoading(false)
    }
  }

  async function onSelect(name: string) {
    setCharacter(name)
    setCart(new Map())
    // En la tienda el catálogo sale al elegir personaje; en el regalo hay que
    // decir antes a quién y con qué token ("Mostrar Regalos").
    if (name && !gift) await loadCatalog()
  }

  /**
   * Paso 1 del regalo: valida y solo entonces enseña el catálogo.
   *
   * La confirmación del nombre se compara aquí (es cosa de la pantalla), pero
   * que el personaje de destino exista y que el token sea correcto lo dice el
   * SERVIDOR: si no, elegirías objetos para descubrir al final que el destino no
   * existe. El nombre del destino puede ir en mayúsculas o minúsculas; el
   * servidor devuelve el canónico y es el que se usa a partir de aquí.
   */
  async function showGifts(e: React.FormEvent) {
    e.preventDefault()
    if (!character || !destination.trim() || !token.trim()) return setFormError(t('gift.errMissing'))
    if (destination.trim().toLowerCase() !== confirmDest.trim().toLowerCase()) {
      return setFormError(t('gift.errConfirm'))
    }
    setFormError('')
    setLoading(true)
    try {
      const res = await fetch('/api/gift/check', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({ source: character, destination: destination.trim(), security_token: token }),
      })
      const data: { success?: boolean; error?: string; destination?: string } = await res.json()
      if (!data.success) {
        setFormError(t.has(`errors.${data.error}`) ? t(`errors.${data.error}`) : t('errors.generic'))
        return
      }
      if (data.destination) setDestination(data.destination)
    } catch {
      setFormError(t('errors.generic'))
      return
    } finally {
      setLoading(false)
    }
    await loadCatalog()
    setShown(true)
  }

  // Volver a añadir un ítem ya en el carrito suma una copia (como send-gift).
  function addItem(it: StoreItem) {
    setCart((prev) => {
      const next = new Map(prev)
      const cur = next.get(it.id)
      next.set(it.id, { it, copies: Math.min(MAX_COPIES, (cur?.copies ?? 0) + 1) })
      return next
    })
  }
  function setCopies(id: number, n: number) {
    setCart((prev) => {
      const cur = prev.get(id)
      if (!cur) return prev
      const next = new Map(prev)
      // El <input type=number> deja escribir vacío o basura: se acota siempre.
      next.set(id, { ...cur, copies: Math.max(1, Math.min(MAX_COPIES, Math.floor(n) || 1)) })
      return next
    })
  }
  function removeItem(id: number) {
    setCart((prev) => {
      const next = new Map(prev)
      next.delete(id)
      return next
    })
  }

  const lines = [...cart.values()]
  // Las filas del carrito las repinta React: hay que volver a teñirlas.
  useEffect(() => {
    refreshWowheadLinks()
  }, [cart])

  const pdTotal = lines.filter((l) => l.it.currency === 'pd').reduce((s, l) => s + l.it.price * l.copies, 0)
  const vpTotal = lines.filter((l) => l.it.currency === 'pv').reduce((s, l) => s + l.it.price * l.copies, 0)
  // Total de la columna "Cant": como en el original, la SUMA de las cantidades de
  // cada línea (un ítem que da un mazo de 20 cuenta 20), no el número de líneas.
  const qtyTotal = lines.reduce((s, l) => s + l.it.qty * l.copies, 0)
  // Clase de color del personaje elegido, para pintarlo como el original.
  const charClass = characters.find((c) => c.name === character)?.classCss ?? ''
  // El mismo cálculo que valida y cobra /api/store/send (módulo compartido).
  const eurTotal = storeEuroTotal(pdTotal, vpTotal)
  // Por debajo del mínimo de la pasarela, su API rechaza el cobro: se desactiva
  // la opción y se dice por qué, en vez de dejar que falle al pulsar Enviar.
  const cardTooLow = (m: PayMethod) => (m === 'stripe' || m === 'sumup') && eurTotal < CARD_MIN_EUR[m]
  // Método REAL, derivado en el render: si el carrito baja del mínimo con una
  // tarjeta ya elegida, esa opción se desactiva y se cae al saldo. Derivado y no
  // un setState en un efecto, que provocaría renders en cascada.
  const activeMethod: PayMethod = cardTooLow(method) ? 'balance' : method
  // Con tarjeta se paga en euros y con saldo en PD/PV: se muestran siempre los
  // dos, y se apaga el que no se va a cobrar para que no haya duda.
  const payingCard = activeMethod === 'stripe' || activeMethod === 'sumup'

  const payOptions: { id: PayMethod; label: string; sub: string; disabled?: boolean }[] = [
    { id: 'balance', label: t('payBalance'), sub: `${pdTotal} PD · ${vpTotal} PV` },
    ...(['stripe', 'sumup'] as const).map((id) => ({
      id: id as PayMethod,
      label: t(id === 'stripe' ? 'payStripe' : 'paySumUp'),
      sub: cardTooLow(id)
        ? t('minCard', { min: eurNumber(CARD_MIN_EUR[id], locale) })
        : formatEur(eurTotal, locale),
      disabled: cardTooLow(id),
    })),
  ]

  async function send() {
    if (sending || cart.size === 0) return
    const card = payingCard
    const who = gift ? destination.trim() : character
    const ok = card
      ? window.confirm(t('confirmCard', { eur: eurNumber(eurTotal, locale), character: who }))
      : window.confirm(t('confirm', { character: who, pd: pdTotal, vp: vpTotal }))
    if (!ok) return
    setSending(true)
    try {
      const items = lines.map((l) => ({ id: l.it.id, copies: l.copies }))
      const res = await fetch(gift ? '/api/gift/send' : '/api/store/send', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify(
          gift
            ? { source: character, destination: destination.trim(), security_token: token, items, provider: activeMethod, locale }
            : { character, items, provider: activeMethod, locale },
        ),
      })
      const data: { success?: boolean; error?: string; url?: string; min?: number } = await res.json()
      if (data.success && data.url) {
        // .assign() y no `location.href = …`: es equivalente, pero la regla del
        // compilador de React prohíbe asignar a algo definido fuera del componente.
        window.location.assign(data.url) // pasarela (Stripe/SumUp)
        return
      }
      if (data.success) {
        setModal({ ok: true, text: t(gift ? 'gift.successMsg' : 'successMsg', { character: gift ? destination.trim() : character }) })
        setCart(new Map())
      } else if (data.error === 'amountTooLow') {
        setModal({ ok: false, text: t('errors.amountTooLow', { min: eurNumber(data.min ?? 1, locale) }) })
      } else {
        setModal({ ok: false, text: t.has(`errors.${data.error}`) ? t(`errors.${data.error}`) : t('errors.generic') })
      }
    } catch {
      setModal({ ok: false, text: t('errors.generic') })
    } finally {
      setSending(false)
    }
  }

  return (
    <>
      {/* Paso 1 del regalo (#char-select-div en el original): de quién a quién y
          token. Desaparece al pulsar "Mostrar Regalos" y deja sitio al catálogo. */}
      {gift && !shown && (
        <div className="centered" id="char-select-div">
          <br />
          <p>{t('gift.choosePlayer')}</p>
          <br />
          <form noValidate onSubmit={showGifts} acceptCharset="utf-8">
            <CharacterSelect characters={characters} value={character} onChange={onSelect} placeholder={t('selectPlaceholder')} />
            <table className="middle-center-table">
              <tbody>
                <tr>
                  <td>
                    <input
                      type="text"
                      maxLength={12}
                      value={destination}
                      onChange={(e) => setDestination(e.target.value)}
                      placeholder={t('gift.destPlaceholder')}
                    />
                  </td>
                </tr>
                <tr>
                  <td>
                    <input
                      type="text"
                      maxLength={12}
                      value={confirmDest}
                      onChange={(e) => setConfirmDest(e.target.value)}
                      placeholder={t('gift.confirmPlaceholder')}
                    />
                  </td>
                </tr>
                <tr>
                  <td>
                    <SecretInput value={token} onChange={setToken} placeholder={t('gift.tokenPlaceholder')} />
                  </td>
                </tr>
                <tr>
                  <td>
                    <button type="submit" className="show-gift-button" disabled={loading}>
                      {loading ? t('gift.showing') : t('gift.show')}
                    </button>
                  </td>
                </tr>
              </tbody>
            </table>
          </form>
          <hr />
          {/* `display:block` a mano: el tema deja `.alert-message` OCULTA
              (display:none) para que la rellene y la revele el JS. Sin esto la
              caja se pintaba pero NO se veía: al pulsar «Mostrar regalos» no
              salía ningún error. El resto de formularios ya lo hacen así. */}
          <div className="alert-message" id="show-gif-response" style={{ display: formError ? 'block' : 'none' }}>
            {formError && <span className="red-form-response">{formError}</span>}
          </div>
        </div>
      )}

      {!gift && (
        <div className="centered">
          <br />
          <p>{t('choosePlayer')}</p>
          <br />
          <CharacterSelect characters={characters} value={character} onChange={onSelect} placeholder={t('selectPlaceholder')} className="store-select" />
          <p className="third-brown pay-method-balance">
            <span className="dp-color">PD</span>: {dp} · <span className="vp-color">PV</span>: {vp}
          </p>
        </div>
      )}

      {loading && <p className="centered second-brown">{t('loading')}</p>}

      {catalog && !loading && (!gift || shown) && (
        <div className="box-content" id="store-div">
          <ul id="store-list">
            {catalog.map((c) => (
              <CategoryNode key={c.code} cat={c} depth={0} cart={cart} onAdd={addItem} />
            ))}
          </ul>

          {/* Carrito: caja propia con título y personaje elegido, como el original. */}
          <div className="title-box-content">
            <h2>
              {t('cartTitle')} - <span className="blue-info">{realm.toUpperCase()}</span>
            </h2>
          </div>
          <div className="body-box-content">
            <p className="centered">
              {t('selectedCharacter')}: <span className={`${charClass} big-font`}>{character}</span>
            </p>
            <div id="cart-list">
              {lines.length > 0 && (
                <div className="pay-method-select">
                  <p className="second-brown">{t('payMethod')}</p>
                  <div className="pay-method-options">
                    {payOptions.map((o) => (
                      <label
                        key={o.id}
                        className={`pay-method-option${activeMethod === o.id ? ' selected' : ''}${o.disabled ? ' disabled' : ''}`}
                      >
                        <input
                          type="radio"
                          name="store-pay"
                          value={o.id}
                          checked={activeMethod === o.id}
                          disabled={o.disabled}
                          onChange={() => setMethod(o.id)}
                        />
                        <span className="pay-method-label">{o.label}</span>
                        <span className="pay-method-sub yellow-info">{o.sub}</span>
                      </label>
                    ))}
                  </div>
                </div>
              )}
              <table className="max-left-table2">
                <tbody>
                  <tr>
                    <th className="width-50-td justified">{t('colItem')}</th>
                    <th className="justified">{t('colQty')}</th>
                    <th className="justified">{t('colValue')}</th>
                    <th className="icon-td">
                      <button type="button" className="store-empty-button" title={t('empty')} onClick={() => setCart(new Map())} disabled={cart.size === 0}>
                        <i className="fas fa-trash" />
                      </button>
                    </th>
                  </tr>
                  {lines.length === 0 ? (
                    <tr>
                      <td colSpan={4} className="second-brown centered">{t('cartEmpty')}</td>
                    </tr>
                  ) : (
                    lines.map((l) => (
                      <tr key={l.it.id}>
                        <td>
                          <WowheadLink id={l.it.itemId}>{l.it.name}</WowheadLink>
                        </td>
                        <td>
                          <input
                            type="number"
                            className="store-copies"
                            min={1}
                            max={MAX_COPIES}
                            value={l.copies}
                            onChange={(e) => setCopies(l.it.id, Number(e.target.value))}
                            aria-label={t('colQty')}
                          />
                          {/* Cada copia entrega un lote (Paño de lino = 20): se dice
                              cuántas unidades salen, que es lo que enseña el total. */}
                          {l.it.qty > 1 && <span className="second-brown"> × {l.it.qty}</span>}
                        </td>
                        <td>
                          <span className={`store-price${payingCard ? ' store-price-off' : ''}`}>
                            <span className={l.it.currency === 'pv' ? 'vp-color' : 'dp-color'}>{l.it.currency === 'pv' ? 'PV' : 'PD'}:</span>{' '}
                            <span>{l.it.price * l.copies}</span>
                          </span>
                          <span className={`store-price store-price-eur${payingCard ? '' : ' store-price-off'}`}>
                            {formatEur(lineEur(l.it.price, l.it.currency) * l.copies, locale)}
                          </span>
                        </td>
                        <td className="icon-td">
                          <button type="button" className="store-remove-button" onClick={() => removeItem(l.it.id)} title={t('remove')}>
                            <i className="fas fa-trash red-info" />
                          </button>
                        </td>
                      </tr>
                    ))
                  )}
                  <tr><td colSpan={4}><hr /></td></tr>
                  <tr>
                    <td></td>
                    <td><span>{qtyTotal}</span></td>
                    <td>
                      <span className={`store-price${payingCard ? ' store-price-off' : ''}`}>
                        <span className="dp-color">PD: </span><span>{pdTotal}</span><br />
                        <span className="vp-color">PV: </span><span>{vpTotal}</span>
                      </span>
                      <span className={`store-price store-price-eur store-price-total${payingCard ? '' : ' store-price-off'}`}>
                        {formatEur(eurTotal, locale)}
                      </span>
                    </td>
                    <td>
                      <button type="button" className="store-send-button" onClick={send} disabled={sending || cart.size === 0}>
                        {sending ? t('sending') : t('send')}
                      </button>
                    </td>
                  </tr>
                </tbody>
              </table>
            </div>
          </div>
        </div>
      )}

      {modal && (
        <div className="modal-div" onClick={() => setModal(null)}>
          <div className="modal-content" onClick={(e) => e.stopPropagation()}>
            <span className="modal-close" role="button" tabIndex={0} onClick={() => setModal(null)}>&times;</span>
            <p className={modal.ok ? 'ok-form-response' : 'red-form-response'}>{modal.text}</p>
          </div>
        </div>
      )}
    </>
  )
}
