import React, { useCallback, useState } from 'react'
import { View } from '../view'
import { Text } from '../text'
import { Input, Textarea, Select } from '../inputs'
import { FileField } from '../inputs/FileField.jsx'
import { Button } from '../button'
import { Tags } from '../tags'
import { useSlot } from '../component-slots'
import { useLocale } from '../locale/index.js'
import { useOptionalFormContext } from '../form/FormContext.jsx'
import { fieldTranslationKey, hasTranslation } from './fieldTranslationKeys.js'
import { useInputSlot } from '../component-slots'
import { designSystemInputId } from '@ossy/schema'
import { resolveFieldDef } from './normalizeFieldType.js'
import { DatePicker } from './DatePicker.jsx'
import {
  datetimeLocalValueToMs,
  isDateRangeValue,
  msToDatetimeLocalValue,
} from './time.js'

const fieldInputStyle = { minWidth: '50%' }
const textareaStyle = {
  minWidth: '100%',
  minHeight: '125px',
  lineHeight: '1.6',
  maxWidth: '600px',
}
const richtextStyle = {
  ...textareaStyle,
  minHeight: '180px',
}

const TextField = ({ label, name, value, onChange, style = {}, ...rest }) => (
  <Input
    {...rest}
    id={name}
    name={name}
    type="text"
    value={value ?? ''}
    onChange={onChange}
    style={{ ...fieldInputStyle, ...style }}
  />
)

const EmailField = ({ label, name, value, onChange, style = {}, ...rest }) => (
  <Input
    {...rest}
    id={name}
    name={name}
    type="email"
    value={value ?? ''}
    onChange={onChange}
    style={{ ...fieldInputStyle, ...style }}
  />
)

const NumberField = ({ label, name, value, onChange, style = {}, ...rest }) => (
  <Input
    {...rest}
    id={name}
    name={name}
    type="number"
    value={value === undefined || value === null ? '' : value}
    onChange={onChange}
    style={{ ...fieldInputStyle, ...style }}
  />
)

const DateField = ({ label, name, value, onChange, style = {}, ...rest }) => (
  <DatePicker
    {...rest}
    id={name}
    name={name}
    value={value}
    onChange={onChange}
    style={{ ...fieldInputStyle, ...style }}
  />
)

const DateRangeField = ({ name, label, value, onChange, style = {}, ...rest }) => {
  const { t } = useLocale()
  const range = isDateRangeValue(value) ? value : { start: undefined, end: undefined }

  const commit = useCallback(
    next => {
      onChange({
        target: {
          id: name,
          name,
          dataset: { ossyDateRangeReplace: 'true' },
          value: JSON.stringify(next),
        },
      })
    },
    [name, onChange],
  )

  const onStartChange = useCallback(
    event => {
      const start = event.target.value
      commit({ start, end: range.end })
    },
    [commit, range.end],
  )

  const onEndChange = useCallback(
    event => {
      const end = event.target.value
      commit({ start: range.start, end })
    },
    [commit, range.start],
  )

  return (
    <View gap="m" style={style} {...rest}>
      <View gap="xs">
        <Text variant="small" style={{ fontWeight: 600 }}>{t('design-system.fields.dateRangeStart')}</Text>
        <DatePicker name={`${name}.start`} value={range.start} onChange={onStartChange} />
      </View>
      <View gap="xs">
        <Text variant="small" style={{ fontWeight: 600 }}>{t('design-system.fields.dateRangeEnd')}</Text>
        <DatePicker name={`${name}.end`} value={range.end} onChange={onEndChange} />
      </View>
    </View>
  )
}

