import { FieldError, fieldErrorProps } from '@/components/FieldError'
import { SectionCard } from '@/components/SectionCard'
import {
  Field,
  FieldContent,
  FieldDescription,
  FieldLabel,
  FieldLegend,
  FieldSet,
  FieldTitle,
} from '@/components/ui/field'
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/select'
import { Switch } from '@/components/ui/switch'
import { t } from '@/lib/i18n'

import { cleanAnswers, imageOf, optionsFor, requestScope, visibleFields } from './conditions'
import type { Scope } from './conditions'
import {
  fieldTitle,
  optionDescription,
  optionLabel,
  serviceLabel,
  toggleDescription,
} from './labels'
import { OptionCards } from './OptionCards'
import { optionIcon } from './optionIcons'
import { SegmentedChoice } from './SegmentedChoice'
import type { AnswerValue, Answers, CatalogField, Condition, ServiceSchema } from './types'

/**
 * El id del control de un campo, que es adonde salta el foco cuando falta
 * contestar. `unit` es la clave del contenedor: su área, o las del grupo.
 */
export function fieldControlId(unit: string, field: string): string {
  return `field-${unit}-${field}`
}

/** El id del selector de servicio de un contenedor. */
export function serviceControlId(unit: string): string {
  return `field-${unit}-service`
}

/** Los campos que nombra una condición, incluidas las de un `all`. */
function referencedFields(condition: Condition | null | undefined): string[] {
  if (!condition) return []
  if ('all' in condition) return condition.all.flatMap(referencedFields)
  return [condition.field]
}

/**
 * Las preguntas en el orden en que se muestran: los interruptores al final y
 * juntos.
 *
 * Un interruptor suelto entre dos listas parte la lectura, y agrupados al pie
 * se leen como lo que son: los extras del trabajo. La excepción se calcula, no
 * se escribe a mano: un interruptor del que depende otra pregunta se queda
 * donde está, porque mandarlo abajo dejaría la llave debajo de la puerta que
 * abre. Es el caso de las franjas de pasto de Hormigón vaciado, que gobiernan
 * la disposición.
 *
 * Es orden de pantalla y nada más: el prompt se arma con el orden del catálogo,
 * del lado del servidor y en `prompt.ts`.
 */
function orderedFields(fields: CatalogField[]): CatalogField[] {
  const gates = new Set(fields.flatMap((field) => referencedFields(field.show_when)))
  const stays = (field: CatalogField) => field.type !== 'toggle' || gates.has(field.id)
  return [...fields.filter(stays), ...fields.filter((field) => !stays(field))]
}

interface Props {
  /** Una, o las que comparten diseño cuando el contenedor está agrupado. */
  areas: string[]
  /**
   * Todas las áreas marcadas en el pedido, no sólo las de esta caja: hay campos
   * que dependen de qué más se pidió, como el coping de Paver.
   */
  selected: string[]
  /** Los servicios que ofrecen todas sus áreas, en el orden del catálogo. */
  services: ServiceSchema[]
  service: string
  answers: Answers
  /** Si el servicio va sobre el área entera, o lo que existe lo deciden las notas. */
  wholeArea: boolean
  onService: (service: string) => void
  onAnswer: (field: string, value: AnswerValue) => void
  onWholeArea: (wholeArea: boolean) => void
  /** Errores por campo, ya traducidos: `service` para el selector, o el id del campo. */
  errors?: Record<string, string>
}

/**
 * Un contenedor: el área —o las que comparten diseño—, su servicio y el
 * esquema de ese servicio (R23).
 *
 * La caja es el área; adentro se elige **un** servicio de los que esa área
 * ofrece, y con él aparecen sus campos. Cómo se contesta cada uno lo dice el
 * catálogo por `type`, no esta pantalla: tarjetas con foto, lista, radios, un
 * interruptor o botones con ícono. Qué se muestra y qué opciones hay lo decide `conditions.ts` con
 * la misma regla que el servidor, y lo que deja de aplicar al cambiar una
 * respuesta anterior ya no está en `answers` cuando llega acá (R34).
 *
 * Un campo que se contesta solo —con una sola opción vigente— no dibuja
 * control (R40): viaja en las respuestas y se ve en el resumen y en el prompt,
 * pero no pide nada. Uno con `default` y varias opciones se dibuja con la suya
 * marcada.
 */
