// shadcn-styled widgets that plug into the @voltro/ui widget seam. Plain,
// accessible HTML with Tailwind utility classes against the design tokens in
// `./tokens.css`. Drop them in app-wide:
//
//   import { shadcnWidgets } from '@voltro/ui-shadcn'
//   import '@voltro/ui-shadcn/tokens.css'
//   <WidgetRegistryProvider widgets={shadcnWidgets}>…</WidgetRegistryProvider>
//
// They implement the SAME `Widget` contract as @voltro/ui's plain defaults —
// the seam falls back to the plain default for any kind not overridden here.

import { useId, type ReactNode } from 'react'
import type { Widget, WidgetProps, WidgetRegistry } from '@voltro/ui'

const cx = {
  field: 'voltro-field flex flex-col gap-1',
  label: 'text-sm font-medium text-foreground',
  control:
    'block w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm ' +
    'focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
  checkbox: 'h-4 w-4 rounded border-input text-primary focus:ring-2 focus:ring-ring',
  error: 'text-sm text-destructive',
}

const asString = (v: unknown): string => (v === undefined || v === null ? '' : String(v))
const describedBy = (id: string, error: string | undefined): string | undefined =>
  error !== undefined ? `${id}-error` : undefined

const Shell = (props: {
  readonly id: string
  readonly label: string
  readonly required: boolean
  readonly error?: string | undefined
  readonly children: ReactNode
}): ReactNode => (
  <div className={cx.field}>
    <label className={cx.label} htmlFor={props.id}>
      {props.label}
      {props.required ? ' *' : ''}
    </label>
    {props.children}
    {props.error !== undefined ? (
      <p id={`${props.id}-error`} className={cx.error} role="alert">
        {props.error}
      </p>
    ) : null}
  </div>
)

const inputWidget = (type: string, toValue: (raw: string) => unknown): Widget => {
  const Component: Widget = (props) => {
    const id = useId()
    return (
      <Shell id={id} label={props.label} required={props.required} error={props.error}>
        <input
          id={id}
          type={type}
          name={props.name}
          className={cx.control}
          value={asString(props.value)}
          onChange={(e) => props.onChange(toValue(e.target.value))}
          disabled={props.disabled}
          required={props.required}
          aria-invalid={props.error !== undefined}
          aria-describedby={describedBy(id, props.error)}
        />
      </Shell>
    )
  }
  return Component
}

const TextareaWidget: Widget = (props) => {
  const id = useId()
  return (
    <Shell id={id} label={props.label} required={props.required} error={props.error}>
      <textarea
        id={id}
        name={props.name}
        className={cx.control}
        value={asString(props.value)}
        onChange={(e) => props.onChange(e.target.value)}
        disabled={props.disabled}
        required={props.required}
        aria-invalid={props.error !== undefined}
        aria-describedby={describedBy(id, props.error)}
      />
    </Shell>
  )
}

const CheckboxWidget: Widget = (props) => {
  const id = useId()
  return (
    <div className="voltro-field flex items-center gap-2">
      <input
        id={id}
        type="checkbox"
        name={props.name}
        className={cx.checkbox}
        checked={props.value === true}
        onChange={(e) => props.onChange(e.target.checked)}
        disabled={props.disabled}
        aria-invalid={props.error !== undefined}
        aria-describedby={describedBy(id, props.error)}
      />
      <label className={cx.label} htmlFor={id}>
        {props.label}
        {props.required ? ' *' : ''}
      </label>
      {props.error !== undefined ? (
        <p id={`${id}-error`} className={cx.error} role="alert">
          {props.error}
        </p>
      ) : null}
    </div>
  )
}

const SwitchWidget: Widget = (props) => {
  const id = useId()
  const on = props.value === true
  return (
    <div className="voltro-field flex items-center gap-2">
      <button
        id={id}
        type="button"
        role="switch"
        aria-checked={on}
        onClick={() => props.onChange(!on)}
        disabled={props.disabled}
        aria-invalid={props.error !== undefined}
        aria-describedby={describedBy(id, props.error)}
        className={
          'relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-input ' +
          'transition-colors focus:outline-none focus:ring-2 focus:ring-ring ' +
          'disabled:cursor-not-allowed disabled:opacity-50 ' +
          (on ? 'bg-primary' : 'bg-input')
        }
      >
        <span
          aria-hidden="true"
          className={
            'block h-4 w-4 rounded-full bg-background shadow-sm transition-transform ' +
            (on ? 'translate-x-4' : 'translate-x-0.5')
          }
        />
      </button>
      <label className={cx.label} htmlFor={id}>
        {props.label}
        {props.required ? ' *' : ''}
      </label>
      {props.error !== undefined ? (
        <p id={`${id}-error`} className={cx.error} role="alert">
          {props.error}
        </p>
      ) : null}
    </div>
  )
}

