#!/usr/bin/env bun
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
import { join } from 'path'

const root = process.cwd()
const outRoot = join(root, 'content_population_exports')
mkdirSync(outRoot, { recursive: true })

function readJson(path: string, fallback: any = null) {
  if (!existsSync(path)) return fallback
  return JSON.parse(readFileSync(path, 'utf8').replace(/^\uFEFF/, ''))
}

function csv(rows: any[], headers: string[]) {
  const esc = (value: any) => {
    if (value === undefined || value === null) value = ''
    if (Array.isArray(value)) value = value.join(' | ')
    value = String(value)
    return /[",\n\r]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value
  }
  return [headers.join(','), ...rows.map(row => headers.map(header => esc(row[header])).join(','))].join('\n') + '\n'
}

const generatedAt = new Date().toISOString()
const sourcePlan = readJson(join(outRoot, 'source_acquisition_plan.json'), {})
const pilot = readJson(join(outRoot, 'dataforseo_pilot_manifest.json'), {})
const dataforseoLive = readJson(join(outRoot, 'dataforseo_live_connection.json'), {})
const dataforseoProductsLiveRun = readJson(join(outRoot, 'dataforseo_products_live_run.json'), {})
const asinLookup = readJson(join(outRoot, 'dataforseo_asin_lookup_queue.json'), {})
const asinWebEvidence = readJson(join(outRoot, 'dataforseo_asin_web_evidence.json'), {})
const unresolvedAsinEvidence = readJson(join(outRoot, 'dataforseo_unresolved_asin_evidence.json'), {})
const asinConfirmation = readJson(join(outRoot, 'dataforseo_asin_confirmation_report.json'), {})
const asinPromotion = readJson(join(outRoot, 'dataforseo_asin_promotion_report.json'), {})
const reviewsLivePilot = readJson(join(outRoot, 'dataforseo_reviews_live_pilot.json'), {})
const reviewsDocsStatus = readJson(join(outRoot, 'dataforseo_reviews_docs_status.json'), {})
const supportEscalation = readJson(join(outRoot, 'dataforseo_support_escalation.json'), {})
const supportResponseGate = readJson(join(outRoot, 'dataforseo_support_response_gate.json'), {})
const approvalRequest = readJson(join(outRoot, 'dataforseo_stage1_approval_request.json'), {})
const manualAmazonCapture = readJson(join(outRoot, 'manual_amazon_review_capture.json'), {})
const manualAmazonCaptureValidation = readJson(join(outRoot, 'manual_amazon_review_capture_validation.json'), {})
const ingest = readJson(join(outRoot, 'dataforseo_ingest_validation.json'), {})
const budgetGate = readJson(join(outRoot, 'source_acquisition_budget_gate.json'), {})
const costPlan = readJson(join(outRoot, 'source_acquisition_cost_plan.json'), {})
const approvalGate = readJson(join(outRoot, 'source_acquisition_approval_gate.json'), {})
const spendGate = readJson(join(outRoot, 'source_acquisition_spend_gate.json'), {})
const review = readJson(join(outRoot, 'dataforseo_pilot_review.json'), {})
const apify = readJson(join(outRoot, 'apify_gapfill_manifest.json'), {})
const apifyPreflight = readJson(join(outRoot, 'apify_task_preflight.json'), {})
const pricingRefs = readJson(join(outRoot, 'source_acquisition_pricing_references.json'), {})
const costScenarios = readJson(join(outRoot, 'source_acquisition_cost_scenarios.json'), {})
const subscriptionDecision = readJson(join(outRoot, 'source_subscription_decision.json'), {})
const requiredPacketFiles = [
  'source_acquisition_plan.json',
  'dataforseo_pilot_manifest.json',
  'dataforseo_live_connection.json',
  'dataforseo_products_live_run.json',
  'dataforseo_asin_lookup_queue.json',
  'dataforseo_asin_web_evidence.json',
  'dataforseo_unresolved_asin_evidence.json',
  'dataforseo_asin_confirmation_report.json',
  'dataforseo_asin_promotion_report.json',
  'dataforseo_reviews_live_pilot.json',
  'dataforseo_reviews_docs_status.json',
  'dataforseo_support_escalation.json',
  'dataforseo_support_response_gate.json',
  'dataforseo_stage1_approval_request.json',
  'manual_amazon_review_capture.json',
  'manual_amazon_review_capture_validation.json',
  'dataforseo_ingest_validation.json',
  'dataforseo_pilot_review.json',
  'apify_gapfill_manifest.json',
  'apify_task_preflight.json',
  'source_acquisition_pricing_references.json',
  'source_acquisition_cost_scenarios.json',
  'source_acquisition_budget_gate.json',
  'source_acquisition_cost_plan.json',
  'source_acquisition_approval_gate.json',
  'source_acquisition_spend_gate.json',
  'source_subscription_decision.json'
]
const requiredPacketFilesPresent = requiredPacketFiles.filter(file => existsSync(join(outRoot, file))).length

const missingAsins = Number(dataforseoLive.counts?.missingAsins ?? pilot.counts?.missingAsins ?? asinLookup.counts?.missingAsins ?? 0)
const asinPresent = Number(dataforseoLive.counts?.exactAsinsConfirmed ?? asinLookup.counts?.asinPresent ?? 0)
const readyTasks = Number(pilot.counts?.readyTasks ?? 0)
const productSearchResultItems = Number(dataforseoProductsLiveRun.counts?.resultItems || dataforseoLive.counts?.candidates || 0)
const exactAsinsConfirmed = Number(dataforseoLive.counts?.exactAsinsConfirmed ?? asinLookup.counts?.asinPresent ?? 0)
const confirmedPromotionCandidates = Number(asinConfirmation.counts?.readyForPromotion ?? 0)
const webLookupAids = Number(asinWebEvidence.counts?.thirdPartyLookupAids ?? 0)
const webRejected = Number(asinWebEvidence.counts?.rejectedNotTarget ?? 0)
const unresolvedKeepOut = Number(unresolvedAsinEvidence.counts?.keepOutOfPaidPayload ?? 0)
const promotedAsins = Number(asinPromotion.counts?.promotable ?? 0)
const convertedReviews = Number(ingest.counts?.convertedReviews ?? 0)
const reviewPilotTasks = Number(reviewsLivePilot.counts?.tasksPosted ?? 0)
const reviewPilotReviews = Number(reviewsLivePilot.counts?.reviewsReturned ?? 0)
const reviewsDocsUnavailable = reviewsDocsStatus.endpointTemporarilyUnavailable === true
const supportEscalationReady = supportEscalation.ready === true
const supportResponsePassing = Number(supportResponseGate.counts?.passing ?? 0)
const supportResponseChecks = Number(supportResponseGate.counts?.checks ?? 0)
const approvalRequestReadyChecks = Number(approvalRequest.counts?.readyChecks ?? 0)
const approvalRequestChecks = Number(approvalRequest.counts?.checks ?? 0)
const manualAmazonCaptureTargets = Number(manualAmazonCapture.counts?.targets ?? 0)
const manualAmazonCaptureFilesPresent = Number(manualAmazonCaptureValidation.counts?.filesPresent ?? 0)
const manualAmazonCaptureFilesValid = Number(manualAmazonCaptureValidation.counts?.filesValid ?? 0)
const manualAmazonCaptureReviews = Number(manualAmazonCaptureValidation.counts?.reviewsCaptured ?? 0)
const dataforseoBudgetAllowed = budgetGate.budgetAllowed?.dataforseoStage1 === true
const apifyBudgetAllowed = budgetGate.budgetAllowed?.apifyStage2 === true
const dataforseoCostReady = costPlan.costReady?.dataforseoStage1 === true
const apifyCostReady = costPlan.costReady?.apifyStage2 === true
const dataforseoApprovalValid = (approvalGate.approvals || []).some((row: any) => row.key === 'dataforseoStage1' && row.valid === true)
const apifyApprovalValid = (approvalGate.approvals || []).some((row: any) => row.key === 'apifyStage2' && row.valid === true)
const dataforseoSpendAllowed = spendGate.spendAllowed?.dataforseoStage1 === true
const apifySpendAllowed = spendGate.spendAllowed?.apifyStage2 === true
const reviewReady = review.ready === true
const reviewDecision = review.decision || 'pending'
const sourcePacketFresh = requiredPacketFilesPresent === requiredPacketFiles.length
const firstBatchApifyTargets = Number(apify.counts?.firstBatchTargets || 0)
const apifyPreflightPassing = Number(apifyPreflight.counts?.passing || 0)
const apifyPreflightChecks = Number(apifyPreflight.counts?.checks || 0)
const pricingReferences = Number(pricingRefs.counts?.references || 0)
const costScenarioCount = Number(costScenarios.counts?.scenarios || 0)
const subscriptionHeadline = subscriptionDecision.headline || ''

const asinExamples = (asinLookup.queue || []).slice(0, 9).map((row: any) => `${row.SKU} (${row.Product})`)

const rows = [
  {
    Sequence: 1,
    Stage: '0',
    Provider: 'No paid provider',
    Action: 'Fill and verify Stage 1 ASIN map',
    Status: missingAsins === 0 && asinPresent > 0 ? 'done' : 'blocked',
    SpendAllowed: 'no spend required',
    Owner: 'Research / project owner',
    Evidence: `${asinPresent} ASINs present / ${missingAsins} missing; ${webLookupAids} web lookup aids; ${webRejected} rejected not-target leads; ${productSearchResultItems} DataForSEO product-result items saved; ${unresolvedKeepOut} unresolved targets have no exact DataForSEO candidate and stay out of the paid payload`,
    CommandOrFile: 'bun run dataforseo:products-live with explicit allow flag, then bun run dataforseo:live-asin-evidence && bun run dataforseo:unresolved-asins; data/sources/dataforseo/asin-map.csv',
    StopRule: 'Only accept exact Just Vitamins Amazon UK product matches; leave blank if uncertain.',
    WhyNext: readyTasks > 0
      ? 'This remains useful no-spend research, but the capped pilot payload already uses only the live-confirmed ASINs.'
      : 'This is the cheapest blocker: without ASINs there is no DataForSEO payload and no reason to approve spend.'
  },
  {
    Sequence: 2,
    Stage: '0',
    Provider: 'No paid provider',
    Action: 'Promote only Amazon-confirmed high-confidence ASIN candidates',
    Status: promotedAsins > 0 ? 'done' : 'blocked',
    SpendAllowed: 'no spend required',
    Owner: 'Research',
    Evidence: `${confirmedPromotionCandidates} promotion confirmations ready; ${promotedAsins} candidates promoted / ${asinPromotion.counts?.candidateRows || 0} candidate rows`,
    CommandOrFile: 'bun run dataforseo:asin-confirmations && bun run dataforseo:promote-asins',
    StopRule: 'Only rows marked ready in the confirmation report plus amazon_confirmed_exact_match and Confidence=high can be copied into asin-map.csv; existing ASINs are not overwritten.',
    WhyNext: 'This closes confirmed ASINs safely while keeping uncertain third-party candidate evidence out of paid task payloads.'
  },
  {
    Sequence: 3,
    Stage: '1',
    Provider: 'DataForSEO',
    Action: 'Regenerate capped Amazon Reviews task payload',
    Status: readyTasks > 0 ? 'ready' : 'blocked',
    SpendAllowed: 'no spend required',
    Owner: 'Research',
    Evidence: `${readyTasks} ready task rows / ${pilot.counts?.stageOneTargets || 0} capped targets; ${missingAsins} unresolved ASINs remain outside the payload`,
    CommandOrFile: 'bun run dataforseo:pilot',
    StopRule: 'Payload must stay within Stage 1 cap before any provider call.',
    WhyNext: 'Turns the verified ASINs into a reviewable paid-call payload without calling DataForSEO.'
  },
  {
    Sequence: 4,
    Stage: '1',
    Provider: 'DataForSEO',
    Action: 'Record an explicit Stage 1 budget cap before approval',
    Status: dataforseoBudgetAllowed ? 'ready' : 'blocked',
    SpendAllowed: dataforseoBudgetAllowed ? 'budget cap recorded' : 'blocked',
    Owner: 'Project owner',
    Evidence: `${budgetGate.counts?.passing || 0}/${budgetGate.counts?.checks || 0} budget checks passing`,
    CommandOrFile: budgetGate.budgetFile || 'data/sources/budgets/source-acquisition-budget.json',
    StopRule: 'Budget must include max spend, max tasks, review depth, and hard stop after the first run. Do not put credentials here.',
    WhyNext: 'A subscription is only safe after the hard cap is written down locally and can be checked by the spend gate.'
  },
  {
    Sequence: 5,
    Stage: '1',
    Provider: 'DataForSEO',
    Action: 'Record current pricing and estimated cost for the capped pilot',
    Status: dataforseoCostReady ? 'ready' : 'blocked',
    SpendAllowed: dataforseoCostReady ? 'cost estimate accepted' : 'blocked',
    Owner: 'Project owner / research',
    Evidence: `${costPlan.estimates?.dataforseoStage1?.estimatedTotal || 0} estimated / ${costPlan.estimates?.dataforseoStage1?.maxSpend || 0} max spend; ${pricingReferences} pricing refs and ${costScenarioCount} scenarios available`,
    CommandOrFile: 'content_population_exports/source_acquisition_cost_scenarios.md + data/sources/budgets/source-acquisition-cost-plan.json && bun run source:cost-plan',
    StopRule: 'Do not enable budget or approval from guessed pricing; record current provider pricing source and keep estimate within max spend.',
    WhyNext: 'This is the anti-credit-burn step before subscription: it turns pricing into an explicit capped first-run estimate.'
  },
  {
    Sequence: 6,
    Stage: '1',
    Provider: 'DataForSEO',
    Action: 'Record human spend approval for one capped pilot run',
    Status: dataforseoSpendAllowed ? 'ready' : 'blocked',
    SpendAllowed: dataforseoSpendAllowed ? 'allowed' : 'blocked',
    Owner: 'Project owner',
    Evidence: `${spendGate.counts?.passing || 0}/${spendGate.counts?.checks || 0} spend-gate checks passing; approval valid=${dataforseoApprovalValid}`,
    CommandOrFile: 'bun run dataforseo:approval-request && bun run source:approvals && ' + (spendGate.approvalFiles?.dataforseoStage1 || 'data/sources/approvals/dataforseo-stage1-approval.json'),
    StopRule: 'Approval should name the cap, provider, marketplace, target count, and expected max spend before credentials are used.',
    WhyNext: 'Prevents accidental credit burn; approval is deliberately local and explicit.'
  },
  {
    Sequence: 7,
    Stage: '1',
    Provider: 'DataForSEO',
    Action: 'Run only the approved DataForSEO pilot and save raw task_get JSON',
    Status: convertedReviews > 0 ? 'done' : (dataforseoSpendAllowed ? 'ready' : 'blocked'),
    SpendAllowed: dataforseoSpendAllowed ? 'allowed for capped pilot only' : 'blocked',
    Owner: 'Research',
    Evidence: `${reviewPilotTasks} review tasks posted / ${reviewPilotReviews} reviews returned; ${ingest.counts?.rawFiles || 0} raw files / ${convertedReviews} converted reviews`,
    CommandOrFile: 'bun run dataforseo:reviews-live then data/sources/dataforseo/raw/',
    StopRule: 'Do not run a second batch until raw output, useful fields, and actual credit cost are reviewed.',
    WhyNext: 'This is the first paid step and should be a single controlled proof of data quality and cost.'
  },
  {
    Sequence: 8,
    Stage: '1 support',
    Provider: 'DataForSEO',
    Action: 'Send provider support escalation if Reviews endpoint remains 50304',
    Status: supportEscalationReady ? 'ready' : 'blocked',
    SpendAllowed: 'no spend required',
    Owner: 'Project owner / support',
    Evidence: `${supportEscalation.issue || 'No support packet generated'}; official docs unavailable=${reviewsDocsUnavailable}`,
    CommandOrFile: 'bun run dataforseo:reviews-docs-status && bun run dataforseo:support-escalation then content_population_exports/dataforseo_support_escalation.md; when support replies, fill data/sources/provider-responses/dataforseo-reviews-endpoint-response.json and run bun run dataforseo:support-response',
    StopRule: 'Escalation packet must contain no credentials and should include endpoint, status code, tiny payload shape, and proof that Products endpoint works.',
    WhyNext: 'This turns the provider-side blocker into an actionable support request instead of more retries or guessing.'
  },
  {
    Sequence: 9,
    Stage: '1 fallback',
    Provider: 'Manual capture',
    Action: 'Capture Amazon reviews manually if DataForSEO support remains blocked',
    Status: manualAmazonCaptureValidation.ready === true ? 'done' : (manualAmazonCaptureTargets > 0 ? 'ready' : 'blocked'),
    SpendAllowed: 'no provider spend',
    Owner: 'Research',
    Evidence: `${manualAmazonCaptureTargets} manual capture targets; ${manualAmazonCaptureFilesPresent}/${manualAmazonCaptureTargets} files present; ${manualAmazonCaptureFilesValid}/${manualAmazonCaptureTargets} files valid; ${manualAmazonCaptureReviews} reviews captured`,
    CommandOrFile: 'bun run amazon:manual-capture && bun run amazon:manual-capture-validate then content_population_exports/manual_amazon_review_capture_validation.md',
    StopRule: 'Capture only the listed Stage 1 ASINs; do not broaden to catalogue scraping.',
    WhyNext: 'This gives a no-spend fallback source path while the DataForSEO Reviews endpoint is unavailable.'
  },
  {
    Sequence: 10,
    Stage: '1',
    Provider: 'DataForSEO',
    Action: 'Normalize raw provider output into the standard Amazon source-drop path',
    Status: ingest.ready === true ? 'done' : (Number(ingest.counts?.rawFiles || 0) > 0 ? 'ready' : 'blocked'),
    SpendAllowed: 'no additional spend',
    Owner: 'Research / dashboard',
    Evidence: `${ingest.counts?.convertedFiles || 0} converted files / ${ingest.counts?.issues || 0} ingest issues`,
    CommandOrFile: 'bun run dataforseo:ingest && bun run validate:sources',
    StopRule: 'Converted source files must preserve review text, rating, date, source URL, SKU, and provider provenance.',
    WhyNext: 'Keeps DataForSEO evidence on the same validation path as manual Amazon drops.'
  },
  {
    Sequence: 11,
    Stage: '1 review',
    Provider: 'DataForSEO',
    Action: 'Review quality, fields, and cost; decide scale vs Apify vs stop',
    Status: reviewReady ? 'ready' : 'blocked',
    SpendAllowed: 'blocks all scale-up spend',
    Owner: 'Project owner / research',
    Evidence: `${review.counts?.passing || 0}/${review.counts?.checks || 0} review checks passing; decision=${reviewDecision}`,
    CommandOrFile: review.reviewFile || 'data/sources/reviews/dataforseo-stage1-review.json',
    StopRule: 'Decision must be one of scale_dataforseo, run_apify_gapfill, or stop_adjust_source_map.',
    WhyNext: 'Prevents the pilot from silently turning into full-catalogue scraping.'
  },
  {
    Sequence: 12,
    Stage: '2',
    Provider: 'Apify selective',
    Action: 'Run Apify only for named gaps after DataForSEO review',
    Status: apifySpendAllowed ? 'ready' : 'blocked',
    SpendAllowed: apifySpendAllowed ? 'allowed for named gap only' : 'blocked',
    Owner: 'Research',
    Evidence: `${firstBatchApifyTargets} first-batch targets / ${apify.counts?.holdTargets || 0} held; Apify preflight ${apifyPreflightPassing}/${apifyPreflightChecks}; Apify budget allowed=${apifyBudgetAllowed}; cost ready=${apifyCostReady}; approval valid=${apifyApprovalValid}; Apify spend allowed=${apifySpendAllowed}`,
    CommandOrFile: 'bun run apify:task-preflight then content_population_exports/apify_competitor_pdp_input_template.json',
    StopRule: 'One actor, first batch only, inspect raw dataset quality/cost before any further run.',
    WhyNext: 'Apify is useful for PDP/social/browser gaps, but only after the cheaper structured pilot proves what is missing.'
  },
  {
    Sequence: 13,
    Stage: 'control',
    Provider: 'All source packet lanes',
    Action: 'Refresh source packet and delivery index after every change',
    Status: sourcePacketFresh ? 'ready' : 'blocked',
    SpendAllowed: 'no spend required',
    Owner: 'Dashboard',
    Evidence: `${requiredPacketFilesPresent}/${requiredPacketFiles.length} required source packet files present`,
    CommandOrFile: 'bun run source:refresh-packet && bun run delivery:index',
    StopRule: 'Packet freshness does not mean final handoff readiness; verify:handoff remains the final gate.',
    WhyNext: 'Keeps the Export Console and handoff packet aligned with the latest source-acquisition state.'
  }
]

const openRows = rows.filter(row => row.Status !== 'done')
const immediateRows = openRows.filter(row => row.Sequence <= 5)
const blockedPaidRows = rows.filter(row => row.Provider !== 'No paid provider' && String(row.SpendAllowed).includes('blocked'))
const manifest = {
  generatedAt,
  ready: reviewReady && (reviewDecision === 'scale_dataforseo' || reviewDecision === 'run_apify_gapfill' || reviewDecision === 'stop_adjust_source_map'),
  recommendation: sourcePlan.recommendation || 'Use DataForSEO first for the capped structured pilot; reserve Apify for named gaps after review.',
  currentInstruction: missingAsins > 0 && readyTasks > 0
    ? `Do not broaden spend. ${readyTasks} DataForSEO payload rows are ready from live-confirmed ASINs; ${missingAsins} ASINs remain unresolved and must stay out of the payload unless exact evidence is found.`
    : missingAsins > 0
      ? `Do not subscribe or spend yet. Fill ${missingAsins} missing ASINs first.`
    : dataforseoSpendAllowed
      ? 'DataForSEO Stage 1 spend is allowed for the capped pilot only; do not scale until pilot review is complete.'
      : 'ASINs/payload may be ready, but spend remains blocked until approval and gate checks pass.',
  counts: {
    actions: rows.length,
    open: openRows.length,
    immediate: immediateRows.length,
    blockedPaidActions: blockedPaidRows.length,
    missingAsins,
    confirmedAsins: exactAsinsConfirmed,
    confirmedPromotionCandidates,
    webLookupAids,
    webRejected,
    unresolvedKeepOut,
    promotedAsins,
    readyTasks,
    productSearchResultItems,
    reviewPilotTasks,
    reviewPilotReviews,
    reviewsDocsUnavailable,
    supportEscalationReady,
    supportResponsePassing,
    supportResponseChecks,
    approvalRequestReadyChecks,
    approvalRequestChecks,
    manualAmazonCaptureTargets,
    manualAmazonCaptureFilesPresent,
    manualAmazonCaptureFilesValid,
    manualAmazonCaptureReviews,
    convertedReviews
  },
  asinExamples,
  subscriptionDecision: subscriptionHeadline,
  actions: rows,
  immediateActions: immediateRows,
  paidSpendBlockedActions: blockedPaidRows
}

writeFileSync(join(outRoot, 'source_acquisition_next_actions.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8')
writeFileSync(join(outRoot, 'source_acquisition_next_actions.csv'), csv(rows, ['Sequence', 'Stage', 'Provider', 'Action', 'Status', 'SpendAllowed', 'Owner', 'Evidence', 'CommandOrFile', 'StopRule', 'WhyNext']), 'utf8')
writeFileSync(join(outRoot, 'source_acquisition_next_actions.md'), [
  '# Source acquisition next actions',
  '',
  `Generated: ${generatedAt}`,
  '',
  `Recommendation: ${manifest.recommendation}`,
  '',
  `Current instruction: ${manifest.currentInstruction}`,
  '',
  '## Immediate no/low-spend actions',
  ...immediateRows.map(row => `- [${row.Status}] ${row.Sequence}. ${row.Action} — ${row.Evidence}. Use: \`${row.CommandOrFile}\``),
  '',
  '## Stage sequence',
  ...rows.map(row => [
    `### ${row.Sequence}. ${row.Provider} / Stage ${row.Stage}: ${row.Action}`,
    `- Status: ${row.Status}`,
    `- Spend: ${row.SpendAllowed}`,
    `- Owner: ${row.Owner}`,
    `- Evidence: ${row.Evidence}`,
    `- Command/file: \`${row.CommandOrFile}\``,
    `- Stop rule: ${row.StopRule}`,
    `- Why this order: ${row.WhyNext}`,
    ''
  ].join('\n')),
  '## ASIN lookup targets',
  ...asinExamples.map(item => `- ${item}`),
  '',
  '## Guardrail',
  'DataForSEO is the default first paid lane. Apify stays blocked until DataForSEO raw output is inspected and a named gap is recorded.',
  ''
].join('\n'), 'utf8')

console.log(`Source acquisition next actions ready: ${manifest.ready}`)
console.log(`Immediate actions: ${manifest.counts.immediate}`)
console.log(`Missing ASINs: ${manifest.counts.missingAsins}`)
