import * as React from "react"
import { cva } from "class-variance-authority"

import { cn } from "@/lib/utils"

type TextareaState = "placeholder" | "filled" | "disabled"
type TextareaType = "default" | "tags"

type TextareaTag = {
  id?: string
  label: React.ReactNode
  avatar?: React.ReactNode
  avatarSrc?: string
  removeLabel?: string
}

type TextareaProps = Omit<
  React.ComponentProps<"textarea">,
  "children" | "className" | "dir"
> & {
  className?: string
  textareaClassName?: string
  labelClassName?: string
  helperClassName?: string
  surfaceClassName?: string
  bodyText?: string
  errorMessage?: string
  hasError?: boolean
  hasHelperText?: boolean
  hasInformationIcon?: boolean
  hasLabel?: boolean
  hasHelperIcon?: boolean
  isOptional?: boolean
  isRequired?: boolean
  label?: string
  optionalText?: string
  message?: string
  onRemoveTag?: (tag: TextareaTag, index: number) => void
  state?: TextareaState
  tags?: TextareaTag[]
  tagInputText?: string
  type?: TextareaType
  dir?: "ltr" | "rtl" | "auto"
}

const textareaRoot = cva(
  "grid w-[var(--bh-textarea-width)] max-w-full gap-[var(--bh-textarea-gap)]"
)

const textareaSurface = cva(
  [
    "relative flex w-full rounded-[var(--bh-textarea-radius)]",
    "min-h-[var(--bh-textarea-min-height)] p-[var(--bh-textarea-padding)]",
    "transition-[background-color,box-shadow]",
    "[--bh-textarea-border:var(--bh-border-input)] [--shadow-textarea:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-textarea-border,var(--bh-border-input)),var(--shadow-component-default)]",
    "after:pointer-events-none after:absolute after:inset-[var(--bh-textarea-focus-ring-inset)] after:rounded-[var(--bh-textarea-radius)] after:border-[length:var(--bh-textarea-focus-ring-width)] after:border-[var(--bh-border-focus)] after:opacity-0 after:content-['']",
    "focus-within:after:opacity-[var(--bh-opacity-30)]",
  ],
  {
    variants: {
      type: {
        default: "items-start",
        tags: "items-start gap-[var(--bh-textarea-gap)]",
      },
      state: {
        placeholder:
          "bg-[var(--bh-interactive-input-default)] [box-shadow:var(--shadow-textarea)]",
        filled:
          "bg-[var(--bh-interactive-input-default)] [box-shadow:var(--shadow-textarea)]",
        disabled:
          "bg-[var(--bh-interactive-input-disabled)] [box-shadow:inset_0px_0px_0px_var(--bh-border-width-default)_var(--bh-border-disabled)] after:hidden",
      },
      invalid: {
        true:
          "[--bh-textarea-border:var(--bh-border-danger-strong)] [box-shadow:var(--shadow-textarea)] focus-within:after:hidden",
        false: "",
      },
    },
    defaultVariants: {
      type: "default",
      state: "placeholder",
      invalid: false,
    },
  }
)

const textareaControl = cva(
  [
    "min-w-0 flex-1 resize-none border-0 bg-transparent p-0",
    "text-start text-[length:var(--bh-text-body-md-regular-font-size)]",
    "font-[var(--bh-text-body-md-regular-font-weight)]",
    "leading-[var(--bh-text-body-md-regular-line-height)]",
    "tracking-[var(--bh-text-body-md-regular-letter-spacing)]",
    "text-[var(--bh-content-default)] outline-none",
    "placeholder:text-[var(--bh-content-muted)]",
    "disabled:cursor-not-allowed disabled:text-[var(--bh-content-disabled)] disabled:placeholder:text-[var(--bh-content-disabled)]",
  ],
  {
    variants: {
      type: {
        default:
          "min-h-[calc(var(--bh-textarea-min-height)_-_var(--bh-textarea-padding)_-_var(--bh-textarea-padding))]",
        tags:
          "h-[var(--bh-text-body-md-regular-line-height)] min-h-[var(--bh-text-body-md-regular-line-height)] overflow-hidden",
      },
    },
    defaultVariants: {
      type: "default",
    },
  }
)

