import { join, dirname } from 'path'
import { readdir, writeFile, mkdir } from 'fs/promises'

const PORT = 3456
const PIPELINE_ROOT = process.env.PIPELINE_ROOT ||
  join(import.meta.dir, '..', 'data', 'pipeline')
const INTEL_ROOT = join(import.meta.dir, '..', 'data', 'intelligence')
const CONTENT_ROOT = join(import.meta.dir, '..', 'data', 'content')
const CONTENT_PRODUCTS_ROOT = join(CONTENT_ROOT, 'products')
const CONTENT_WORKFLOW_ROOT = join(CONTENT_ROOT, 'workflow')
const RESEARCH_PROFILE_ROOT = join(import.meta.dir, '..', 'data', 'research-profiles')
const SOURCE_ROOT = join(import.meta.dir, '..', 'data', 'sources')
const GENERATED_ASSET_ROOT = join(import.meta.dir, '..', 'data', 'generated-assets')
const DASHBOARD_EXPORT_ROOT = join(import.meta.dir, '..', 'content_population_exports')
const MIGRATION_EXPORT_ROOT = join(import.meta.dir, '..', '..', 'JV Migration to shopify', 'content_population_exports')
const MIGRATION_PRODUCTS_CSV = join(import.meta.dir, '..', '..', 'JV Migration to shopify', 'products_import_lewis_v4.csv')
const ASSET_ROOT = process.env.ASSET_ROOT || join(import.meta.dir, '..', '..', '..', 'jv-assets')
const DIST_ROOT = join(import.meta.dir, '..', 'dist')
// Editorial dashboard (separate Vite build mounted under /editorial/*)
const EDITORIAL_DIST_ROOT = join(import.meta.dir, '..', 'dist-editorial')
const PILOT_EXTENDED_ROOT = join(import.meta.dir, '..', 'data', 'content', 'pilot-extended')
const COMPETITOR_EXTRACTS_ROOT = join(import.meta.dir, '..', 'data', 'sources', 'competitor-section-extracts')
const PRODUCT_TRUTH_ROOT = join(import.meta.dir, '..', 'data', 'sources', 'jv-product-truth')
const IS_PROD = process.env.NODE_ENV === 'production'

const cors = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, PUT, POST, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type',
  'Content-Type': 'application/json'
}

async function readJson(path: string) {
  const f = Bun.file(path)
  if (!(await f.exists())) return null
  return f.json()
}

async function readText(path: string) {
  const f = Bun.file(path)
  if (!(await f.exists())) return null
  return f.text()
}

