export type NormalizedAskUserOption = Readonly<{
  label: string
  description?: string
}>

export type NormalizedAskUserQuestion = Readonly<{
  allowCustom: boolean
  id: string
  header: string
  kind: 'choice' | 'input'
  multiline: boolean
  placeholder: string
  question: string
  options: readonly NormalizedAskUserOption[]
  multiSelect: boolean
}>

function asRecord(value: unknown): Record<string, unknown> | null {
  if (!value || typeof value !== 'object' || Array.isArray(value))
    return null
  return value as Record<string, unknown>
}

function normalizeAskUserText(value: unknown): string {
  return typeof value === 'string' ? value.trim() : ''
}

function fallbackAskUserQuestionId(index: number): string {
  return index === 0 ? 'answer' : `answer_${index + 1}`
}

function normalizeAskUserQuestionCandidates(raw: unknown): readonly unknown[] {
  if (Array.isArray(raw))
    return raw
  if (typeof raw === 'string')
    return [{ question: raw }]

  const record = asRecord(raw)
  if (!record)
    return []
  return Array.isArray(record.questions) ? record.questions : [record]
}

function normalizeAskUserKind(
  value: unknown,
  hasOptions: boolean,
): 'choice' | 'input' {
  const kind = normalizeAskUserText(value)
  if (kind === 'choice')
    return 'choice'
  if (kind === 'input')
    return 'input'
  return hasOptions ? 'choice' : 'input'
}

function normalizeAskUserOption(
  value: unknown,
): NormalizedAskUserOption | null {
  if (typeof value === 'string') {
    const label = normalizeAskUserText(value)
    return label ? { label } : null
  }
  const record = asRecord(value)
  if (!record)
    return null
  const label = normalizeAskUserText(record.label)
  if (!label)
    return null
  const description = normalizeAskUserText(record.description)
  return description ? { label, description } : { label }
}

export function getAskUserQuestionId(
  question: Readonly<{ id?: unknown }> | undefined,
  index: number,
): string {
  return normalizeAskUserText(question?.id) || fallbackAskUserQuestionId(index)
}

export function normalizeAskUserQuestions(
  raw: unknown,
): NormalizedAskUserQuestion[] {
  const candidates = normalizeAskUserQuestionCandidates(raw)
  const questions: NormalizedAskUserQuestion[] = []
  for (const [index, item] of candidates.entries()) {
    const questionRecord = asRecord(item)
    if (!questionRecord)
      continue
    const optionsRaw = Array.isArray(questionRecord.options)
      ? questionRecord.options
      : []
    const normalizedOptions: NormalizedAskUserOption[] = []
    for (const rawOption of optionsRaw) {
      const option = normalizeAskUserOption(rawOption)
      if (option)
        normalizedOptions.push(option)
    }
    const kind = normalizeAskUserKind(
      questionRecord.kind,
      optionsRaw.length > 0,
    )
    questions.push({
      allowCustom: questionRecord.allowCustom !== false,
      id: getAskUserQuestionId(questionRecord, index),
      header: normalizeAskUserText(questionRecord.header),
      kind,
      multiline: questionRecord.multiline === true,
      placeholder: normalizeAskUserText(questionRecord.placeholder),
      question: normalizeAskUserText(questionRecord.question),
      options: normalizedOptions,
      multiSelect: questionRecord.multiSelect === true,
    })
  }
  return questions
}

export function mergeAskUserQuestions(
  currentRaw: unknown,
  fallbackRaw: unknown,
): NormalizedAskUserQuestion[] {
  const current = normalizeAskUserQuestions(currentRaw)
  const fallback = normalizeAskUserQuestions(fallbackRaw)
  if (fallback.length === 0)
    return current
  if (current.length === 0)
    return fallback

  const merged: NormalizedAskUserQuestion[] = []
  for (let index = 0; index < Math.max(current.length, fallback.length); index += 1) {
    const currentQuestion = current[index]
    const fallbackQuestion = fallback[index]
    if (!currentQuestion) {
      if (fallbackQuestion)
        merged.push(fallbackQuestion)
      continue
    }
    if (!fallbackQuestion) {
      merged.push(currentQuestion)
      continue
    }
    const syntheticId = fallbackAskUserQuestionId(index)
    const mergedId = currentQuestion.id === syntheticId
      && fallbackQuestion.id !== syntheticId
      ? fallbackQuestion.id
      : currentQuestion.id || fallbackQuestion.id
    const shouldBackfillChoice = currentQuestion.kind === 'input'
      && currentQuestion.options.length === 0
      && fallbackQuestion.options.length > 0
    if (shouldBackfillChoice) {
      merged.push({
        ...fallbackQuestion,
        id: mergedId,
        header: currentQuestion.header || fallbackQuestion.header,
        question: currentQuestion.question || fallbackQuestion.question,
      })
      continue
    }
    merged.push({
      allowCustom: currentQuestion.allowCustom ?? fallbackQuestion.allowCustom,
      id: mergedId,
      header: currentQuestion.header || fallbackQuestion.header,
      kind: currentQuestion.kind || fallbackQuestion.kind,
      multiline: currentQuestion.multiline || fallbackQuestion.multiline,
      placeholder: currentQuestion.placeholder || fallbackQuestion.placeholder,
      question: currentQuestion.question || fallbackQuestion.question,
      options:
        currentQuestion.options.length >= fallbackQuestion.options.length
          ? currentQuestion.options
          : fallbackQuestion.options,
      multiSelect: currentQuestion.multiSelect || fallbackQuestion.multiSelect,
    })
  }
  return merged
}
