import { useEffect, useMemo, useState } from 'react' import { AlertTriangle, CheckCircle2, HelpCircle, PenSquare } from 'lucide-react' import { Button, Card, Loader } from '../../components/common' import { endpoints, getErrorMessage } from '../../api/client' import { t, tf } from '../../lib/i18n' import type { MetaField, PostStructure, Reporter, SavedTechnicalRules } from '../../types' import type { GenerationConfig } from '../../components/generator' import { ConversationPhaseStep } from './ConversationPhaseStep' import { createEmptyTechnicalState, extractTechnicalStateFromSavedRules, getTechnicalClarificationCount, getTechnicalFieldMappingByCategory, mergeTechnicalFieldMapping, normalizeTechnicalFieldForEditing, normalizeTechnicalFieldMapping, normalizeOpenQuestions, type TechnicalContentStructureSection, type TechnicalFieldDiagnosis, type TechnicalFieldFillBehavior, type TechnicalFieldMappingEntry, } from './conversation-utils' interface TechnicalStepProps { config: GenerationConfig reporter: Reporter | null savedRules: SavedTechnicalRules | null forceEdit: boolean sessionId: string | null initialState: Record useSprintMode?: boolean onSessionChange: (sessionId: string) => void onStateChange: (state: Record) => void onBack: () => void onUseSavedRules: () => void onEditSavedRules: () => void onContinue: (state: Record) => void onSkip: () => void } interface TechnicalSampleSeo { plugin?: string meta_title?: string meta_description?: string focus_keyword?: string } interface TechnicalSampleFeaturedImage { id?: number | null url?: string } interface TechnicalSample { id?: number title?: string content?: string excerpt?: string meta?: Record seo?: TechnicalSampleSeo featured_image?: TechnicalSampleFeaturedImage | null } const CAPTURED_KEY_TRANSLATIONS: Record = { post_content_structure: 'Post content structure', seo_fields_generation: 'SEO fields generation', featured_image_generation: 'Featured image generation', excerpt_generation: 'Excerpt generation', content_structure: 'Content structure', content_structure_flexibility: 'Content structure flexibility', content_length: 'Content length', body_structure: 'Body structure', gallery_images: 'Gallery images', taxonomy_assignment: 'Taxonomy assignment', category_assignment: 'Category assignment', meta_fields_handling: 'Meta fields handling', field_fill_strategy: 'Field fill strategy', excerpt_handling: 'Excerpt handling', image_generation: 'Image generation', seo_handling: 'SEO handling', seo_and_image_generation: 'SEO and image generation', seo_fields: 'SEO fields', seo_generation: 'SEO generation', generate_seo_fields: 'Generate SEO fields', content_flexibility: 'Content flexibility', structure_flexibility: 'Structure flexibility', fill_behavior: 'Fill behavior', footnotes_usage: 'Footnotes usage', footnotes_handling: 'Footnotes handling', footnotes: 'Footnotes', generate_footnotes: 'Generate footnotes', internal_links: 'Internal links', external_links: 'External links', linking_strategy: 'Linking strategy', featured_image: 'Featured image', generate_featured_image: 'Generate featured image', excerpt: 'Excerpt', generate_excerpt: 'Generate excerpt', post_author: 'Post author', author: 'Author', question_id: 'Question', answer: 'Answer', field: 'Field', scope: 'Scope', rule: 'Rule', value: 'Value', } const CAPTURED_VALUE_TRANSLATIONS: Record = { completely_flexible: 'Completely flexible', always_generate: 'Always generate', never_generate: 'Never generate', generate_when_missing: 'Generate when missing', follow_pattern: 'Follow pattern', follow_existing_pattern: 'Follow existing pattern', always_fill: 'Always fill', fill_when_relevant: 'Fill when relevant', do_not_fill: 'Do not fill', skip: 'Skip', skipped: 'Skipped', always: 'Always', never: 'Never', when_relevant: 'When relevant', strict: 'Strict', varied: 'Varied', free: 'Free', flexible: 'Flexible', strict_structure: 'Strict structure', flexible_structure: 'Flexible structure', yes: 'Yes', no: 'No', enabled: 'Enabled', disabled: 'Disabled', relevant: 'When relevant', choose_from_existing: 'Choose from existing', never_generate_footnotes: 'Never generate footnotes', always_generate_footnotes: 'Always generate footnotes', generate_when_relevant: 'Generate when relevant', auto: 'Auto', } function translateCapturedKey(raw: string): string { const exactKey = raw.trim().toLowerCase().replace(/\s+/g, '_') if (CAPTURED_KEY_TRANSLATIONS[exactKey]) { return t(CAPTURED_KEY_TRANSLATIONS[exactKey]) } for (const [dictKey, translation] of Object.entries(CAPTURED_KEY_TRANSLATIONS)) { if (exactKey.includes(dictKey) || dictKey.includes(exactKey)) { return t(translation) } } const cleaned = raw.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()) return t(cleaned) } function translateCapturedValue(raw: string): string { const normalized = raw.trim().toLowerCase().replace(/\s+/g, '_') const englishKey = CAPTURED_VALUE_TRANSLATIONS[normalized] return englishKey ? t(englishKey) : raw.replace(/_/g, ' ') } function createBaseDiagnosis(partial?: Partial): TechnicalFieldDiagnosis { return { description: '', sample_values: [], fill_rate: '', pattern: '', gemini_plan: '', confidence: 'medium', needs_clarification: false, fill_behavior: 'relevant', ...partial, } } function formatSectionSource(source: TechnicalContentStructureSection['source']): string { return source === 'new' ? t('New section') : t('Found in samples') } function formatFillRate(filled: number, total: number): string { if (total <= 0) { return '0/0 (0%)' } return `${filled}/${total} (${Math.round((filled / total) * 100)}%)` } function stringifySampleValue(value: unknown): string { if (value === null || value === undefined) { return '' } if (Array.isArray(value)) { if (value.length === 0) { return '' } const preview = value .map((item) => stringifySampleValue(item)) .filter(Boolean) .slice(0, 3) .join(', ') return preview || String(value.length) } if (typeof value === 'object') { const raw = value as Record if (typeof raw.slug === 'string') { return raw.slug } if (typeof raw.name === 'string') { return raw.name } try { return JSON.stringify(value) } catch { return '' } } return String(value).trim() } function collectSampleValues(values: unknown[]): string[] { return values .map((value) => stringifySampleValue(value)) .filter(Boolean) .slice(0, 5) } function countFilledValues(values: unknown[]): number { return values.filter((value) => stringifySampleValue(value) !== '').length } function buildCoreFieldMapping(samples: TechnicalSample[]): TechnicalFieldMappingEntry[] { const totalSamples = samples.length const titles = collectSampleValues(samples.map((sample) => sample.title)) const excerpts = collectSampleValues(samples.map((sample) => sample.excerpt)) const featuredIds = collectSampleValues(samples.map((sample) => sample.featured_image?.id)) return [ { field_key: 'post_title', field_label: t('Title'), field_type: 'text', field_source: 'core', diagnosis: createBaseDiagnosis({ sample_values: titles, fill_rate: formatFillRate(countFilledValues(samples.map((sample) => sample.title)), totalSamples), }), }, { field_key: 'post_content', field_label: t('Main Content'), field_type: 'wysiwyg', field_source: 'core', diagnosis: createBaseDiagnosis({ fill_rate: formatFillRate(countFilledValues(samples.map((sample) => sample.content)), totalSamples), content_structure: { common_sections: [], structure_flexibility: 'varied', note: '', }, }), }, { field_key: 'post_excerpt', field_label: t('Excerpt'), field_type: 'textarea', field_source: 'core', diagnosis: createBaseDiagnosis({ sample_values: excerpts, fill_rate: formatFillRate(countFilledValues(samples.map((sample) => sample.excerpt)), totalSamples), }), }, { field_key: '_thumbnail_id', field_label: t('Featured Image'), field_type: 'media', field_source: 'core', diagnosis: createBaseDiagnosis({ sample_values: featuredIds, fill_rate: formatFillRate(countFilledValues(samples.map((sample) => sample.featured_image?.id)), totalSamples), }), }, ] } function buildSeoFieldMapping(samples: TechnicalSample[]): TechnicalFieldMappingEntry[] { const totalSamples = samples.length return [ { field_key: '_yoast_wpseo_title', field_label: t('SEO Title'), field_type: 'text', field_source: 'seo', diagnosis: createBaseDiagnosis({ sample_values: collectSampleValues(samples.map((sample) => sample.seo?.meta_title)), fill_rate: formatFillRate(countFilledValues(samples.map((sample) => sample.seo?.meta_title)), totalSamples), }), }, { field_key: '_yoast_wpseo_metadesc', field_label: t('Meta Description'), field_type: 'textarea', field_source: 'seo', diagnosis: createBaseDiagnosis({ sample_values: collectSampleValues(samples.map((sample) => sample.seo?.meta_description)), fill_rate: formatFillRate(countFilledValues(samples.map((sample) => sample.seo?.meta_description)), totalSamples), }), }, { field_key: '_yoast_wpseo_focuskw', field_label: t('Focus Keyword'), field_type: 'text', field_source: 'seo', diagnosis: createBaseDiagnosis({ sample_values: collectSampleValues(samples.map((sample) => sample.seo?.focus_keyword)), fill_rate: formatFillRate(countFilledValues(samples.map((sample) => sample.seo?.focus_keyword)), totalSamples), }), }, ] } function buildCustomFieldMapping(metaFields: MetaField[], samples: TechnicalSample[]): TechnicalFieldMappingEntry[] { const totalSamples = samples.length return metaFields.map((field) => ({ field_key: field.name, field_label: field.title || field.name, field_type: field.type || 'text', field_source: field.source || 'custom', diagnosis: createBaseDiagnosis({ sample_values: collectSampleValues(samples.map((sample) => sample.meta?.[field.name])), fill_rate: formatFillRate(countFilledValues(samples.map((sample) => sample.meta?.[field.name])), totalSamples), fill_behavior: field.required ? 'always' : 'relevant', }), })) } function buildFieldInventory(structure: PostStructure | null, samples: TechnicalSample[]): TechnicalFieldMappingEntry[] { if (!structure) { return [] } return [ ...buildCoreFieldMapping(samples), ...buildSeoFieldMapping(samples), ...buildCustomFieldMapping(structure.meta_fields || [], samples), ] } function getConfidenceBadge(confidence: string) { if (confidence === 'high') { return { label: t('Confidence: High'), icon: , className: 'bg-emerald-50 text-emerald-700 border border-emerald-200', } } if (confidence === 'low') { return { label: t('Confidence: Low'), icon: , className: 'bg-amber-50 text-amber-700 border border-amber-200', } } return { label: t('Confidence: Medium'), icon: , className: 'bg-yellow-50 text-yellow-700 border border-yellow-200', } } function getFillBehaviorLabel(value: TechnicalFieldFillBehavior): string { if (value === 'always') { return t('Always fill') } if (value === 'never') { return t('Do not fill this field') } return t('Fill when relevant') } function getFieldKeyPreview(entries: TechnicalFieldMappingEntry[]): { visibleKeys: string[]; hiddenCount: number } { const keys = entries .map((entry) => entry.field_key.trim()) .filter(Boolean) return { visibleKeys: keys.slice(0, 6), hiddenCount: Math.max(0, keys.length - 6), } } function FieldMappingRow({ entry, isEditing, canEdit, onEdit, onCancel, onChange, }: { entry: TechnicalFieldMappingEntry isEditing: boolean canEdit: boolean onEdit: () => void onCancel: () => void onChange: (nextEntry: TechnicalFieldMappingEntry) => void }) { const normalized = normalizeTechnicalFieldForEditing(entry) const badge = getConfidenceBadge(normalized.diagnosis.confidence) const contentStructure = normalized.diagnosis.content_structure const seoNote = contentStructure?.note || t('SEO note: Google prefers structural variety between posts') return (
{normalized.field_key} {normalized.field_label}
{normalized.field_type} {t('Field source')}: {t(normalized.field_source)} {getFillBehaviorLabel(normalized.diagnosis.fill_behavior)}
{badge.icon} {badge.label} {canEdit && !isEditing ? ( ) : null}
{t('Description')}:{' '} {normalized.diagnosis.description || '—'}
{t('Structural rule')}:{' '} {normalized.diagnosis.gemini_plan || '—'}
{t('Fill rate')}: {normalized.diagnosis.fill_rate || '—'} {t('Pattern detected')}: {normalized.diagnosis.pattern || '—'}
{normalized.diagnosis.sample_values.length > 0 ? (
{t('Sample values')}
{normalized.diagnosis.sample_values.map((value, index) => ( {value} ))}
) : null} {normalized.diagnosis.needs_clarification ? (
{t('Needs clarification')}
{normalized.diagnosis.question || '—'}
) : null} {normalized.field_key === 'post_content' ? (
{t('Content Structure')}
{contentStructure?.common_sections?.length ? (
{t('Common sections')}
{contentStructure.common_sections.map((section: TechnicalContentStructureSection, index: number) => (
{section.heading || t('Unnamed section')}
{section.type ? {t('Section type')}: {section.type} : null} {section.frequency ? {t('Frequency')}: {section.frequency} : null} {section.approx_length ? {t('Approx. length')}: {section.approx_length} : null} {t('Source')}: {formatSectionSource(section.source)}
))}
) : null}
{t('Structure Flexibility')}: {t( contentStructure?.structure_flexibility === 'strict' ? 'Strict — follow detected pattern closely' : contentStructure?.structure_flexibility === 'free' ? 'Free — minimal structure guidance' : 'Varied — use patterns as inspiration (recommended)', )}
{seoNote}
) : null}
{isEditing ? (