function redactRaw(value: any) {
  const text = typeof value === 'string' ? value : JSON.stringify(value, null, 2)
  return String(text || '')
    .replace(/apify_api_[A-Za-z0-9]+/g, '[REDACTED_APIFY_TOKEN]')
    .replace(/apify_proxy_[A-Za-z0-9]+/g, '[REDACTED_APIFY_PROXY]')
    .replace(/("?(?:password|token|api[_-]?key|authorization)"?\s*:\s*")([^"]+)(")/gi, '$1[REDACTED]$3')
    .replace(/(Bearer\s+)[A-Za-z0-9._-]+/gi, '$1[REDACTED]')
}

function clipText(text: any, limit = 12000) {
  const value = redactRaw(text)
  return {
    text: value.length > limit ? `${value.slice(0, limit)}\n\n[... clipped ${value.length - limit} characters in dashboard preview ...]` : value,
    chars: value.length,
    clipped: value.length > limit,
    limit
  }
}

const RAW_EVIDENCE_ITEM_LIMIT = Math.min(1000, Math.max(100, Number(Bun.env.RAW_EVIDENCE_ITEM_LIMIT || 1000)))
const RAW_EVIDENCE_BLOCK_LIMIT = Math.min(1000, Math.max(100, Number(Bun.env.RAW_EVIDENCE_BLOCK_LIMIT || 250)))
const RAW_EVIDENCE_PREVIEW_CHARS = Math.min(20000, Math.max(2500, Number(Bun.env.RAW_EVIDENCE_PREVIEW_CHARS || 8000)))

async function rawFileBlock(filePath: string, label: string, limit = 12000) {
  const file = Bun.file(filePath)
  if (!(await file.exists())) return null
  return {
    label,
    path: filePath,
    bytes: file.size,
    preview: {
      text: '',
      chars: file.size,
      clipped: file.size > limit,
      limit: 0
    }
  }
}

function normaliseApifyItems(items: any) {
  if (Array.isArray(items)) return items
  if (Array.isArray(items?.value)) return items.value
  return []
}

function compactComprehensiveItem(item: any) {
  return {
    sku: item?.sku,
    sourceType: item?.sourceType,
    label: item?.label,
    competitor: item?.competitor || null,
    requestedUrl: item?.requestedUrl,
    loadedUrl: item?.loadedUrl,
    statusCode: item?.statusCode,
    title: item?.title,
    h1: item?.h1,
    headings: item?.headings || [],
    prices: item?.prices || [],
    reviewLike: item?.reviewLike || [],
    textPreview: clipText(item?.text || '', 1400).text,
    textChars: item?.textChars || String(item?.text || '').length,
    htmlChars: item?.htmlChars || 0,
    capturedAt: item?.capturedAt
  }
}

function compactSpecialistItem(job: string, item: any) {
  if (String(job || '').includes('amazon-reviews')) {
    return {
      sku: item?.sku,
      matchedAsin: item?.matchedAsin,
      productAsin: item?.productAsin,
      matchContext: item?.matchContext,
      matchNote: item?.matchNote,
      reviewTitle: item?.reviewTitle,
      reviewDescription: item?.reviewDescription,
      ratingScore: item?.ratingScore,
      reviewId: item?.reviewId,
      date: item?.date,
      reviewedIn: item?.reviewedIn,
      reviewUrl: item?.reviewUrl,
      isVerified: item?.isVerified,
      variant: item?.variant,
      totalCategoryRatings: item?.totalCategoryRatings,
      totalCategoryReviews: item?.totalCategoryReviews
    }
  }
  if (job === 'trustpilot') {
    return {
      title: item?.title,
      text: item?.text || item?.reviewText || item?.content,
      rating: item?.rating,
      date: item?.date || item?.publishedDate,
      url: item?.url || item?.reviewUrl,
      consumerName: item?.consumerName || item?.author
    }
  }
  if (job === 'reddit') {
    return {
      sku: item?.sku,
      title: item?.title,
      text: item?.text || item?.body || item?.selftext,
      url: item?.url || item?.permalink,
      subreddit: item?.subreddit,
      score: item?.score,
      createdAt: item?.createdAt || item?.created_utc
    }
  }
  if (String(job || '').includes('serp')) {
    return {
      sku: item?.sku,
      sourceType: item?.sourceType,
      label: item?.label,
      matchContext: item?.matchContext,
      searchQuery: item?.searchQuery,
      url: item?.url,
      conclusion: item?.conclusion,
      organicResults: (item?.organicResults || []).slice(0, 10).map((row: any) => ({
        title: row?.title,
        websiteTitle: row?.websiteTitle,
        url: row?.url,
        displayedUrl: row?.displayedUrl,
        description: row?.description,
        position: row?.position
      })),
      note: item?.note
    }
  }
  if (job === 'healthspan-retailer') {
    return {
      sku: item?.sku,
      sourceType: item?.sourceType,
      label: item?.label,
      competitor: item?.competitor,
      retailer: item?.retailer,
      matchContext: item?.matchContext,
      requestedUrl: item?.requestedUrl,
      loadedUrl: item?.loadedUrl,
      title: item?.title,
      h1: item?.h1,
      headings: item?.headings || [],
      prices: item?.prices || [],
      textPreview: clipText(item?.text || '', 1800).text,
      textChars: item?.textChars,
      htmlChars: item?.htmlChars,
      note: item?.note
    }
  }
  return {
    preview: clipText(item, 5000).text,
    chars: clipText(item, 5000).chars,
    clipped: clipText(item, 5000).clipped
  }
}

function compactAmazonItem(item: any) {
  const rating = typeof item?.rating === 'object' ? item.rating : null
  return {
    type: item?.type || null,
    rankGroup: item?.rank_group ?? null,
    rankAbsolute: item?.rank_absolute ?? null,
    title: item?.title || null,
    url: item?.url || null,
    asin: item?.data_asin || item?.asin || null,
    priceFrom: item?.price_from ?? item?.price ?? null,
    currency: item?.currency || null,
    rating: rating?.value ?? item?.rating ?? null,
    votesCount: rating?.votes_count ?? item?.votes_count ?? null,
    boughtPastMonth: item?.bought_past_month ?? null,
    isSponsored: item?.is_sponsored ?? item?.type === 'amazon_paid'
  }
}

function compactRawEvidencePayload(sections: any[]) {
  return sections.map(section => ({
    ...section,
    displayLimits: {
      itemLimit: RAW_EVIDENCE_ITEM_LIMIT,
      rawBlockLimit: RAW_EVIDENCE_BLOCK_LIMIT,
      previewChars: RAW_EVIDENCE_PREVIEW_CHARS
    },
    itemCount: (section.items || []).length,
    rawBlockCount: (section.rawBlocks || []).length,
    files: (section.files || []).map((file: any) => ({
      label: file.label,
      path: file.path,
      bytes: file.bytes,
      chars: file.preview?.chars ?? file.chars,
      clipped: file.preview?.clipped ?? file.clipped,
      previewLimit: file.preview?.limit
    })),
    items: (section.items || []).slice(0, RAW_EVIDENCE_ITEM_LIMIT),
    rawBlocks: (section.rawBlocks || []).slice(0, RAW_EVIDENCE_BLOCK_LIMIT).map((block: any) => {
      const preview = clipText(block.body || '', RAW_EVIDENCE_PREVIEW_CHARS)
      return {
        title: block.title,
        url: block.url,
        body: preview.text,
        chars: block.chars ?? preview.chars,
        clipped: Boolean(block.clipped || preview.clipped),
        previewLimit: preview.limit
      }
    })
  }))
}

function collectAmazonRows(node: any, rows: any[] = []) {
  if (!node || rows.length >= RAW_EVIDENCE_ITEM_LIMIT) return rows
  if (Array.isArray(node)) {
    for (const child of node) collectAmazonRows(child, rows)
    return rows
  }
  if (typeof node === 'object') {
    if ((node.title && node.url && (node.data_asin || node.rank_group || node.type)) || node.data_asin) {
      rows.push(compactAmazonItem(node))
    }
    for (const value of Object.values(node)) collectAmazonRows(value, rows)
  }
  return rows
}

function filterRawRowsForSku(sku: string, rows: any[]) {
  const rules: Record<string, RegExp> = {
    'JV-TURMERIC500': /turmeric|curcumin|bioperine|black pepper/i,
    'JV-D1000': /vitamin d|vitamin d3|1000\s?iu|effervescent|fizz|colecalciferol/i,
    'JV-DEFF1000': /vitamin d|vitamin d3|1000\s?iu|effervescent|fizz|colecalciferol/i,
    'JV-VISISOFT': /visisoft|lutein|zeaxanthin|bilberry|vision|eye/i
  }
  const rule = rules[sku]
  if (!rule) return rows
  const filtered = rows.filter(row => rule.test(JSON.stringify(row)))
  return filtered.length ? filtered : rows
}

function filterStrictRawRowsForSku(sku: string, rows: any[]) {
  const rules: Record<string, RegExp> = {
    'JV-TURMERIC500': /turmeric|curcumin|bioperine|black pepper/i,
    'JV-D1000': /vitamin d|vitamin d3|1000\s?iu|effervescent|fizz|colecalciferol|deficiency|iu/i,
    'JV-DEFF1000': /vitamin d|vitamin d3|1000\s?iu|effervescent|fizz|colecalciferol|deficiency|iu/i,
    'JV-VISISOFT': /visisoft|lutein|zeaxanthin|bilberry|vision|eye/i
  }
  const rule = rules[sku]
  return rule ? rows.filter(row => rule.test(JSON.stringify(row))) : rows
}

function sourceSkuAliases(sku: string) {
  if (sku === 'JV-DEFF1000') return ['JV-DEFF1000', 'JV-D1000']
  if (sku === 'COLLAGEN-NPD') return ['COLLAGEN-NPD', 'JV-COLLAGEN400']
  return [sku]
}

const imageCarouselTypes = [
  { key: '01-hero-outcome', label: 'Hero Outcome Image' },
  { key: '02-offer-value-prop', label: 'Offer / Value Proposition Image' },
  { key: '03-social-proof-trust', label: 'Social Proof / Trust Image' },
  { key: '04-product-clarity-feature', label: 'Product Clarity / Feature Image' },
  { key: '05-problem-solution-transformation', label: 'Problem / Solution Transformation Image' },
  { key: '06-lifestyle-identity', label: 'Lifestyle / Identity Image' }
]

async function getProductImageGenerationSet(sku: string) {
  const imageSourceSku = sku === 'JV-DEFF1000' ? 'JV-D1000' : sku
  const truth = await getJvDatabaseTruth(imageSourceSku)
  const directRoot = join(GENERATED_ASSET_ROOT, 'top3-direct-imagegen-final', sku)
  const promptSummaryPath = join(GENERATED_ASSET_ROOT, 'top3-direct-imagegen-final', 'top3-direct-imagegen-summary.json')
  const promptLockPath = join(GENERATED_ASSET_ROOT, 'top3-direct-imagegen-final', 'locked-article-prompt-format.md')
  const generated = []
  for (const type of imageCarouselTypes) {
    const filename = `${type.key}.png`
    const filePath = join(directRoot, filename)
    const file = Bun.file(filePath)
    if (await file.exists()) {
      generated.push({
        ...type,
        filename,
        url: `/api/generated-product-image/${sku}/${filename}`,
        bytes: file.size
      })
    } else {
      generated.push({
        ...type,
        filename,
        url: null,
        missing: true
      })
    }
  }
  const promptSummary = await readJson(promptSummaryPath)
  return {
    sku,
    generatedAt: new Date().toISOString(),
    source: {
      database: MIGRATION_PRODUCTS_CSV,
      requestedSku: sku,
      imageSourceSku,
      correctionNote: sku === 'JV-DEFF1000'
        ? 'DEFF1000 migration image is a non-JV effervescent tube; dashboard image source is corrected to the current Just Vitamins pouch reference from JV-D1000.'
        : null,
      title: truth?.product?.title || null,
      handle: truth?.product?.handle || null,
      liveImageUrl: truth?.product?.imageSrc || null,
      localOriginalUrl: `/api/original-product-image/${sku}`
    },
    lockedPrompt: {
      path: promptLockPath,
      summaryPath: promptSummaryPath,
      mode: promptSummary?.mode || 'direct_imagegen_in_image_product',
      note: 'Article prompt format is locked. These are direct in-image generations: product was requested inside the ImageGen output, not post-composited into the final assets.'
    },
    generated,
    ready: generated.every(row => !row.missing)
  }
}

function parseCsv(text: string) {
  const rows: string[][] = []
  let row: string[] = []
  let cell = ''
  let quoted = false
  for (let i = 0; i < text.length; i++) {
    const char = text[i]
    const next = text[i + 1]
    if (char === '"') {
      if (quoted && next === '"') {
        cell += '"'
        i++
      } else {
        quoted = !quoted
      }
    } else if (char === ',' && !quoted) {
      row.push(cell)
      cell = ''
    } else if ((char === '\n' || char === '\r') && !quoted) {
      if (char === '\r' && next === '\n') i++
      row.push(cell)
      if (row.some(value => value !== '')) rows.push(row)
      row = []
      cell = ''
    } else {
      cell += char
    }
  }
  row.push(cell)
  if (row.some(value => value !== '')) rows.push(row)
  const headers = rows.shift() || []
  return rows.map(values => Object.fromEntries(headers.map((header, index) => [header, values[index] || ''])))
}

async function getJvDatabaseTruth(sku: string) {
  const text = await readText(MIGRATION_PRODUCTS_CSV)
  if (!text) return null
  const rows = parseCsv(text)
  const variants = rows.filter((row: any) => row['Variant SKU'] === sku || String(row['Variant SKU'] || '').startsWith(`${sku}-`))
  if (!variants.length) return null
  const master = variants.find((row: any) => row.Title || row['Body HTML']) || variants[0]
  return {
    sourceOfTruth: 'JV migration database export / products_import_lewis_v4.csv',
    sourcePath: MIGRATION_PRODUCTS_CSV,
    sku,
    product: {
      handle: master.Handle,
      title: master.Title,
      bodyHtml: master['Body HTML'],
      tags: master.Tags,
      imageSrc: master['Image Src'],
      status: master.Status,
      titleTag: master['Metafield: title_tag [string]'],
      descriptionTag: master['Metafield: description_tag [string]'],
      ingredients: master['Metafield: pdp.ingredients [rich_text_field]'],
      recommendedIntake: master['Metafield: pdp.recommended_intake [single_line_text_field]'],
      strength: master['Metafield: custom.strength [single_line_text_field]'] || master['Metafield: filter.strength [single_line_text_field]'],
      strapline: master['Metafield: pdp.strapline [single_line_text_field]'],
      format: master['Metafield: filter.format [single_line_text_field]'],
      legalCategory: master['Metafield: custom.legal_category [single_line_text_field]'],
      dimensions: master['Metafield: custom.dimensions [single_line_text_field]'],
      quickCode: master['Metafield: code.quickcode [single_line_text_field]'],
      uspList: master['Metafield: usp.list [list.single_line_text_field]'],
      warning: master['Metafield: pdp.warning [multi_line_text_field]'],
      allergens: master['Metafield: pdp.allergens [rich_text_field]'],
      guid: master['Metafield: code.guid [single_line_text_field]'],
      oldUrl: master['Metafield: temp.url [single_line_text_field]']
    },
    variants: variants.map((row: any) => ({
      sku: row['Variant SKU'],
      option: row['Option1 Value'],
      price: row['Variant Price'],
      compareAtPrice: row['Variant Compare At Price'],
      barcode: row['Variant Barcode'],
      inventoryQty: row['Variant Inventory Qty'],
      weight: row['Variant Weight'],
      weightUnit: row['Variant Weight Unit'],
      binLocation: row['Variant Metafield: shipping.bin_location [single_line_text_field]'],
      largeLetterFriendly: row['Variant Metafield: shipping.large_letter_friendly [boolean]'],
      packMultiplier: row['Variant Metafield: shipping.pack_multiplier [single_line_text_field]']
    }))
  }
}

async function getRawEvidence(sku: string) {
  const sections: any[] = []
  const files: any[] = []
  const pushFile = (block: any) => {
    if (block) files.push({ label: block.label, path: block.path, bytes: block.bytes, chars: block.preview.chars, clipped: block.preview.clipped })
    return block
  }
  const aliases = sourceSkuAliases(sku)
  const internetSourceAliases = sku === 'JV-D1000' ? [] : aliases

  const jvTruth = await getJvDatabaseTruth(sku)
  sections.push({
    key: 'jv_truth',
    label: 'JV database product truth',
    status: jvTruth ? 'source_of_truth_available' : 'missing',
    files: jvTruth ? [{ label: 'JV migration product export', path: MIGRATION_PRODUCTS_CSV, bytes: Bun.file(MIGRATION_PRODUCTS_CSV).size }] : [],
    items: jvTruth ? [jvTruth.product, ...jvTruth.variants] : [],
    rawBlocks: jvTruth ? [{ title: 'JV database product truth row + variants', body: clipText(jvTruth, 18000).text, chars: clipText(jvTruth, 18000).chars, clipped: clipText(jvTruth, 18000).clipped }] : []
  })

  const officialPath = sku === 'JV-D1000' ? '' : join(SOURCE_ROOT, 'top3-public-evidence', `${sku}.json`)
  const officialBlock = officialPath ? pushFile(await rawFileBlock(officialPath, 'Top-3 public product evidence', 16000)) : null
  sections.push({
    key: 'official',
    label: 'Official / public product evidence',
    status: sku === 'JV-D1000' ? 'skipped_stale_effervescent_evidence_moved_to_jv_deff1000' : (officialBlock ? 'available' : 'missing'),
    files: officialBlock ? [officialBlock] : [],
    rawBlocks: officialBlock ? [{ title: officialBlock.label, body: officialBlock.preview.text, chars: officialBlock.preview.chars, clipped: officialBlock.preview.clipped }] : []
  })

  const apifyMainPath = join(SOURCE_ROOT, 'apify', 'raw', 'top3-market-competitor-capped-apify.json')
  const apifyMain = await readJson(apifyMainPath)
  const apifyItems = normaliseApifyItems(apifyMain?.items)
  const apifyItem = apifyItems.find((item: any) => internetSourceAliases.includes(item?.sku))
  const apifyBlocks: any[] = []
  if (apifyItem) {
    apifyBlocks.push({
      title: `${apifyItem.competitor || 'Competitor'} raw scraped page text`,
      url: apifyItem.loadedUrl || apifyItem.url,
      body: clipText({
        sku: apifyItem.sku,
        competitor: apifyItem.competitor,
        url: apifyItem.url,
        loadedUrl: apifyItem.loadedUrl,
        title: apifyItem.title,
        h1: apifyItem.h1,
        headings: apifyItem.headings,
        prices: apifyItem.prices,
        bullets: apifyItem.bullets,
        text: apifyItem.text
      }, 18000).text,
      chars: clipText(apifyItem.text || '', 18000).chars,
      clipped: clipText(apifyItem.text || '', 18000).clipped
    })
  }
  const apifyFiles = []
  const apifyMainBlock = pushFile(await rawFileBlock(apifyMainPath, 'Apify capped competitor crawl raw run', 8000))
  if (apifyMainBlock) apifyFiles.push(apifyMainBlock)
  if (sku === 'JV-VISISOFT') {
    const visisoftPath = join(SOURCE_ROOT, 'apify', 'raw', 'top3-visisoft-competitor-healthspan-capped-apify.json')
    const visisoftBlock = pushFile(await rawFileBlock(visisoftPath, 'Apify Healthspan capped one-request attempt', 8000))
    if (visisoftBlock) apifyFiles.push(visisoftBlock)
  }
  sections.push({
    key: 'apify',
    label: 'Apify competitor raw scrape',
    status: apifyItem ? 'scraped' : (apifyMain ? 'run_available_no_item_for_sku' : 'missing'),
    run: apifyMain ? {
      actor: apifyMain.actor,
      runId: apifyMain.runId,
      datasetId: apifyMain.datasetId,
      status: apifyMain.status,
      statusMessage: apifyMain.statusMessage,
      requestCap: apifyMain.requestCap,
      itemCount: apifyMain.itemCount,
      computeUnits: apifyMain.computeUnits
    } : null,
    items: apifyItem ? [apifyItem] : [],
    files: apifyFiles,
    rawBlocks: apifyBlocks
  })

  const comprehensiveApifyFiles = [
    join(SOURCE_ROOT, 'apify', 'raw', 'top3-comprehensive-source-scrape-latest.json'),
    join(SOURCE_ROOT, 'apify', 'raw', 'top3-social-source-scrape-latest.json')
  ]
  const comprehensiveItems: any[] = []
  const comprehensiveRawBlocks: any[] = []
  const comprehensiveFiles = []
  for (const filePath of comprehensiveApifyFiles) {
    const raw = await readJson(filePath)
    const block = pushFile(await rawFileBlock(filePath, `Apify comprehensive/source run: ${filePath.split(/[\\/]/).pop()}`, 8000))
    if (block) comprehensiveFiles.push(block)
    const rows = Array.isArray(raw?.items) ? raw.items : []
    const matching = rows.filter((item: any) => item.sku === sku || item.sku === 'ALL_TOP3' || (sku === 'JV-DEFF1000' && item.sku === 'JV-D1000'))
    comprehensiveItems.push(...matching.map(compactComprehensiveItem))
    for (const item of matching) {
      comprehensiveRawBlocks.push({
        title: `${item.sourceType}: ${item.label}`,
        url: item.loadedUrl || item.requestedUrl,
        body: clipText({
          sourceType: item.sourceType,
          label: item.label,
          title: item.title,
          h1: item.h1,
          metaDescription: item.metaDescription,
          headings: item.headings,
          prices: item.prices,
          reviewLike: item.reviewLike,
          text: item.text
        }, 16000).text,
        chars: clipText(item.text || '', 16000).chars,
        clipped: clipText(item.text || '', 16000).clipped
      })
    }
  }
  sections.push({
    key: 'apify_comprehensive',
    label: 'Apify comprehensive raw source scrape',
    status: comprehensiveItems.length ? 'captured' : (comprehensiveFiles.length ? 'run_available_no_item_for_sku' : 'missing'),
    files: comprehensiveFiles,
    items: comprehensiveItems,
    rawBlocks: comprehensiveRawBlocks
  })

  const specialistRawDir = join(SOURCE_ROOT, 'apify', 'raw')
  const specialistApifyFiles = (await readdir(specialistRawDir).catch(() => []))
    .filter((file: string) => /^top3-specialist-.*-latest\.json$/.test(file))
    .sort()
    .map((file: string) => join(specialistRawDir, file))
  const specialistItems: any[] = []
  const specialistBlocks: any[] = []
  const specialistFiles = []
  const matchesSpecialist = (item: any, job: string) => {
    if (sku === 'COLLAGEN-NPD') return item?.sku === 'COLLAGEN-NPD'
    if (item?.sku === sku || item?.sku === 'ALL_TOP3') return true
    if (item?.sku) return false
    if (String(job || '').includes('amazon-reviews')) return false
    if (job === 'trustpilot') return true
    return filterStrictRawRowsForSku(sku, [item]).length > 0
  }
  for (const filePath of specialistApifyFiles) {
    const raw = await readJson(filePath)
    if (!raw) continue
    const block = pushFile(await rawFileBlock(filePath, `Apify specialist run: ${raw.job || filePath.split(/[\\/]/).pop()}`, 8000))
    if (block) specialistFiles.push(block)
    const rows = Array.isArray(raw.items) ? raw.items : []
    const matching = rows.filter((item: any) => matchesSpecialist(item, raw.job))
    for (const item of matching) {
      const wrapped = { job: raw.job, actor: raw.actor, runId: raw.run?.id, item: compactSpecialistItem(raw.job, item) }
      specialistItems.push(wrapped)
      specialistBlocks.push({
        title: `${raw.job}: ${item.title || item.url || item.id || 'raw item'}`,
        url: item.url || item.link || item.businessUrl || null,
        body: clipText(wrapped, 12000).text,
        chars: clipText(wrapped, 12000).chars,
        clipped: clipText(wrapped, 12000).clipped
      })
    }
  }
  sections.push({
    key: 'apify_specialists',
    label: 'Apify specialist raw scrapes',
    status: specialistItems.length ? 'captured' : (specialistFiles.length ? 'run_available_no_item_for_sku' : 'missing'),
    files: specialistFiles,
    items: specialistItems,
    rawBlocks: specialistBlocks
  })

  if (sku === 'COLLAGEN-NPD') {
    const collagenCompetitorDir = join(SOURCE_ROOT, 'competitor-pdps')
    const competitorFiles = (await readdir(collagenCompetitorDir).catch(() => []))
      .filter((file: string) => file.endsWith('.json'))
      .sort()
      .map((file: string) => join(collagenCompetitorDir, file))
    const competitorBlocks = []
    const competitorItems = []
    for (const filePath of competitorFiles) {
      const raw = await readJson(filePath)
      const brand = raw?.brand || filePath.split(/[\\/]/).pop()?.replace(/\.json$/, '')
      const pages = Array.isArray(raw?.pages) ? raw.pages : []
      competitorItems.push({
        brand,
        domain: raw?.domain,
        pages: pages.length,
        captured: pages.filter((page: any) => page.ok).length,
        sourceFile: filePath.split(/[\\/]/).pop()
      })
      const block = pushFile(await rawFileBlock(filePath, `Collagen competitor PDP raw: ${brand}`, 12000))
      if (block) competitorBlocks.push(block)
    }
    sections.push({
      key: 'collagen_competitors',
      label: 'Collagen competitor PDP/raw positioning captures',
      status: competitorItems.length ? 'captured' : 'missing',
      files: competitorBlocks,
      items: competitorItems,
      rawBlocks: competitorBlocks.map(block => ({ title: block.label, body: block.preview.text, chars: block.preview.chars, clipped: block.preview.clipped }))
    })
  }

  const dataforseoDir = join(SOURCE_ROOT, 'dataforseo', 'products_raw')
  const dataforseoFiles = (await readdir(dataforseoDir).catch(() => []))
    .filter((file: string) => file.endsWith('.json') && internetSourceAliases.some(alias => file.includes(alias)))
    .sort()
  const dataforseoBlocks = []
  const amazonRows = []
  for (const file of dataforseoFiles) {
    const filePath = join(dataforseoDir, file)
    const raw = await readJson(filePath)
    amazonRows.push(...collectAmazonRows(raw).map(row => ({ ...row, rawFile: file })))
    const block = pushFile(await rawFileBlock(filePath, `DataForSEO Amazon products raw: ${file}`, 7000))
    if (block) dataforseoBlocks.push(block)
  }
  const amazonSnippetPath = join(SOURCE_ROOT, 'amazon', 'amazon-uk-vitamin-d3-k2-public-snippet-2026-05-19.json')
  const amazonSnippetBlock = internetSourceAliases.includes('JV-D1000') ? pushFile(await rawFileBlock(amazonSnippetPath, 'Amazon UK public Vitamin D category snippet', 10000)) : null
  if (amazonSnippetBlock) dataforseoBlocks.push(amazonSnippetBlock)
  sections.push({
    key: 'amazon',
    label: 'Amazon / DataForSEO raw marketplace data',
    status: dataforseoBlocks.length ? 'available' : 'missing',
    files: dataforseoBlocks,
    items: filterRawRowsForSku(sku, amazonRows).slice(0, RAW_EVIDENCE_ITEM_LIMIT),
    rawBlocks: dataforseoBlocks.map(block => ({ title: block.label, body: block.preview.text, chars: block.preview.chars, clipped: block.preview.clipped }))
  })

  const redditPath = join(SOURCE_ROOT, 'reddit', 'supplements-vitamin-d-turmeric-public-snippets-2026-05-19.json')
  const redditRaw = await readJson(redditPath)
  const redditBlock = pushFile(await rawFileBlock(redditPath, 'Reddit public snippets raw capture', 12000))
  const redditRows = filterStrictRawRowsForSku(sku, Array.isArray(redditRaw?.items) ? redditRaw.items : Array.isArray(redditRaw?.snippets) ? redditRaw.snippets : Array.isArray(redditRaw) ? redditRaw : [])
  const redditPulse = await readJson(join(INTEL_ROOT, sku, 'reddit-pulse.json'))
  sections.push({
    key: 'reddit',
    label: 'Reddit raw snippets and public source links',
    status: redditRows.length ? 'raw_snippets_available' : (redditPulse ? 'generated_public_links_available' : 'missing'),
    files: redditBlock ? [redditBlock] : [],
    items: redditRows,
    generatedPulse: redditRows.length ? null : redditPulse,
    rawBlocks: redditRows.length
      ? [{ title: 'Filtered Reddit raw snippet rows for this SKU', body: clipText(redditRows, 14000).text, chars: clipText(redditRows, 14000).chars, clipped: clipText(redditRows, 14000).clipped }]
      : (redditPulse ? [{ title: 'Generated Reddit pulse with public source links', body: clipText(redditPulse, 14000).text, chars: clipText(redditPulse, 14000).chars, clipped: clipText(redditPulse, 14000).clipped }] : [])
  })

  return {
    sku,
    generatedAt: new Date().toISOString(),
    note: `Raw source material is shown for inspection. Dashboard item caps are raised for top-3 research: up to ${RAW_EVIDENCE_ITEM_LIMIT} rows and ${RAW_EVIDENCE_BLOCK_LIMIT} raw blocks per section, with redacted/clipped previews for browser stability. File paths are listed for full local inspection.`,
    sections: compactRawEvidencePayload(sections),
    files
  }
}

async function readLatestDailyStatus() {
  try {
    const files = (await readdir(DASHBOARD_EXPORT_ROOT))
      .filter((file: string) => /^daily_status_\d{4}-\d{2}-\d{2}\.json$/.test(file))
      .sort()
    const latest = files.at(-1)
    return latest ? readJson(join(DASHBOARD_EXPORT_ROOT, latest)) : null
  } catch (_) {
    return null
  }
}

function safeSku(value: string) {
  if (!/^[A-Z0-9_-]+$/i.test(value)) return null
  return value
}

function normaliseListField(value: any) {
  if (Array.isArray(value)) return value.map((item: any) => String(item).trim()).filter(Boolean)
  if (value === undefined || value === null || value === '') return []
  if (typeof value === 'string') return value.split(',').map(item => item.trim()).filter(Boolean)
  return [String(value)]
}

function normaliseProductContent(data: any) {
  const metafields = data?.metafields || {}
  for (const field of ['filterDietaryTag', 'filterHealthGoals', 'pdpProductBenefits']) {
    if (field in metafields) metafields[field] = normaliseListField(metafields[field])
  }
  return { ...data, metafields }
}

async function writeJson(path: string, data: any) {
  await mkdir(dirname(path), { recursive: true })
  await writeFile(path, JSON.stringify(data, null, 2) + '\n', 'utf8')
}

async function listProductContent() {
  try {
    const files = (await readdir(CONTENT_PRODUCTS_ROOT)).filter((file: string) => file.endsWith('.json'))
    const rows = []
    for (const file of files) {
      const data = await readJson(join(CONTENT_PRODUCTS_ROOT, file))
      if (data) rows.push(data)
    }
    return rows.sort((a: any, b: any) => String(a.sku).localeCompare(String(b.sku)))
  } catch (_) {
    return []
  }
}

async function getExportStatus() {
  const products = await listProductContent()
  const metaobjects = await readJson(join(CONTENT_ROOT, 'metaobjects.json'))
  const collections = await readJson(join(CONTENT_ROOT, 'smart-collections.json'))
  const handoffManifest = await readJson(join(DASHBOARD_EXPORT_ROOT, 'handoff_manifest.json'))
  const readinessAudit = await readJson(join(DASHBOARD_EXPORT_ROOT, 'content_population_readiness.json'))
  const intelligenceWorkqueue = await readJson(join(DASHBOARD_EXPORT_ROOT, 'intelligence_generation_workqueue.json'))
  const handoffActionPlan = await readJson(join(DASHBOARD_EXPORT_ROOT, 'handoff_action_plan.json'))
  const handoffPacketRefresh = await readJson(join(DASHBOARD_EXPORT_ROOT, 'handoff_packet_refresh.json'))
  const handoffArtifactFreshness = await readJson(join(DASHBOARD_EXPORT_ROOT, 'handoff_artifact_freshness.json'))
  const unmatchedClaimsReview = await readJson(join(DASHBOARD_EXPORT_ROOT, 'unmatched_claims_review_packet.json'))
  const productApprovalReview = await readJson(join(DASHBOARD_EXPORT_ROOT, 'product_approval_review_packet.json'))
  const productApprovalDecisionGate = await readJson(join(DASHBOARD_EXPORT_ROOT, 'product_approval_decision_gate.json'))
  const executiveDecisionBrief = await readJson(join(DASHBOARD_EXPORT_ROOT, 'executive_decision_brief.json'))
  const blockerBurndown = await readJson(join(DASHBOARD_EXPORT_ROOT, 'blocker_burndown.json'))
  const handoffEvidenceDossier = await readJson(join(DASHBOARD_EXPORT_ROOT, 'handoff_evidence_dossier.json'))
  const reviewerDecisionWorkbook = await readJson(join(DASHBOARD_EXPORT_ROOT, 'reviewer_decision_workbook.json'))
  const reviewerInputPreflight = await readJson(join(DASHBOARD_EXPORT_ROOT, 'reviewer_input_preflight.json'))
  const ownerHandoffMessages = await readJson(join(DASHBOARD_EXPORT_ROOT, 'owner_handoff_messages.json'))
  const ownerResponseIntake = await readJson(join(DASHBOARD_EXPORT_ROOT, 'owner_response_intake.json'))
  const handoffGateDependencyMap = await readJson(join(DASHBOARD_EXPORT_ROOT, 'handoff_gate_dependency_map.json'))
  const postOwnerResponseRerunPlan = await readJson(join(DASHBOARD_EXPORT_ROOT, 'post_owner_response_rerun_plan.json'))
  const ownerEvidenceAcceptanceGuide = await readJson(join(DASHBOARD_EXPORT_ROOT, 'owner_evidence_acceptance_guide.json'))
  const dailyStatus = await readLatestDailyStatus()
  const stagingHandoffPacket = await readJson(join(DASHBOARD_EXPORT_ROOT, 'staging_handoff_packet.json'))
  const stagingChecklist = await readJson(join(DASHBOARD_EXPORT_ROOT, 'staging_import_checklist.json'))
  const stagingEvidence = await readJson(join(DASHBOARD_EXPORT_ROOT, 'staging_evidence_validation.json'))
  const stagingEvidenceIntake = await readJson(join(DASHBOARD_EXPORT_ROOT, 'staging_evidence_intake.json'))
  const handoffVerification = await readJson(join(DASHBOARD_EXPORT_ROOT, 'handoff_verification_run.json'))
  const deliveryIndex = await readJson(join(DASHBOARD_EXPORT_ROOT, 'delivery_index.json'))
  const sourceAcquisitionPlan = await readJson(join(DASHBOARD_EXPORT_ROOT, 'source_acquisition_plan.json'))
  const dataforseoPilot = await readJson(join(DASHBOARD_EXPORT_ROOT, 'dataforseo_pilot_manifest.json'))
  const dataforseoLiveConnection = await readJson(join(DASHBOARD_EXPORT_ROOT, 'dataforseo_live_connection.json'))
  const dataforseoProductsLiveRun = await readJson(join(DASHBOARD_EXPORT_ROOT, 'dataforseo_products_live_run.json'))
  const dataforseoReviewsLivePilot = await readJson(join(DASHBOARD_EXPORT_ROOT, 'dataforseo_reviews_live_pilot.json'))
  const dataforseoReviewsDocsStatus = await readJson(join(DASHBOARD_EXPORT_ROOT, 'dataforseo_reviews_docs_status.json'))
  const dataforseoSupportEscalation = await readJson(join(DASHBOARD_EXPORT_ROOT, 'dataforseo_support_escalation.json'))
  const dataforseoSupportResponseGate = await readJson(join(DASHBOARD_EXPORT_ROOT, 'dataforseo_support_response_gate.json'))
  const dataforseoApprovalRequest = await readJson(join(DASHBOARD_EXPORT_ROOT, 'dataforseo_stage1_approval_request.json'))
  const manualAmazonReviewCapture = await readJson(join(DASHBOARD_EXPORT_ROOT, 'manual_amazon_review_capture.json'))
  const manualAmazonReviewCaptureValidation = await readJson(join(DASHBOARD_EXPORT_ROOT, 'manual_amazon_review_capture_validation.json'))
  const dataforseoIngest = await readJson(join(DASHBOARD_EXPORT_ROOT, 'dataforseo_ingest_validation.json'))
  const dataforseoAsinLookup = await readJson(join(DASHBOARD_EXPORT_ROOT, 'dataforseo_asin_lookup_queue.json'))
  const dataforseoAsinCandidates = await readJson(join(DASHBOARD_EXPORT_ROOT, 'dataforseo_asin_candidate_report.json'))
  const dataforseoAsinWebEvidence = await readJson(join(DASHBOARD_EXPORT_ROOT, 'dataforseo_asin_web_evidence.json'))
  const dataforseoUnresolvedAsinEvidence = await readJson(join(DASHBOARD_EXPORT_ROOT, 'dataforseo_unresolved_asin_evidence.json'))
  const dataforseoAsinConfirmations = await readJson(join(DASHBOARD_EXPORT_ROOT, 'dataforseo_asin_confirmation_report.json'))
  const dataforseoAsinPromotion = await readJson(join(DASHBOARD_EXPORT_ROOT, 'dataforseo_asin_promotion_report.json'))
  const dataforseoTaskPreflight = await readJson(join(DASHBOARD_EXPORT_ROOT, 'dataforseo_task_preflight.json'))
  const apifyGapfill = await readJson(join(DASHBOARD_EXPORT_ROOT, 'apify_gapfill_manifest.json'))
  const apifyTaskPreflight = await readJson(join(DASHBOARD_EXPORT_ROOT, 'apify_task_preflight.json'))
  const sourcePricingReferences = await readJson(join(DASHBOARD_EXPORT_ROOT, 'source_acquisition_pricing_references.json'))
  const sourceCostScenarios = await readJson(join(DASHBOARD_EXPORT_ROOT, 'source_acquisition_cost_scenarios.json'))
  const sourceBudgetGate = await readJson(join(DASHBOARD_EXPORT_ROOT, 'source_acquisition_budget_gate.json'))
  const sourceCostPlan = await readJson(join(DASHBOARD_EXPORT_ROOT, 'source_acquisition_cost_plan.json'))
  const sourceApprovalGate = await readJson(join(DASHBOARD_EXPORT_ROOT, 'source_acquisition_approval_gate.json'))
  const sourceSpendGate = await readJson(join(DASHBOARD_EXPORT_ROOT, 'source_acquisition_spend_gate.json'))
  const dataforseoPilotReview = await readJson(join(DASHBOARD_EXPORT_ROOT, 'dataforseo_pilot_review.json'))
  const sourceAcquisitionControlRun = await readJson(join(DASHBOARD_EXPORT_ROOT, 'source_acquisition_control_run.json'))
  const sourceSubscriptionDecision = await readJson(join(DASHBOARD_EXPORT_ROOT, 'source_subscription_decision.json'))
  const sourceAcquisitionNextActions = await readJson(join(DASHBOARD_EXPORT_ROOT, 'source_acquisition_next_actions.json'))
  const clinicalBlankDecisionTemplate = await readJson(join(DASHBOARD_EXPORT_ROOT, 'clinical_blank_decision_template.json'))
  const clinicalBlankDecisionFile = await readJson(join(CONTENT_ROOT, 'compliance', 'clinical-blank-decisions.json'))
  const clinicalBlankDecisionBySku = new Map((clinicalBlankDecisionFile?.decisions || []).map((row: any) => [row.sku, row]))
  const isApprovedBlankClinicalDecision = (decision: any) => decision?.decision === 'approved_blank' &&
    Boolean(decision.approvedBy) &&
    Boolean(decision.approvedAt) &&
    Boolean(decision.rationale) &&
    decision.constraintsAcknowledged === true
  const assetStatus = await getAssetStatus()
  const blockers: any[] = []
  for (const product of products) {
    const m = product.metafields || {}
    if (!m.pdpClinicallyShownTo && !isApprovedBlankClinicalDecision(clinicalBlankDecisionBySku.get(product.sku))) blockers.push({ sku: product.sku, type: 'compliance', message: 'No clinically_shown_to reference or approved blank compliance decision yet' })
    for (const field of ['pdpResults', 'pdpResultsMobile', 'pdpHowWeCompare']) {
      if (!m[field]) blockers.push({ sku: product.sku, type: 'asset', message: `${field} file reference missing` })
    }
    if (!product.approval || product.approval.content !== 'approved') blockers.push({ sku: product.sku, type: 'approval', message: 'Content not approved' })
  }
  for (const item of assetStatus.missingReferences || []) {
    blockers.push({ sku: item.owner, type: 'asset_reference', message: `${item.field} filename missing` })
  }
  for (const item of assetStatus.missingUploads || []) {
    blockers.push({ sku: item.owner, type: 'asset_upload', message: `${item.field} file not found/uploaded: ${item.filename}` })
  }
  return {
    generatedAt: new Date().toISOString(),
    productCount: products.length,
    metaobjectDefinitions: Object.keys(metaobjects?.definitions || {}).length,
    collectionMappings: collections?.collections?.length || 0,
    handoffManifest,
    readinessAudit: readinessAudit ? {
      ready: readinessAudit.ready,
      summary: readinessAudit.summary,
      gates: readinessAudit.gates,
      nextActions: readinessAudit.nextActions
    } : null,
    intelligenceWorkqueue: intelligenceWorkqueue ? {
      ready: intelligenceWorkqueue.ready,
      totalProducts: intelligenceWorkqueue.totalProducts,
      missingTasks: intelligenceWorkqueue.missingTasks,
      affectedSkus: intelligenceWorkqueue.affectedSkus,
      gateSummary: intelligenceWorkqueue.gateSummary,
      topSkus: intelligenceWorkqueue.topSkus
    } : null,
    handoffActionPlan: handoffActionPlan ? {
      ready: handoffActionPlan.ready,
      counts: handoffActionPlan.counts,
      actions: handoffActionPlan.actions
    } : null,
    handoffPacketRefresh: handoffPacketRefresh ? {
      refreshReady: handoffPacketRefresh.refreshReady,
      handoffReady: handoffPacketRefresh.handoffReady,
      objective: handoffPacketRefresh.objective,
      counts: handoffPacketRefresh.counts,
      steps: handoffPacketRefresh.steps
    } : null,
    handoffArtifactFreshness: handoffArtifactFreshness ? {
      ready: handoffArtifactFreshness.ready,
      note: handoffArtifactFreshness.note,
      refreshStartedAt: handoffArtifactFreshness.refreshStartedAt,
      counts: handoffArtifactFreshness.counts,
      staleRows: handoffArtifactFreshness.staleRows,
      rows: handoffArtifactFreshness.rows
    } : null,
    unmatchedClaimsReview: unmatchedClaimsReview ? {
      ready: unmatchedClaimsReview.ready,
      purpose: unmatchedClaimsReview.purpose,
      rule: unmatchedClaimsReview.rule,
      counts: unmatchedClaimsReview.counts,
      rows: unmatchedClaimsReview.rows
    } : null,
    productApprovalReview: productApprovalReview ? {
      ready: productApprovalReview.ready,
      purpose: productApprovalReview.purpose,
      counts: productApprovalReview.counts,
      rows: productApprovalReview.rows
    } : null,
    productApprovalDecisionGate: productApprovalDecisionGate ? {
      ready: productApprovalDecisionGate.ready,
      decisionFile: productApprovalDecisionGate.decisionFile,
      counts: productApprovalDecisionGate.counts,
      guardrail: productApprovalDecisionGate.guardrail,
      rows: productApprovalDecisionGate.rows
    } : null,
    executiveDecisionBrief: executiveDecisionBrief ? {
      ready: executiveDecisionBrief.ready,
      purpose: executiveDecisionBrief.purpose,
      statusLine: executiveDecisionBrief.statusLine,
      counts: executiveDecisionBrief.counts,
      decisions: executiveDecisionBrief.decisions,
      readinessRows: executiveDecisionBrief.readinessRows
    } : null,
    blockerBurndown: blockerBurndown ? {
      ready: blockerBurndown.ready,
      headline: blockerBurndown.headline,
      counts: blockerBurndown.counts,
      lanes: blockerBurndown.lanes,
      nextActions: blockerBurndown.nextActions
    } : null,
    handoffEvidenceDossier: handoffEvidenceDossier ? {
      ready: handoffEvidenceDossier.ready,
      headline: handoffEvidenceDossier.headline,
      counts: handoffEvidenceDossier.counts,
      requirements: handoffEvidenceDossier.requirements,
      blockerSources: handoffEvidenceDossier.blockerSources
    } : null,
    reviewerDecisionWorkbook: reviewerDecisionWorkbook ? {
      ready: reviewerDecisionWorkbook.ready,
      purpose: reviewerDecisionWorkbook.purpose,
      counts: reviewerDecisionWorkbook.counts,
      files: reviewerDecisionWorkbook.files,
      ownerRows: reviewerDecisionWorkbook.ownerRows,
      completionRows: reviewerDecisionWorkbook.completionRows,
      currentStatus: reviewerDecisionWorkbook.currentStatus
    } : null,
    reviewerInputPreflight: reviewerInputPreflight ? {
      ready: reviewerInputPreflight.ready,
      purpose: reviewerInputPreflight.purpose,
      counts: reviewerInputPreflight.counts,
      files: reviewerInputPreflight.files,
      rows: reviewerInputPreflight.rows,
      blockingRows: reviewerInputPreflight.blockingRows
    } : null,
    ownerHandoffMessages: ownerHandoffMessages ? {
      ready: ownerHandoffMessages.ready,
      purpose: ownerHandoffMessages.purpose,
      status: ownerHandoffMessages.status,
      counts: ownerHandoffMessages.counts,
      guardrails: ownerHandoffMessages.guardrails,
      messages: ownerHandoffMessages.messages,
      files: ownerHandoffMessages.files
    } : null,
    ownerResponseIntake: ownerResponseIntake ? {
      ready: ownerResponseIntake.ready,
      purpose: ownerResponseIntake.purpose,
      intakeFile: ownerResponseIntake.intakeFile,
      counts: ownerResponseIntake.counts,
      rows: ownerResponseIntake.rows,
      files: ownerResponseIntake.files
    } : null,
    handoffGateDependencyMap: handoffGateDependencyMap ? {
      ready: handoffGateDependencyMap.ready,
      purpose: handoffGateDependencyMap.purpose,
      counts: handoffGateDependencyMap.counts,
      blockedRows: handoffGateDependencyMap.blockedRows,
      ownerGroups: handoffGateDependencyMap.ownerGroups,
      files: handoffGateDependencyMap.files
    } : null,
    postOwnerResponseRerunPlan: postOwnerResponseRerunPlan ? {
      ready: postOwnerResponseRerunPlan.ready,
      purpose: postOwnerResponseRerunPlan.purpose,
      counts: postOwnerResponseRerunPlan.counts,
      rows: postOwnerResponseRerunPlan.rows,
      files: postOwnerResponseRerunPlan.files
    } : null,
    ownerEvidenceAcceptanceGuide: ownerEvidenceAcceptanceGuide ? {
      ready: ownerEvidenceAcceptanceGuide.ready,
      purpose: ownerEvidenceAcceptanceGuide.purpose,
      counts: ownerEvidenceAcceptanceGuide.counts,
      rows: ownerEvidenceAcceptanceGuide.rows,
      files: ownerEvidenceAcceptanceGuide.files
    } : null,
    dailyStatus: dailyStatus ? {
      ready: dailyStatus.ready,
      date: dailyStatus.date,
      headline: dailyStatus.headline,
      guardrails: dailyStatus.guardrails,
      rows: dailyStatus.rows
    } : null,
    stagingHandoffPacket: stagingHandoffPacket ? {
      ready: stagingHandoffPacket.ready,
      purpose: stagingHandoffPacket.purpose,
      counts: stagingHandoffPacket.counts,
      importFiles: stagingHandoffPacket.importFiles,
      sampleRows: stagingHandoffPacket.sampleRows,
      actionRows: stagingHandoffPacket.actionRows
    } : null,
    clinicalBlankDecisionTemplate: clinicalBlankDecisionTemplate ? {
      ready: clinicalBlankDecisionTemplate.ready,
      decisionFile: clinicalBlankDecisionTemplate.decisionFile,
      counts: clinicalBlankDecisionTemplate.counts,
      guardrail: clinicalBlankDecisionTemplate.guardrail,
      rows: clinicalBlankDecisionTemplate.rows
    } : null,
    stagingChecklist: stagingChecklist ? {
      ready: stagingChecklist.ready,
      counts: stagingChecklist.counts,
      checks: stagingChecklist.checks
    } : null,
    stagingEvidence: stagingEvidence ? {
      ready: stagingEvidence.ready,
      counts: stagingEvidence.counts,
      gates: stagingEvidence.gates,
      templates: stagingEvidence.templates
    } : null,
    stagingEvidenceIntake: stagingEvidenceIntake ? {
      ready: stagingEvidenceIntake.ready,
      purpose: stagingEvidenceIntake.purpose,
      cutoverCreated: stagingEvidenceIntake.cutoverCreated,
      counts: stagingEvidenceIntake.counts,
      rows: stagingEvidenceIntake.rows,
      files: stagingEvidenceIntake.files
    } : null,
    handoffVerification: handoffVerification ? {
      ready: handoffVerification.ready,
      counts: handoffVerification.counts,
      checks: handoffVerification.checks
    } : null,
    deliveryIndex: deliveryIndex ? {
      ready: deliveryIndex.ready,
      counts: deliveryIndex.counts,
      groups: deliveryIndex.groups,
      files: deliveryIndex.files
    } : null,
    sourceAcquisitionPlan: sourceAcquisitionPlan ? {
      ready: sourceAcquisitionPlan.ready,
      recommendation: sourceAcquisitionPlan.recommendation,
      counts: sourceAcquisitionPlan.counts,
      stages: sourceAcquisitionPlan.stages,
      targets: sourceAcquisitionPlan.targets
    } : null,
    dataforseoPilot: dataforseoPilot ? {
      ready: dataforseoPilot.ready,
      endpoint: dataforseoPilot.endpoint,
      guardrails: dataforseoPilot.guardrails,
      counts: dataforseoPilot.counts,
      files: dataforseoPilot.files,
      targets: dataforseoPilot.targets
    } : null,
    dataforseoLiveConnection: dataforseoLiveConnection ? {
      ready: dataforseoLiveConnection.ready,
      credentialPrinted: dataforseoLiveConnection.credentialPrinted,
      accountConnection: dataforseoLiveConnection.accountConnection,
      counts: dataforseoLiveConnection.counts,
      files: dataforseoLiveConnection.files,
      reviewEndpointStatus: dataforseoLiveConnection.reviewEndpointStatus,
      blocker: dataforseoLiveConnection.blocker,
      confirmedRows: dataforseoLiveConnection.confirmedRows
    } : null,
    dataforseoProductsLiveRun: dataforseoProductsLiveRun ? {
      ready: dataforseoProductsLiveRun.ready,
      blocked: dataforseoProductsLiveRun.blocked,
      blocker: dataforseoProductsLiveRun.blocker,
      endpoint: dataforseoProductsLiveRun.endpoint,
      allowToken: dataforseoProductsLiveRun.allowToken,
      counts: dataforseoProductsLiveRun.counts,
      files: dataforseoProductsLiveRun.files,
      accountConnection: dataforseoProductsLiveRun.accountConnection,
      post: dataforseoProductsLiveRun.post,
      taskSummary: dataforseoProductsLiveRun.taskSummary
    } : null,
    dataforseoReviewsLivePilot: dataforseoReviewsLivePilot ? {
      ready: dataforseoReviewsLivePilot.ready,
      blocked: dataforseoReviewsLivePilot.blocked,
      blocker: dataforseoReviewsLivePilot.blocker,
      endpoint: dataforseoReviewsLivePilot.endpoint,
      allowToken: dataforseoReviewsLivePilot.allowToken,
      counts: dataforseoReviewsLivePilot.counts,
      files: dataforseoReviewsLivePilot.files,
      accountConnection: dataforseoReviewsLivePilot.accountConnection,
      post: dataforseoReviewsLivePilot.post,
      latestArchivedReviewPost: dataforseoReviewsLivePilot.latestArchivedReviewPost,
      taskSummary: dataforseoReviewsLivePilot.taskSummary,
      preflightBlockers: dataforseoReviewsLivePilot.preflightBlockers
    } : null,
    dataforseoReviewsDocsStatus: dataforseoReviewsDocsStatus ? {
      ready: dataforseoReviewsDocsStatus.ready,
      provider: dataforseoReviewsDocsStatus.provider,
      endpoint: dataforseoReviewsDocsStatus.endpoint,
      docsUrl: dataforseoReviewsDocsStatus.docsUrl,
      httpStatus: dataforseoReviewsDocsStatus.httpStatus,
      credentialPrinted: dataforseoReviewsDocsStatus.credentialPrinted,
      endpointTemporarilyUnavailable: dataforseoReviewsDocsStatus.endpointTemporarilyUnavailable,
      recommendation: dataforseoReviewsDocsStatus.recommendation,
      counts: dataforseoReviewsDocsStatus.counts,
      checks: dataforseoReviewsDocsStatus.checks
    } : null,
    dataforseoSupportEscalation: dataforseoSupportEscalation ? {
      ready: dataforseoSupportEscalation.ready,
      provider: dataforseoSupportEscalation.provider,
      endpoint: dataforseoSupportEscalation.endpoint,
      issue: dataforseoSupportEscalation.issue,
      credentialPrinted: dataforseoSupportEscalation.credentialPrinted,
      counts: dataforseoSupportEscalation.counts,
      files: dataforseoSupportEscalation.files,
      issueRows: dataforseoSupportEscalation.issueRows
    } : null,
    dataforseoSupportResponseGate: dataforseoSupportResponseGate ? {
      ready: dataforseoSupportResponseGate.ready,
      recommendation: dataforseoSupportResponseGate.recommendation,
      responseFile: dataforseoSupportResponseGate.responseFile,
      supportPacket: dataforseoSupportResponseGate.supportPacket,
      counts: dataforseoSupportResponseGate.counts,
      checks: dataforseoSupportResponseGate.checks,
      nextActions: dataforseoSupportResponseGate.nextActions
    } : null,
    dataforseoApprovalRequest: dataforseoApprovalRequest ? {
      ready: dataforseoApprovalRequest.ready,
      approvalAlreadyValid: dataforseoApprovalRequest.approvalAlreadyValid,
      recommendation: dataforseoApprovalRequest.recommendation,
      approvalFile: dataforseoApprovalRequest.approvalFile,
      counts: dataforseoApprovalRequest.counts,
      checks: dataforseoApprovalRequest.checks,
      files: dataforseoApprovalRequest.files,
      proposedApproval: dataforseoApprovalRequest.proposedApproval
    } : null,
    manualAmazonReviewCapture: manualAmazonReviewCapture ? {
      ready: manualAmazonReviewCapture.ready,
      recommendation: manualAmazonReviewCapture.recommendation,
      counts: manualAmazonReviewCapture.counts,
      files: manualAmazonReviewCapture.files,
      queue: manualAmazonReviewCapture.queue
    } : null,
    manualAmazonReviewCaptureValidation: manualAmazonReviewCaptureValidation ? {
      ready: manualAmazonReviewCaptureValidation.ready,
      recommendation: manualAmazonReviewCaptureValidation.recommendation,
      counts: manualAmazonReviewCaptureValidation.counts,
      rows: manualAmazonReviewCaptureValidation.rows,
      blockers: manualAmazonReviewCaptureValidation.blockers
    } : null,
    dataforseoIngest: dataforseoIngest ? {
      ready: dataforseoIngest.ready,
      rawInputDir: dataforseoIngest.rawInputDir,
      amazonOutputDir: dataforseoIngest.amazonOutputDir,
      counts: dataforseoIngest.counts,
      issues: dataforseoIngest.issues,
      outputs: dataforseoIngest.outputs
    } : null,
    dataforseoAsinLookup: dataforseoAsinLookup ? {
      ready: dataforseoAsinLookup.ready,
      counts: dataforseoAsinLookup.counts,
      files: dataforseoAsinLookup.files,
      guardrails: dataforseoAsinLookup.guardrails,
      queue: dataforseoAsinLookup.queue
    } : null,
    dataforseoAsinCandidates: dataforseoAsinCandidates ? {
      ready: dataforseoAsinCandidates.ready,
      counts: dataforseoAsinCandidates.counts,
      files: dataforseoAsinCandidates.files,
      guardrails: dataforseoAsinCandidates.guardrails,
      candidates: dataforseoAsinCandidates.candidates
    } : null,
    dataforseoAsinWebEvidence: dataforseoAsinWebEvidence ? {
      ready: dataforseoAsinWebEvidence.ready,
      counts: dataforseoAsinWebEvidence.counts,
      files: dataforseoAsinWebEvidence.files,
      guardrails: dataforseoAsinWebEvidence.guardrails,
      evidence: dataforseoAsinWebEvidence.evidence
    } : null,
    dataforseoUnresolvedAsinEvidence: dataforseoUnresolvedAsinEvidence ? {
      ready: dataforseoUnresolvedAsinEvidence.ready,
      recommendation: dataforseoUnresolvedAsinEvidence.recommendation,
      counts: dataforseoUnresolvedAsinEvidence.counts,
      files: dataforseoUnresolvedAsinEvidence.files,
      unresolvedTargets: dataforseoUnresolvedAsinEvidence.unresolvedTargets
    } : null,
    dataforseoAsinConfirmations: dataforseoAsinConfirmations ? {
      ready: dataforseoAsinConfirmations.ready,
      counts: dataforseoAsinConfirmations.counts,
      files: dataforseoAsinConfirmations.files,
      guardrails: dataforseoAsinConfirmations.guardrails,
      confirmations: dataforseoAsinConfirmations.confirmations
    } : null,
    dataforseoAsinPromotion: dataforseoAsinPromotion ? {
      ready: dataforseoAsinPromotion.ready,
      applied: dataforseoAsinPromotion.applied,
      counts: dataforseoAsinPromotion.counts,
      files: dataforseoAsinPromotion.files,
      guardrails: dataforseoAsinPromotion.guardrails,
      promotions: dataforseoAsinPromotion.promotions
    } : null,
    dataforseoTaskPreflight: dataforseoTaskPreflight ? {
      ready: dataforseoTaskPreflight.ready,
      endpoint: dataforseoTaskPreflight.endpoint,
      command: dataforseoTaskPreflight.command,
      paidRunCommand: dataforseoTaskPreflight.paidRunCommand,
      counts: dataforseoTaskPreflight.counts,
      files: dataforseoTaskPreflight.files,
      checks: dataforseoTaskPreflight.checks,
      blockers: dataforseoTaskPreflight.blockers,
      payloadPreview: dataforseoTaskPreflight.payloadPreview
    } : null,
    apifyGapfill: apifyGapfill ? {
      ready: apifyGapfill.ready,
      recommendation: apifyGapfill.recommendation,
      counts: apifyGapfill.counts,
      guardrails: apifyGapfill.guardrails,
      files: apifyGapfill.files,
      targets: apifyGapfill.targets
    } : null,
    apifyTaskPreflight: apifyTaskPreflight ? {
      ready: apifyTaskPreflight.ready,
      command: apifyTaskPreflight.command,
      paidRunCommand: apifyTaskPreflight.paidRunCommand,
      counts: apifyTaskPreflight.counts,
      files: apifyTaskPreflight.files,
      checks: apifyTaskPreflight.checks,
      blockers: apifyTaskPreflight.blockers,
      targetPreview: apifyTaskPreflight.targetPreview
    } : null,
    sourcePricingReferences: sourcePricingReferences ? {
      ready: sourcePricingReferences.ready,
      note: sourcePricingReferences.note,
      counts: sourcePricingReferences.counts,
      guardrails: sourcePricingReferences.guardrails,
      references: sourcePricingReferences.references
    } : null,
    sourceCostScenarios: sourceCostScenarios ? {
      ready: sourceCostScenarios.ready,
      note: sourceCostScenarios.note,
      counts: sourceCostScenarios.counts,
      recommendation: sourceCostScenarios.recommendation,
      guardrails: sourceCostScenarios.guardrails,
      scenarios: sourceCostScenarios.scenarios,
      budgetState: sourceCostScenarios.budgetState
    } : null,
    sourceBudgetGate: sourceBudgetGate ? {
      ready: sourceBudgetGate.ready,
      budgetAllowed: sourceBudgetGate.budgetAllowed,
      budgetFile: sourceBudgetGate.budgetFile,
      currency: sourceBudgetGate.currency,
      counts: sourceBudgetGate.counts,
      checks: sourceBudgetGate.checks,
      nextActions: sourceBudgetGate.nextActions
    } : null,
    sourceCostPlan: sourceCostPlan ? {
      ready: sourceCostPlan.ready,
      costReady: sourceCostPlan.costReady,
      costFile: sourceCostPlan.costFile,
      currency: sourceCostPlan.currency,
      counts: sourceCostPlan.counts,
      estimates: sourceCostPlan.estimates,
      guardrails: sourceCostPlan.guardrails,
      checks: sourceCostPlan.checks,
      nextActions: sourceCostPlan.nextActions
    } : null,
    sourceApprovalGate: sourceApprovalGate ? {
      ready: sourceApprovalGate.ready,
      counts: sourceApprovalGate.counts,
      files: sourceApprovalGate.files,
      guardrails: sourceApprovalGate.guardrails,
      approvals: sourceApprovalGate.approvals,
      checks: sourceApprovalGate.checks,
      nextActions: sourceApprovalGate.nextActions
    } : null,
    sourceSpendGate: sourceSpendGate ? {
      ready: sourceSpendGate.ready,
      spendAllowed: sourceSpendGate.spendAllowed,
      counts: sourceSpendGate.counts,
      approvalFiles: sourceSpendGate.approvalFiles,
      checks: sourceSpendGate.checks,
      nextActions: sourceSpendGate.nextActions
    } : null,
    dataforseoPilotReview: dataforseoPilotReview ? {
      ready: dataforseoPilotReview.ready,
      decision: dataforseoPilotReview.decision,
      reviewFile: dataforseoPilotReview.reviewFile,
      counts: dataforseoPilotReview.counts,
      checks: dataforseoPilotReview.checks,
      recommendations: dataforseoPilotReview.recommendations
    } : null,
    sourceAcquisitionControlRun: sourceAcquisitionControlRun ? {
      ready: sourceAcquisitionControlRun.ready,
      note: sourceAcquisitionControlRun.note,
      counts: sourceAcquisitionControlRun.counts,
      steps: sourceAcquisitionControlRun.steps,
      blockedSteps: sourceAcquisitionControlRun.blockedSteps
    } : null,
    sourceSubscriptionDecision: sourceSubscriptionDecision ? {
      ready: sourceSubscriptionDecision.ready,
      headline: sourceSubscriptionDecision.headline,
      currentDecision: sourceSubscriptionDecision.currentDecision,
      counts: sourceSubscriptionDecision.counts,
      files: sourceSubscriptionDecision.files,
      rows: sourceSubscriptionDecision.rows
    } : null,
    sourceAcquisitionNextActions: sourceAcquisitionNextActions ? {
      ready: sourceAcquisitionNextActions.ready,
      recommendation: sourceAcquisitionNextActions.recommendation,
      currentInstruction: sourceAcquisitionNextActions.currentInstruction,
      counts: sourceAcquisitionNextActions.counts,
      asinExamples: sourceAcquisitionNextActions.asinExamples,
      actions: sourceAcquisitionNextActions.actions,
      immediateActions: sourceAcquisitionNextActions.immediateActions,
      paidSpendBlockedActions: sourceAcquisitionNextActions.paidSpendBlockedActions
    } : null,
    assetStatus: {
      ready: assetStatus.ready,
      requiredCount: assetStatus.requiredCount,
      missingUploadCount: assetStatus.missingUploadCount,
      missingReferenceCount: assetStatus.missingReferenceCount
    },
    blockerSummary: blockers.reduce((acc: any, blocker: any) => {
      acc[blocker.type] = (acc[blocker.type] || 0) + 1
      return acc
    }, {}),
    outputFiles: {
      dashboard: await listOutputFiles(DASHBOARD_EXPORT_ROOT),
      migration: await listOutputFiles(MIGRATION_EXPORT_ROOT)
    },
    blockers,
    ready: blockers.length === 0 && readinessAudit?.ready !== false && intelligenceWorkqueue?.ready !== false
  }
}

async function listAssetFiles(root: string): Promise<any[]> {
  try {
    const entries = await readdir(root, { withFileTypes: true })
    const files = []
    for (const entry of entries) {
      const path = join(root, entry.name)
      if (entry.isDirectory()) files.push(...await listAssetFiles(path))
      else if (entry.isFile()) files.push({ name: entry.name, path })
    }
    return files
  } catch (_) {
    return []
  }
}

async function listJsonFiles(root: string): Promise<any[]> {
  try {
    const entries = await readdir(root, { withFileTypes: true })
    const files = []
    for (const entry of entries) {
      const path = join(root, entry.name)
      if (entry.isDirectory()) files.push(...await listJsonFiles(path))
      else if (entry.isFile() && entry.name.endsWith('.json')) files.push({ name: entry.name, path })
    }
    return files
  } catch (_) {
    return []
  }
}

async function getSourceStatus() {
  const products = await listProductContent()
  const competitorConfig = await readJson(join(import.meta.dir, '..', 'data', 'sources', 'competitor-sources.json'))
  const competitorFiles = await listJsonFiles(join(import.meta.dir, '..', 'data', 'sources', 'competitor-pdps'))
  const feefoFiles = (await listJsonFiles(INTEL_ROOT)).filter((file: any) => file.name === '_raw_reviews.json')
  const sourceKinds = [
    {
      key: 'feefo',
      label: 'JV Feefo reviews',
      requiredFor: 'first-party VoC, benefit copy, blockers, FAQs',
      files: feefoFiles,
      expected: products.filter((p: any) => p.status !== 'pending_intelligence').length,
      note: 'Canonical first-party evidence. Existing pilot intelligence was built from Feefo exports.'
    },
    {
      key: 'competitor-pdps',
      label: 'Competitor PDP crawl',
      requiredFor: 'competitor comparison, image/claim positioning, collagen NPD whitespace',
      files: competitorFiles,
      expected: competitorConfig?.competitors?.length || 7,
      note: 'Umar competitor list PDP probes. Does not replace Amazon/Trustpilot review evidence.'
    },
    {
      key: 'amazon',
      label: 'Amazon JV + competitor reviews',
      requiredFor: 'market-wide shopper objections and competitor review themes',
      files: await listJsonFiles(join(import.meta.dir, '..', 'data', 'sources', 'amazon')),
      expected: products.length,
      note: 'Raw Amazon source drops are still required; current per-SKU files are extraction queues only.'
    },
    {
      key: 'trustpilot',
      label: 'Trustpilot reviews',
      requiredFor: 'brand-level trust objections and competitor validation',
      files: await listJsonFiles(join(import.meta.dir, '..', 'data', 'sources', 'trustpilot')),
      expected: 1,
      note: 'Raw Trustpilot source drops are still required.'
    },
    {
      key: 'reddit',
      label: 'Reddit/social listening',
      requiredFor: 'unprompted audience language, collagen flavour concerns, problem framing',
      files: await listJsonFiles(join(import.meta.dir, '..', 'data', 'sources', 'reddit')),
      expected: 1,
      note: 'Raw Reddit exports/search results are still required.'
    },
    {
      key: 'claims',
      label: 'Clinical claims library',
      requiredFor: 'pdp.clinically_shown_to metaobjects and compliance approval',
      files: await listJsonFiles(join(import.meta.dir, '..', 'data', 'sources', 'claims')),
      expected: 1,
      note: 'Non-negotiable compliance input. Feefo themes cannot be used as clinical claims.'
    }
  ].map((source: any) => ({
    ...source,
    fileCount: source.files.length,
    status: source.files.length >= source.expected && source.expected > 0 ? 'ready' : source.files.length ? 'partial' : 'missing'
  }))

  const intelligenceFiles = [
    'review-aspects',
    'strengths-weaknesses',
    'conversion-blockers',
    'improvements',
    'image-audit',
    'photo-brief',
    'competitor-comparison',
    'amazon-reviews',
    'reddit-pulse',
    'flavor-intelligence',
    'audience-profile',
    'claims-evidence'
  ]
  const coverage = []
  for (const view of intelligenceFiles) {
    let count = 0
    const skus = []
    for (const product of products) {
      const file = Bun.file(join(INTEL_ROOT, product.sku, `${view}.json`))
      if (await file.exists()) {
        count += 1
        skus.push(product.sku)
      }
    }
    coverage.push({
      view,
      count,
      total: products.length,
      percent: products.length ? Math.round((count / products.length) * 100) : 0,
      missing: products.length - count,
      skus
    })
  }

  const configuredCompetitors = competitorConfig?.competitors || []
  const competitorCoverage = configuredCompetitors.map((competitor: any) => {
    const slug = String(competitor.brand || '').toLowerCase().replace(/&/g, 'and').replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')
    const file = competitorFiles.find((item: any) => item.name === `${slug}.json`)
    return {
      brand: competitor.brand,
      domain: competitor.domain,
      pdpProbe: Boolean(file),
      file: file?.path || null
    }
  })

  return {
    generatedAt: new Date().toISOString(),
    productCount: products.length,
    sourceKinds,
    coverage,
    competitorCoverage,
    blockers: sourceKinds
      .filter((source: any) => source.status !== 'ready')
      .map((source: any) => ({
        key: source.key,
        label: source.label,
        status: source.status,
        message: `${source.fileCount}/${source.expected} required source files available`
      })),
    ready: sourceKinds.every((source: any) => source.status === 'ready') && coverage.every((row: any) => row.count === products.length)
  }
}

async function getAssetStatus() {
  const products = await listProductContent()
  const metaobjects = await readJson(join(CONTENT_ROOT, 'metaobjects.json'))
  const collections = await readJson(join(CONTENT_ROOT, 'smart-collections.json'))
  const assetFiles = await listAssetFiles(ASSET_ROOT)
  const available = new Map(assetFiles.map((file: any) => [file.name, file.path]))
  const required: any[] = []
  const missingReferences: any[] = []

  for (const product of products) {
    const m = product.metafields || {}
    for (const field of ['pdpResults', 'pdpResultsMobile', 'pdpHowWeCompare']) {
      const filename = m[field]
      if (filename) required.push({ kind: 'product', owner: product.sku, field, filename, exists: available.has(filename), path: available.get(filename) || null })
      else missingReferences.push({ kind: 'product', owner: product.sku, field, reason: 'No filename assigned' })
    }
  }
  for (const [definition, rows] of Object.entries(metaobjects?.definitions || {}) as any) {
    for (const row of rows) {
      for (const field of ['icon', 'image', 'background_image']) {
        const filename = row[field]
        if (filename) required.push({ kind: 'metaobject', owner: `${definition}.${row.handle || row.name || ''}`, field, filename, exists: available.has(filename), path: available.get(filename) || null })
      }
    }
  }
  for (const row of collections?.collections || []) {
    const filename = row.backgroundImage || row.background_image
    if (filename) required.push({ kind: 'collection', owner: row.handle, field: 'backgroundImage', filename, exists: available.has(filename), path: available.get(filename) || null })
  }
  const missingUploads = required.filter(item => !item.exists)
  return {
    generatedAt: new Date().toISOString(),
    assetRoot: ASSET_ROOT,
    availableCount: assetFiles.length,
    requiredCount: required.length,
    uploadedCount: required.length - missingUploads.length,
    missingUploadCount: missingUploads.length,
    missingReferenceCount: missingReferences.length,
    ready: missingUploads.length === 0 && missingReferences.length === 0,
    required,
    missingUploads,
    missingReferences
  }
}

async function getComplianceStatus() {
  const products = await listProductContent()
  const rows = []

  for (const product of products) {
    const sku = product.sku
    const claims = await readJson(join(INTEL_ROOT, sku, 'claims-evidence.json'))
    const workflow = await readJson(join(CONTENT_WORKFLOW_ROOT, `${sku}.json`))
    const claimRef = product.metafields?.pdpClinicallyShownTo || ''
    const sourceBackedClaims = claims?.claims || []
    const candidateThemes = claims?.candidateNonClinicalThemes || []
    const field = workflow?.fields?.pdpClinicallyShownTo || null
    const approval = product.approval?.compliance || 'blocked'
    const blockers = []

    if (!claims) blockers.push('claims-evidence.json missing')
    if (claims && sourceBackedClaims.length === 0) blockers.push('no source-backed clinical claims')
    if (!claimRef) blockers.push('pdp.clinically_shown_to metafield empty')
    if (!['approved', 'reviewed', 'ready'].includes(approval)) blockers.push(`compliance approval is ${approval}`)

    let status = 'blocked_source'
    if (product.status === 'pending_intelligence') status = 'pending_intelligence'
    else if (sourceBackedClaims.length > 0 && !claimRef) status = 'blocked_mapping'
    else if (sourceBackedClaims.length > 0 && claimRef && !['approved', 'reviewed', 'ready'].includes(approval)) status = 'needs_review'
    else if (sourceBackedClaims.length > 0 && claimRef) status = 'ready'

    rows.push({
      sku,
      name: product.name,
      productStatus: product.status,
      status,
      approval,
      claimRef,
      complianceGate: claims?.complianceGate || (claims ? 'unknown' : 'missing_claims_evidence'),
      sourceBackedClaimCount: sourceBackedClaims.length,
      candidateThemeCount: candidateThemes.length,
      sourceBackedClaims,
      candidateThemes: candidateThemes.slice(0, 5),
      fieldStatus: field?.status || null,
      citations: field?.citations || [],
      blockers
    })
  }

  const summary = rows.reduce((acc: any, row: any) => {
    acc.total += 1
    acc[row.status] = (acc[row.status] || 0) + 1
    acc.sourceBackedClaims += row.sourceBackedClaimCount
    acc.candidateThemes += row.candidateThemeCount
    return acc
  }, { total: 0, sourceBackedClaims: 0, candidateThemes: 0 })

  return {
    generatedAt: new Date().toISOString(),
    summary,
    rows,
    ready: rows.every((row: any) => row.status === 'ready')
  }
}

async function listOutputFiles(root: string) {
  try {
    const files = (await readdir(root)).filter((file: string) => /\.(csv|json|md)$/i.test(file))
    return files.map((file: string) => ({ name: file, path: join(root, file) })).sort((a: any, b: any) => a.name.localeCompare(b.name))
  } catch (_) {
    return []
  }
}

async function getProducts() {
  const catalog = await readJson(join(PIPELINE_ROOT, 'catalog.json')) as any[]
  const images = await readJson(join(PIPELINE_ROOT, 'product-images.json')) as Record<string, any>
  const contentRows = await listProductContent()
  const contentBySku = new Map(contentRows.map((row: any) => [row.sku, row]))

  const rows = catalog.map((p: any) => {
    const img = images?.[p.sku] || {}
    const content: any = contentBySku.get(p.sku) || null
    const intelPath = join(INTEL_ROOT, p.sku, 'review-aspects.json')
    return {
      sku: p.sku,
      name: content?.name || img.name || p.sku,
      status: content?.status || null,
      approval: content?.approval || null,
      totalReviews: p.total_reviews || 0,
      variants: content?.variants || p.variants || [],
      imageUrl: img.image_url || null,
      productUrl: img.url ? `https://www.justvitamins.co.uk${img.url}` : null,
      hasIntelligence: Bun.file(intelPath).exists()
    }
  }).sort((a: any, b: any) => b.totalReviews - a.totalReviews)

  const collagenFlavorIntel = join(INTEL_ROOT, 'COLLAGEN-NPD', 'flavor-intelligence.json')
  const collagenProfile = join(RESEARCH_PROFILE_ROOT, 'COLLAGEN-NPD.json')
  rows.unshift({
    sku: 'COLLAGEN-NPD',
    name: 'Collagen NPD market intelligence',
    status: 'priority_npd_workstream',
    approval: null,
    totalReviews: 0,
    variants: ['unflavoured', 'vanilla', 'chocolate', 'fresh-berry'],
    imageUrl: null,
    productUrl: null,
    hasIntelligence: Bun.file(collagenFlavorIntel).exists() || Bun.file(collagenProfile).exists(),
    isNpdWorkstream: true
  })

  return rows
}

// Map view name -> { items array key in main file, items array key in supporting file }
const SUPPORTING_VIEWS: Record<string, { itemsKey: string; supportingFile: string; supportingKey: string }> = {
  'review-aspects':       { itemsKey: 'aspects',      supportingFile: 'review-aspects-supporting.json',      supportingKey: 'supportingReviews' },
  'strengths-weaknesses': { itemsKey: 'items',        supportingFile: 'strengths-weaknesses-supporting.json', supportingKey: 'items' },
  'conversion-blockers':  { itemsKey: 'blockers',     supportingFile: 'conversion-blockers-supporting.json',  supportingKey: 'blockers' },
  'improvements':         { itemsKey: 'improvements', supportingFile: 'improvements-supporting.json',         supportingKey: 'improvements' },
  'competitor-comparison': { itemsKey: 'jvSignals',    supportingFile: 'competitor-comparison-supporting.json', supportingKey: 'jvSignals' },
  'amazon-reviews':       { itemsKey: 'themes',       supportingFile: 'amazon-reviews-supporting.json',       supportingKey: 'themes' },
  'reddit-pulse':         { itemsKey: 'pulses',       supportingFile: 'reddit-pulse-supporting.json',         supportingKey: 'pulses' },
  'audience-profile':     { itemsKey: 'profiles',     supportingFile: 'audience-profile-supporting.json',     supportingKey: 'profiles' }
}

async function enrichWithSupporting(data: any, view: string, sku: string) {
  const cfg = SUPPORTING_VIEWS[view]
  if (!cfg) return
  const items = data[cfg.itemsKey]
  if (!Array.isArray(items)) return
  const supporting: any = await readJson(join(INTEL_ROOT, sku, cfg.supportingFile))
  const supportingItems = supporting?.[cfg.supportingKey]
  if (!Array.isArray(supportingItems)) return
  const byRank = new Map<number, number[]>()
  for (const s of supportingItems) byRank.set(s.rank, s.reviewIndices || [])
  data[cfg.itemsKey] = items.map((it: any) => ({
    ...it,
    supportingReviewIndices: byRank.get(it.rank) || []
  }))
}

async function serveStatic(pathname: string): Promise<Response | null> {
  if (!IS_PROD) return null
  // Strip query string, decode
  const clean = decodeURIComponent(pathname.split('?')[0])
  const filePath = join(DIST_ROOT, clean === '/' ? 'index.html' : clean)
  const f = Bun.file(filePath)
  if (await f.exists()) {
    // Hashed assets under /assets/* are immutable - cache aggressively
    // HTML and other non-asset files MUST NOT be cached so a deploy flushes immediately
    const isAsset = clean.startsWith('/assets/')
    const headers: Record<string, string> = isAsset
      ? { 'Cache-Control': 'public, max-age=31536000, immutable' }
      : { 'Cache-Control': 'no-cache, no-store, must-revalidate' }
    return new Response(f, { headers })
  }
  // Never fall back to index.html for missing static asset paths - return a real 404
  // so missing/hash-mismatched files surface clearly instead of being served as HTML.
  if (clean.startsWith('/assets/') || /\.[a-z0-9]+$/i.test(clean)) {
    return new Response('Not found', { status: 404 })
  }
  // SPA fallback - serve index.html for all non-file app routes
  const index = Bun.file(join(DIST_ROOT, 'index.html'))
  if (await index.exists()) {
    return new Response(index, {
      headers: {
        'Content-Type': 'text/html',
        'Cache-Control': 'no-cache, no-store, must-revalidate'
      }
    })
  }
  return null
}

// In-memory cache for scraped image URLs per SKU
const imageCache: Record<string, string[]> = {}

async function scrapeProductImages(sku: string): Promise<string[]> {
  if (imageCache[sku]) return imageCache[sku]
  const images = await readJson(join(PIPELINE_ROOT, 'product-images.json')) as Record<string, any>
  const productPath = images?.[sku]?.url
  if (!productPath) return []
  try {
    const res = await fetch(`https://www.justvitamins.co.uk${productPath}`, {
      headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' }
    })
    const html = await res.text()
    // Extract filenames like {uuid}-{n}.jpg from anywhere in the page
    const matches = [...html.matchAll(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}-\d+)\.jpg/g)]
    const filenames = [...new Set(matches.map(m => m[1]))]
    const urls = filenames.map(f => `https://images.justvitamins.co.uk/product-images/Normal/${f}.jpg`)
    imageCache[sku] = urls
    return urls
  } catch { return [] }
}

