import {
  ComponentType,
  type EastingNorthingFieldComponent
} from '@defra/forms-model'
import { type LanguageMessages, type ObjectSchema } from 'joi'
import lowerFirst from 'lodash/lowerFirst.js'

import { ComponentCollection } from './ComponentCollection.js'
import {
  FormComponent,
  isFormState
} from './FormComponent.js'
import {
  deduplicateErrorsByHref,
  getLocationFieldViewModel
} from './LocationFieldHelpers.js'
import { NumberField } from './NumberField.js'
import { createLowerFirstExpression } from './helpers/index.js'
import { type EastingNorthingState } from './types.js'
import { messageTemplate } from '../pageControllers/validationOptions.js'
import {
  type ErrorMessageTemplateList,
  type FormPayload,
  type FormState,
  type FormStateValue,
  type FormSubmissionError,
  type FormSubmissionState
} from '../types.js'
import { convertToLanguageMessages } from '../../../utils/type-utils.js'

// British National Grid coordinate limits
const DEFAULT_EASTING_MIN = 0
const DEFAULT_EASTING_MAX = 700000
const DEFAULT_NORTHING_MIN = 0
const DEFAULT_NORTHING_MAX = 1300000

export class EastingNorthingField extends FormComponent {
  declare options: EastingNorthingFieldComponent['options']
  declare formSchema: ObjectSchema<FormPayload>
  declare stateSchema: ObjectSchema<FormState>
  declare collection: ComponentCollection

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

    const { name, options, schema } = def

    const isRequired = options.required !== false

    const eastingMin = schema?.easting?.min ?? DEFAULT_EASTING_MIN
    const eastingMax = schema?.easting?.max ?? DEFAULT_EASTING_MAX
    const northingMin = schema?.northing?.min ?? DEFAULT_NORTHING_MIN
    const northingMax = schema?.northing?.max ?? DEFAULT_NORTHING_MAX

    const eastingRequired = 'Enter easting'
    const northingRequired = 'Enter northing'

    const eastingDigitsMessage = `{{#label}} for ${lowerFirst(this.label)} must be between 1 and 6 digits`
    const northingDigitsMessage = `{{#label}} for ${lowerFirst(this.label)} must be between 1 and 7 digits`

    const customValidationMessages: LanguageMessages =
      convertToLanguageMessages({
        'any.required': eastingRequired,
        'number.base': eastingRequired,
        'number.min': `{{#label}} for ${lowerFirst(this.label)} must be between {{#limit}} and ${eastingMax}`,
        'number.max': `{{#label}} for ${lowerFirst(this.label)} must be between ${eastingMin} and {{#limit}}`,
        'number.precision': eastingDigitsMessage,
        'number.integer': eastingDigitsMessage,
        'number.unsafe': eastingDigitsMessage
      })

    const northingValidationMessages: LanguageMessages =
      convertToLanguageMessages({
        'any.required': northingRequired,
        'number.base': northingRequired,
        'number.min': `{{#label}} for ${lowerFirst(this.label)} must be between {{#limit}} and ${northingMax}`,
        'number.max': `{{#label}} for ${lowerFirst(this.label)} must be between ${northingMin} and {{#limit}}`,
        'number.precision': northingDigitsMessage,
        'number.integer': northingDigitsMessage,
        'number.unsafe': northingDigitsMessage
      })

    this.collection = new ComponentCollection(
      [
        {
          type: ComponentType.NumberField,
          name: `${name}__easting`,
          title: 'Easting',
          schema: {
            min: eastingMin,
            max: eastingMax,
            precision: 0
          },
          options: {
            required: isRequired,
            optionalText: true,
            classes: 'govuk-input--width-10',
            customValidationMessages
          }
        },
        {
          type: ComponentType.NumberField,
          name: `${name}__northing`,
          title: 'Northing',
          schema: {
            min: northingMin,
            max: northingMax,
            precision: 0
          },
          options: {
            required: isRequired,
            optionalText: true,
            classes: 'govuk-input--width-10',
            customValidationMessages: northingValidationMessages
          }
        }
      ],
      { ...props, parent: this },
      {
        peers: [`${name}__easting`, `${name}__northing`]
      }
    )

    this.options = options
    this.formSchema = this.collection.formSchema
    this.stateSchema = this.collection.stateSchema
  }

  getFormValueFromState(state: FormSubmissionState) {
    const value = super.getFormValueFromState(state)
    return EastingNorthingField.isEastingNorthing(value) ? value : undefined
  }

  getDisplayStringFromFormValue(
    value: EastingNorthingState | undefined
  ): string {
    if (!value) {
      return ''
    }

    // CYA page format: <<eastingvalue, northingvalue>>
    return `${value.easting}, ${value.northing}`
  }

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

    return this.getDisplayStringFromFormValue(value)
  }

  getContextValueFromFormValue(
    value: EastingNorthingState | undefined
  ): string | null {
    if (!value) {
      return null
    }

    return `Easting: ${value.easting}\nNorthing: ${value.northing}`
  }

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

    return this.getContextValueFromFormValue(value)
  }

  getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {
    const viewModel = super.getViewModel(payload, errors)
    return getLocationFieldViewModel(this, viewModel, payload, errors)
  }

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

  isState(value?: FormStateValue | FormState) {
    return EastingNorthingField.isEastingNorthing(value)
  }

  /**
   * For error preview page that shows all possible errors on a component
   */
  getAllPossibleErrors(): ErrorMessageTemplateList {
    return EastingNorthingField.getAllPossibleErrors()
  }

  /**
   * Static version of getAllPossibleErrors that doesn't require a component instance.
   */
  static getAllPossibleErrors(): ErrorMessageTemplateList {
    return {
      baseErrors: [
        { type: 'required', template: messageTemplate.required },
        {
          type: 'eastingFormat',
          template: createLowerFirstExpression(
            'Easting for {{lowerFirst(#title)}} must be between 1 and 6 digits'
          )
        },
        {
          type: 'northingFormat',
          template: createLowerFirstExpression(
            'Northing for {{lowerFirst(#title)}} must be between 1 and 7 digits'
          )
        }
      ],
      advancedSettingsErrors: [
        {
          type: 'eastingMin',
          template: createLowerFirstExpression(
            `Easting for {{lowerFirst(#title)}} must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`
          )
        },
        {
          type: 'eastingMax',
          template: createLowerFirstExpression(
            `Easting for {{lowerFirst(#title)}} must be between ${DEFAULT_EASTING_MIN} and ${DEFAULT_EASTING_MAX}`
          )
        },
        {
          type: 'northingMin',
          template: createLowerFirstExpression(
            `Northing for {{lowerFirst(#title)}} must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`
          )
        },
        {
          type: 'northingMax',
          template: createLowerFirstExpression(
            `Northing for {{lowerFirst(#title)}} must be between ${DEFAULT_NORTHING_MIN} and ${DEFAULT_NORTHING_MAX}`
          )
        }
      ]
    }
  }

  static isEastingNorthing(
    value?: FormStateValue | FormState
  ): value is EastingNorthingState {
    return (
      isFormState(value) &&
      NumberField.isNumber(value.easting) &&
      NumberField.isNumber(value.northing)
    )
  }
}
