/**
 * BoostMedia AI Content Generator Admin - Content Generator Page
 *
 * @package BoostMedia_AI
 * @license GPL-2.0-or-later
 */

import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { flushSync } from 'react-dom'
import { AlertCircle, CopyPlus, LogOut, RotateCcw, Save, FileText, Newspaper } from 'lucide-react'
import { useLocation, useNavigate } from 'react-router-dom'
import { Button, Loader } from '../components/common'
import { useStructures } from '../hooks'
import type {
  ContentPlan,
  GeneratedPost,
  PostStructure,
  PostTypeRulesResponse,
  Reporter,
  SavedContentPlan,
  SavedTechnicalRules,
  SprintArticle,
  SprintResult,
} from '../types'
import { Header } from '../components/layout/Header'
import { t } from '../lib/i18n'
import { endpoints, getErrorMessage } from '../api/client'
import {
  StepsProgress,
  SettingsStep,
  type GeneratorStep,
  type GenerationConfig,
} from '../components/generator'
import { TechnicalStep } from './content-generator/TechnicalStep'
import { ContentStep } from './content-generator/ContentStep'
import { IntentStep } from './content-generator/IntentStep'
import {
  buildTechnicalRulesPayload,
  createEmptyContentPlan,
  createEmptyTechnicalState,
  extractTechnicalStateFromSavedRules,
  normalizeTechnicalFieldMapping,
} from './content-generator/conversation-utils'

const initialConfig: GenerationConfig = {
  postType: '',
  taxonomy: '',
  term: '',
  planName: '',
  reporterId: null,
  count: 10,
  countPerCategory: false,
  topic: '',
  keywords: [],
  length: 'auto',
  scheduling: 'none',
  generationType: 'one_time',
  repeatFrequency: 'weekly',
  repeatEvery: 1,
  repeatUnit: 'week',
  repeatWeekdays: [],
  repeatHour: 9,
  stopAfterMonths: null,
  maxTotalRuns: null,
  maxTotalPosts: null,
  remainingRuns: null,
  remainingPosts: null,
  isActive: true,
}

interface ContentGeneratorLocationState {
  planId?: number
  intent?: 'new' | 'run' | 'review'
}

interface PersistedPlanEditorDraft {
  version: 1
  planId: number
  intent: 'run' | 'review'
  step: GeneratorStep
  config: GenerationConfig
  technicalState: Record<string, unknown>
  contentState: Record<string, unknown>
  technicalSessionId: string | null
  contentSessionId: string | null
  structureHash: string
  intentState?: Record<string, unknown>
  intentSessionId?: string | null
  sprintResult?: SprintResult | null
  sprintArticles?: SprintArticle[]
}

const PLAN_EDITOR_DRAFT_STORAGE_PREFIX = 'bc:content-plan-editor'
const NEW_FLOW_DRAFT_STORAGE_KEY = 'bc:content-new-flow-draft'
const NEW_FLOW_MAX_AGE_MS = 2 * 60 * 60 * 1000
const VALID_GENERATOR_STEPS: GeneratorStep[] = [
  'settings', 'select', 'reporter', 'configure', 'technical',
  'content', 'intent', 'sprint', 'article-review',
  'generate', 'review',
]

function normalizeGeneratorStep(value: string | null | undefined): GeneratorStep {
  if (!value || !VALID_GENERATOR_STEPS.includes(value as GeneratorStep)) return 'settings'
  if (value === 'select' || value === 'reporter' || value === 'configure') return 'settings'
  if (value === 'sprint' || value === 'article-review' || value === 'generate' || value === 'review') return 'settings'
  return value as GeneratorStep
}

function getPlanEditorDraftStorageKey(planId: number): string {
  return `${PLAN_EDITOR_DRAFT_STORAGE_PREFIX}:${planId}`
}

function readPlanEditorDraft(planId: number): PersistedPlanEditorDraft | null {
  if (typeof window === 'undefined' || planId <= 0) {
    return null
  }

  try {
    const raw = window.sessionStorage.getItem(getPlanEditorDraftStorageKey(planId))
    if (!raw) {
      return null
    }

    const parsed = JSON.parse(raw) as Partial<PersistedPlanEditorDraft>
    if (parsed.version !== 1 || parsed.planId !== planId) {
      return null
    }

    return {
      version: 1,
      planId,
      intent: parsed.intent === 'run' ? 'run' : 'review',
      step: normalizeGeneratorStep(parsed.step),
      config: parsed.config as GenerationConfig,
      technicalState: (parsed.technicalState as Record<string, unknown>) || {},
      contentState: (parsed.contentState as Record<string, unknown>) || {},
      technicalSessionId: typeof parsed.technicalSessionId === 'string' ? parsed.technicalSessionId : null,
      contentSessionId: typeof parsed.contentSessionId === 'string' ? parsed.contentSessionId : null,
      structureHash: typeof parsed.structureHash === 'string' ? parsed.structureHash : '',
      intentState: (parsed.intentState as Record<string, unknown>) || {},
      intentSessionId: typeof parsed.intentSessionId === 'string' ? parsed.intentSessionId : null,
      sprintResult: (parsed.sprintResult as SprintResult) || null,
      sprintArticles: Array.isArray(parsed.sprintArticles) ? (parsed.sprintArticles as SprintArticle[]) : [],
    }
  } catch {
    return null
  }
}

function writePlanEditorDraft(planId: number, draft: PersistedPlanEditorDraft): void {
  if (typeof window === 'undefined' || planId <= 0) {
    return
  }

  window.sessionStorage.setItem(getPlanEditorDraftStorageKey(planId), JSON.stringify(draft))
}

function clearPlanEditorDraft(planId: number | null): void {
  if (typeof window === 'undefined' || !planId) {
    return
  }

  window.sessionStorage.removeItem(getPlanEditorDraftStorageKey(planId))
}

interface PersistedNewFlowDraft {
  version: 1
  timestamp: number
  step: GeneratorStep
  config: GenerationConfig
  technicalState: Record<string, unknown>
  contentState: Record<string, unknown>
  technicalSessionId: string | null
  contentSessionId: string | null
  structureHash: string
  intentState?: Record<string, unknown>
  intentSessionId?: string | null
  sprintResult?: SprintResult | null
  sprintArticles?: SprintArticle[]
}

function readNewFlowDraft(): PersistedNewFlowDraft | null {
  if (typeof window === 'undefined') {
    return null
  }

  try {
    const raw = window.sessionStorage.getItem(NEW_FLOW_DRAFT_STORAGE_KEY)
    if (!raw) {
      return null
    }

    const parsed = JSON.parse(raw) as Partial<PersistedNewFlowDraft>
    if (parsed.version !== 1) {
      return null
    }

    if (typeof parsed.timestamp === 'number' && Date.now() - parsed.timestamp > NEW_FLOW_MAX_AGE_MS) {
      window.sessionStorage.removeItem(NEW_FLOW_DRAFT_STORAGE_KEY)
      return null
    }

    return {
      version: 1,
      timestamp: parsed.timestamp || Date.now(),
      step: normalizeGeneratorStep(parsed.step),
      config: parsed.config as GenerationConfig,
      technicalState: (parsed.technicalState as Record<string, unknown>) || {},
      contentState: (parsed.contentState as Record<string, unknown>) || {},
      technicalSessionId: typeof parsed.technicalSessionId === 'string' ? parsed.technicalSessionId : null,
      contentSessionId: typeof parsed.contentSessionId === 'string' ? parsed.contentSessionId : null,
      structureHash: typeof parsed.structureHash === 'string' ? parsed.structureHash : '',
      intentState: (parsed.intentState as Record<string, unknown>) || undefined,
      intentSessionId: typeof parsed.intentSessionId === 'string' ? parsed.intentSessionId : null,
      sprintResult: (parsed.sprintResult as SprintResult) || null,
      sprintArticles: Array.isArray(parsed.sprintArticles) ? (parsed.sprintArticles as SprintArticle[]) : undefined,
    }
  } catch {
    return null
  }
}

function writeNewFlowDraft(draft: PersistedNewFlowDraft): void {
  if (typeof window === 'undefined') {
    return
  }

  window.sessionStorage.setItem(NEW_FLOW_DRAFT_STORAGE_KEY, JSON.stringify(draft))
}

