import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'

import { describe, expect, it } from 'vitest'

import { isQuestionHardFilteredAfterAnswers } from '@/lib/gekanatorQuestionFilters'

import type {
    GekanatorAnswerLog,
    GekanatorAnswerValue,
    GekanatorQuestion,
    GekanatorQuestionCondition,
} from '@/lib/gekanator'


const question = (
    condition:  GekanatorQuestionCondition,
): GekanatorQuestion => ({
  id: `${ condition.type }:candidate`,
  text: 'candidate?',
  kind: condition.type === 'source'
      ? 'source'
      : condition.type.startsWith ('original-')
	? 'original_date'
	: condition.type.startsWith ('title-')
	  ? 'title'
	  : 'tag',
  condition,
  source: 'default',
  priorityWeight: 1,
  test: () => false,
})


const answer = (
    condition:  GekanatorQuestionCondition,
    value:      GekanatorAnswerValue,
): GekanatorAnswerLog => ({
  questionId: 'previous',
  questionText: 'previous?',
  questionCondition: condition,
  answer: value,
  originalAnswer: value,
})


const blocked = (
    candidate:  GekanatorQuestionCondition,
    previous:   GekanatorQuestionCondition,
    value:      GekanatorAnswerValue,
): boolean =>
    isQuestionHardFilteredAfterAnswers (question (candidate), [answer (previous, value)])


const gekanatorPageSource = readFileSync (
    resolve (process.cwd (), 'src/pages/GekanatorPage.tsx'),
    'utf8')

const gekanatorBackdropSource = gekanatorPageSource.slice (
    gekanatorPageSource.indexOf ('const GekanatorBackdrop'),
    gekanatorPageSource.indexOf ('const expectedAnswerFor'))

const gekanatorChooseQuestionSource = gekanatorPageSource.slice (
    gekanatorPageSource.indexOf ('const chooseQuestion'),
    gekanatorPageSource.indexOf ('const winningRunPriorityFor'))

const gekanatorFallbackQuestionSource = gekanatorPageSource.slice (
    gekanatorPageSource.indexOf ('const chooseFallbackQuestion'),
    gekanatorPageSource.indexOf ('const shouldEnterGuessPhase'))


