import { type GeospatialFieldComponent } from '@defra/forms-model'
import { type ArraySchema } from 'joi'

import { type ComponentBase } from './ComponentBase.js'
import {
  FormComponent,
  isGeospatialState
} from './FormComponent.js'
import { getGeospatialSchema } from './helpers/geospatial.js'
import { messageTemplate } from '../pageControllers/validationOptions.js'
import {
  type ErrorMessageTemplateList,
  type FormPayload,
  type FormState,
  type FormStateValue,
  type FormSubmissionError,
  type FormSubmissionState,
  type GeospatialState
} from '../types.js'

export class GeospatialField extends FormComponent {
  declare options: GeospatialFieldComponent['options']
  declare formSchema: ArraySchema<GeospatialState>
  declare stateSchema: ArraySchema<GeospatialState>

  constructor(
    def: GeospatialFieldComponent,
    props: ConstructorParameters<typeof ComponentBase>[1]
  ) {
    super(def, props)

    const { options } = def

    const formSchema = getGeospatialSchema(def)
      .label(this.label)
      .messages({
        'array.min': messageTemplate.featuresMin as string,
        'array.max': messageTemplate.featuresMax as string,
        'array.length': messageTemplate.featuresLength as string
      })

    this.formSchema = formSchema
    this.stateSchema = formSchema.default(null)

    if (options.required === false) {
      this.stateSchema = this.stateSchema.allow(null)
    }

    this.options = options
  }

  getFormValueFromState(state: FormSubmissionState) {
    const { name } = this
    return this.getFormValue(state[name])
  }

  getFormValue(value?: FormStateValue | FormState) {
    return this.isValue(value) ? value : undefined
  }

  getDisplayStringFromFormValue(features: GeospatialState | undefined): string {
    if (!features?.length) {
      return ''
    }

    const unit = features.length === 1 ? 'location' : 'locations'

    return `Added ${features.length} ${unit}`
  }

  getDisplayStringFromState(state: FormSubmissionState) {
    const features = this.getFormValueFromState(state)

    return this.getDisplayStringFromFormValue(features)
  }

  getContextValueFromFormValue(
    features: GeospatialState | undefined
  ): string[] | null {
    return features?.map(({ id }) => id) ?? null
  }

  getContextValueFromState(state: FormSubmissionState) {
    const features = this.getFormValueFromState(state)

    return this.getContextValueFromFormValue(features)
  }

  getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {
    const viewModel = super.getViewModel(payload, errors)
    const value =
      typeof viewModel.value === 'string'
        ? viewModel.value
        : JSON.stringify(viewModel.value, null, 2)

    return {
      ...viewModel,
      country: this.options.countries?.at(0),
      geometryTypes: this.options.geometryTypes,
      value
    }
  }

  getErrors(errors?: FormSubmissionError[]): FormSubmissionError[] | undefined {
    const fieldErrors = super.getErrors(errors)

    fieldErrors?.forEach((err) => {
      if (err.name === 'description') {
        err.href = `#description_${err.path[1]}`
        err.text = `Enter description for location ${Number(err.path[1]) + 1}`
      } else if (typeof err.name === 'number' && err.context?.country) {
        err.href = `#description_${err.path[1]}`
        err.text = `Location ${Number(err.path[1]) + 1} must be in ${err.context.country}`
      }
    })

    return fieldErrors
  }

  getViewErrors(
    errors?: FormSubmissionError[]
  ): FormSubmissionError[] | undefined {
    return this.getErrors(errors)
  }

  isValue(value?: FormStateValue | FormState): value is GeospatialState {
    return isGeospatialState(value)
  }

  /**
   * For error preview page that shows all possible errors on a component
   */
  getAllPossibleErrors(): ErrorMessageTemplateList {
    const staticErrors = GeospatialField.getAllPossibleErrors()
    return {
      ...staticErrors,
      advancedSettingsErrors: [...staticErrors.advancedSettingsErrors]
    }
  }

  /**
   * Static version of getAllPossibleErrors that doesn't require a component instance.
   */
  static getAllPossibleErrors(): ErrorMessageTemplateList {
    return {
      baseErrors: [
        { type: 'required', template: messageTemplate.selectRequired },
        {
          type: 'array.min',
          template: '{{#title}} must contain at least 1 items'
        },
        { type: 'object.invalidjson', template: messageTemplate.format }
      ],
      advancedSettingsErrors: []
    }
  }
}
