import { useForm, usePage } from '@inertiajs/react'
import { FlaskConical } from 'lucide-react'
import type { FormEvent } from 'react'

import { FieldError, fieldErrorProps } from '@/components/FieldError'
import { SavedNotice } from '@/components/SavedNotice'
import { SectionCard } from '@/components/SectionCard'
import { TaskColumn } from '@/components/TaskColumn'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select'
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
import { AppLayout } from '@/layouts/AppLayout'
import { faultText } from '@/lib/errors'
import { t } from '@/lib/i18n'
import type { TranslationKey } from '@/lib/i18n'
import { route } from '@/lib/routes'

/** Un modelo de imagen con las calidades que admite. Todo son códigos. */
interface ImageModelOption {
  value: string
  qualities: string[]
}

/** Un badge de marca de agua. La rotación no tiene imagen propia: `image` es nulo. */
interface WatermarkOption {
  value: string
  image: string | null
}

/** Lo que se guarda: los cinco códigos elegidos y los dos topes. Es también la forma del envío. */
interface Settings {
  image_model: string
  image_quality: string
  vision_model: string
  markup_mode: string
  watermark: string
  /** Cuántas imágenes puede generar un visitante de la landing por hora y por día. */
  website_hourly_limit: number | string
  website_daily_limit: number | string
}

interface Props {
  settings: Settings
  options: {
    image_models: ImageModelOption[]
    image_qualities: string[]
    vision_models: string[]
    /** Cómo viajan las marcas al modelo. Espejo de `markup.MarkupMode`. */
    markup_modes: string[]
    /** Qué badge se quema en el resultado. Espejo de `watermark.Watermark`. */
    watermarks: WatermarkOption[]
  }
  /** Que el envío anterior guardó, para anunciarlo. Falso cuando se llegó sin guardar. */
  saved: boolean
  /** Que corre el proveedor de prueba (`apps/ai/fake.py`): sólo en desarrollo. */
  fake_provider: boolean
}

/** El rótulo comercial de un modelo. El código es el que viaja; esto es lo que se lee. */
function modelLabel(value: string): string {
  return t(`ai.models.${value}` as TranslationKey)
}

function qualityLabel(value: string): string {
  return t(`ai.quality.${value}` as TranslationKey)
}

function markupModeLabel(value: string): string {
  return t(`ai.config.markupMode.${value}` as TranslationKey)
}

function markupModeHelp(value: string): string {
  return t(`ai.config.markupMode.${value}.help` as TranslationKey)
}

function watermarkLabel(value: string): string {
  return t(`ai.config.watermark.${value}` as TranslationKey)
}

function watermarkHelp(value: string): string {
  return t(`ai.config.watermark.${value}.help` as TranslationKey)
}

/**
 * La miniatura de una opción de marca de agua. La rotación muestra los badges
 * entre los que rota, encimados, en lugar de una imagen propia que no existe.
 * Es decorativa: el rótulo de al lado ya dice qué es, así que va sin `alt`.
 */
function WatermarkThumb({ option, badges }: { option: WatermarkOption; badges: string[] }) {
  const images = option.image ? [option.image] : badges
  return (
    <span data-icon="inline-start" className="flex -space-x-2">
      {images.map((image) => (
        <img key={image} src={image} alt="" className="size-5 rounded-full" />
      ))}
    </span>
  )
}

/**
 * La configuración del módulo de IA: qué modelos usa la compañía, a qué calidad,
 * cómo viajan las marcas dibujadas sobre la foto y qué marca de agua lleva el resultado.
 *
 * Tres selects de lista cerrada, dos selectores y los dos topes por visitante de la
 * landing, sobre un solo registro. Ninguno arranca vacío:
 * el servidor manda siempre un valor elegido, así que la pantalla no tiene un
 * estado «sin configurar» que dibujar. Lo que se ofrece es lo que el código
 * declara apto, y agregar un modelo es tocar esa lista, no esta pantalla.
 *
 * **La calidad depende del modelo.** No todos admiten las mismas, así que el
 * select de calidad se recorta según el modelo elegido y, si el que estaba
 * elegido deja de existir para el modelo nuevo, se pasa al más alto que ese
 * modelo admite. Es la misma regla que en los contenedores del generador: una
 * respuesta que ya no aplica se descarta en el acto, no al enviar.
 */