describe('GekanatorBackdrop regression structure', () => {
  it('keeps displayedBackdropMode as the render-time source of truth', () => {
    expect(gekanatorBackdropSource).not.toContain ('isLeavingGuessBackdrop')
    expect(gekanatorBackdropSource).not.toContain ('renderBackdropMode')
    expect(gekanatorBackdropSource).not.toContain ('renderWinningRunCount')
    expect(gekanatorBackdropSource).not.toContain ('renderThumbnails')
    expect(gekanatorBackdropSource).not.toContain ('renderIsCrossfading')

    expect(gekanatorBackdropSource).toContain (
      "const renderedSettings = settingsForMode (displayedBackdropMode)")
    expect(gekanatorBackdropSource).toContain (
      'scaleForMode (displayedBackdropMode, displayedWinningRunCount)')
    expect(gekanatorBackdropSource).toContain (
      "backdropMode === 'guess' || displayedBackdropMode === 'guess'")
  })

  it('does not split guess into a separate renderer or force a remount', () => {
    expect(gekanatorBackdropSource).not.toContain ('renderStaticGuessBackdrop')
    expect(gekanatorBackdropSource).not.toContain ('guessZoomAnimationKey')
    expect(gekanatorBackdropSource).not.toContain ('shouldAnimateGuessZoomIn')
    expect(gekanatorBackdropSource).not.toContain ('previousBackdropModeRef')
    expect(gekanatorBackdropSource).not.toContain (
      'if (isGuessPresentation && guessThumbnail)')
  })

  it('keeps tile keys independent from backdrop mode', () => {
    expect(gekanatorBackdropSource).toContain ('key={duplicate}')
    expect(gekanatorBackdropSource).toContain ('key={`${ duplicate }:${ index }`}')
    expect(gekanatorBackdropSource).not.toMatch (/key=\{`\$\{\s*mode/)
    expect(gekanatorBackdropSource).not.toMatch (/key=\{`\$\{\s*displayedBackdropMode/)
  })

  it('keeps guess on the shared scale, x, and y animation path', () => {
    expect(gekanatorBackdropSource).toContain ('animate={{ scale: renderedScale')
    expect(gekanatorBackdropSource).toContain (
      "x: displayedBackdropMode === 'guess' ? guessFocusOffset.x : '0%'")
    expect(gekanatorBackdropSource).toContain (
      "y: displayedBackdropMode === 'guess' ? guessFocusOffset.y : '0%'")
  })
})


describe('Gekanator question selection regression structure', () => {
  it('prefers normal questions after user_suggested quota has been met', () => {
    const normalFallbackIndex = gekanatorChooseQuestionSource.indexOf (
      'else if (normalPool.length > 0)')
    const effectiveFallbackIndex = gekanatorChooseQuestionSource.indexOf (
      'else if (effectiveUserSuggestedPool.length > 0)')

    expect(normalFallbackIndex).toBeGreaterThan(0)
    expect(effectiveFallbackIndex).toBeGreaterThan(0)
    expect(normalFallbackIndex).toBeLessThan(effectiveFallbackIndex)
  })

  it('does not let fallback questions bypass user_suggested purpose tracking', () => {
    expect(gekanatorFallbackQuestionSource).toContain (
      "question.source !== 'user_suggested'")
  })

  it('does not show a fixed extra-question count in the extra learning UI', () => {
    expect(gekanatorPageSource).not.toContain ('追加で 2 問まで答えてください。')
    expect(gekanatorPageSource).toContain ('追加で質問に答えてください。')
  })
})


describe('isQuestionHardFilteredAfterAnswers', () => {
  it('blocks only contradictory or redundant month questions after a yes answer', () => {
    const previous: GekanatorQuestionCondition = { type: 'original-month', month: 12 }

    expect(blocked ({ type: 'original-month', month: 12 }, previous, 'yes')).toBe(true)
    expect(blocked ({ type: 'original-month', month: 2 }, previous, 'yes')).toBe(true)
    expect(blocked ({ type: 'original-month-day', monthDay: '2-14' }, previous, 'yes'))
      .toBe(true)
    expect(blocked ({ type: 'original-month-day', monthDay: '12-25' }, previous, 'yes'))
      .toBe(false)
    expect(blocked ({ type: 'original-year', year: 2024 }, previous, 'yes')).toBe(false)
    expect(blocked ({ type: 'source', host: 'example.com' }, previous, 'yes')).toBe(false)
    expect(blocked ({ type: 'tag', key: 'character:喜多郁代' }, previous, 'yes')).toBe(false)
  })

  it('blocks same-month facts after a no answer', () => {
    const previous: GekanatorQuestionCondition = { type: 'original-month', month: 12 }

    expect(blocked ({ type: 'original-month', month: 12 }, previous, 'no')).toBe(true)
    expect(blocked ({ type: 'original-month', month: 2 }, previous, 'no')).toBe(false)
    expect(blocked ({ type: 'original-month-day', monthDay: '12-25' }, previous, 'no'))
      .toBe(true)
    expect(blocked ({ type: 'original-month-day', monthDay: '2-14' }, previous, 'no'))
      .toBe(false)
  })

  it('blocks all month and month-day questions after a month-day yes answer', () => {
    const previous: GekanatorQuestionCondition = {
      type: 'original-month-day',
      monthDay: '12-25',
    }

    expect(blocked ({ type: 'original-month', month: 12 }, previous, 'yes')).toBe(true)
    expect(blocked ({ type: 'original-month', month: 2 }, previous, 'yes')).toBe(true)
    expect(blocked ({ type: 'original-month-day', monthDay: '12-25' }, previous, 'yes'))
      .toBe(true)
    expect(blocked ({ type: 'original-month-day', monthDay: '12-26' }, previous, 'yes'))
      .toBe(true)
  })

  it('blocks the same month-day only after a month-day no answer', () => {
    const previous: GekanatorQuestionCondition = {
      type: 'original-month-day',
      monthDay: '12-25',
    }

    expect(blocked ({ type: 'original-month-day', monthDay: '12-25' }, previous, 'no'))
      .toBe(true)
    expect(blocked ({ type: 'original-month-day', monthDay: '12-26' }, previous, 'no'))
      .toBe(false)
    expect(blocked ({ type: 'original-month', month: 12 }, previous, 'no')).toBe(false)
  })

  it('blocks year and source as single-value facts', () => {
    expect(blocked (
      { type: 'original-year', year: 2025 },
      { type: 'original-year', year: 2024 },
      'yes',
    )).toBe(true)
    expect(blocked (
      { type: 'original-year', year: 2024 },
      { type: 'original-year', year: 2024 },
      'no',
    )).toBe(true)
    expect(blocked (
      { type: 'source', host: 'b.example' },
      { type: 'source', host: 'a.example' },
      'yes',
    )).toBe(true)
    expect(blocked (
      { type: 'source', host: 'b.example' },
      { type: 'source', host: 'a.example' },
      'no',
    )).toBe(false)
  })

  it('does not hard-filter partial, probably_no, or unknown fact answers', () => {
    const previous: GekanatorQuestionCondition = { type: 'original-month', month: 12 }
    const candidate: GekanatorQuestionCondition = { type: 'original-month', month: 2 }

    expect(blocked (candidate, previous, 'partial')).toBe(false)
    expect(blocked (candidate, previous, 'probably_no')).toBe(false)
    expect(blocked (candidate, previous, 'unknown')).toBe(false)
  })

  it('keeps title-length hard redundancy for yes and no only', () => {
    const previous: GekanatorQuestionCondition = {
      type: 'title-length-at-least',
      length: 30,
    }

    expect(blocked ({ type: 'title-length-at-least', length: 20 }, previous, 'yes'))
      .toBe(true)
    expect(blocked ({ type: 'title-length-at-least', length: 40 }, previous, 'yes'))
      .toBe(false)
    expect(blocked ({ type: 'title-length-at-least', length: 40 }, previous, 'no'))
      .toBe(true)
    expect(blocked ({ type: 'title-length-at-least', length: 20 }, previous, 'no'))
      .toBe(false)
    expect(blocked ({ type: 'title-length-at-least', length: 20 }, previous, 'partial'))
      .toBe(false)
  })
})