export function AreaContainer({
  areas,
  selected,
  services,
  service,
  answers,
  wholeArea,
  onService,
  onAnswer,
  onWholeArea,
  errors = {},
}: Props) {
  const key = areas.join('-')
  const wholeAreaId = fieldControlId(key, 'whole-area')
  const schema = services.find((candidate) => candidate.service === service)
  const serviceId = serviceControlId(key)
  const serviceErrorId = `error-${key}-service`
  const scope = requestScope(selected)
  const cleaned = schema ? cleanAnswers(schema, answers, scope) : {}

  return (
    // El título no repite el área: eso ya lo dice la pestaña de arriba, y una
    // caja que nombra lo mismo que la solapa que la abrió no agrega nada. Lo
    // que la tarjeta contiene es la configuración del servicio.
    <SectionCard
      title={{ text: t('ai.containers.title') }}
      description={t('ai.containers.description')}
      id={`container-${key}`}
    >
      {/*
        Con tarjetas y no con una lista desplegable: elegir el servicio es la
        decisión que abre todo lo demás, y un nombre suelto —«Revestimiento de
        hormigón»— no dice de qué trabajo se trata. La foto sí. En el teléfono
        van de a dos; en el escritorio, todas en una fila, que son entre dos y
        cinco según el área.
      */}
      <FieldSet className="gap-2">
        <OptionCards
          items={services.map((candidate) => ({
            value: candidate.service,
            image: candidate.image,
            label: serviceLabel(candidate.service),
          }))}
          value={service}
          onChange={onService}
          id={serviceId}
          // El grupo lo nombra el título de la tarjeta, que ya dice «Servicio»:
          // repetirlo adentro deja la misma palabra dos veces, una encima de la
          // otra.
          labelledBy={`section-card-container-${key}`}
          describedBy={errors.service ? serviceErrorId : undefined}
          invalid={Boolean(errors.service)}
          // Los servicios de un área son entre dos y cinco, así que de `md`
          // para arriba van todos en una fila, con el ancho de una tarjeta.
          className="md:grid-cols-5"
        />
        <FieldError id={serviceErrorId} message={errors.service} />
      </FieldSet>

      {/*
        Una pregunta debajo de la otra, siempre. Partir el contenedor en dos
        dejaba un «Tono» y un «Tipo de borde» lado a lado, cada uno con un ancho
        distinto al de la pregunta de arriba. El ancho lo administra cada
        control: las tarjetas con foto se reparten la fila y un select no se
        estira más allá de lo que se lee.
      */}
      {schema ? (
        <div className="flex flex-col gap-5">
          {orderedFields(visibleFields(schema, cleaned, scope)).map((field) => (
            <SchemaField
              key={field.id}
              unit={key}
              schema={schema}
              field={field}
              context={{ ...scope, ...cleaned }}
              value={cleaned[field.id]}
              onChange={(value) => onAnswer(field.id, value)}
              error={errors[field.id]}
            />
          ))}

          {/*
            Al pie y no entre las preguntas del servicio: no pregunta cómo es el
            trabajo sino sobre qué parte de la foto va. Salió de un pedido que
            quería conservar el patio existente y extenderlo: con la sección
            diciendo «si ves el área, editala», el patio se repavimentaba entero
            por más que la nota dijera lo contrario.
          */}
          <FieldLabel htmlFor={wholeAreaId}>
            <Field orientation="horizontal">
              <FieldContent>
                <FieldTitle>{t('ai.containers.wholeArea.title')}</FieldTitle>
                <FieldDescription>
                  {wholeArea
                    ? t('ai.containers.wholeArea.on')
                    : t('ai.containers.wholeArea.off')}
                </FieldDescription>
              </FieldContent>
              <Switch id={wholeAreaId} checked={wholeArea} onCheckedChange={onWholeArea} />
            </Field>
          </FieldLabel>
        </div>
      ) : null}
    </SectionCard>
  )
}