const textareaTag = cva(
  [
    "inline-flex h-[var(--bh-textarea-tag-height)] shrink-0 items-center",
    "gap-[var(--bh-textarea-inline-gap)] rounded-[var(--bh-control-md)] border border-solid",
    "px-[var(--bh-space-xs-4)] text-center",
    "text-[length:var(--bh-text-body-xs-medium-font-size)]",
    "font-[var(--bh-text-body-xs-medium-font-weight)]",
    "leading-[var(--bh-text-body-xs-medium-line-height)]",
    "tracking-[var(--bh-text-body-xs-medium-letter-spacing)]",
  ],
  {
    variants: {
      disabled: {
        true:
          "border-[var(--bh-border-disabled)] bg-[var(--bh-interactive-secondary-disabled)] text-[var(--bh-content-disabled)]",
        false:
          "border-[var(--bh-border-default)] bg-[var(--bh-interactive-secondary-default)] text-[var(--bh-content-default)]",
      },
    },
    defaultVariants: {
      disabled: false,
    },
  }
)

const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
  (
    {
      className,
      textareaClassName,
      labelClassName,
      helperClassName,
      surfaceClassName,
      bodyText,
      defaultValue,
      dir,
      disabled,
      errorMessage,
      hasError = false,
      hasHelperIcon = true,
      hasHelperText,
      hasInformationIcon = true,
      hasLabel,
      id,
      isOptional = false,
      isRequired = false,
      label,
      message,
      onRemoveTag,
      optionalText,
      placeholder,
      readOnly,
      required,
      rows = 2,
      state = "placeholder",
      tagInputText,
      tags,
      type = "default",
      value,
      ...props
    },
    ref
  ) => {
    const generatedId = React.useId()
    const textareaId = id || generatedId
    const helperId = `${textareaId}-helper`
    const textareaRef = React.useRef<HTMLTextAreaElement | null>(null)
    const resolvedDir = dir
    const visualState = disabled ? "disabled" : state
    const isDisabled = visualState === "disabled"
    const isInvalid = hasError && !isDisabled
    const isTags = type === "tags"
    const selectedPlaceholder = placeholder
    const selectedLabel = label
    const selectedOptionalText = optionalText
    const inferredDefaultValue = getInferredDefaultValue({
      bodyText,
      defaultValue,
      isTags,
      state: visualState,
      tagInputText,
      value,
    })
    const helperText = isInvalid ? errorMessage : message
    const selectedTags = tags ?? []
    const shouldRenderLabel = hasLabel ?? hasRenderableContent(selectedLabel)
    const shouldRenderHelperText =
      hasHelperText ?? hasRenderableContent(helperText)

    function focusTextarea() {
      if (!isDisabled) textareaRef.current?.focus()
    }

    function setTextareaRef(node: HTMLTextAreaElement | null) {
      textareaRef.current = node

      if (typeof ref === "function") {
        ref(node)
      } else if (ref) {
        ref.current = node
      }
    }

    return (
      <div
        data-slot="textarea-root"
        data-state={visualState}
        data-type={type}
        dir={resolvedDir}
        className={cn(textareaRoot(), className)}
      >
        {shouldRenderLabel ? (
          <TextareaLabel
            className={labelClassName}
            hasInformationIcon={hasInformationIcon}
            htmlFor={textareaId}
            isOptional={isOptional}
            isRequired={isRequired}
            label={selectedLabel}
            optionalText={selectedOptionalText}
          />
        ) : null}

        <div
          data-slot="textarea-surface"
          data-state={visualState}
          data-type={type}
          aria-disabled={isDisabled || undefined}
          className={cn(
            textareaSurface({
              type,
              state: visualState,
              invalid: isInvalid,
            }),
            surfaceClassName
          )}
          onClick={focusTextarea}
        >
          {isTags && shouldRenderTags(visualState, selectedTags) ? (
            <TextareaTagList
              disabled={isDisabled}
              onRemoveTag={onRemoveTag}
              tags={selectedTags}
            />
          ) : null}

          <textarea
            aria-describedby={shouldRenderHelperText ? helperId : undefined}
            aria-invalid={isInvalid || undefined}
            data-slot="textarea-control"
            data-state={visualState}
            data-type={type}
            disabled={isDisabled}
            id={textareaId}
            placeholder={selectedPlaceholder}
            readOnly={readOnly}
            ref={setTextareaRef}
            required={required}
            rows={isTags ? 1 : rows}
            value={value}
            defaultValue={inferredDefaultValue}
            className={cn(textareaControl({ type }), textareaClassName)}
            {...props}
          />
        </div>

        {shouldRenderHelperText ? (
          <TextareaHelperText
            className={helperClassName}
            id={helperId}
            hasIcon={hasHelperIcon}
            type={isInvalid ? "error" : "default"}
          >
            {helperText}
          </TextareaHelperText>
        ) : null}
      </div>
    )
  }
)
Textarea.displayName = "Textarea"