function clearNewFlowDraft(): void {
  if (typeof window === 'undefined') {
    return
  }

  window.sessionStorage.removeItem(NEW_FLOW_DRAFT_STORAGE_KEY)
}

function buildGeneratorSearch(params: {
  planId?: number | null
  intent?: 'new' | 'run' | 'review' | null
  step?: GeneratorStep | null
}): string {
  const search = new URLSearchParams()

  if (params.planId && params.planId > 0) {
    search.set('planId', String(params.planId))
  }

  if (params.intent) {
    search.set('intent', params.intent)
  }

  if (params.step) {
    search.set('step', params.step)
  }

  const query = search.toString()
  return query ? `?${query}` : ''
}

function normalizeScopeValue(value: string | null | undefined): string {
  return value && value !== '-' ? value : ''
}

function splitScopeList(value: string | null | undefined): string[] {
  return (value || '')
    .split(',')
    .map((item) => item.trim())
    .filter(Boolean)
}

function normalizePositiveNumber(value: unknown): number | null {
  const nextValue = typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : NaN
  if (!Number.isFinite(nextValue) || nextValue <= 0) {
    return null
  }
  return Math.round(nextValue)
}

function normalizeScheduleConfig(
  value: unknown,
  fallbackFrequency: 'daily' | 'weekly' | 'monthly',
): Pick<
  GenerationConfig,
  | 'repeatEvery'
  | 'repeatUnit'
  | 'repeatWeekdays'
  | 'repeatHour'
  | 'stopAfterMonths'
  | 'maxTotalRuns'
  | 'maxTotalPosts'
> {
  const raw = value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}
  const unit = raw.unit === 'day' || raw.unit === 'week' || raw.unit === 'month'
    ? raw.unit
    : fallbackFrequency === 'daily'
      ? 'day'
      : fallbackFrequency === 'monthly'
        ? 'month'
        : 'week'
  const rawHour = typeof raw.hour === 'number' ? raw.hour : typeof raw.hour === 'string' ? Number(raw.hour) : 9

  return {
    repeatEvery: Math.max(1, normalizePositiveNumber(raw.interval) || 1),
    repeatUnit: unit,
    repeatWeekdays: Array.isArray(raw.weekdays)
      ? raw.weekdays
        .map((item) => Number(item))
        .filter((item) => Number.isInteger(item) && item >= 0 && item <= 6)
      : [],
    repeatHour: Math.min(23, Math.max(0, Number.isFinite(rawHour) ? Math.round(rawHour) : 9)),
    stopAfterMonths: normalizePositiveNumber(raw.stop_after_months),
    maxTotalRuns: normalizePositiveNumber(raw.max_total_runs),
    maxTotalPosts: normalizePositiveNumber(raw.max_total_posts),
  }
}

function normalizeScheduleCounters(value: unknown): Pick<GenerationConfig, 'remainingRuns' | 'remainingPosts'> {
  const raw = value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}
  return {
    remainingRuns: normalizePositiveNumber(raw.remaining_runs),
    remainingPosts: normalizePositiveNumber(raw.remaining_posts),
  }
}

function normalizeContentPlanState(state: Record<string, unknown>) {
  return {
    phase: typeof state.phase === 'string' ? state.phase : 'content',
    ready: Boolean(state.ready),
    captured_answers: Array.isArray(state.captured_answers) ? state.captured_answers as Record<string, unknown>[] : [],
    open_questions: Array.isArray(state.open_questions) ? state.open_questions : [],
    question_cards: Array.isArray(state.question_cards) ? state.question_cards : [],
    question_responses: (
      state.question_responses
      && typeof state.question_responses === 'object'
      && !Array.isArray(state.question_responses)
    ) ? state.question_responses as Record<string, unknown> : {},
    content_plan: ((state.content_plan as ContentPlan | undefined) || createEmptyContentPlan()),
  }
}

function buildPlanSnapshot(params: {
  config: GenerationConfig
  technicalState: Record<string, unknown>
  contentState: Record<string, unknown>
  technicalSessionId: string | null
  contentSessionId: string | null
  activePlanId: number | null
}) {
  return JSON.stringify({
    activePlanId: params.activePlanId,
    config: params.config,
    technicalState: {
      ...createEmptyTechnicalState(),
      ...params.technicalState,
      captured_answers: Array.isArray(params.technicalState.captured_answers) ? params.technicalState.captured_answers : [],
      open_questions: Array.isArray(params.technicalState.open_questions) ? params.technicalState.open_questions : [],
      technical_summary: String(params.technicalState.technical_summary || ''),
      field_mapping: normalizeTechnicalFieldMapping(params.technicalState.field_mapping),
    },
    contentState: normalizeContentPlanState(params.contentState),
    technicalSessionId: params.technicalSessionId,
    contentSessionId: params.contentSessionId,
  })
}

function hasMeaningfulContentPlan(plan: ContentPlan | null | undefined) {
  if (!plan) {
    return false
  }

  const emptyPlan = createEmptyContentPlan()
  return JSON.stringify(plan) !== JSON.stringify(emptyPlan)
}

function resolveDraftSessionId(
  draftValue: string | null | undefined,
  ...fallbacks: Array<string | null | undefined>
): string | null {
  if (typeof draftValue === 'string' && draftValue.trim()) {
    return draftValue
  }

  for (const fallback of fallbacks) {
    if (typeof fallback === 'string' && fallback.trim()) {
      return fallback
    }
  }

  return null
}

function PrerequisiteGuard({ children }: { children: React.ReactNode }) {
  const navigate = useNavigate()
  const { structures, loading: structuresLoading } = useStructures()
  const [reporters, setReporters] = useState<Reporter[] | null>(null)
  const [reportersLoading, setReportersLoading] = useState(true)

  useEffect(() => {
    let cancelled = false
    endpoints.getReporters().then((res) => {
      if (!cancelled) {
        setReporters((res.data as Reporter[]) || [])
        setReportersLoading(false)
      }
    }).catch(() => {
      if (!cancelled) {
        setReporters([])
        setReportersLoading(false)
      }
    })
    return () => { cancelled = true }
  }, [])

  if (structuresLoading || reportersLoading) {
    return (
      <div className="min-h-screen">
        <Header
          title={t('Create Content')}
          subtitle={t('Create new content using AI in your site\'s style')}
        />
        <div className="py-16 flex items-center justify-center">
          <div className="rounded-bc-lg bg-white px-8 py-6 shadow-sm">
            <Loader size="lg" text={t('Loading...')} />
          </div>
        </div>
      </div>
    )
  }

  if (!structures || structures.length === 0) {
    return (
      <div className="min-h-screen">
        <Header
          title={t('Create Content')}
          subtitle={t('Create new content using AI in your site\'s style')}
        />
        <div className="flex flex-col items-center justify-center p-8 text-center mt-12">
          <div className="w-16 h-16 rounded-full bg-bc-primary-light flex items-center justify-center mb-4">
            <FileText className="w-8 h-8 text-bc-primary" />
          </div>
          <h2 className="text-xl font-bold mb-2 text-bc-gray-800">{t('Content types not scanned yet')}</h2>
          <p className="text-bc-gray-500 mb-6 max-w-md">
            {t('Before creating content, the plugin needs to scan your site\'s content types. This is a one-time setup step.')}
          </p>
          <Button onClick={() => navigate('/post-types')}>
            {t('Go to Content Types')}
          </Button>
        </div>
      </div>
    )
  }

  if (!reporters || reporters.length === 0) {
    return (
      <div className="min-h-screen">
        <Header
          title={t('Create Content')}
          subtitle={t('Create new content using AI in your site\'s style')}
        />
        <div className="flex flex-col items-center justify-center p-8 text-center mt-12">
          <div className="w-16 h-16 rounded-full bg-bc-primary-light flex items-center justify-center mb-4">
            <Newspaper className="w-8 h-8 text-bc-primary" />
          </div>
          <h2 className="text-xl font-bold mb-2 text-bc-gray-800">{t('No reporter available')}</h2>
          <p className="text-bc-gray-500 mb-6 max-w-md">
            {t('A reporter is needed to create content. Go to the Reporters page to set one up.')}
          </p>
          <Button onClick={() => navigate('/reporters')}>
            {t('Go to Reporters')}
          </Button>
        </div>
      </div>
    )
  }

  return <>{children}</>
}

