import { interpolate, translate, useLanguage } from '@stack-spot/portal-translate'
import { Fragment, MutableRefObject, useMemo } from 'react'
import { useJinjaContext } from '../context/JinjaContext'
import { JinjaExpression, JinjaExpressionVariable } from '../context/JinjaVariable'
import { useComputedInputsEffect, useIsRepeating } from '../form/hooks'
import { JinjaForm } from '../form/JinjaForm'
import { JinjaFormModel } from '../form/JinjaFormModel'
import { FieldComponent, FieldConfig, FormModel, RenderedFieldComponent, StaticFieldConfig, VariableFieldConfig } from '../form/types'
import { debouncePromise } from '../utils/promise'
import { BooleanField } from './components/BooleanField'
import { IntField } from './components/IntField'
import { ListField } from './components/ListField'
import { MultiselectField } from './components/MultiselectField'
import { PasswordField } from './components/Password'
import { RequiredConnectionField } from './components/RequiredConnectionField'
import { SelectField } from './components/SelectField'
import { TextField } from './components/TextField'
import { VariableField } from './components/VariableField'
import { ConnectionInterfaceConfig, GeneratedConnectionsManager, useConnectionInterfaceConfig, useGeneratedConnectionsManager } from './ConnectionInterface'
import { errorDictionary } from './error-dictionary'
import './style.css'
import { ComputedInput, ContextInput, InputType, InputValuesByEnv } from './types'
import { validateGeneratedConnection } from './validate-generated-connection'
import { globalVariablesContextName, PluginVariablesContext, UserVariableConfig } from './VariablesContext'

const components = {
  bool: BooleanField,
  int: IntField,
  multiselect: MultiselectField,
  list: ListField,
  'required-connection': RequiredConnectionField,
  password: PasswordField,
  select: SelectField,
  text: VariableField,
  'generated-connection': TextField,
} satisfies Record<Exclude<InputType, 'object'>, FieldComponent>

function getDefaultAndDisabled(inputEnv: boolean, def: any, valuesByEnv: InputValuesByEnv[] = []) {
  if (!inputEnv || valuesByEnv.length === 1) {
    return {
      default: valuesByEnv[0]?.value ?? def,
      disabled: valuesByEnv[0]?.mandate,
    }
  }
  if (!valuesByEnv.length) {
    return { default: def, disabled: false }
  }
  const byEnvMap: Record<string, any> = {}
  const disabled: Record<string, boolean> = {}
  for (const env of valuesByEnv) {
    byEnvMap[env.envName] = env.value
    disabled[env.envName] = !!env.mandate
  }
  return { default: byEnvMap, disabled }
}

function getDefaultValueByType(type: InputType) {
  switch (type) {
    case 'list': return []
    case 'multiselect': return []
    case 'object': return {}
    case 'bool': return true
    default: return undefined
  }
}