function TextareaLabel({
  className,
  hasInformationIcon,
  htmlFor,
  isOptional,
  isRequired,
  label,
  optionalText,
}: {
  className?: string
  hasInformationIcon: boolean
  htmlFor: string
  isOptional: boolean
  isRequired: boolean
  label?: React.ReactNode
  optionalText?: React.ReactNode
}) {
  return (
    <label
      data-slot="textarea-label"
      htmlFor={htmlFor}
      className={cn(
        "flex w-full items-center gap-[var(--bh-textarea-inline-gap)]",
        "text-start",
        className
      )}
    >
      <span
        data-slot="textarea-label-text"
        dir="auto"
        className="min-w-0 shrink-0 whitespace-nowrap text-[length:var(--bh-text-body-sm-medium-font-size)] font-[var(--bh-text-body-sm-medium-font-weight)] leading-[var(--bh-text-body-sm-medium-line-height)] tracking-[var(--bh-text-body-sm-medium-letter-spacing)] text-[var(--bh-content-default)]"
      >
        {label}
      </span>
      {isRequired ? (
        <span
          aria-hidden="true"
          data-slot="textarea-label-required"
          className="shrink-0 whitespace-nowrap text-[length:var(--bh-text-body-xs-regular-font-size)] font-[var(--bh-text-body-xs-regular-font-weight)] leading-[var(--bh-text-body-xs-regular-line-height)] tracking-[var(--bh-text-body-xs-regular-letter-spacing)] text-[var(--bh-content-danger-default)]"
        >
          *
        </span>
      ) : null}
      {isOptional && hasRenderableContent(optionalText) ? (
        <span
          data-slot="textarea-label-optional"
          dir="auto"
          className="min-w-0 shrink-0 whitespace-nowrap text-[length:var(--bh-text-body-xs-regular-font-size)] font-[var(--bh-text-body-xs-regular-font-weight)] leading-[var(--bh-text-body-xs-regular-line-height)] tracking-[var(--bh-text-body-xs-regular-letter-spacing)] text-[var(--bh-content-subtle)]"
        >
          {optionalText}
        </span>
      ) : null}
      {hasInformationIcon ? (
        <TextareaIcon className="text-[var(--bh-content-muted)]" type="info" />
      ) : null}
    </label>
  )
}

function hasRenderableContent(content: React.ReactNode) {
  return (
    content !== undefined &&
    content !== null &&
    content !== false &&
    content !== ""
  )
}