function SchemaField({
  unit,
  schema,
  field,
  context,
  value,
  error,
  onChange,
}: {
  /** La clave del contenedor: su área, o las del grupo. */
  unit: string
  schema: ServiceSchema
  field: CatalogField
  /** Lo contestado en el contenedor más las áreas del pedido: con qué se filtran las opciones. */
  context: Scope
  value: AnswerValue | undefined
  error?: string
  onChange: (value: AnswerValue) => void
}) {
  const id = fieldControlId(unit, field.id)
  const errorId = `error-${unit}-${field.id}`
  const title = fieldTitle(schema.service, field.id)
  const options = optionsFor(field, context)

  if (field.type === 'toggle') {
    const on = value === true
    // Una tarjeta, no un interruptor suelto: el rótulo solo nombra la pregunta
    // —«Borde»— y deja sin decir qué pasa al prenderlo. Adentro de un
    // `FieldLabel`, un `Field` se dibuja como tarjeta y toda ella es el área
    // sensible, así que la descripción también prende y apaga.
    return (
      <FieldSet className="gap-2">
        <FieldLabel htmlFor={id}>
          <Field orientation="horizontal">
            <FieldContent>
              <FieldTitle>{title}</FieldTitle>
              <FieldDescription>
                {toggleDescription(schema.service, field.id, on)}
              </FieldDescription>
            </FieldContent>
            <Switch
              id={id}
              checked={on}
              onCheckedChange={(checked) => onChange(checked)}
              {...fieldErrorProps(errorId, error)}
            />
          </Field>
        </FieldLabel>
        <FieldError id={errorId} message={error} />
      </FieldSet>
    )
  }

  if (field.type === 'segmented') {
    return (
      <FieldSet className="gap-2">
        <FieldLegend id={`${id}-label`} variant="label" className="mb-0">
          {title}
        </FieldLegend>
        <SegmentedChoice
          items={options.map((option) => ({
            value: option.value,
            label: optionLabel(schema.service, field.id, option.value),
            description: optionDescription(schema.service, field.id, option.value),
            icon: optionIcon(option.icon),
          }))}
          value={typeof value === 'string' ? value : ''}
          onChange={onChange}
          id={id}
          labelledBy={`${id}-label`}
          describedBy={error ? errorId : undefined}
          invalid={Boolean(error)}
        />
        <FieldError id={errorId} message={error} />
      </FieldSet>
    )
  }

  if (field.type === 'radio-card') {
    return (
      <FieldSet className="gap-2">
        <FieldLegend id={`${id}-label`} variant="label" className="mb-0">
          {title}
        </FieldLegend>
        <OptionCards
          items={options.map((option) => ({
            value: option.value,
            // La foto puede depender de otra respuesta: la piedra del overlay
            // se muestra en la forma elegida arriba.
            image: imageOf(field, option, context) ?? '',
            label: optionLabel(schema.service, field.id, option.value),
            description: optionDescription(schema.service, field.id, option.value),
          }))}
          value={typeof value === 'string' ? value : ''}
          onChange={onChange}
          id={id}
          labelledBy={`${id}-label`}
          describedBy={error ? errorId : undefined}
          invalid={Boolean(error)}
        />
        <FieldError id={errorId} message={error} />
      </FieldSet>
    )
  }

  if (field.type === 'radio') {
    return (
      <FieldSet className="gap-2">
        <FieldLegend id={`${id}-label`} variant="label" className="mb-0">
          {title}
        </FieldLegend>
        <RadioGroup
          id={id}
          tabIndex={-1}
          value={typeof value === 'string' ? value : ''}
          onValueChange={onChange}
          aria-labelledby={`${id}-label`}
          aria-describedby={error ? errorId : undefined}
          aria-invalid={error ? true : undefined}
        >
          {options.map((option) => {
            const optionId = `${id}-${option.value}`
            const description = optionDescription(schema.service, field.id, option.value)
            return (
              <Field key={option.value} orientation="horizontal" className="items-start">
                <RadioGroupItem id={optionId} value={option.value} className="mt-0.5" />
                {/*
                  `items-start` porque `Label` viene centrado, pensado para una
                  fila: al volverlo columna, ese centrado se aplica al eje
                  horizontal y cada opción quedaba a una distancia distinta de
                  su círculo, centrada en lo que sobraba.
                */}
                <FieldLabel htmlFor={optionId} className="flex-col items-start gap-0.5">
                  <span>{optionLabel(schema.service, field.id, option.value)}</span>
                  {description ? (
                    <span className="text-muted-foreground text-xs font-normal">{description}</span>
                  ) : null}
                </FieldLabel>
              </Field>
            )
          })}
        </RadioGroup>
        <FieldError id={errorId} message={error} />
      </FieldSet>
    )
  }

  return (
    // Acotado: una lista de cuatro opciones estirada de lado a lado de la
    // pantalla no se lee mejor, sólo se lee más lejos del rótulo.
    <Field className="sm:max-w-xs">
      <FieldLabel htmlFor={id}>{title}</FieldLabel>
      <Select value={typeof value === 'string' ? value : ''} onValueChange={onChange}>
        <SelectTrigger id={id} className="w-full" {...fieldErrorProps(errorId, error)}>
          <SelectValue placeholder={t('ai.containers.choose')} />
        </SelectTrigger>
        <SelectContent>
          {options.map((option) => {
            const description = optionDescription(schema.service, field.id, option.value)
            return (
              <SelectItem key={option.value} value={option.value}>
                {optionLabel(schema.service, field.id, option.value)}
                {description ? (
                  <span className="text-muted-foreground ml-2 text-xs">{description}</span>
                ) : null}
              </SelectItem>
            )
          })}
        </SelectContent>
      </Select>
      <FieldError id={errorId} message={error} />
    </Field>
  )
}