function inputToFieldConfig(
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  input: ContextInput,
  envs: string[],
  language: 'pt' | 'en',
  formName: string,
  connectionInterfacesConfig: ConnectionInterfaceConfig,
  generatedConnectionsManager: GeneratedConnectionsManager,
): FieldConfig {
  const {
    type, name, required, default: defaultValue, condition, help, pattern, inputEnv = false, inputs, isGlobal, valuesByEnv, ...props
  } = input
  const label = required ? `${props.label} *` : props.label
  const startRepeating = (valuesByEnv?.length ?? 0) > 1
  const def = defaultValue ?? getDefaultValueByType(type)
  if (type === 'object') {
    return {
      name,
      label,
      shouldRender: condition,
      children: inputs?.map(
        i => inputToFieldConfig({
          ...i, valuesByEnv: startRepeating ? [] : valuesByEnv?.map(byEnv => ({ ...byEnv, value: byEnv.value?.[i.name] })),
        }, envs, language, formName, connectionInterfacesConfig, generatedConnectionsManager),
      ) ?? [],
      repeat: inputEnv ? envs : undefined,
      startRepeating,
      ascend: isGlobal ? globalVariablesContextName : undefined,
      props,
      metadata: input,
      controlled: false,
      ...getDefaultAndDisabled(inputEnv, def, valuesByEnv),
    }
  }
  const isConnectionInterfaceNameAvailable = debouncePromise(connectionInterfacesConfig.isConnectionInterfaceNameAvailable, 500)
  const v: VariableFieldConfig | StaticFieldConfig = {
    jinja: type === 'text' ? { mode: 'replace-variable' } : false,
    name,
    label,
    component: components[type] ?? Fragment,
    required: required ?? true,
    shouldRender: condition,
    repeat: inputEnv ? envs : undefined,
    startRepeating,
    ascend: isGlobal ? globalVariablesContextName : undefined,
    help,
    // @ts-ignore
    model: input.input
      ? inputToFieldConfig(input.input, envs, language, formName, connectionInterfacesConfig, generatedConnectionsManager)
      : undefined,
    metadata: input,
    controlled: type != 'list' || input.input?.type != 'object',
    validation: async (value) => {
      const patternError = pattern
        ? `${value}`.match(`^${pattern}$`) ? undefined : interpolate(translate(errorDictionary, language).pattern, pattern)
        : undefined
      const connectionNameError = await validateGeneratedConnection(
        value, input, language, formName, isConnectionInterfaceNameAvailable, generatedConnectionsManager,
      )
      if (type === 'int') {
        const parsed = parseInt(value)
        if (parsed > Number.MAX_SAFE_INTEGER || parsed < Number.MIN_SAFE_INTEGER) {
          return translate(errorDictionary, language).numberOutOfBounds
        }
      }
      return patternError || connectionNameError
    },
    props,
    ...getDefaultAndDisabled(inputEnv, def, valuesByEnv),
  }
  return v
}

interface Props extends Omit<React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>, 'onSubmit'> {
  inputs: ContextInput[],
  computedInputs?: ComputedInput[],
  globalComputedInputs?: ComputedInput[],
  pluginVariables?: UserVariableConfig[],
  name: string,
  envs: string[],
  onSubmit: (form: FormModel) => void,
  formModelRef?: MutableRefObject<JinjaFormModel | undefined>,
}

export const FieldRenderer = ({ Component, input }: { Component: RenderedFieldComponent, input: ContextInput }) => {
  const isRepeating = useIsRepeating(Component.variable)
  const children = isRepeating ? Component.repeat : Component.fields

  return (
    <div className="dynamic-field" data-field-type={input.type.toLowerCase()}>
      {children
        ? (
          <Component>
            {children.map((c, i) => <FieldRenderer key={`${c.name}-${i}`} Component={c} input={isRepeating ? input : input.inputs![i]} />)}
          </Component>
        ) : <Component />}
    </div>
  )
}

const Form = ({ inputs, computedInputs, globalComputedInputs, name, onSubmit, envs, children, formModelRef, ...props }: Props) => {
  const parent = useJinjaContext()
  const language = useLanguage()
  const connectionInterfacesConfig = useConnectionInterfaceConfig()
  const generatedConnectionsManager = useGeneratedConnectionsManager()
  const formModel = useMemo(() => new JinjaFormModel({
    fields: inputs.map(i => inputToFieldConfig(i, envs, language, name, connectionInterfacesConfig, generatedConnectionsManager)),
    computed: computedInputs?.reduce((result, value) => {
      result[value.name] = value.expression
      return result
    }, {} as Record<string, JinjaExpression>),
    name,
    onSubmit,
    parent,
  }), [inputs, parent])
  if (formModelRef) {
    formModelRef.current = formModel
  }
  const globalComputedVariables = useMemo(() => {
    const ctx = parent?.closest(globalVariablesContextName)
    const vars = globalComputedInputs?.map((g) => new JinjaExpressionVariable(g.name, g.expression))
    if (ctx && vars) ctx.addVariables(vars)
    return vars
  }, [globalComputedInputs, parent])

  useComputedInputsEffect(globalComputedVariables, formModel.context)

  return (
    <JinjaForm {...props} model={formModel} className="dynamic-form">
      {formModel.fields.map((f, i) => <FieldRenderer key={`${f.name}-${i}`} Component={f} input={inputs[i]} />)}
      {children}
    </JinjaForm>
  )
}

export const DynamicForm = ({ pluginVariables = [], ...props }: Props) => (
  <PluginVariablesContext variables={pluginVariables}>
    <Form {...props} />
  </PluginVariablesContext>
)