function TextareaHelperText({
  children,
  className,
  hasIcon,
  id,
  type,
}: {
  children: React.ReactNode
  className?: string
  hasIcon: boolean
  id: string
  type: "default" | "error"
}) {
  const isError = type === "error"

  return (
    <p
      data-slot="textarea-helper-text"
      id={id}
      className={cn(
        "flex w-full items-center gap-[var(--bh-textarea-inline-gap)]",
        "text-start text-[length:var(--bh-text-body-xs-regular-font-size)]",
        "font-[var(--bh-text-body-xs-regular-font-weight)]",
        "leading-[var(--bh-text-body-xs-regular-line-height)]",
        "tracking-[var(--bh-text-body-xs-regular-letter-spacing)]",
        isError ? "text-[var(--bh-content-danger-default)]" : "text-[var(--bh-content-subtle)]",
        className
      )}
    >
      {hasIcon ? (
        <TextareaIcon
          className={
            isError
              ? "text-[var(--bh-content-danger-default)]"
              : "text-[var(--bh-content-muted)]"
          }
          type={isError ? "error" : "info"}
        />
      ) : null}
      <span data-slot="textarea-helper-label" dir="auto" className="min-w-0 flex-1">
        {children}
      </span>
    </p>
  )
}

function TextareaTagList({
  disabled,
  onRemoveTag,
  tags,
}: {
  disabled: boolean
  onRemoveTag?: (tag: TextareaTag, index: number) => void
  tags: TextareaTag[]
}) {
  return (
    <div
      data-slot="textarea-tag-list"
      className="flex shrink-0 flex-wrap items-start gap-[var(--bh-textarea-gap)]"
    >
      {tags.map((tag, index) => (
        <TextareaTagChip
          key={tag.id || index}
          disabled={disabled}
          index={index}
          onRemoveTag={onRemoveTag}
          tag={tag}
        />
      ))}
    </div>
  )
}

function TextareaTagChip({
  disabled,
  index,
  onRemoveTag,
  tag,
}: {
  disabled: boolean
  index: number
  onRemoveTag?: (tag: TextareaTag, index: number) => void
  tag: TextareaTag
}) {
  return (
    <span
      data-slot="textarea-tag"
      className={cn(textareaTag({ disabled }))}
    >
      <TextareaTagAvatar
        avatar={tag.avatar}
        avatarSrc={tag.avatarSrc}
        disabled={disabled}
        label={tag.label}
      />
      <span data-slot="textarea-tag-label" dir="auto" className="min-w-0 whitespace-nowrap">
        {tag.label}
      </span>
      <button
        aria-label={tag.removeLabel}
        data-slot="textarea-tag-remove"
        disabled={disabled}
        type="button"
        className="flex size-[var(--bh-textarea-tag-avatar-size)] shrink-0 items-center justify-center rounded-[var(--bh-radius-full)] text-[var(--bh-content-subtle)] disabled:opacity-[var(--bh-opacity-50)]"
        onClick={(event) => {
          event.stopPropagation()
          onRemoveTag?.(tag, index)
        }}
      >
        <CloseIcon />
      </button>
    </span>
  )
}

function TextareaTagAvatar({
  avatar,
  avatarSrc,
  disabled,
  label,
}: {
  avatar?: React.ReactNode
  avatarSrc?: string
  disabled: boolean
  label: React.ReactNode
}) {
  if (avatar) {
    return (
      <span
        data-slot="textarea-tag-avatar"
        className={cn(
          "size-[var(--bh-textarea-tag-avatar-size)] shrink-0 overflow-hidden rounded-[var(--bh-radius-full)]",
          disabled && "opacity-[var(--bh-opacity-50)]"
        )}
      >
        {avatar}
      </span>
    )
  }

  return (
    <span
      data-slot="textarea-tag-avatar"
      className={cn(
        "flex size-[var(--bh-textarea-tag-avatar-size)] shrink-0 items-center justify-center overflow-hidden rounded-[var(--bh-radius-full)] border border-[var(--bh-border-subtle)] bg-[var(--bh-bg-accent-blue-soft)] text-[length:var(--bh-textarea-tag-avatar-font-size)] font-[var(--bh-font-weight-semibold)] leading-none text-[var(--bh-content-accent-blue-strong)]",
        disabled && "opacity-[var(--bh-opacity-50)]"
      )}
    >
      {avatarSrc ? (
        <img alt="" className="size-full object-cover" src={avatarSrc} />
      ) : (
        getAvatarInitial(label)
      )}
    </span>
  )
}