const server = Bun.serve({
  port: PORT,
  async fetch(req) {
    const url = new URL(req.url)
    const path = url.pathname

    if (req.method === 'OPTIONS') return new Response(null, { headers: cors })

    // API routes
    if (path.startsWith('/api/')) {
      // GET /api/content/products â€” all seeded product-content rows
      if (path === '/api/content/products') {
        return new Response(JSON.stringify(await listProductContent()), { headers: cors })
      }

      // GET /api/content/workflow â€” review workflow index
      if (path === '/api/content/workflow') {
        const data = await readJson(join(CONTENT_WORKFLOW_ROOT, 'index.json'))
        return new Response(JSON.stringify(data || { products: [] }), { headers: cors })
      }

      // GET/PUT/POST /api/content/workflow/:sku â€” per-SKU status, field citations, review log
      const workflowMatch = path.match(/^\/api\/content\/workflow\/([^/]+)$/)
      if (workflowMatch) {
        const sku = safeSku(workflowMatch[1])
        if (!sku) return new Response(JSON.stringify({ error: 'Invalid SKU' }), { status: 400, headers: cors })
        const file = join(CONTENT_WORKFLOW_ROOT, `${sku}.json`)
        const existing = await readJson(file)
        if (!existing && req.method !== 'PUT') {
          return new Response(JSON.stringify({ error: 'Not found' }), { status: 404, headers: cors })
        }
        if (req.method === 'PUT') {
          const body = normaliseProductContent(await req.json())
          if (body.sku && body.sku !== sku) return new Response(JSON.stringify({ error: 'SKU mismatch' }), { status: 400, headers: cors })
          await writeJson(file, { ...body, sku, updatedAt: new Date().toISOString() })
          return new Response(JSON.stringify({ ok: true }), { headers: cors })
        }
        if (req.method === 'POST') {
          const body = await req.json()
          const event = {
            at: new Date().toISOString(),
            type: body.type || 'review_note',
            actor: body.actor || 'dashboard',
            field: body.field || null,
            from: body.from ?? null,
            to: body.to ?? null,
            summary: body.summary || ''
          }
          existing.events = Array.isArray(existing.events) ? existing.events : []
          existing.events.unshift(event)
          if (body.field && existing.fields?.[body.field] && body.status) {
            existing.fields[body.field].status = body.status
            existing.fields[body.field].lastReviewedAt = event.at
            existing.fields[body.field].reviewer = event.actor
            existing.fields[body.field].decision = body.status
          }
          await writeJson(file, { ...existing, updatedAt: new Date().toISOString() })
          return new Response(JSON.stringify({ ok: true, event }), { headers: cors })
        }
        return new Response(JSON.stringify(existing), { headers: cors })
      }

      // GET/PUT /api/content/products/:sku â€” product content editor persistence
      const approvalMatch = path.match(/^\/api\/content\/products\/([^/]+)\/approval$/)
      if (approvalMatch) {
        const sku = safeSku(approvalMatch[1])
        if (!sku) return new Response(JSON.stringify({ error: 'Invalid SKU' }), { status: 400, headers: cors })
        if (req.method !== 'POST') return new Response(JSON.stringify({ error: 'Method not allowed' }), { status: 405, headers: cors })

        const file = join(CONTENT_PRODUCTS_ROOT, `${sku}.json`)
        const product = await readJson(file)
        if (!product) return new Response(JSON.stringify({ error: 'Not found' }), { status: 404, headers: cors })

        const body = await req.json()
        const area = body.area
        const allowedAreas = ['content', 'compliance', 'images', 'export']
        if (!allowedAreas.includes(area)) return new Response(JSON.stringify({ error: 'Invalid approval area' }), { status: 400, headers: cors })
        const status = String(body.status || '').trim()
        if (!status) return new Response(JSON.stringify({ error: 'Missing approval status' }), { status: 400, headers: cors })

        const at = new Date().toISOString()
        product.approval = { ...(product.approval || {}), [area]: status }
        await writeJson(file, normaliseProductContent({ ...product, sku, updatedAt: at }))

        const workflowFile = join(CONTENT_WORKFLOW_ROOT, `${sku}.json`)
        const workflow = await readJson(workflowFile)
        if (workflow) {
          workflow.approval = { ...(workflow.approval || {}), [area]: status }
          workflow.events = Array.isArray(workflow.events) ? workflow.events : []
          workflow.events.unshift({
            at,
            type: 'approval_updated',
            actor: body.actor || 'dashboard',
            field: area,
            from: body.from ?? null,
            to: status,
            summary: body.summary || `${area} approval set to ${status}`
          })
          await writeJson(workflowFile, { ...workflow, updatedAt: at })
        }

        const indexFile = join(CONTENT_WORKFLOW_ROOT, 'index.json')
        const index = await readJson(indexFile)
        if (index?.products) {
          index.products = index.products.map((row: any) => row.sku === sku
            ? { ...row, approval: { ...(row.approval || {}), [area]: status } }
            : row)
          await writeJson(indexFile, { ...index, updatedAt: at })
        }

        return new Response(JSON.stringify({ ok: true, sku, area, status }), { headers: cors })
      }

      const contentProductMatch = path.match(/^\/api\/content\/products\/([^/]+)$/)
      if (contentProductMatch) {
        const sku = safeSku(contentProductMatch[1])
        if (!sku) return new Response(JSON.stringify({ error: 'Invalid SKU' }), { status: 400, headers: cors })
        const file = join(CONTENT_PRODUCTS_ROOT, `${sku}.json`)
        if (req.method === 'PUT') {
          const body = normaliseProductContent(await req.json())
          if (body.sku && body.sku !== sku) return new Response(JSON.stringify({ error: 'SKU mismatch' }), { status: 400, headers: cors })
          await writeJson(file, { ...body, sku, updatedAt: new Date().toISOString() })
          return new Response(JSON.stringify({ ok: true }), { headers: cors })
        }
        const data = await readJson(file)
        return data
          ? new Response(JSON.stringify(data), { headers: cors })
          : new Response(JSON.stringify({ error: 'Not found' }), { status: 404, headers: cors })
      }

      // GET /api/content/metaobjects - metaobject production library
      if (path === '/api/content/metaobjects') {
        const data = await readJson(join(CONTENT_ROOT, 'metaobjects.json'))
        return data
          ? new Response(JSON.stringify(data), { headers: cors })
          : new Response(JSON.stringify({ error: 'Not found' }), { status: 404, headers: cors })
      }

      // GET /api/content/collections - smart collection promo card mappings
      if (path === '/api/content/collections') {
        const data = await readJson(join(CONTENT_ROOT, 'smart-collections.json'))
        return data
          ? new Response(JSON.stringify(data), { headers: cors })
          : new Response(JSON.stringify({ error: 'Not found' }), { status: 404, headers: cors })
      }

      // GET /api/export/status - readiness summary for Lewis/Euan handoff
      if (path === '/api/export/status') {
        return new Response(JSON.stringify(await getExportStatus()), { headers: cors })
      }

      // GET /api/assets/status - Shopify Files/local asset manifest readiness
      if (path === '/api/assets/status') {
        return new Response(JSON.stringify(await getAssetStatus()), { headers: cors })
      }

      // GET /api/product-image-generation/:sku - original/live product image plus direct ImageGen carousel set
      const productImageGenerationMatch = path.match(/^\/api\/product-image-generation\/([^/]+)$/)
      if (productImageGenerationMatch) {
        const sku = safeSku(productImageGenerationMatch[1])
        if (!sku) return new Response(JSON.stringify({ error: 'Invalid SKU' }), { status: 400, headers: cors })
        return new Response(JSON.stringify(await getProductImageGenerationSet(sku)), { headers: cors })
      }

      // GET /api/generated-product-image/:sku/:filename - generated direct ImageGen PNG
      const generatedProductImageMatch = path.match(/^\/api\/generated-product-image\/([^/]+)\/([^/]+)$/)
      if (generatedProductImageMatch) {
        const sku = safeSku(generatedProductImageMatch[1])
        const filename = generatedProductImageMatch[2]
        if (!sku || !/^[a-z0-9-]+\.png$/i.test(filename)) return new Response('Invalid image path', { status: 400 })
        const filePath = join(GENERATED_ASSET_ROOT, 'top3-direct-imagegen-final', sku, filename)
        const file = Bun.file(filePath)
        if (!(await file.exists())) return new Response('Not found', { status: 404 })
        return new Response(file, { headers: { 'Content-Type': 'image/png', 'Cache-Control': 'no-cache' } })
      }

      // GET /api/original-product-image/:sku - original live image downloaded from database URL
      const originalProductImageMatch = path.match(/^\/api\/original-product-image\/([^/]+)$/)
      if (originalProductImageMatch) {
        const sku = safeSku(originalProductImageMatch[1])
        if (!sku) return new Response('Invalid SKU', { status: 400 })
        const filePath = join(GENERATED_ASSET_ROOT, 'top3-originals', `${sku}-original.jpg`)
        const file = Bun.file(filePath)
        if (!(await file.exists())) return new Response('Not found', { status: 404 })
        return new Response(file, { headers: { 'Content-Type': 'image/jpeg', 'Cache-Control': 'no-cache' } })
      }

      // GET /api/sources/status - raw source availability and intelligence coverage
      if (path === '/api/sources/status') {
        return new Response(JSON.stringify(await getSourceStatus()), { headers: cors })
      }

      // GET /api/compliance/status - clinical claims evidence and approval gate
      if (path === '/api/compliance/status') {
        return new Response(JSON.stringify(await getComplianceStatus()), { headers: cors })
      }

      // GET /api/products
      if (path === '/api/products') {
        const products = await getProducts()
        const resolved = await Promise.all(
          products.map(async (p: any) => ({ ...p, hasIntelligence: await p.hasIntelligence }))
        )
        return new Response(JSON.stringify(resolved), { headers: cors })
      }

      // GET /api/product-images/:sku
      const imgMatch = path.match(/^\/api\/product-images\/([^/]+)$/)
      if (imgMatch) {
        const urls = await scrapeProductImages(imgMatch[1])
        return new Response(JSON.stringify({ urls }), { headers: cors })
      }

      // GET /api/research-profile/:sku - complete PDP research profile for content generation
      const researchProfileMatch = path.match(/^\/api\/research-profile\/([^/]+)$/)
      if (researchProfileMatch) {
        const sku = safeSku(researchProfileMatch[1])
        if (!sku) return new Response(JSON.stringify({ error: 'Invalid SKU' }), { status: 400, headers: cors })
        const data = await readJson(join(RESEARCH_PROFILE_ROOT, `${sku}.json`))
        return data
          ? new Response(JSON.stringify(data), { headers: cors })
          : new Response(JSON.stringify({ error: 'Not found' }), { status: 404, headers: cors })
      }

      // GET /api/raw-evidence/:sku - raw scraped/source content behind synthesis
      const rawEvidenceMatch = path.match(/^\/api\/raw-evidence\/([^/]+)$/)
      if (rawEvidenceMatch) {
        const sku = safeSku(rawEvidenceMatch[1])
        if (!sku) return new Response(JSON.stringify({ error: 'Invalid SKU' }), { status: 400, headers: cors })
        return new Response(JSON.stringify(await getRawEvidence(sku)), { headers: cors })
      }

      // GET /api/reviews/:sku - raw reviews (for resolving supportingReviewIndices)
      const reviewsMatch = path.match(/^\/api\/reviews\/([^/]+)$/)
      if (reviewsMatch) {
        const [, sku] = reviewsMatch
        const reviews = await readJson(join(INTEL_ROOT, sku, '_raw_reviews.json'))
        return reviews
          ? new Response(JSON.stringify(reviews), { headers: cors })
          : new Response(JSON.stringify({ error: 'Not found' }), { status: 404, headers: cors })
      }

      // GET /api/intelligence/:sku/:view
      const intelMatch = path.match(/^\/api\/intelligence\/([^/]+)\/([^/]+)$/)
      if (intelMatch) {
        const [, sku, view] = intelMatch
        if (view === 'all') {
          const views = [
            'review-aspects', 'conversion-driver', 'strengths-weaknesses', 'conversion-blockers',
            'improvements', 'image-audit', 'photo-brief', 'competitor-analysis',
            'competitor-comparison', 'amazon-reviews', 'reddit-pulse',
            'flavor-intelligence', 'audience-profile', 'claims-evidence'
          ]
          const result: Record<string, any> = {}
          for (const v of views) {
            const data = await readJson(join(INTEL_ROOT, sku, `${v}.json`))
            if (data) result[v.replace(/-([a-z])/g, (_: string, c: string) => c.toUpperCase())] = data
          }
          return Object.keys(result).length
            ? new Response(JSON.stringify(result), { headers: cors })
            : new Response(JSON.stringify({ error: 'No intelligence data' }), { status: 404, headers: cors })
        }
        const data: any = await readJson(join(INTEL_ROOT, sku, `${view}.json`))
        if (!data) return new Response(JSON.stringify({ error: 'Not found' }), { status: 404, headers: cors })
        await enrichWithSupporting(data, view, sku)
        return new Response(JSON.stringify(data), { headers: cors })
      }

      // ==========================================================
      // EDITORIAL DASHBOARD — pilot batch + competitor extracts
      // ==========================================================

      // GET /api/editorial/pilot — batch summary across 5 SKUs
      if (path === '/api/editorial/pilot') {
        const summary = await readJson(join(PILOT_EXTENDED_ROOT, '_summary.json'))
        return summary
          ? new Response(JSON.stringify(summary), { headers: cors })
          : new Response(JSON.stringify({ error: 'Pilot summary not generated yet. Run: npm run pilot:all' }), { status: 404, headers: cors })
      }

      // GET /api/editorial/pilot/:sku — full per-SKU draft with QA + sources
      const pilotMatch = path.match(/^\/api\/editorial\/pilot\/([^/]+)$/)
      if (pilotMatch) {
        const sku = safeSku(pilotMatch[1])
        if (!sku) return new Response(JSON.stringify({ error: 'Invalid SKU' }), { status: 400, headers: cors })
        const data = await readJson(join(PILOT_EXTENDED_ROOT, `${sku}.json`))
        return data
          ? new Response(JSON.stringify(data), { headers: cors })
          : new Response(JSON.stringify({ error: 'Not found' }), { status: 404, headers: cors })
      }

      // GET /api/editorial/competitors — competitor section-extract index
      if (path === '/api/editorial/competitors') {
        const idx = await readJson(join(COMPETITOR_EXTRACTS_ROOT, '_index.json'))
        return idx
          ? new Response(JSON.stringify(idx), { headers: cors })
          : new Response(JSON.stringify({ error: 'Competitor index not generated yet' }), { status: 404, headers: cors })
      }

      // GET /api/editorial/competitor/:handle — single brand section extract
      const competitorMatch = path.match(/^\/api\/editorial\/competitor\/([^/]+)$/)
      if (competitorMatch) {
        const handle = competitorMatch[1].replace(/[^a-z0-9-]/gi, '')
        const data = await readJson(join(COMPETITOR_EXTRACTS_ROOT, `${handle}.json`))
        return data
          ? new Response(JSON.stringify(data), { headers: cors })
          : new Response(JSON.stringify({ error: 'Not found' }), { status: 404, headers: cors })
      }

      // GET /api/editorial/product-truth/:sku — DB-truth metadata for ATF/render
      const truthMatch = path.match(/^\/api\/editorial\/product-truth\/([^/]+)$/)
      if (truthMatch) {
        const sku = safeSku(truthMatch[1])
        if (!sku) return new Response(JSON.stringify({ error: 'Invalid SKU' }), { status: 400, headers: cors })
        const data = await readJson(join(PRODUCT_TRUTH_ROOT, `${sku}.json`))
        return data
          ? new Response(JSON.stringify(data), { headers: cors })
          : new Response(JSON.stringify({ error: 'Not found' }), { status: 404, headers: cors })
      }

      return new Response(JSON.stringify({ error: 'Not found' }), { status: 404, headers: cors })
    }

    // Editorial dashboard static serving (production) — mounted at /editorial/*
    if (IS_PROD && path.startsWith('/editorial')) {
      const clean = decodeURIComponent(path.split('?')[0])
      const rel = clean === '/editorial' || clean === '/editorial/' ? 'index.html' : clean.replace(/^\/editorial\/?/, '')
      const filePath = join(EDITORIAL_DIST_ROOT, rel)
      const f = Bun.file(filePath)
      if (await f.exists()) {
        const headers: Record<string, string> = rel.startsWith('assets/')
          ? { 'Cache-Control': 'public, max-age=31536000, immutable' }
          : {}
        return new Response(f, { headers })
      }
      // SPA fallback for editorial app routes
      if (!/\.[a-z0-9]+$/i.test(rel)) {
        const index = Bun.file(join(EDITORIAL_DIST_ROOT, 'index.html'))
        if (await index.exists()) {
          return new Response(index, {
            headers: { 'Content-Type': 'text/html', 'Cache-Control': 'no-cache, must-revalidate' }
          })
        }
      }
      return new Response('Not found', { status: 404 })
    }

    // Static file serving in production
    const static_ = await serveStatic(path)
    if (static_) return static_

    return new Response('Not found', { status: 404 })
  }
})

console.log(`JV Dashboard running on http://localhost:${PORT} (${IS_PROD ? 'production' : 'dev'})`)