const SelectWidget: Widget = (props) => {
  const id = useId()
  return (
    <Shell id={id} label={props.label} required={props.required} error={props.error}>
      <select
        id={id}
        name={props.name}
        className={cx.control}
        value={asString(props.value)}
        onChange={(e) => props.onChange(e.target.value)}
        disabled={props.disabled}
        required={props.required}
        aria-invalid={props.error !== undefined}
        aria-describedby={describedBy(id, props.error)}
      >
        <option value="">—</option>
        {(props.options ?? []).map((o) => (
          <option key={o.value} value={o.value}>
            {o.label}
          </option>
        ))}
      </select>
    </Shell>
  )
}

const RadioWidget: Widget = (props) => {
  const id = useId()
  return (
    <fieldset className={cx.field} aria-invalid={props.error !== undefined}>
      <legend className={cx.label}>
        {props.label}
        {props.required ? ' *' : ''}
      </legend>
      <div className="flex flex-col gap-1">
        {(props.options ?? []).map((o) => {
          const oid = `${id}-${o.value}`
          return (
            <span key={o.value} className="flex items-center gap-2">
              <input
                id={oid}
                type="radio"
                name={props.name}
                value={o.value}
                className={cx.checkbox}
                checked={asString(props.value) === o.value}
                onChange={() => props.onChange(o.value)}
                disabled={props.disabled}
              />
              <label className="text-sm text-foreground" htmlFor={oid}>
                {o.label}
              </label>
            </span>
          )
        })}
      </div>
      {props.error !== undefined ? (
        <p id={`${id}-error`} className={cx.error} role="alert">
          {props.error}
        </p>
      ) : null}
    </fieldset>
  )
}

const MultiSelectWidget: Widget = (props) => {
  const id = useId()
  const selected = Array.isArray(props.value) ? props.value.map(String) : []
  return (
    <Shell id={id} label={props.label} required={props.required} error={props.error}>
      <select
        id={id}
        name={props.name}
        multiple
        className={cx.control + ' min-h-24'}
        value={selected}
        onChange={(e) => props.onChange(Array.from(e.target.selectedOptions, (o) => o.value))}
        disabled={props.disabled}
        aria-invalid={props.error !== undefined}
        aria-describedby={describedBy(id, props.error)}
      >
        {(props.options ?? []).map((o) => (
          <option key={o.value} value={o.value}>
            {o.label}
          </option>
        ))}
      </select>
    </Shell>
  )
}

const FileWidget: Widget = (props) => {
  const id = useId()
  return (
    <Shell id={id} label={props.label} required={props.required} error={props.error}>
      <input
        id={id}
        type="file"
        name={props.name}
        className={
          cx.control +
          ' file:mr-3 file:rounded file:border-0 file:bg-primary file:px-2 file:py-1 ' +
          'file:text-sm file:text-primary-foreground'
        }
        onChange={(e) => props.onChange(e.target.value)}
        disabled={props.disabled}
        required={props.required}
        aria-invalid={props.error !== undefined}
        aria-describedby={describedBy(id, props.error)}
      />
    </Shell>
  )
}

/** Tailwind-styled widgets keyed by widget kind. Pass to
 *  `<WidgetRegistryProvider widgets={shadcnWidgets}>`. Any kind not listed
 *  here falls back to @voltro/ui's plain accessible default — today that is
 *  only `hidden` (invisible anyway) and `custom` (the render-prop signal).
 *  Value contracts mirror the plain defaults exactly: `daterange` and
 *  `async-select` are styled text inputs until the framework grows richer
 *  bindings for those kinds (same ceiling as @voltro/ui's defaults). */
export const shadcnWidgets: WidgetRegistry = {
  text: inputWidget('text', (s) => s),
  number: inputWidget('number', (s) => (s === '' ? undefined : Number(s))),
  date: inputWidget('date', (s) => s),
  datetime: inputWidget('datetime-local', (s) => s),
  daterange: inputWidget('text', (s) => s),
  'async-select': inputWidget('text', (s) => s),
  textarea: TextareaWidget,
  checkbox: CheckboxWidget,
  switch: SwitchWidget,
  select: SelectWidget,
  radio: RadioWidget,
  'multi-select': MultiSelectWidget,
  file: FileWidget,
}
