import type {
  FieldDefinition as LegacyFieldDefinition,
  FieldGroup as LegacyFieldGroup,
  FieldValue as LegacyFieldValue,
} from './model.js'
import {
  canonicalFieldGroupHash,
  parseFieldDefinitionVersion,
  type AnyFieldDefinition,
  type FieldDefinitionVersion,
  type FieldGroup,
  type FieldsDefinitionOrigin,
  type FieldValueNodeRow,
} from './schema.js'

function plainDefault(value: LegacyFieldValue | undefined): unknown {
  if (value === undefined) return undefined
  if (value instanceof Date) return value.toISOString()
  if (Array.isArray(value)) return value.map((item) => plainDefault(item as LegacyFieldValue))
  if (typeof value === 'object' && value !== null) {
    const output: Record<string, unknown> = {}
    for (const [key, child] of Object.entries(value)) {
      output[key] = child instanceof Date ? child.toISOString() : child
    }
    return output
  }
  return value
}

function sharedField(
  field: LegacyFieldDefinition,
): {
  key: string
  name: string
  label: string
  instructions?: string
  required?: boolean
  defaultValue?: unknown
} {
  const defaultValue = plainDefault(field.defaultValue)
  return {
    key: field.key,
    name: field.key,
    label: field.label,
    ...(field.help ? { instructions: field.help } : {}),
    ...(field.required === undefined ? {} : { required: field.required }),
    ...(defaultValue === undefined ? {} : { defaultValue }),
  }
}

export function legacyFieldToFinalDefinition(field: LegacyFieldDefinition): AnyFieldDefinition {
  const shared = sharedField(field)
  switch (field.type) {
    case 'text':
      return {
        ...shared,
        type: 'text',
        settings: {
          ...(field.maxLength === undefined ? {} : { maxLength: field.maxLength }),
          ...(field.pattern === undefined ? {} : { pattern: { source: field.pattern } }),
        },
      } as AnyFieldDefinition
    case 'textarea':
      return {
        ...shared,
        type: 'textarea',
        settings: {
          newline: 'preserve',
          ...(field.maxLength === undefined ? {} : { maxLength: field.maxLength }),
        },
      } as AnyFieldDefinition
    case 'number':
      return {
        ...shared,
        type: 'number',
        settings: {
          ...(field.min === undefined ? {} : { min: field.min }),
          ...(field.max === undefined ? {} : { max: field.max }),
          ...(field.integer === undefined ? {} : { integer: field.integer }),
        },
      } as AnyFieldDefinition
    case 'boolean':
      return { ...shared, type: 'boolean', settings: {} } as AnyFieldDefinition
    case 'select': {
      const choices = Object.fromEntries(field.options.map((option) => [option.value, option.label]))
      return {
        ...shared,
        type: 'select',
        settings: {
          choices,
          ...(field.multiple === undefined ? {} : { multiple: field.multiple }),
        },
      } as AnyFieldDefinition
    }
    case 'date':
      return {
        ...shared,
        type: 'date',
        settings: {
          inputFormat: 'yyyy-MM-dd',
          displayFormat: 'yyyy-MM-dd',
          returnFormat: 'yyyy-MM-dd',
        },
      } as AnyFieldDefinition
    case 'color':
      return { ...shared, type: 'color', settings: {} } as AnyFieldDefinition
    case 'url':
      return {
        ...shared,
        type: 'url',
        settings: { schemes: ['http', 'https'] },
      } as AnyFieldDefinition
    case 'email':
      return { ...shared, type: 'email', settings: {} } as AnyFieldDefinition
    case 'media': {
      const settings = {
        ...(field.accept?.length ? { mimeTypes: [...field.accept] } : {}),
        return: 'ref' as const,
      }
      return field.multiple
        ? ({ ...shared, type: 'gallery', settings } as AnyFieldDefinition)
        : ({ ...shared, type: 'file', settings } as AnyFieldDefinition)
    }
    case 'relationship':
      return {
        ...shared,
        type: 'relationship',
        settings: {
          entityTypes: [field.targetEntityType],
          return: 'ref',
          ...(field.multiple === false ? { max: 1 } : {}),
        },
      } as AnyFieldDefinition
  }
}

export function legacyFieldGroupToFinalDefinition(group: LegacyFieldGroup): FieldGroup {
  const location = [[
    { parameter: 'entityType', operator: 'eq' as const, value: group.location.entityType },
    ...(group.location.subType === undefined
      ? []
      : [{ parameter: 'subType', operator: 'eq' as const, value: group.location.subType }]),
  ]]

  return Object.freeze({
    key: group.key,
    title: group.label,
    fields: Object.freeze(group.fields.map(legacyFieldToFinalDefinition)),
    location: Object.freeze(location.map((rules) => Object.freeze(rules))),
    ...(group.position === undefined ? {} : { order: group.position }),
    active: true,
  }) as FieldGroup
}

export async function materializeLegacyFieldDefinitionVersion(
  group: LegacyFieldGroup,
  origin: FieldsDefinitionOrigin = 'db',
): Promise<FieldDefinitionVersion> {
  const definition = legacyFieldGroupToFinalDefinition(group)
  const canonicalHash = await canonicalFieldGroupHash(definition)
  return parseFieldDefinitionVersion({
    groupKey: definition.key,
    revision: 1,
    canonicalHash,
    definition,
    createdAt: new Date(0),
    origin,
  })
}

export interface FieldDefinitionReference {
  readonly groupKey: string
  readonly revision: number
}

export function referencedFieldDefinitionVersions(
  nodes: readonly FieldValueNodeRow[],
): readonly FieldDefinitionReference[] {
  const refs = new Map<string, FieldDefinitionReference>()
  for (const node of nodes) {
    const key = `${node.groupKey}\u0000${node.definitionRevision}`
    refs.set(key, Object.freeze({ groupKey: node.groupKey, revision: node.definitionRevision }))
  }
  return Object.freeze([...refs.values()].sort((left, right) =>
    left.groupKey.localeCompare(right.groupKey) || left.revision - right.revision,
  ))
}