function TextareaIcon({
  className,
  type,
}: {
  className?: string
  type: "info" | "error"
}) {
  return (
    <span
      aria-hidden="true"
      data-slot={`textarea-${type}-icon`}
      className={cn(
        "flex size-[var(--bh-textarea-icon-size)] shrink-0 items-center justify-center",
        className
      )}
      >
      <svg
        className="size-[var(--bh-textarea-helper-icon-size)]"
        fill="none"
        focusable="false"
        viewBox="0 0 13.3333 13.3333"
      >
        {type === "error" ? <ErrorIconPath /> : <InfoIconPath />}
      </svg>
    </span>
  )
}

function ErrorIconPath() {
  return (
    <path
      d="M6.66667 13.3333C2.98477 13.3333 0 10.3485 0 6.66667C0 2.98477 2.98477 0 6.66667 0C10.3485 0 13.3333 2.98477 13.3333 6.66667C13.3333 10.3485 10.3485 13.3333 6.66667 13.3333ZM6 8.66667V10H7.33333V8.66667H6ZM6 3.33333V7.33333H7.33333V3.33333H6Z"
      fill="currentColor"
    />
  )
}

function InfoIconPath() {
  return (
    <path
      d="M6.66667 13.3333C2.98477 13.3333 0 10.3485 0 6.66667C0 2.98477 2.98477 0 6.66667 0C10.3485 0 13.3333 2.98477 13.3333 6.66667C13.3333 10.3485 10.3485 13.3333 6.66667 13.3333ZM6.66667 5C7.21893 5 7.66667 4.55229 7.66667 4C7.66667 3.44771 7.21893 3 6.66667 3C6.1144 3 5.66667 3.44771 5.66667 4C5.66667 4.55229 6.1144 5 6.66667 5ZM8 8.66667H7.33333V5.66667H5.33333V7H6V8.66667H5.33333V10H8V8.66667Z"
      fill="currentColor"
    />
  )
}

function CloseIcon() {
  return (
    <span
      aria-hidden="true"
      className="flex size-[var(--bh-textarea-close-button-size)] items-center justify-center"
    >
      <svg
        className="size-[var(--bh-textarea-close-icon-size)]"
        fill="none"
        focusable="false"
        viewBox="0 0 6.36398 6.36394"
      >
        <path
          d="M3.18198 2.47489L5.65688 0L6.36398 0.707105L3.88908 3.18199L6.36398 5.65684L5.65688 6.36394L3.18198 3.88909L0.70711 6.36394L0 5.65684L2.47488 3.18199L0 0.707105L0.70711 0L3.18198 2.47489Z"
          fill="currentColor"
        />
      </svg>
    </span>
  )
}

function getInferredDefaultValue({
  bodyText,
  defaultValue,
  isTags,
  state,
  tagInputText,
  value,
}: {
  bodyText?: string
  defaultValue?: string | number | readonly string[]
  isTags: boolean
  state: TextareaState
  tagInputText?: string
  value?: string | number | readonly string[]
}) {
  if (value !== undefined || defaultValue !== undefined) return defaultValue
  if (state === "placeholder" || state === "disabled") return undefined

  if (isTags) return tagInputText

  return bodyText
}

function shouldRenderTags(state: TextareaState, tags: TextareaTag[]) {
  return tags.length > 0 && (state === "filled" || state === "disabled")
}

function getAvatarInitial(label: React.ReactNode) {
  if (typeof label === "string" && label.length > 0) {
    return label.trim().slice(0, 1)
  }

  if (typeof label === "number") return String(label).slice(0, 1)

  return ""
}

export { Textarea }
export type { TextareaProps, TextareaState, TextareaTag, TextareaType }