const DateRangesField = ({ name, label, value, onChange, style = {}, ...rest }) => {
  const { t } = useLocale()
  const ranges = Array.isArray(value) ? value : []

  const commit = useCallback(
    next => {
      onChange({
        target: {
          id: name,
          name,
          dataset: { ossyDateRangesReplace: 'true' },
          value: JSON.stringify(next),
        },
      })
    },
    [name, onChange],
  )

  const updateRange = useCallback(
    (index, patch) => {
      const next = ranges.map((range, i) => (i === index ? { ...range, ...patch } : range))
      commit(next)
    },
    [commit, ranges],
  )

  const addRange = useCallback(() => {
    commit([...ranges, { start: undefined, end: undefined }])
  }, [commit, ranges])

  const removeRange = useCallback(
    index => {
      commit(ranges.filter((_, i) => i !== index))
    },
    [commit, ranges],
  )

  return (
    <View gap="m" style={style} {...rest}>
      {ranges.map((range, index) => (
        <View
          key={`${name}-range-${index}`}
          gap="s"
          surface="secondary"
          roundness="m"
          inset="s"
        >
          <View layout="row" justifyContent="space-between" alignItems="center">
            <Text variant="small" style={{ fontWeight: 600 }}>
              {t('design-system.fields.dateRangeItem', { index: index + 1 })}
            </Text>
            <Button
              type="button"
              variant="command-danger"
              prefix="close"
              size="s"
              aria-label={t('design-system.fields.dateRangeRemove')}
              onClick={() => removeRange(index)}
            />
          </View>
          <View gap="xs">
            <Text variant="small">{t('design-system.fields.dateRangeStart')}</Text>
            <DatePicker
              name={`${name}.${index}.start`}
              value={range?.start}
              onChange={event => updateRange(index, { start: event.target.value })}
            />
          </View>
          <View gap="xs">
            <Text variant="small">{t('design-system.fields.dateRangeEnd')}</Text>
            <DatePicker
              name={`${name}.${index}.end`}
              value={range?.end}
              onChange={event => updateRange(index, { end: event.target.value })}
            />
          </View>
        </View>
      ))}
      <Button type="button" variant="neutral" prefix="math-plus" onClick={addRange}>
        {t('design-system.fields.dateRangeAdd')}
      </Button>
    </View>
  )
}

const TimestampField = ({ label, name, value, onChange, style = {}, ...rest }) => {
  const commitMs = useCallback(
    (inputValue) => {
      const ms = datetimeLocalValueToMs(inputValue)
      onChange({
        target: {
          id: name,
          name,
          type: 'number',
          value: ms,
        },
      })
    },
    [name, onChange],
  )

  return (
    <Input
      {...rest}
      id={name}
      name={name}
      type="datetime-local"
      value={msToDatetimeLocalValue(value)}
      onChange={(e) => commitMs(e.target.value)}
      style={{ ...fieldInputStyle, ...style }}
    />
  )
}

const TextareaField = ({ label, name, value, onChange, style = {}, ...rest }) => (
  <Textarea
    {...rest}
    id={name}
    name={name}
    value={value ?? ''}
    onChange={onChange}
    rows={2}
    cols={50}
    style={{ ...textareaStyle, ...style }}
  />
)

/** Plain multi-line input until a dedicated rich-text editor is wired. */
const RichtextField = ({ label, name, value, onChange, style = {}, ...rest }) => (
  <Textarea
    {...rest}
    id={name}
    name={name}
    value={value ?? ''}
    onChange={onChange}
    rows={6}
    cols={50}
    style={{ ...richtextStyle, ...style }}
  />
)

const SelectField = ({ label, name, options = [], value, onChange, style = {}, ...rest }) => (
  <Select
    {...rest}
    id={name}
    name={name}
    value={value ?? ''}
    onChange={onChange}
    style={{ ...fieldInputStyle, ...style }}
  >
    <option value="" />
    {options.map(option => (
      <option key={option} value={option}>
        {option}
      </option>
    ))}
  </Select>
)

/**
 * Multiselect as a tag cloud (same interaction model as {@link Tags}): click to
 * toggle. Suggested options plus any selected/custom tags appear in the cloud;
 * the row below adds arbitrary tags not in `options`.
 */
const MultiselectTagsField = ({ name, label, options = [], value, onChange, style = {}, ...rest }) => {
  const { t } = useLocale()
  const selected = Array.isArray(value) ? value : []
  const [draft, setDraft] = useState('')

  const commit = useCallback(
    next => {
      onChange({
        target: {
          id: name,
          name,
          dataset: { ossyMultiselectReplace: 'true' },
          value: JSON.stringify(next),
        },
      })
    },
    [name, onChange],
  )

  const toggleTag = useCallback(
    tag => {
      commit(
        selected.includes(tag) ? selected.filter(t => t !== tag) : [...selected, tag],
      )
    },
    [selected, commit],
  )

  const addDraft = useCallback(() => {
    const t = draft.trim()
    if (!t || selected.includes(t)) return
    commit([...selected, t])
    setDraft('')
  }, [draft, selected, commit])

  const cloudTags = [...new Set([...(options || []), ...selected])].sort((a, b) =>
    a.localeCompare(b),
  )

  return (
    <View gap="m" style={style} {...rest}>
      {cloudTags.length > 0 ? (
        <Tags tags={cloudTags} activeTags={selected} onSelect={toggleTag} size="s" gap="xs" />
      ) : (
        <Text as="span" style={{ fontSize: 14, opacity: 0.7 }}>
          {t('design-system.fields.noTagSuggestions')}
        </Text>
      )}
      <View layout="row" gap="s" style={{ alignItems: 'center', flexWrap: 'wrap' }}>
        <Input
          type="text"
          value={draft}
          onChange={e => setDraft(e.target.value)}
          onKeyDown={e => {
            if (e.key === 'Enter') {
              e.preventDefault()
              addDraft()
            }
          }}
          placeholder={t('design-system.fields.addTagPlaceholder')}
          style={{ minWidth: 140, flex: '1 1 140px', maxWidth: '100%' }}
          aria-label={`Add tag for ${label}`}
        />
        <Button type="button" variant="neutral" onClick={addDraft}>
          {t('design-system.fields.addTag')}
        </Button>
      </View>
    </View>
  )
}