export default function ContentGenerator() {
  const location = useLocation()
  const navigate = useNavigate()
  const [step, setStep] = useState<GeneratorStep>('settings')
  const [config, setConfig] = useState<GenerationConfig>(initialConfig)
  const [, setGeneratedPosts] = useState<GeneratedPost[]>([])
  const [selectedReporter, setSelectedReporter] = useState<Reporter | null>(null)
  const [savedTechnicalRules, setSavedTechnicalRules] = useState<SavedTechnicalRules | null>(null)
  const [savedContentPlan, setSavedContentPlan] = useState<SavedContentPlan | null>(null)
  const [structureHash, setStructureHash] = useState('')
  const [activePlanId, setActivePlanId] = useState<number | null>(null)
  const [planIntent, setPlanIntent] = useState<'new' | 'run' | 'review' | null>(null)
  const [loadingStep, setLoadingStep] = useState(false)
  const [stepError, setStepError] = useState<string | null>(null)
  const [forceTechnicalReview, setForceTechnicalReview] = useState(false)
  const [forceContentReview, setForceContentReview] = useState(false)
  const [technicalSessionId, setTechnicalSessionId] = useState<string | null>(null)
  const [contentSessionId, setContentSessionId] = useState<string | null>(null)
  const [savingPlanAction, setSavingPlanAction] = useState<'save' | 'saveAs' | null>(null)
  const [lastSavedSnapshot, setLastSavedSnapshot] = useState('')
  const [showExitPrompt, setShowExitPrompt] = useState(false)
  const [, setRestoredDraft] = useState(false)
  const loadedLocationKeyRef = useRef('')
  const saveGenerationRef = useRef(0)
  const lastLoadedSaveGenRef = useRef(0)
  const configRef = useRef<GenerationConfig>(initialConfig)
  const [technicalState, setTechnicalState] = useState<Record<string, unknown>>({ ...createEmptyTechnicalState() })
  const [contentState, setContentState] = useState<Record<string, unknown>>({
    phase: 'content',
    ready: false,
    captured_answers: [],
    open_questions: [],
    question_cards: [],
    question_responses: {},
    content_plan: createEmptyContentPlan(),
  })

  // Sprint 2.0 state
  const [intentState, setIntentState] = useState<Record<string, unknown>>({})
  const [intentSessionId, setIntentSessionId] = useState<string | null>(null)
  const [sprintResult, setSprintResult] = useState<SprintResult | null>(null)
  const [sprintArticles, setSprintArticles] = useState<SprintArticle[]>([])
  const [, setSprintError] = useState<string | null>(null)
  const [, setActiveSprintJobId] = useState<string | null>(null)
  const [submittingIntent, setSubmittingIntent] = useState(false)

  useEffect(() => {
    configRef.current = config
  }, [config])

  const useSprintMode = useMemo(() => {
    if (activePlanId && savedContentPlan && !forceContentReview) {
      const plan = savedContentPlan.content_plan
      if (plan && hasMeaningfulContentPlan(plan)) {
        return false
      }
    }
    return true
  }, [activePlanId, savedContentPlan, forceContentReview])

  const resetTechnicalState = useCallback(() => {
    setTechnicalSessionId(null)
    setTechnicalState({ ...createEmptyTechnicalState() })
    setForceTechnicalReview(false)
  }, [])

  const resetContentState = useCallback(() => {
    setContentSessionId(null)
    setContentState({
      phase: 'content',
      ready: false,
      captured_answers: [],
      open_questions: [],
      question_cards: [],
      question_responses: {},
      content_plan: createEmptyContentPlan(),
    })
    setForceContentReview(false)
  }, [])

  const resetSprintState = useCallback(() => {
    setIntentSessionId(null)
    setIntentState({})
    setSprintResult(null)
    setSprintArticles([])
    setSprintError(null)
    setActiveSprintJobId(null)
    if (typeof window !== 'undefined') {
      sessionStorage.removeItem('bc:active-sprint-job')
    }
  }, [])

  const clearAdaptiveState = useCallback(() => {
    setSelectedReporter(null)
    setSavedTechnicalRules(null)
    setSavedContentPlan(null)
    setStructureHash('')
    resetTechnicalState()
    resetContentState()
    resetSprintState()
  }, [resetContentState, resetSprintState, resetTechnicalState])

  const clearPlanSelection = useCallback(() => {
    clearPlanEditorDraft(activePlanId)
    loadedLocationKeyRef.current = ''
    setActivePlanId(null)
    setPlanIntent(null)
    setLastSavedSnapshot('')
    navigate('/generate?intent=new&step=settings', { replace: true })
  }, [activePlanId, navigate])

  const applySavedTechnicalRules = useCallback((rules: SavedTechnicalRules | null) => {
    if (!rules) {
      resetTechnicalState()
      return
    }

    setTechnicalSessionId(rules.session_id)
    setTechnicalState({ ...extractTechnicalStateFromSavedRules(rules) })
  }, [resetTechnicalState])

  const applySavedContentPlan = useCallback((plan: SavedContentPlan | null) => {
    if (!plan) {
      resetContentState()
      return
    }

    setContentSessionId(plan.content_session_id)
    setContentState({
      phase: 'content',
      ready: true,
      captured_answers: plan.content_answers,
      open_questions: [],
      question_cards: [],
      question_responses: {},
      content_plan: plan.content_plan,
    })
  }, [resetContentState])

  const handleConfigUpdate = useCallback((newConfig: GenerationConfig) => {
    configRef.current = newConfig
    flushSync(() => {
      setConfig((current) => {
        const postTypeChanged = current.postType !== newConfig.postType
        const taxonomyChanged = current.taxonomy !== newConfig.taxonomy || current.term !== newConfig.term
        const reporterChanged = current.reporterId !== newConfig.reporterId

        if (postTypeChanged) {
          clearAdaptiveState()
        } else if (taxonomyChanged) {
          setSavedTechnicalRules(null)
          setSavedContentPlan(null)
          resetTechnicalState()
          resetContentState()
        } else if (reporterChanged) {
          setSavedContentPlan(null)
          resetContentState()
        }

        return newConfig
      })
    })
  }, [clearAdaptiveState, resetContentState, resetTechnicalState])

  const handleStartOver = useCallback(() => {
    clearNewFlowDraft()

    if (typeof window !== 'undefined') {
      const keysToRemove: string[] = []
      for (let i = 0; i < window.sessionStorage.length; i++) {
        const key = window.sessionStorage.key(i)
        if (key && key.startsWith('bc:')) {
          keysToRemove.push(key)
        }
      }
      keysToRemove.forEach((key) => window.sessionStorage.removeItem(key))
    }

    restoredNewFlowRef.current = false
    setRestoredDraft(false)
    setStep('settings')
    setConfig(initialConfig)
    setGeneratedPosts([])
    setStepError(null)
    setShowExitPrompt(false)
    clearAdaptiveState()
    clearPlanSelection()
  }, [clearAdaptiveState, clearPlanSelection])

  const conversationPayload = useMemo(() => {
    const technicalSummary = String(technicalState.technical_summary || '')
    const technicalAnswers = buildTechnicalRulesPayload({
      captured_answers: Array.isArray(technicalState.captured_answers) ? (technicalState.captured_answers as Record<string, unknown>[]) : [],
      field_mapping: normalizeTechnicalFieldMapping(technicalState.field_mapping),
    })

    if (useSprintMode && sprintResult) {
      return {
        technicalSummary,
        technicalAnswers,
        contentPlan: {
          ...((intentState.content_plan as ContentPlan | undefined) || createEmptyContentPlan()),
          research_enabled: false,
        } as ContentPlan,
        contentAnswers: Array.isArray(intentState.captured_answers) ? (intentState.captured_answers as Record<string, unknown>[]) : [],
        sprintMode: true,
        sprintResult,
        sprintArticles,
      }
    }

    return {
      technicalSummary,
      technicalAnswers,
      contentPlan: (contentState.content_plan as ContentPlan | undefined) || createEmptyContentPlan(),
      contentAnswers: Array.isArray(contentState.captured_answers) ? (contentState.captured_answers as Record<string, unknown>[]) : [],
    }
  }, [contentState, intentState, sprintArticles, sprintResult, technicalState, useSprintMode])

  const currentSnapshot = useMemo(() => buildPlanSnapshot({
    config,
    technicalState,
    contentState,
    technicalSessionId,
    contentSessionId,
    activePlanId,
  }), [activePlanId, config, contentSessionId, contentState, technicalSessionId, technicalState])

  const routeState = (location.state || {}) as ContentGeneratorLocationState
  const routePlanId = Number(new URLSearchParams(location.search).get('planId') || routeState.planId || 0)
  const routeIntent = (() => {
    const searchIntent = new URLSearchParams(location.search).get('intent')
    if (searchIntent === 'new' || searchIntent === 'run' || searchIntent === 'review') {
      return searchIntent
    }
    if (routeState.intent === 'new' || routeState.intent === 'run' || routeState.intent === 'review') {
      return routeState.intent
    }
    return routePlanId > 0 ? 'review' : 'new'
  })()
  const routeStep = normalizeGeneratorStep(new URLSearchParams(location.search).get('step'))
  const routePostType = new URLSearchParams(location.search).get('postType')
  const isPlanContext = Boolean(activePlanId || savedContentPlan || routePlanId)
  const showPlanBar = step !== 'settings' || isPlanContext
  const hasUnsavedPlanChanges = isPlanContext && lastSavedSnapshot !== '' && currentSnapshot !== lastSavedSnapshot

  const exitPlanEditor = useCallback(() => {
    setShowExitPrompt(false)
    clearPlanSelection()
    navigate('/plans')
  }, [clearPlanSelection, navigate])

  const hydratePlanFromSessions = useCallback(async (plan: SavedContentPlan) => {
    let nextPlan = plan
    let nextRules = plan.technical_rules

    if (plan.technical_session_id) {
      try {
        const technicalSessionRes = await endpoints.getChatSession(plan.technical_session_id)
        const technicalStructuredState = ((technicalSessionRes.data as any)?.structured_state || {}) as Record<string, unknown>
        const technicalRulesPayload = buildTechnicalRulesPayload({
          captured_answers: Array.isArray(technicalStructuredState.captured_answers)
            ? technicalStructuredState.captured_answers as Record<string, unknown>[]
            : [],
          field_mapping: normalizeTechnicalFieldMapping(technicalStructuredState.field_mapping),
        })

        if (!nextRules || technicalRulesPayload.length > 0 || String(technicalStructuredState.technical_summary || '').trim()) {
          nextRules = {
            id: nextRules?.id || 0,
            structure_hash: nextRules?.structure_hash || plan.structure_hash,
            still_valid: nextRules?.still_valid ?? plan.still_valid,
            summary: String(technicalStructuredState.technical_summary || nextRules?.summary || ''),
            rules: technicalRulesPayload.length > 0 ? technicalRulesPayload : (nextRules?.rules || []),
            updated_at: nextRules?.updated_at || null,
            source_session_id: nextRules?.source_session_id || null,
            session_id: plan.technical_session_id,
          }
        }
      } catch {
        // Keep the stored technical payload when session recovery fails.
      }
    }

    if (plan.content_session_id) {
      try {
        const contentSessionRes = await endpoints.getChatSession(plan.content_session_id)
        const contentStructuredState = ((contentSessionRes.data as any)?.structured_state || {}) as Record<string, unknown>
        const recoveredPlan = (contentStructuredState.content_plan as ContentPlan | undefined) || createEmptyContentPlan()
        const recoveredAnswers = Array.isArray(contentStructuredState.captured_answers)
          ? contentStructuredState.captured_answers as Record<string, unknown>[]
          : []
        const shouldRecoverPlan = !hasMeaningfulContentPlan(plan.content_plan) && hasMeaningfulContentPlan(recoveredPlan)
        const shouldRecoverAnswers = (!Array.isArray(plan.content_answers) || plan.content_answers.length === 0) && recoveredAnswers.length > 0
        const recoveredSummary = String(recoveredPlan.summary || plan.summary || '')

        if (shouldRecoverPlan || shouldRecoverAnswers || (!plan.summary && recoveredSummary)) {
          nextPlan = {
            ...nextPlan,
            summary: recoveredSummary,
            content_plan: shouldRecoverPlan ? recoveredPlan : nextPlan.content_plan,
            content_answers: shouldRecoverAnswers ? recoveredAnswers : nextPlan.content_answers,
          }
        }
      } catch {
        // Keep the stored content-plan payload when session recovery fails.
      }
    }

    return {
      plan: {
        ...nextPlan,
        technical_session_id: nextPlan.technical_session_id || nextRules?.session_id || null,
        technical_rules: nextRules,
      },
      technicalRules: nextRules,
    }
  }, [])

  const saveTechnicalRulesState = useCallback(async (stateOverride?: Record<string, unknown>) => {
    const currentConfig = configRef.current
    const sourceState = {
      ...createEmptyTechnicalState(),
      ...(stateOverride || technicalState),
    }

    if (!currentConfig.postType || !structureHash) {
      throw new Error(t('Structure data missing. Go back to Settings and continue from there.'))
    }

    const answers = buildTechnicalRulesPayload({
      captured_answers: Array.isArray(sourceState.captured_answers) ? sourceState.captured_answers as Record<string, unknown>[] : [],
      field_mapping: normalizeTechnicalFieldMapping(sourceState.field_mapping),
    })

    const res = await endpoints.savePostTypeRules({
      post_type: currentConfig.postType,
      taxonomy_scope: normalizeScopeValue(currentConfig.taxonomy) || undefined,
      term_scope: normalizeScopeValue(currentConfig.term) || undefined,
      selected_term_slugs: splitScopeList(currentConfig.term),
      structure_hash: structureHash,
      rules_json: answers,
      summary: String(sourceState.technical_summary || ''),
      technical_session_id: technicalSessionId || undefined,
    })

    const data = res.data as any
    const nextRules = (data.technical_rules || null) as SavedTechnicalRules | null
    if (nextRules) {
      setSavedTechnicalRules(nextRules)
      setTechnicalSessionId(nextRules.session_id)
    }

    return nextRules
  }, [structureHash, technicalSessionId, technicalState])

  const saveContentPlanState = useCallback(async (options?: {
    saveAsNew?: boolean
    contentStateOverride?: Record<string, unknown>
    technicalStateOverride?: Record<string, unknown>
  }) => {
    const currentConfig = configRef.current
    if (!currentConfig.postType || !currentConfig.reporterId) {
      throw new Error(t('Select a reporter before saving this plan.'))
    }

    const saveAsNew = Boolean(options?.saveAsNew)
    const nextContentState = normalizeContentPlanState(options?.contentStateOverride || contentState)
    const nextTechnicalState = options?.technicalStateOverride || technicalState

    const nextTechnicalRules = await saveTechnicalRulesState(nextTechnicalState)
    const resolvedTechnicalSessionId = nextTechnicalRules?.session_id
      || technicalSessionId
      || savedContentPlan?.technical_session_id
      || null

    const res = await endpoints.savePostTypeRules({
      id: saveAsNew ? undefined : activePlanId || undefined,
      post_type: currentConfig.postType,
      taxonomy_scope: normalizeScopeValue(currentConfig.taxonomy) || undefined,
      term_scope: normalizeScopeValue(currentConfig.term) || undefined,
      selected_term_slugs: splitScopeList(currentConfig.term),
      name: currentConfig.planName || undefined,
      reporter_id: currentConfig.reporterId,
      structure_hash: structureHash,
      content_plan: nextContentState.content_plan,
      content_answers: nextContentState.captured_answers,
      content_plan_summary: nextContentState.content_plan.summary || '',
      topic: currentConfig.topic,
      keywords: currentConfig.keywords,
      count: currentConfig.count,
      length: currentConfig.length,
      scheduling: currentConfig.scheduling,
      count_per_category: currentConfig.countPerCategory,
      generation_type: currentConfig.generationType,
      repeat_frequency: currentConfig.generationType === 'repeating' ? currentConfig.repeatFrequency : undefined,
      schedule_config: currentConfig.generationType === 'repeating'
        ? {
            interval: currentConfig.repeatEvery,
            unit: currentConfig.repeatUnit,
            weekdays: currentConfig.repeatWeekdays,
            hour: currentConfig.repeatHour,
            stop_after_months: currentConfig.stopAfterMonths,
            max_total_runs: currentConfig.maxTotalRuns,
            max_total_posts: currentConfig.maxTotalPosts,
          }
        : undefined,
      is_active: currentConfig.isActive,
      technical_session_id: resolvedTechnicalSessionId || undefined,
      content_session_id: contentSessionId || savedContentPlan?.content_session_id || undefined,
    })

    const data = res.data as any
    const nextPlan = (data.plan || null) as SavedContentPlan | null
    if (nextPlan) {
      if (saveAsNew && activePlanId && activePlanId !== nextPlan.id) {
        clearPlanEditorDraft(activePlanId)
      }
      setSavedContentPlan(nextPlan)
      setActivePlanId(nextPlan.id)
      setPlanIntent((current) => current === 'run' ? 'run' : 'review')
      setContentSessionId(nextPlan.content_session_id)
      setConfig((current) => ({
        ...current,
        planName: nextPlan.name || current.planName,
        scheduling: (nextPlan.publishing_schedule || current.scheduling) as 'none' | 'immediate' | 'daily' | 'weekly',
        ...normalizeScheduleConfig(nextPlan.schedule_config, nextPlan.repeat_frequency || 'weekly'),
        ...normalizeScheduleCounters(nextPlan.schedule_counters),
      }))
      setLastSavedSnapshot(buildPlanSnapshot({
        config: {
          ...currentConfig,
          planName: nextPlan.name || currentConfig.planName,
          scheduling: (nextPlan.publishing_schedule || currentConfig.scheduling) as 'none' | 'immediate' | 'daily' | 'weekly',
          ...normalizeScheduleConfig(nextPlan.schedule_config, nextPlan.repeat_frequency || 'weekly'),
          ...normalizeScheduleCounters(nextPlan.schedule_counters),
        },
        technicalState: nextTechnicalState,
        contentState: nextContentState,
        technicalSessionId: resolvedTechnicalSessionId,
        contentSessionId: nextPlan.content_session_id,
        activePlanId: nextPlan.id,
      }))
    }

    return nextPlan
  }, [activePlanId, contentSessionId, contentState, saveTechnicalRulesState, savedContentPlan?.content_session_id, savedContentPlan?.technical_session_id, structureHash, technicalSessionId, technicalState])

  const resolveReporter = useCallback(async (preferredReporterId?: number | null) => {
    const reportersRes = await endpoints.getReporters()
    const reporters = (reportersRes.data as Reporter[]) || []
    const selected = reporters.find((item) => item.id === (preferredReporterId || 0))
      || [...reporters]
        .sort((a, b) => new Date(b.last_used_at || 0).getTime() - new Date(a.last_used_at || 0).getTime())[0]
      || reporters.find((item) => Boolean(item.is_default))
      || reporters[0]
      || null

    return { reporters, selected }
  }, [])

  const loadFreshContext = useCallback(async (postType: string, reporterId?: number | null) => {
    const structureRes = await endpoints.getStructure(postType)
    const structure = structureRes.data as PostStructure
    const nextHash = structure.structure_hash || ''
    const { selected } = await resolveReporter(reporterId)

    if (selected && reporterId !== selected.id) {
      setConfig((current) => ({ ...current, reporterId: selected.id }))
    }

    setStructureHash(nextHash)
    setSelectedReporter(selected)
    setSavedTechnicalRules(null)
    setSavedContentPlan(null)
    setTechnicalSessionId(null)
    setContentSessionId(null)

    return {
      structureHash: nextHash,
      reporter: selected,
    }
  }, [resolveReporter])

  const loadAdaptiveContext = useCallback(async (postType: string, reporterId?: number | null) => {
    const structureRes = await endpoints.getStructure(postType)
    const structure = structureRes.data as PostStructure
    const nextHash = structure.structure_hash || ''
    const { selected } = await resolveReporter(reporterId)

    if (selected && reporterId !== selected.id) {
      setConfig((current) => ({ ...current, reporterId: selected.id }))
    }

    const rulesRes = await endpoints.getPostTypeRules(postType, {
      reporter_id: selected?.id,
      structure_hash: nextHash || undefined,
      taxonomy_scope: normalizeScopeValue(config.taxonomy) || undefined,
      term_scope: normalizeScopeValue(config.term) || undefined,
    })

    const rules = rulesRes.data as PostTypeRulesResponse
    setStructureHash(nextHash)
    setSelectedReporter(selected)
    setSavedTechnicalRules(rules.technical_rules)
    setSavedContentPlan(rules.content_plan)
    setTechnicalSessionId(rules.technical_rules?.session_id ?? null)
    setContentSessionId(rules.content_plan?.content_session_id ?? null)

    if (rules.content_plan) {
      setConfig((current) => ({
        ...current,
        planName: rules.content_plan?.name || current.planName,
        count: rules.content_plan?.post_count || current.count,
        countPerCategory: rules.content_plan?.count_per_category ?? current.countPerCategory,
        topic: rules.content_plan?.topic || current.topic,
        keywords: Array.isArray(rules.content_plan?.keywords) && rules.content_plan.keywords.length > 0
          ? rules.content_plan.keywords
          : current.keywords,
        length: rules.content_plan?.post_length || current.length,
        scheduling: (rules.content_plan?.publishing_schedule || current.scheduling) as 'none' | 'immediate' | 'daily' | 'weekly',
        generationType: rules.content_plan?.generation_type || current.generationType,
        repeatFrequency: rules.content_plan?.repeat_frequency || current.repeatFrequency,
        ...normalizeScheduleConfig(rules.content_plan?.schedule_config, rules.content_plan?.repeat_frequency || 'weekly'),
        ...normalizeScheduleCounters(rules.content_plan?.schedule_counters),
        isActive: rules.content_plan?.is_active ?? current.isActive,
      }))
    }

    return {
      structureHash: nextHash,
      reporter: selected,
      technicalRules: rules.technical_rules,
      contentPlan: rules.content_plan,
    }
  }, [config.taxonomy, config.term, resolveReporter])

  const loadPlanFromRoute = useCallback(async (planId: number, intent: 'run' | 'review', requestedStep: GeneratorStep) => {
    setLoadingStep(true)
    setStepError(null)

    try {
      const res = await endpoints.getContentPlanById(planId)
      const hydrated = await hydratePlanFromSessions(res.data as SavedContentPlan)
      const plan = hydrated.plan
      const storedDraft = readPlanEditorDraft(plan.id)
      let reporter: Reporter | null = null

      const reporterId = storedDraft?.config?.reporterId ?? plan.reporter_id

      if (reporterId) {
        try {
          const reporterRes = await endpoints.getReporter(reporterId)
          reporter = reporterRes.data as Reporter
        } catch {
          reporter = null
        }
      }

      setActivePlanId(plan.id)
      setPlanIntent(intent)
      setSavedContentPlan(plan)
      setSavedTechnicalRules(hydrated.technicalRules)
      setStructureHash(storedDraft?.structureHash || plan.structure_hash)
      setSelectedReporter(reporter)
      setGeneratedPosts([])
      const baseConfig: GenerationConfig = {
        postType: plan.post_type,
        taxonomy: normalizeScopeValue(plan.taxonomy_scope),
        term: normalizeScopeValue(plan.term_scope),
        planName: plan.name || '',
        reporterId: plan.reporter_id || null,
        count: plan.post_count || 10,
        countPerCategory: plan.count_per_category,
        topic: plan.topic || '',
        keywords: Array.isArray(plan.keywords) ? plan.keywords : [],
        length: plan.post_length || 'auto',
        scheduling: (plan.publishing_schedule || 'none') as 'none' | 'immediate' | 'daily' | 'weekly',
        generationType: plan.generation_type || 'one_time',
        repeatFrequency: plan.repeat_frequency || 'weekly',
        ...normalizeScheduleConfig(plan.schedule_config, plan.repeat_frequency || 'weekly'),
        ...normalizeScheduleCounters(plan.schedule_counters),
        isActive: plan.is_active,
      }
      const nextConfig: GenerationConfig = storedDraft?.config
        ? {
            ...baseConfig,
            ...storedDraft.config,
          }
        : baseConfig
      setConfig(nextConfig)

      const nextTechnicalState = storedDraft?.technicalState
        ? {
            ...createEmptyTechnicalState(),
            ...storedDraft.technicalState,
            captured_answers: Array.isArray(storedDraft.technicalState.captured_answers) ? storedDraft.technicalState.captured_answers : [],
            open_questions: Array.isArray(storedDraft.technicalState.open_questions) ? storedDraft.technicalState.open_questions : [],
          }
        : (hydrated.technicalRules ? { ...extractTechnicalStateFromSavedRules(hydrated.technicalRules) } : { ...createEmptyTechnicalState() })
      const nextContentState = storedDraft?.contentState
        ? normalizeContentPlanState(storedDraft.contentState)
        : {
            phase: 'content',
            ready: true,
            captured_answers: plan.content_answers,
            open_questions: [],
            content_plan: plan.content_plan,
          }

      const resolvedTechnicalSessionId = resolveDraftSessionId(
        storedDraft?.technicalSessionId,
        plan.technical_session_id,
        hydrated.technicalRules?.session_id,
      )
      const resolvedContentSessionId = resolveDraftSessionId(
        storedDraft?.contentSessionId,
        plan.content_session_id,
      )
      setTechnicalSessionId(resolvedTechnicalSessionId)
      setContentSessionId(resolvedContentSessionId)
      setTechnicalState(nextTechnicalState)
      setContentState(nextContentState)

      if (storedDraft?.intentState && Object.keys(storedDraft.intentState).length > 0) {
        setIntentState(storedDraft.intentState)
      }
      if (storedDraft?.intentSessionId) {
        setIntentSessionId(storedDraft.intentSessionId)
      }
      if (storedDraft?.sprintResult) {
        setSprintResult(storedDraft.sprintResult)
      }
      if (storedDraft?.sprintArticles && storedDraft.sprintArticles.length > 0) {
        setSprintArticles(storedDraft.sprintArticles)
      }

      setLastSavedSnapshot(buildPlanSnapshot({
        config: nextConfig,
        technicalState: nextTechnicalState,
        contentState: nextContentState,
        technicalSessionId: resolvedTechnicalSessionId,
        contentSessionId: resolvedContentSessionId,
        activePlanId: plan.id,
      }))

      if (intent === 'review') {
        const validReviewSteps: GeneratorStep[] = ['settings', 'technical', 'intent', 'content']
        const targetStep = validReviewSteps.includes(requestedStep) ? requestedStep : 'settings'
        setStep(targetStep)
      } else if (intent === 'run') {
        if (!storedDraft) {
          try {
            const updated = await endpoints.markContentPlanRun(plan.id)
            const refreshedPlan = updated.data as SavedContentPlan
            setSavedContentPlan(refreshedPlan)
          } catch {
            // Non-fatal. The plan can still run.
          }
        }
        const validRunSteps: GeneratorStep[] = ['settings', 'technical', 'intent', 'content']
        const targetRunStep = requestedStep && validRunSteps.includes(requestedStep as GeneratorStep)
          ? (requestedStep as GeneratorStep)
          : 'settings'
        setStep(targetRunStep)
      } else {
        setStep(storedDraft?.step || requestedStep)
      }
    } catch (error) {
      setStepError(getErrorMessage(error, t('Unknown error')))
      setStep('settings')
    } finally {
      setLoadingStep(false)
    }
  }, [hydratePlanFromSessions])

  useEffect(() => {
    const planId = routePlanId
    const intent = routeIntent
    const requestedStep = routeStep
    const loadKey = `${planId}:${intent || 'none'}`

    if (!planId || !intent || intent === 'new' || loadedLocationKeyRef.current === loadKey) {
      return
    }

    if (saveGenerationRef.current > lastLoadedSaveGenRef.current) {
      loadedLocationKeyRef.current = loadKey
      lastLoadedSaveGenRef.current = saveGenerationRef.current
      return
    }

    loadedLocationKeyRef.current = loadKey
    void loadPlanFromRoute(planId, intent, requestedStep)
  }, [loadPlanFromRoute, routeIntent, routePlanId, routeStep])

  useEffect(() => {
    if (activePlanId || routePlanId) {
      return
    }

    if (planIntent !== 'new') {
      setPlanIntent('new')
    }
  }, [activePlanId, planIntent, routePlanId])

  const appliedRoutePostTypeRef = useRef(false)
  useEffect(() => {
    if (appliedRoutePostTypeRef.current || !routePostType || routePlanId || activePlanId) {
      return
    }
    appliedRoutePostTypeRef.current = true
    setConfig((prev) => ({ ...prev, postType: routePostType }))
  }, [activePlanId, routePlanId, routePostType])

  const restoredNewFlowRef = useRef(false)
  useEffect(() => {
    if (restoredNewFlowRef.current || activePlanId || routePlanId) {
      return
    }

    if (planIntent !== 'new' || (step !== 'settings' && step !== 'select')) {
      return
    }

    const saved = readNewFlowDraft()
    if (!saved || !saved.config?.postType) {
      return
    }

    restoredNewFlowRef.current = true
    setRestoredDraft(true)
    setConfig(saved.config)
    setStep(saved.step)
    setTechnicalState(saved.technicalState || { ...createEmptyTechnicalState() })
    setContentState(saved.contentState || {
      phase: 'content',
      ready: false,
      captured_answers: [],
      open_questions: [],
      question_cards: [],
      question_responses: {},
      content_plan: createEmptyContentPlan(),
    })
    setTechnicalSessionId(saved.technicalSessionId)
    setContentSessionId(saved.contentSessionId)
    setStructureHash(saved.structureHash || '')
    if (saved.intentState) setIntentState(saved.intentState)
    if (saved.intentSessionId) setIntentSessionId(saved.intentSessionId)
    if (saved.sprintResult) setSprintResult(saved.sprintResult)
    if (saved.sprintArticles) setSprintArticles(saved.sprintArticles)

    // Restore active sprint job if we're on the sprint step
    if (saved.step === 'sprint') {
      const savedJobId = sessionStorage.getItem('bc:active-sprint-job')
      if (savedJobId) {
        setActiveSprintJobId(savedJobId)
      }
    }
  }, [activePlanId, planIntent, routePlanId, step])

  useEffect(() => {
    if (!activePlanId || !planIntent) {
      return
    }

    const nextSearch = buildGeneratorSearch({ planId: activePlanId, intent: planIntent, step })
    if (location.pathname === '/generate' && location.search === nextSearch) {
      return
    }

    navigate(`/generate${nextSearch}`, { replace: true })
  }, [activePlanId, location.pathname, location.search, navigate, planIntent, step])

  useEffect(() => {
    if (activePlanId || planIntent !== 'new') {
      return
    }

    const nextSearch = buildGeneratorSearch({ intent: 'new', step })
    if (location.pathname === '/generate' && location.search === nextSearch) {
      return
    }

    navigate(`/generate${nextSearch}`, { replace: true })
  }, [activePlanId, location.pathname, location.search, navigate, planIntent, step])

  useEffect(() => {
    if (!activePlanId || !planIntent) {
      return
    }

    writePlanEditorDraft(activePlanId, {
      version: 1,
      planId: activePlanId,
      intent: planIntent === 'run' ? 'run' : 'review',
      step,
      config,
      technicalState,
      contentState,
      technicalSessionId,
      contentSessionId,
      structureHash,
      intentState,
      intentSessionId,
      sprintResult,
      sprintArticles,
    })
  }, [activePlanId, config, contentSessionId, contentState, intentSessionId, intentState, planIntent, sprintArticles, sprintResult, step, structureHash, technicalSessionId, technicalState])

  useEffect(() => {
    if (activePlanId || planIntent !== 'new') {
      return
    }

    const hasProgress = (step !== 'settings' && step !== 'select') || config.postType !== ''
    if (!hasProgress) {
      return
    }

    writeNewFlowDraft({
      version: 1,
      timestamp: Date.now(),
      step,
      config,
      technicalState,
      contentState,
      technicalSessionId,
      contentSessionId,
      structureHash,
      intentState,
      intentSessionId,
      sprintResult,
      sprintArticles,
    })
  }, [activePlanId, config, contentSessionId, contentState, intentSessionId, intentState, planIntent, sprintArticles, sprintResult, step, structureHash, technicalSessionId, technicalState])

  const settingsCompletingRef = useRef(false)
  const previousConfigRef = useRef({ postType: config.postType, taxonomy: config.taxonomy, term: config.term, reporterId: config.reporterId })

  const handleSettingsComplete = useCallback(async () => {
    if (settingsCompletingRef.current) return
    settingsCompletingRef.current = true

    setLoadingStep(true)
    setStepError(null)
    try {
      const prev = previousConfigRef.current
      const postTypeChanged = config.postType !== prev.postType
      const taxonomyChanged = config.taxonomy !== prev.taxonomy || config.term !== prev.term
      const reporterChanged = config.reporterId !== prev.reporterId
      const structuralChange = postTypeChanged || taxonomyChanged

      previousConfigRef.current = { postType: config.postType, taxonomy: config.taxonomy, term: config.term, reporterId: config.reporterId }

      const shouldStayFresh = planIntent === 'new' && !activePlanId
      const isLoadedPlanSelection = Boolean(
        activePlanId
        && savedContentPlan
        && savedContentPlan.post_type === config.postType
        && normalizeScopeValue(savedContentPlan.taxonomy_scope) === normalizeScopeValue(config.taxonomy)
        && normalizeScopeValue(savedContentPlan.term_scope) === normalizeScopeValue(config.term)
      )

      if (structuralChange || shouldStayFresh) {
        if (shouldStayFresh) {
          await loadFreshContext(config.postType, config.reporterId)
        } else if (isLoadedPlanSelection) {
          const structureRes = await endpoints.getStructure(config.postType)
          const structure = structureRes.data as PostStructure
          const { selected } = await resolveReporter(config.reporterId)
          setStructureHash(structure.structure_hash || '')
          setSelectedReporter(selected)
        } else {
          await loadAdaptiveContext(config.postType, config.reporterId)
        }
      } else if (reporterChanged) {
        const { selected } = await resolveReporter(config.reporterId)
        setSelectedReporter(selected)
      }

      setStep('technical')
    } catch (error) {
      setStepError(getErrorMessage(error, t('Failed to load technical context')))
    } finally {
      setLoadingStep(false)
      settingsCompletingRef.current = false
    }
  }, [activePlanId, config.postType, config.reporterId, config.taxonomy, config.term, loadAdaptiveContext, loadFreshContext, planIntent, resolveReporter, savedContentPlan])

  const handleUseSavedTechnicalRules = useCallback(() => {
    const nextStep = useSprintMode ? 'intent' : 'content'
    if (!savedTechnicalRules) {
      setStep(nextStep)
      return
    }

    applySavedTechnicalRules(savedTechnicalRules)
    setStep(nextStep)
  }, [applySavedTechnicalRules, savedTechnicalRules, useSprintMode])

  const handleTechnicalContinue = useCallback(async (state: Record<string, unknown>) => {
    setTechnicalState(state)

    if (!structureHash) {
      setStepError(t('Structure data missing. Go back to Settings and continue from there.'))
      return
    }

    try {
      await saveTechnicalRulesState(state)
      setStep(useSprintMode ? 'intent' : 'content')
    } catch (error) {
      setStepError(getErrorMessage(error, t('Failed to save technical rules')))
    }
  }, [saveTechnicalRulesState, structureHash, useSprintMode])

  const handleReuseSavedContentPlan = useCallback(async () => {
    if (savedTechnicalRules) {
      applySavedTechnicalRules(savedTechnicalRules)
    }

    if (!savedContentPlan) {
      setStep('intent')
      return
    }

    applySavedContentPlan(savedContentPlan)

    if (activePlanId) {
      try {
        const updated = await endpoints.markContentPlanRun(activePlanId)
        setSavedContentPlan(updated.data as SavedContentPlan)
      } catch {
        // Non-fatal. Generation can continue.
      }
    }

    setStep('intent')
  }, [activePlanId, applySavedContentPlan, applySavedTechnicalRules, savedContentPlan, savedTechnicalRules])

  const primeContentStateFromSavedPlan = useCallback(() => {
    if (!savedContentPlan) {
      return
    }

    applySavedContentPlan(savedContentPlan)
  }, [applySavedContentPlan, savedContentPlan])

  const handleContentContinue = useCallback(async (state: Record<string, unknown>) => {
    setContentState(state)

    try {
      await saveContentPlanState({ contentStateOverride: state })
      setStep('intent')
    } catch (error) {
      setStepError(getErrorMessage(error, t('Failed to save content plan')))
    }
  }, [saveContentPlanState])

  const [saveToast, setSaveToast] = useState<string | null>(null)
  const toastTimerRef = useRef<number | null>(null)

  useEffect(() => {
    return () => {
      if (toastTimerRef.current) clearTimeout(toastTimerRef.current)
    }
  }, [])

  const handleSaveCurrentPlan = useCallback(async (saveAsNew = false) => {
    saveGenerationRef.current += 1
    setSavingPlanAction(saveAsNew ? 'saveAs' : 'save')
    setStepError(null)

    try {
      const savedPlan = await saveContentPlanState({ saveAsNew })
      if (saveAsNew) {
        setShowExitPrompt(false)
      }

      clearNewFlowDraft()

      if (savedPlan && planIntent === 'new') {
        setPlanIntent('review')
      }

      if (savedPlan?.id) {
        const intent = planIntent === 'run' ? 'run' : 'review'
        loadedLocationKeyRef.current = `${savedPlan.id}:${intent}`
        lastLoadedSaveGenRef.current = saveGenerationRef.current
      }

      setSaveToast(t('All current changes saved.'))
      if (toastTimerRef.current) clearTimeout(toastTimerRef.current)
      toastTimerRef.current = window.setTimeout(() => setSaveToast(null), 3000)

      return savedPlan
    } catch (error) {
      setStepError(getErrorMessage(error, t('Unknown error')))
      return null
    } finally {
      setSavingPlanAction(null)
    }
  }, [planIntent, saveContentPlanState])

  return (
    <PrerequisiteGuard>
    <div className="min-h-screen">
      <Header
        title={t('Create Content')}
        subtitle={t('Create new content using AI in your site\'s style')}
        helpContent={[
          t('Create content in 3 steps: Settings, Technical questions, and Intent collection.'),
          t('Settings: Choose the content type, reporter, topic, and keywords.'),
          t('Technical: Answer questions about your site\'s field structure.'),
          t('Intent: Define the audience, goals, and scope for the research.'),
          t('After Intent, the AI researches and generates articles — track progress in the Jobs tab.'),
        ]}
      />

      <div className="p-6">
        {showPlanBar && (
          <div className="mx-auto mb-4 max-w-6xl rounded-bc border border-bc-primary/20 bg-bc-primary-light px-4 py-4">
            {isPlanContext ? (
              <div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
                <div>
                  <div className="text-xs font-semibold uppercase tracking-wide text-bc-primary">
                    {t('Editing plan')}
                  </div>
                  <div className="mt-1 text-lg font-semibold text-bc-gray-800">
                    {config.planName || savedContentPlan?.name || t('Untitled Plan')}
                  </div>
                  <div className="mt-1 text-sm text-bc-gray-600">
                    {hasUnsavedPlanChanges
                      ? t('You have unsaved changes in this plan.')
                      : t('All current plan changes are saved.')}
                  </div>
                </div>
                <div className="flex flex-wrap gap-2">
                  <Button
                    size="sm"
                    variant="secondary"
                    loading={savingPlanAction === 'save'}
                    onClick={() => void handleSaveCurrentPlan(false)}
                    icon={<Save className="h-4 w-4" />}
                  >
                    {t('Save')}
                  </Button>
                  <Button
                    size="sm"
                    variant="ghost"
                    loading={savingPlanAction === 'saveAs'}
                    onClick={() => void handleSaveCurrentPlan(true)}
                    icon={<CopyPlus className="h-4 w-4" />}
                  >
                    {t('Save as new')}
                  </Button>
                  <Button
                    size="sm"
                    variant="ghost"
                    onClick={() => {
                      if (hasUnsavedPlanChanges) {
                        setShowExitPrompt(true)
                        return
                      }
                      exitPlanEditor()
                    }}
                    icon={<LogOut className="h-4 w-4" />}
                  >
                    {t('Exit')}
                  </Button>
                </div>
              </div>
            ) : (
              <div className="flex items-center justify-between gap-3">
                <span className="text-sm text-bc-gray-500">{t('New plan (unsaved)')}</span>
                <div className="flex gap-2 ms-auto">
                  <Button
                    size="sm"
                    variant="ghost"
                    loading={savingPlanAction === 'saveAs'}
                    onClick={() => void handleSaveCurrentPlan(true)}
                    icon={<Save className="h-4 w-4" />}
                  >
                    {t('Save plan')}
                  </Button>
                  <Button
                    size="sm"
                    variant="ghost"
                    onClick={() => {
                      if (confirm(t('Start over? All current progress will be lost.'))) {
                        handleStartOver()
                      }
                    }}
                    icon={<RotateCcw className="h-4 w-4" />}
                    className="text-bc-gray-400"
                  >
                    {t('Start over')}
                  </Button>
                </div>
              </div>
            )}
          </div>
        )}

        <StepsProgress currentStep={step} config={config} reporterLabel={selectedReporter?.name || savedContentPlan?.reporter_name || null} useSprintMode={useSprintMode} />

        {saveToast ? (
          <div className="mt-4 max-w-4xl mx-auto rounded-bc border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-700 flex items-start gap-2">
            <Save className="w-4 h-4 mt-0.5 shrink-0" />
            <span>{saveToast}</span>
          </div>
        ) : null}

        {stepError ? (
          <div className="mt-4 max-w-4xl mx-auto rounded-bc border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 flex items-start gap-2">
            <AlertCircle className="w-4 h-4 mt-0.5 shrink-0" />
            <span>{stepError}</span>
          </div>
        ) : null}

        <div className="mt-8">
          {loadingStep ? (
            <div className="py-16 flex items-center justify-center">
              <div className="rounded-bc-lg bg-white px-8 py-6 shadow-sm">
                <Loader size="lg" text={t('Loading...')} />
              </div>
            </div>
          ) : null}

          {!loadingStep && step === 'settings' && (
            <SettingsStep
              config={config}
              onUpdate={handleConfigUpdate}
              onComplete={() => void handleSettingsComplete()}
            />
          )}

          {!loadingStep && step === 'technical' && (
            <TechnicalStep
              config={config}
              reporter={selectedReporter}
              savedRules={savedTechnicalRules}
              forceEdit={forceTechnicalReview}
              sessionId={technicalSessionId}
              initialState={technicalState}
              useSprintMode={useSprintMode}
              onSessionChange={setTechnicalSessionId}
              onStateChange={setTechnicalState}
              onBack={() => setStep('settings')}
              onUseSavedRules={handleUseSavedTechnicalRules}
              onEditSavedRules={() => {
                setTechnicalSessionId(savedTechnicalRules?.session_id ?? null)
                setForceTechnicalReview(true)
              }}
              onContinue={(state) => void handleTechnicalContinue(state)}
              onSkip={() => setStep(useSprintMode ? 'intent' : 'content')}
            />
          )}

          {!loadingStep && step === 'content' && (
            <ContentStep
              config={config}
              reporter={selectedReporter}
              technicalSummary={conversationPayload.technicalSummary || ''}
              technicalAnswers={conversationPayload.technicalAnswers || []}
              savedPlan={savedContentPlan}
              forceEdit={forceContentReview}
              sessionId={contentSessionId}
              initialState={contentState}
              onSessionChange={setContentSessionId}
              onStateChange={setContentState}
              onBack={() => setStep('technical')}
              onReuseSavedPlan={handleReuseSavedContentPlan}
              onEditSavedPlan={() => {
                primeContentStateFromSavedPlan()
                setContentSessionId(savedContentPlan?.content_session_id ?? null)
                setForceContentReview(true)
              }}
              onStartFresh={() => {
                setSavedContentPlan(null)
                resetContentState()
                setForceContentReview(true)
              }}
              onContinue={(state) => void handleContentContinue(state)}
              onSkip={() => setStep('intent')}
            />
          )}

          {!loadingStep && step === 'intent' && (
            <IntentStep
              config={config}
              reporter={selectedReporter}
              savedPlan={savedContentPlan}
              sessionId={intentSessionId}
              initialState={intentState}
              onSessionChange={setIntentSessionId}
              onStateChange={setIntentState}
              onContinue={(state) => {
                if (submittingIntent) return
                void (async () => {
                  setIntentState(state)
                  setSubmittingIntent(true)
                  setStepError(null)
                  try {
                    const savedPlan = await handleSaveCurrentPlan(false)
                    const planId = savedPlan?.id || activePlanId

                    if (!planId) {
                      setStepError(t('Failed to save plan. Please try again.'))
                      setSubmittingIntent(false)
                      return
                    }

                    if (config.generationType === 'repeating') {
                      navigate('/jobs')
                    } else {
                      await endpoints.sprintGenerate({
                        post_type: config.postType,
                        taxonomy: config.taxonomy || undefined,
                        term: config.term || undefined,
                        reporter_id: config.reporterId,
                        topic: config.topic,
                        keywords: config.keywords,
                        count: config.count,
                        length: config.length,
                        intent_state: state,
                        technical_state: technicalState,
                        intent_session_id: intentSessionId,
                        plan_id: planId,
                      })
                      navigate('/jobs')
                    }
                  } catch (err) {
                    setSubmittingIntent(false)
                    setStepError(getErrorMessage(err, t('Unknown error')))
                  }
                })()
              }}
              onBack={() => setStep('technical')}
              onSkip={() => {
                if (submittingIntent) return
                void (async () => {
                  setSubmittingIntent(true)
                  setStepError(null)
                  try {
                    const savedPlan = await handleSaveCurrentPlan(false)
                    const planId = savedPlan?.id || activePlanId

                    if (!planId) {
                      setStepError(t('Failed to save plan. Please try again.'))
                      setSubmittingIntent(false)
                      return
                    }

                    if (config.generationType === 'repeating') {
                      navigate('/jobs')
                    } else {
                      await endpoints.sprintGenerate({
                        post_type: config.postType,
                        taxonomy: config.taxonomy || undefined,
                        term: config.term || undefined,
                        reporter_id: config.reporterId,
                        topic: config.topic,
                        keywords: config.keywords,
                        count: config.count,
                        length: config.length,
                        intent_state: intentState,
                        technical_state: technicalState,
                        intent_session_id: intentSessionId,
                        plan_id: planId,
                      })
                      navigate('/jobs')
                    }
                  } catch (err) {
                    setSubmittingIntent(false)
                    setStepError(getErrorMessage(err, t('Unknown error')))
                  }
                })()
              }}
              submitting={submittingIntent}
            />
          )}
        </div>

        {showExitPrompt ? (
          <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
            <div className="w-full max-w-lg rounded-bc-md bg-white p-6 shadow-2xl">
              <h3 className="text-xl font-semibold text-bc-gray-800">{t('Unsaved changes')}</h3>
              <p className="mt-2 text-sm text-bc-gray-600">
                {t('This plan has unsaved changes. Choose how you want to leave this editor.')}
              </p>
              <div className="mt-5 flex flex-wrap gap-2">
                <Button
                  size="sm"
                  loading={savingPlanAction === 'save'}
                  onClick={() => void handleSaveCurrentPlan(false).then((saved) => {
                    if (saved) {
                      exitPlanEditor()
                    }
                  })}
                  icon={<Save className="h-4 w-4" />}
                >
                  {t('Save')}
                </Button>
                <Button
                  size="sm"
                  variant="secondary"
                  loading={savingPlanAction === 'saveAs'}
                  onClick={() => void handleSaveCurrentPlan(true).then((saved) => {
                    if (saved) {
                      exitPlanEditor()
                    }
                  })}
                  icon={<CopyPlus className="h-4 w-4" />}
                >
                  {t('Save as new')}
                </Button>
                <Button size="sm" variant="ghost" onClick={exitPlanEditor}>
                  {t('Exit without saving')}
                </Button>
                <Button size="sm" variant="ghost" onClick={() => setShowExitPrompt(false)}>
                  {t('Cancel')}
                </Button>
              </div>
            </div>
          </div>
        ) : null}
      </div>
    </div>
    </PrerequisiteGuard>
  )
}
