#!/usr/bin/env bun
// Extract review fragments that mention onset/duration ("after 3 weeks", "took a month
// or so") from _raw_reviews.json for each pilot SKU. Bucket by time-window so the
// Timeline generator can quote real customer language per phase.
//
// Output: data/intelligence/<sku>/review-time-mentions.json
//
// Usage: bun scripts/extract-review-time-mentions.ts [SKU1 SKU2 ...]

import { join } from 'path'
import { readFileSync, writeFileSync, existsSync } from 'fs'

const ROOT = join(import.meta.dir, '..')
const PILOT_FILE = join(ROOT, 'data', 'pipeline', 'pilot-skus.json')
const INTEL = join(ROOT, 'data', 'intelligence')

type Review = { comment: string; stars: number }
type Pilot = { sku: string; name: string; category: string; timelineCadence: 'weeks' | 'months' | string; phases: string[] }
type Pilots = { skus: Pilot[] }

// Patterns that capture an onset duration. Each match is normalised to "days".
const PATTERNS: { re: RegExp; toDays: (m: RegExpExecArray) => number }[] = [
  { re: /\b(?:after|in|within|takes?|took|by)\s+(?:about\s+|around\s+|nearly\s+)?(\d{1,3})\s*(day|week|month|year)s?\b/gi, toDays: m => Number(m[1]) * unitDays(m[2]) },
  { re: /\b(?:a\s+couple|couple)\s+of\s+(week|month|day)s?\b/gi, toDays: m => 2 * unitDays(m[1]) },
  { re: /\bfew\s+(week|month|day)s?\b/gi, toDays: m => 3 * unitDays(m[1]) },
  { re: /\b(?:first|second|third|fourth|fifth|sixth)\s+(week|month)\b/gi, toDays: m => 1 * unitDays(m[1]) },
  { re: /\bover (?:the\s+)?(?:past|last)\s+(\d{1,3})\s*(day|week|month|year)s?\b/gi, toDays: m => Number(m[1]) * unitDays(m[2]) },
  { re: /\b(\d{1,3})\s*[-/]\s*(\d{1,3})\s*(week|month|day)s?\b/gi, toDays: m => ((Number(m[1]) + Number(m[2])) / 2) * unitDays(m[3]) },
  { re: /\bday\s*one\b/gi, toDays: () => 1 },
  { re: /\bovernight\b/gi, toDays: () => 1 },
  { re: /\bstraight\s*away\b|\bimmediately\b/gi, toDays: () => 1 }
]
function unitDays(unit: string): number {
  const u = unit.toLowerCase()
  if (u.startsWith('day')) return 1
  if (u.startsWith('week')) return 7
  if (u.startsWith('month')) return 30
  if (u.startsWith('year')) return 365
  return 7
}

// Map a phase label (e.g. "Week 1-2", "Month 3", "Month 4+") to a [minDays, maxDays] window.
function phaseToDays(label: string): [number, number] {
  const l = label.toLowerCase()
  const rangeMatch = l.match(/(week|month|day)\s*(\d{1,3})\s*[-/]\s*(\d{1,3})/)
  if (rangeMatch) {
    const u = unitDays(rangeMatch[1])
    return [Number(rangeMatch[2]) * u, Number(rangeMatch[3]) * u]
  }
  const plusMatch = l.match(/(week|month|day)\s*(\d{1,3})\s*\+/)
  if (plusMatch) {
    const u = unitDays(plusMatch[1])
    return [Number(plusMatch[2]) * u, 99 * u]
  }
  const singleMatch = l.match(/(week|month|day)\s*(\d{1,3})/)
  if (singleMatch) {
    const u = unitDays(singleMatch[1])
    const n = Number(singleMatch[2])
    return [(n - 1) * u + 1, n * u]
  }
  return [1, 365]
}

function extractMentions(reviews: Review[]) {
  type Mention = { reviewIdx: number; stars: number; days: number; matchedPhrase: string; sentence: string }
  const out: Mention[] = []
  for (let i = 0; i < reviews.length; i++) {
    const text = reviews[i]?.comment || ''
    if (!text) continue
    // Split into rough sentences for context windowing
    const sentences = text.split(/(?<=[.!?])\s+/)
    for (const sentence of sentences) {
      for (const { re, toDays } of PATTERNS) {
        re.lastIndex = 0
        let m: RegExpExecArray | null
        while ((m = re.exec(sentence)) !== null) {
          const days = toDays(m)
          if (!Number.isFinite(days) || days <= 0 || days > 730) continue
          out.push({
            reviewIdx: i,
            stars: reviews[i]?.stars ?? 0,
            days,
            matchedPhrase: m[0],
            sentence: sentence.trim().slice(0, 280)
          })
        }
      }
    }
  }
  // Dedupe: per (reviewIdx, sentence) keep the smallest day-value mention
  const seen = new Map<string, Mention>()
  for (const m of out) {
    const k = `${m.reviewIdx}::${m.sentence}`
    const cur = seen.get(k)
    if (!cur || m.days < cur.days) seen.set(k, m)
  }
  return [...seen.values()].sort((a, b) => a.days - b.days)
}

function bucketByPhase(mentions: ReturnType<typeof extractMentions>, phases: string[]) {
  const buckets: Record<string, typeof mentions> = {}
  for (const p of phases) buckets[p] = []
  // Build phase windows in order
  const windows = phases.map(p => ({ label: p, range: phaseToDays(p) }))
  for (const m of mentions) {
    const w = windows.find(w => m.days >= w.range[0] && m.days <= w.range[1])
    if (w) buckets[w.label].push(m)
  }
  return { buckets, windows }
}

function processSku(skuInfo: Pilot) {
  const reviewsPath = join(INTEL, skuInfo.sku, '_raw_reviews.json')
  if (!existsSync(reviewsPath)) {
    console.log(`[${skuInfo.sku}] no _raw_reviews.json — skipping`)
    return null
  }
  const reviews: Review[] = JSON.parse(readFileSync(reviewsPath, 'utf8'))
  const mentions = extractMentions(reviews)
  const { buckets, windows } = bucketByPhase(mentions, skuInfo.phases)
  const out = {
    sku: skuInfo.sku,
    name: skuInfo.name,
    category: skuInfo.category,
    generatedAt: new Date().toISOString(),
    generatedBy: 'scripts/extract-review-time-mentions.ts',
    sourceReviews: reviewsPath,
    totalReviews: reviews.length,
    totalMentions: mentions.length,
    phases: windows.map(w => ({
      label: w.label,
      windowDays: w.range,
      mentionCount: buckets[w.label].length,
      topMentions: buckets[w.label]
        .slice()
        .sort((a, b) => (b.stars || 0) - (a.stars || 0) || a.days - b.days)
        .slice(0, 5)
    }))
  }
  const outFile = join(INTEL, skuInfo.sku, 'review-time-mentions.json')
  writeFileSync(outFile, JSON.stringify(out, null, 2))
  console.log(`[${skuInfo.sku}] ${mentions.length} time-mentions across ${reviews.length} reviews`)
  for (const p of out.phases) console.log(`  ${p.label}: ${p.mentionCount}`)
  return out
}

async function main() {
  const pilot: Pilots = JSON.parse(readFileSync(PILOT_FILE, 'utf8'))
  const requested = process.argv.slice(2)
  const targets = requested.length ? pilot.skus.filter(s => requested.includes(s.sku)) : pilot.skus
  for (const s of targets) processSku(s)
  console.log('\nDone.')
}

await main()