const checkboxFieldStyles = `
  [data-ossy-field-checkbox-track] {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.375rem;
    height: 1.375rem;
    flex-shrink: 0;
  }

  [data-ossy-field-checkbox-track] input {
    position: absolute;
    inset: 0;
    margin: 0;
    opacity: 0;
    cursor: pointer;
    z-index: 1;
  }

  [data-ossy-field-checkbox-track] input:disabled {
    cursor: not-allowed;
  }

  [data-ossy-field-checkbox-box] {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.375rem;
    height: 1.375rem;
    box-sizing: border-box;
    border-width: 1px;
    border-style: solid;
    border-color: var(--separator-primary);
    border-radius: var(--space-xs, 6px);
    background-color: var(--surface-primary);
    transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease;
    pointer-events: none;
  }

  [data-ossy-field-checkbox-track] input:focus-visible + [data-ossy-field-checkbox-box] {
    outline: none;
    border-color: var(--color-accent);
    box-shadow: 0 0 0 1px var(--color-accent);
  }

  [data-ossy-field-checkbox-track] input:checked + [data-ossy-field-checkbox-box] {
    background-color: var(--color-accent);
    border-color: var(--color-accent);
  }

  [data-ossy-field-checkbox-mark] {
    width: 0.3rem;
    height: 0.55rem;
    border: solid var(--surface-primary);
    border-width: 0 2px 2px 0;
    transform: rotate(45deg) translate(-0.04rem, -0.04rem);
    opacity: 0;
    transition: opacity 0.12s ease;
    box-sizing: border-box;
  }

  [data-ossy-field-checkbox-track] input:checked + [data-ossy-field-checkbox-box] [data-ossy-field-checkbox-mark] {
    opacity: 1;
  }

  [data-ossy-field-checkbox-track] input:disabled + [data-ossy-field-checkbox-box] {
    opacity: 0.55;
  }
`

const BooleanField = ({ name, label, value, onChange, style = {}, disabled, ...rest }) => {
  const { t } = useLocale()
  return (
  <>
    <style href="@ossy/design-system/field-checkbox" precedence="medium">
      {checkboxFieldStyles}
    </style>
    <label
      data-ossy-field-checkbox
      htmlFor={name}
      style={{
        display: 'inline-flex',
        alignItems: 'center',
        gap: 'var(--space-s, 8px)',
        cursor: disabled ? 'not-allowed' : 'pointer',
        minHeight: 'var(--space-l, 24px)',
        ...style,
      }}
      {...rest}
    >
      <span data-ossy-field-checkbox-track>
        <input
          id={name}
          name={name}
          type="checkbox"
          checked={!!value}
          onChange={onChange}
          disabled={disabled}
          aria-label={label || name}
        />
        <span data-ossy-field-checkbox-box aria-hidden="true">
          <span data-ossy-field-checkbox-mark />
        </span>
      </span>
      <Text
        as="span"
        style={{
          fontSize: 'var(--text-default-font-size, 16px)',
          fontFamily: 'var(--text-default-font-family, sans-serif)',
          lineHeight: 1.2,
          color: value
            ? 'var(--text-default-color, CanvasText)'
            : 'color-mix(in srgb, var(--text-default-color, CanvasText) 52%, transparent)',
          userSelect: 'none',
        }}
      >
        {value ? t('design-system.fields.booleanOn') : t('design-system.fields.booleanOff')}
      </Text>
    </label>
  </>
  )
}

