import { useMemo, useState } from 'react'
import JSZip from 'jszip'
import { de } from '../strings/de'
import type { Gerbil } from '../api/types'
import { listGerbilPhotos, photoSrc, type GerbilPhoto } from '../api/photos'
import { generateSaleAd } from '../api/saleAd'
import { useApi, useMutation } from '../hooks/useApi'
import { formatDate, genderLabel } from '../format/labels'
import FarbschlagImage from './FarbschlagImage'

type SaleStatus = 'free' | 'loose' | 'reserved'

export interface GroupComposerProps {
  /** 1-based group number for the heading. */
  groupNumber: number
  animals: Gerbil[]
  /** Resolve a gerbil's Farbschlag display name (stored ColorVariety or '—'). */
  farbschlagOf: (g: Gerbil) => string
}

/** Group gender label for the heading ("männlich"/"weiblich"/"gemischt"). */
function groupGender(animals: Gerbil[]): string {
  const g = de.pages.abgabe.listing
  const genders = new Set(animals.map((a) => a.gender))
  if (genders.size === 1 && genders.has('male')) return g.genderMale
  if (genders.size === 1 && genders.has('female')) return g.genderFemale
  return g.genderMixed
}

export default function GroupComposer({ groupNumber, animals, farbschlagOf }: GroupComposerProps) {
  const t = de.pages.abgabe
  const [status, setStatus] = useState<SaleStatus>('free')
  const [reservedName, setReservedName] = useState('')
  const [tagline, setTagline] = useState('')
  // Per-animal personality text, seeded from notes on first render.
  const [personality, setPersonality] = useState<Record<string, string>>(() =>
    Object.fromEntries(animals.map((a) => [a.id, a.notes ?? ''])),
  )
  const [selectedPhotos, setSelectedPhotos] = useState<Record<string, boolean>>({})
  const [hints, setHints] = useState('')
  const [notice, setNotice] = useState<string | null>(null)
  const [aiText, setAiText] = useState<string | null>(null)
  const [zipping, setZipping] = useState(false)

  const animalIds = animals.map((a) => a.id).join(',')
  // Load photos per animal; tolerate a missing photo endpoint (-> empty).
  const photoData = useApi(
    () =>
      Promise.all(
        animals.map((a) =>
          listGerbilPhotos(a.id)
            .then((photos) => ({ id: a.id, photos }))
            .catch(() => ({ id: a.id, photos: [] as GerbilPhoto[] })),
        ),
      ),
    [animalIds],
  )
  const photosById = useMemo(() => {
    const m = new Map<string, GerbilPhoto[]>()
    for (const e of photoData.data ?? []) m.set(e.id, e.photos)
    return m
  }, [photoData.data])

  const ai = useMutation(generateSaleAd)

  const statusLine = (() => {
    if (status === 'loose') return `${t.listing.statusLooseReserved} ${reservedName}`.trim()
    if (status === 'reserved') return t.listing.statusReserved
    return t.listing.statusFree
  })()

  const listingText = useMemo(() => {
    const heading = t.listing.headingFor(groupNumber, groupGender(animals))
    const lines: string[] = [heading, `${t.listing.status}: ${statusLine}`, '']
    if (tagline.trim()) lines.push(tagline.trim(), '')
    for (const a of animals) {
      const fs = farbschlagOf(a)
      const born = a.dateOfBirth ? `, ${t.listing.bornOn} ${formatDate(a.dateOfBirth)}` : ''
      lines.push(`${a.name} – ${fs}${born}`)
      const p = (personality[a.id] ?? '').trim()
      if (p) lines.push(p)
      lines.push('')
    }
    return lines.join('\n').trimEnd()
  }, [groupNumber, animals, statusLine, tagline, personality, farbschlagOf, t])

  function togglePhoto(photoId: string) {
    setSelectedPhotos((s) => ({ ...s, [photoId]: !isSelected(photoId) }))
  }
  // Default: a photo is selected unless explicitly toggled off.
  function isSelected(photoId: string): boolean {
    return selectedPhotos[photoId] ?? true
  }

  async function copyText() {
    setNotice(null)
    try {
      await navigator.clipboard.writeText(aiText ?? listingText)
      setNotice(t.export.copied)
    } catch {
      setNotice(t.export.copyFailed)
    }
  }

  async function downloadPhotos() {
    setNotice(null)
    const zip = new JSZip()
    const folder = zip.folder(`gruppe-${groupNumber}`)!
    let count = 0
    for (const a of animals) {
      const photos = (photosById.get(a.id) ?? []).filter((p) => isSelected(p.id))
      let n = 1
      for (const p of photos) {
        try {
          const blob = await fetch(photoSrc(p)).then((r) => (r.ok ? r.blob() : null))
          if (!blob) continue
          const ext = (p.fileName.split('.').pop() ?? 'jpg').toLowerCase()
          folder.file(`${slug(a.name)}-${n}.${ext}`, blob)
          n += 1
          count += 1
        } catch {
          /* skip unreachable photo */
        }
      }
    }
    if (count === 0) {
      setNotice(t.export.noPhotosSelected)
      return
    }
    try {
      setZipping(true)
      const out = await zip.generateAsync({ type: 'blob' })
      triggerDownload(out, `gruppe-${groupNumber}-fotos.zip`)
    } catch {
      setNotice(t.export.zipFailed)
    } finally {
      setZipping(false)
    }
  }

  async function improveWithAi() {
    setNotice(null)
    const result = await ai.run({
      animals: animals.map((a) => ({
        name: a.name,
        farbschlag: farbschlagOf(a),
        dateOfBirth: a.dateOfBirth,
        notes: personality[a.id] ?? a.notes ?? null,
      })),
      statusLine,
      hints,
    })
    if (result.ok) setAiText(result.value.text)
    // On failure (endpoint not configured yet) the notice below shows the German hint.
  }

  return (
    <section className="group-composer">
      <h3>{t.listing.headingFor(groupNumber, groupGender(animals))}</h3>

      <div className="group-composer__controls">
        <label className="field">
          <span>{t.listing.status}</span>
          <select value={status} onChange={(e) => setStatus(e.target.value as SaleStatus)}>
            <option value="free">{t.listing.statusFree}</option>
            <option value="loose">{t.listing.statusLooseReserved}</option>
            <option value="reserved">{t.listing.statusReserved}</option>
          </select>
        </label>
        {status === 'loose' && (
          <label className="field">
            <span>{t.listing.reservedName}</span>
            <input
              className="input"
              value={reservedName}
              onChange={(e) => setReservedName(e.target.value)}
            />
          </label>
        )}
      </div>

      <label className="field">
        <span>{t.listing.tagline}</span>
        <input
          className="input"
          value={tagline}
          placeholder={t.listing.taglinePlaceholder}
          onChange={(e) => setTagline(e.target.value)}
        />
      </label>

      {animals.map((a) => (
        <div key={a.id} className="group-composer__animal">
          <div className="group-composer__animal-head">
            <FarbschlagImage name={farbschlagOf(a)} size={36} />
            <strong>{a.name}</strong>
            <span className="muted">
              {farbschlagOf(a)} · {genderLabel(a.gender)}
              {a.dateOfBirth ? ` · ${t.listing.bornOn} ${formatDate(a.dateOfBirth)}` : ''}
            </span>
          </div>
          <label className="field">
            <span>{t.listing.personality}</span>
            <textarea
              value={personality[a.id] ?? ''}
              placeholder={t.listing.personalityPlaceholder}
              onChange={(e) => setPersonality((s) => ({ ...s, [a.id]: e.target.value }))}
            />
          </label>
          <div className="photo-select">
            {(photosById.get(a.id) ?? []).length === 0 ? (
              <small className="muted">{t.listing.noPhotos}</small>
            ) : (
              (photosById.get(a.id) ?? []).map((p) => (
                <button
                  key={p.id}
                  type="button"
                  className={isSelected(p.id) ? 'photo-chip photo-chip--on' : 'photo-chip'}
                  onClick={() => togglePhoto(p.id)}
                  aria-pressed={isSelected(p.id)}
                >
                  <img src={photoSrc(p)} alt={p.caption ?? a.name} loading="lazy" />
                </button>
              ))
            )}
          </div>
        </div>
      ))}

      <label className="field">
        <span>{t.ai.hintsLabel}</span>
        <input className="input" value={hints} onChange={(e) => setHints(e.target.value)} />
      </label>

      <pre className="listing-preview">{aiText ?? listingText}</pre>

      <div className="group-composer__actions">
        <button type="button" className="btn btn--primary" onClick={copyText}>
          {t.export.copyText}
        </button>
        <button type="button" className="btn" disabled={zipping} onClick={downloadPhotos}>
          {zipping ? t.export.zipping : t.export.downloadPhotos}
        </button>
        <button type="button" className="btn" disabled={ai.pending} onClick={improveWithAi}>
          {ai.pending ? t.ai.generating : t.ai.improve}
        </button>
        <button type="button" className="btn" title={t.export.finishHint} disabled>
          {t.export.finish}
        </button>
      </div>

      {ai.error && <div className="alert alert--warning">{t.ai.notConfigured}</div>}
      {notice && <div className="alert">{notice}</div>}
    </section>
  )
}

function slug(name: string): string {
  return name
    .toLowerCase()
    .replace(/[äöü]/g, (c) => ({ ä: 'ae', ö: 'oe', ü: 'ue' })[c] ?? c)
    .replace(/ß/g, 'ss')
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/(^-|-$)/g, '')
}

function triggerDownload(blob: Blob, filename: string) {
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = filename
  document.body.appendChild(a)
  a.click()
  a.remove()
  URL.revokeObjectURL(url)
}