export default function Config({ settings, options, saved, fake_provider }: Props) {
  const { errors } = usePage().props
  const form = useForm({ ...settings })

  const admittedQualities = (model: string): string[] => {
    const option = options.image_models.find((candidate) => candidate.value === model)
    return option ? option.qualities : options.image_qualities
  }

  const badges = options.watermarks.flatMap((option) => (option.image ? [option.image] : []))

  const qualities = options.image_qualities.filter((quality) =>
    admittedQualities(form.data.image_model).includes(quality)
  )

  const chooseModel = (model: string) => {
    const admitted = admittedQualities(model)
    form.setData({
      ...form.data,
      image_model: model,
      image_quality: admitted.includes(form.data.image_quality)
        ? form.data.image_quality
        : admitted[admitted.length - 1] ?? form.data.image_quality,
    })
  }

  const submit = (event: FormEvent) => {
    event.preventDefault()
    form.post(route('ai.config'), { preserveScroll: true })
  }

  return (
    <AppLayout title={t('ai.config.title')} description={t('ai.config.description')}>
      <TaskColumn className="max-w-3xl">
        {fake_provider ? (
          <Alert variant="attention">
            <FlaskConical />
            <AlertTitle>{t('ai.config.fakeProvider.title')}</AlertTitle>
            <AlertDescription>{t('ai.config.fakeProvider.description')}</AlertDescription>
          </Alert>
        ) : null}
        {/*
          El formulario envuelve a la tarjeta y no al revés: los campos viven en
          el contenido y el botón en el pie, que son dos regiones distintas, y
          un `<form>` adentro de una de las dos dejaría al botón fuera de su
          alcance.
        */}
        <form onSubmit={submit} noValidate className="flex flex-col gap-6">
          <SectionCard
            title={{ text: t('ai.config.models.title') }}
            description={t('ai.config.models.description')}
          >
            <div className="grid gap-2">
              <Label htmlFor="image_model">{t('ai.config.imageModel.label')}</Label>
              <Select value={form.data.image_model} onValueChange={chooseModel}>
                <SelectTrigger
                  id="image_model"
                  className="w-full"
                  {...fieldErrorProps('error-image-model', faultText(errors.image_model))}
                >
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  {options.image_models.map((option) => (
                    <SelectItem key={option.value} value={option.value}>
                      {modelLabel(option.value)}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
              <p className="text-muted-foreground text-sm">{t('ai.config.imageModel.help')}</p>
              <FieldError id="error-image-model" message={faultText(errors.image_model)} />
            </div>

            <div className="grid gap-2">
              <Label htmlFor="image_quality">{t('ai.config.imageQuality.label')}</Label>
              <Select
                value={form.data.image_quality}
                onValueChange={(value) => form.setData('image_quality', value)}
              >
                <SelectTrigger
                  id="image_quality"
                  className="w-full"
                  {...fieldErrorProps('error-image-quality', faultText(errors.image_quality))}
                >
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  {qualities.map((quality) => (
                    <SelectItem key={quality} value={quality}>
                      {qualityLabel(quality)}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
              <p className="text-muted-foreground text-sm">{t('ai.config.imageQuality.help')}</p>
              <FieldError id="error-image-quality" message={faultText(errors.image_quality)} />
            </div>

            <div className="grid gap-2">
              <Label htmlFor="vision_model">{t('ai.config.visionModel.label')}</Label>
              <Select
                value={form.data.vision_model}
                onValueChange={(value) => form.setData('vision_model', value)}
              >
                <SelectTrigger
                  id="vision_model"
                  className="w-full"
                  {...fieldErrorProps('error-vision-model', faultText(errors.vision_model))}
                >
                  <SelectValue />
                </SelectTrigger>
                <SelectContent>
                  {options.vision_models.map((model) => (
                    <SelectItem key={model} value={model}>
                      {modelLabel(model)}
                    </SelectItem>
                  ))}
                </SelectContent>
              </Select>
              <p className="text-muted-foreground text-sm">{t('ai.config.visionModel.help')}</p>
              <FieldError id="error-vision-model" message={faultText(errors.vision_model)} />
            </div>
          </SectionCard>

          {/*
            Cómo viajan las marcas dibujadas sobre la foto. Es una tarjeta aparte
            porque no es un modelo, pero va en el mismo envío: es el mismo
            registro.
          */}
          <SectionCard
            title={{ text: t('ai.config.markup.title') }}
            description={t('ai.config.markup.description')}
          >
            <div className="grid gap-2">
              <Label id="markup-mode-label">{t('ai.config.markupMode.label')}</Label>
              <ToggleGroup
                type="single"
                variant="outline"
                value={form.data.markup_mode}
                onValueChange={(value) => value && form.setData('markup_mode', value)}
                aria-labelledby="markup-mode-label"
                className="flex-wrap"
                {...fieldErrorProps('error-markup-mode', faultText(errors.markup_mode))}
              >
                {options.markup_modes.map((mode) => (
                  <ToggleGroupItem key={mode} value={mode}>
                    {markupModeLabel(mode)}
                  </ToggleGroupItem>
                ))}
              </ToggleGroup>
              <p className="text-muted-foreground text-sm">{markupModeHelp(form.data.markup_mode)}</p>
              <FieldError id="error-markup-mode" message={faultText(errors.markup_mode)} />
            </div>
          </SectionCard>

          {/*
            Los topes por visitante de la landing. Los cuenta Tools al recibir
            cada pedido por la API, así que se configuran acá y no en la landing.
          */}
          <SectionCard
            title={{ text: t('ai.config.website.title') }}
            description={t('ai.config.website.description')}
          >
            <div className="grid gap-4 sm:grid-cols-2">
              <div className="grid gap-2">
                <Label htmlFor="website_hourly_limit">
                  {t('ai.config.websiteHourlyLimit.label')}
                </Label>
                <Input
                  id="website_hourly_limit"
                  type="number"
                  inputMode="numeric"
                  min={1}
                  step={1}
                  autoComplete="off"
                  className="tabular-nums"
                  value={form.data.website_hourly_limit}
                  onChange={(event) => form.setData('website_hourly_limit', event.target.value)}
                  {...fieldErrorProps(
                    'error-website-hourly-limit',
                    faultText(errors.website_hourly_limit)
                  )}
                />
                <p className="text-muted-foreground text-sm">
                  {t('ai.config.websiteHourlyLimit.help')}
                </p>
                <FieldError
                  id="error-website-hourly-limit"
                  message={faultText(errors.website_hourly_limit)}
                />
              </div>

              <div className="grid gap-2">
                <Label htmlFor="website_daily_limit">{t('ai.config.websiteDailyLimit.label')}</Label>
                <Input
                  id="website_daily_limit"
                  type="number"
                  inputMode="numeric"
                  min={1}
                  step={1}
                  autoComplete="off"
                  className="tabular-nums"
                  value={form.data.website_daily_limit}
                  onChange={(event) => form.setData('website_daily_limit', event.target.value)}
                  {...fieldErrorProps(
                    'error-website-daily-limit',
                    faultText(errors.website_daily_limit)
                  )}
                />
                <p className="text-muted-foreground text-sm">
                  {t('ai.config.websiteDailyLimit.help')}
                </p>
                <FieldError
                  id="error-website-daily-limit"
                  message={faultText(errors.website_daily_limit)}
                />
              </div>
            </div>
          </SectionCard>

          {/*
            La marca de agua que se quema en el resultado. Es la última tarjeta y
            por eso lleva el botón de guardar: queda al final del formulario,
            que es donde termina lo que se guarda.
          */}
          <SectionCard
            title={{ text: t('ai.config.watermark.title') }}
            description={t('ai.config.watermark.description')}
            footer={
              <div className="flex w-full flex-wrap items-center justify-end gap-3">
                {saved ? <SavedNotice message={t('ai.config.saved')} className="mr-auto" /> : null}

                <Button type="submit" disabled={form.processing} aria-busy={form.processing}>
                  {form.processing ? t('common.saving') : t('common.save')}
                </Button>
              </div>
            }
          >
            <div className="grid gap-2">
              <Label id="watermark-label">{t('ai.config.watermark.label')}</Label>
              <ToggleGroup
                type="single"
                variant="outline"
                value={form.data.watermark}
                onValueChange={(value) => value && form.setData('watermark', value)}
                aria-labelledby="watermark-label"
                className="flex-wrap"
                {...fieldErrorProps('error-watermark', faultText(errors.watermark))}
              >
                {options.watermarks.map((option) => (
                  <ToggleGroupItem key={option.value} value={option.value}>
                    <WatermarkThumb option={option} badges={badges} />
                    {watermarkLabel(option.value)}
                  </ToggleGroupItem>
                ))}
              </ToggleGroup>
              <p className="text-muted-foreground text-sm">{watermarkHelp(form.data.watermark)}</p>
              <FieldError id="error-watermark" message={faultText(errors.watermark)} />
            </div>
          </SectionCard>
        </form>
      </TaskColumn>
    </AppLayout>
  )
}