const FIELD_COMPONENTS = {
  text: TextField,
  email: EmailField,
  textarea: TextareaField,
  richtext: RichtextField,
  number: NumberField,
  select: SelectField,
  multiselect: MultiselectTagsField,
  file: FileField,
  image: FileField,
  boolean: BooleanField,
  date: DateField,
  timestamp: TimestampField,
  'date-range': DateRangeField,
  'date-ranges': DateRangesField,
}

/**
 * Default {@link ComponentSlotsProvider} `slots` map for {@link Fields}.
 * Keys are canonical `@ossy/design-system/input/{type}`.
 *
 * @type {Record<string, import('react').ComponentType<any>>}
 */
export const DEFAULT_FORM_FIELD_SLOTS = Object.fromEntries(
  Object.entries(FIELD_COMPONENTS).map(([type, Comp]) => [
    designSystemInputId(type),
    Comp,
  ]),
)

function FormFieldRow({ field, data, onChange, schemaId }) {
  const { t, messages } = useLocale()
  const resolved = resolveFieldDef(field)
  const { name, type, options, required, label: labelOverride, placeholder: placeholderOverride, description: descriptionOverride, accept, min, max, ...fieldRest } = resolved
  const typeKey = typeof type === 'string' ? type.trim() : type || 'text'
  const declaredType = typeof field.type === 'string' ? field.type.trim() : field.type || 'text'
  const Slotted = useInputSlot(schemaId, declaredType === 'image' ? 'image' : typeKey)
  const Fallback = FIELD_COMPONENTS[declaredType] || FIELD_COMPONENTS[typeKey] || TextField
  const Field = Slotted === undefined ? Fallback : Slotted

  const labelKey = labelOverride ?? (schemaId ? fieldTranslationKey(schemaId, name, 'label') : name)
  const placeholderKey = placeholderOverride ?? (schemaId ? fieldTranslationKey(schemaId, name, 'placeholder') : undefined)
  const descriptionKey = descriptionOverride ?? (schemaId ? fieldTranslationKey(schemaId, name, 'description') : undefined)

  const displayLabel = labelOverride
    ?? (schemaId && hasTranslation(messages, labelKey) ? t(labelKey) : name)
  const placeholder = placeholderKey && hasTranslation(messages, placeholderKey)
    ? placeholderKey
    : undefined
  const showDescription = descriptionKey && hasTranslation(messages, descriptionKey)

  const fieldProps = {
    ...fieldRest,
    name,
    label: displayLabel,
    options,
    required,
    accept,
    min,
    max,
    placeholder,
    value: data[name],
    onChange,
  }

  return (
    <View gap="xs">
      <Text as="label" htmlFor={name} variant="small" style={{ fontWeight: 'bold' }}>
        {displayLabel}
        {required ? ' *' : ''}
      </Text>
      {showDescription ? (
        <Text variant="small" text={descriptionKey} style={{ opacity: 0.85, lineHeight: 1.35 }} />
      ) : null}
      {Field === null ? null : <Field {...fieldProps} />}
    </View>
  )
}

/**
 * Renders labeled inputs for each entry in a template `fields` array.
 * Use {@link applyFieldChange} to derive the next `data` object from native
 * `onChange` events (and from multiselect tag commits).
 *
 * Resolves controls via {@link useInputSlot} (ADR 0006 input id chain) first, then built-ins
 * in {@link DEFAULT_FORM_FIELD_SLOTS}. Pass {@link DEFAULT_FORM_FIELD_SLOTS} into
 * {@link ComponentSlotsProvider} when you want slots to resolve without per-type fallbacks.
 *
 * @param {{
 *   data: Record<string, unknown>,
 *   onChange: import('react').ChangeEventHandler,
 *   fields?: { name: string, type?: string, options?: string[] }[],
 *   templateFields?: { name: string, type?: string, options?: string[] }[],
 *   schemaId?: string,
 * }} props
 */
export function Fields({ data, onChange, fields: fieldsProp, templateFields, schemaId: schemaIdProp, ...props }) {
  const formCtx = useOptionalFormContext()
  const schemaId = schemaIdProp ?? formCtx?.schemaId
  const list = fieldsProp ?? templateFields ?? []

  return (
    <View gap="s" {...props}>
      {list.map(field => (
        <FormFieldRow key={field.name} field={field} data={data} onChange={onChange} schemaId={schemaId} />
      ))}
    </View>
  )
}
