#!/usr/bin/env node
// SessionStart hook for linkedin-studio plugin
// Reads persistent state and session context, outputs JSON with additionalContext

import { readFileSync, existsSync, copyFileSync, writeFileSync, mkdirSync, readdirSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { calculateScore } from './personalization-score.mjs';
import { queueToday, queueOverdue, queueUpcoming } from './queue-manager.mjs';
import { applyWeekRollover } from './week-rollover.mjs';
import { getDataRoot } from './data-root.mjs';
import { migrateData } from './migrate-data.mjs';
import {
  activeEditions,
  readSlots,
  slotOccurrences,
  slotVacancies,
  daysUntil,
  formatSlot,
  defaultSlotsPath,
} from './slots.mjs';

const __dirname = dirname(fileURLToPath(import.meta.url));
const PLUGIN_ROOT = join(__dirname, '..', '..');
const HOME = process.env.HOME || process.env.USERPROFILE || '';
const STATE_FILE = join(HOME, '.claude', 'linkedin-studio.local.md');

function extractYaml(content, key) {
  const re = new RegExp(`^${key}: *"?([^"\\n]*)"?`, 'm');
  const m = content.match(re);
  return m ? m[1].trim() : '';
}

function daysSince(dateStr) {
  if (!dateStr || dateStr === 'null') return null;
  const epoch = new Date(dateStr).getTime();
  if (isNaN(epoch)) return null;
  return Math.floor((Date.now() - epoch) / 86400000);
}

// B-S3: newest trend capture (ISO date) from the persistent trend store, read
// as raw JSON. The hook must NOT spawn the tsx CLI at session start (slow +
// needs node_modules/tsx); the store schema (trends[].capturedAt) is stable and
// versioned, so a direct read is safe. Twin of scripts/trends/src/store.ts:
// newestCaptureDate (max capturedAt). Returns null when the store is absent,
// unreadable, or empty — no captures yet ⇒ no nudge.
function trendsNewestCapture(storePath) {
  if (!existsSync(storePath)) return null;
  try {
    const parsed = JSON.parse(readFileSync(storePath, 'utf-8'));
    const trends = Array.isArray(parsed?.trends) ? parsed.trends : [];
    let newest = null;
    for (const t of trends) {
      const d = typeof t?.capturedAt === 'string' ? t.capturedAt : null;
      if (d && (newest === null || d > newest)) newest = d;
    }
    return newest;
  } catch {
    return null;
  }
}

// RE-R2b: the most recent morning brief (date + pre-rendered summary) for
// session-start surfacing. Reads the dated Markdown directly — NO tsx (same
// zero-dep discipline as trendsNewestCapture above): the brief's frontmatter
// carries a single-line `summary` the hook surfaces verbatim (extractYaml's
// [^"\n]* + .trim() guarantees date/summary are newline-free). Returns null when
// the dir is absent / empty / unreadable — no brief yet => no block.
function latestMorningBrief(briefDir) {
  if (!existsSync(briefDir)) return null;
  try {
    const files = readdirSync(briefDir)
      .filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f))
      .sort();
    const name = files[files.length - 1];
    if (!name) return null;
    const content = readFileSync(join(briefDir, name), 'utf-8');
    return {
      date: extractYaml(content, 'date'),
      summary: extractYaml(content, 'summary'),
      file: join(briefDir, name),
    };
  } catch {
    return null;
  }
}

// SB-S2: brain consolidation freshness. Both read zero-dep through getDataRoot —
// the SAME data root the brain CLI's --apply writes to (sidecar reachable by both).
// No tsx, no profile.md parse: a readdir count + a tiny JSON read, cost-bounded.
function brainLastRun(statePath) {
  if (!existsSync(statePath)) return null;
  try {
    const j = JSON.parse(readFileSync(statePath, 'utf-8'));
    return typeof j?.last_run === 'string' ? j.last_run : null;
  } catch {
    return null;
  }
}

function countPublished(publishedDir) {
  if (!existsSync(publishedDir)) return 0;
  try {
    return readdirSync(publishedDir).filter((f) => f.endsWith('.md') && !f.startsWith('.')).length;
  } catch {
    return 0;
  }
}

function isoWeek() {
  const d = new Date();
  const dayNum = d.getUTCDay() || 7;
  d.setUTCDate(d.getUTCDate() + 4 - dayNum);
  const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
  const weekNo = Math.ceil(((d - yearStart) / 86400000 + 1) / 7);
  return `${d.getUTCFullYear()}-W${String(weekNo).padStart(2, '0')}`;
}

function dayOfWeek() {
  const d = new Date().getDay();
  return d === 0 ? 7 : d; // 1=Mon, 7=Sun (ISO)
}

let context = '';

// M0: relocate per-user data to the external root on first run. Idempotent
// (silent no-op once .migrated exists); must precede every moved-path read
// (voice/profile/scaffold scores, queue).
let m0Migration = { status: 'skip', moved: [], copied: [] };
try {
  m0Migration = migrateData();
} catch {
  // Non-critical: never block session start on migration failure.
}

// SB-S2: ensure the brain/ + ingest/ scaffold exists (zero-dep, idempotent).
// UNCONDITIONAL — must run on the fresh-install path too (the reminders block
// below only runs when the state file exists). Profile.md SEEDING needs the tsx
// fold, so it stays the manual `brain init`; here we only mkdir, and remember
// whether to nudge (the nudge is appended AFTER the if/else, since the else
// branch reassigns `context` and would clobber an early append).
let brainProfileMissing = false;
try {
  for (const d of ['brain/journal', 'ingest/inbox', 'ingest/published']) {
    mkdirSync(join(getDataRoot(''), d), { recursive: true });
  }
  brainProfileMissing = !existsSync(join(getDataRoot('brain'), 'profile.md'));
} catch {
  // Non-critical: never block session start on scaffold-ensure failure.
}

if (existsSync(STATE_FILE)) {
  const stateContent = readFileSync(STATE_FILE, 'utf-8');

  // Extract YAML frontmatter values
  const lastPostDate = extractYaml(stateContent, 'last_post_date');
  const lastPostTopic = extractYaml(stateContent, 'last_post_topic');
  const postsThisWeek = parseInt(extractYaml(stateContent, 'posts_this_week') || '0', 10);
  const weeklyGoal = parseInt(extractYaml(stateContent, 'weekly_goal') || '3', 10);
  const currentStreak = parseInt(extractYaml(stateContent, 'current_streak') || '0', 10);
  const currentWeek = extractYaml(stateContent, 'current_week');
  const nextPlannedTopic = extractYaml(stateContent, 'next_planned_topic');
  const lastImportDate = extractYaml(stateContent, 'last_import_date');
  const firstPostDate = extractYaml(stateContent, 'first_post_date');
  const followerCount = parseInt(extractYaml(stateContent, 'follower_count') || '0', 10);
  const followerTarget = parseInt(extractYaml(stateContent, 'follower_target') || '10000', 10);
  const targetDate = extractYaml(stateContent, 'target_date');
  const growthRateNeeded = parseInt(extractYaml(stateContent, 'growth_rate_needed') || '0', 10);
  const projected10kDate = extractYaml(stateContent, 'projected_10k_date');

  // Calculate days since last post
  const daysSincePost = daysSince(lastPostDate);
  const daysSinceImport = daysSince(lastImportDate);
  const daysSinceFirstPost = daysSince(firstPostDate);

  // New creator boost window
  let boostWindowStatus = '';
  let boostDaysRemaining = 0;
  if (daysSinceFirstPost !== null) {
    if (daysSinceFirstPost <= 90) {
      boostWindowStatus = 'ACTIVE';
      boostDaysRemaining = 90 - daysSinceFirstPost;
    } else if (daysSinceFirstPost <= 120) {
      boostWindowStatus = 'TRANSITION';
    } else {
      boostWindowStatus = 'ESTABLISHED';
    }
  }

  // Milestone metrics
  let milestonePhase = '';
  let milestoneStatus = '';
  let followersNeeded = 0;
  let monthsRemaining = 0;
  let ratePerMonth = 0;
  let phaseTransitionAlert = '';

  if (followerCount > 0) {
    if (followerCount < 1000) milestonePhase = 'Foundation';
    else if (followerCount < 3000) milestonePhase = 'Validation';
    else if (followerCount < 6000) milestonePhase = 'Acceleration';
    else if (followerCount < 10000) milestonePhase = 'Authority';
    else milestonePhase = 'Scale';

    // Phase transition proximity
    const thresholds = [
      { limit: 1000, label: 'Validation phase (1,000)' },
      { limit: 3000, label: 'Acceleration phase (3,000)' },
      { limit: 6000, label: 'Authority phase (6,000)' },
      { limit: 10000, label: 'Scale phase (10,000)' }
    ];
    for (const { limit, label } of thresholds) {
      if (followerCount < limit && followerCount >= limit * 0.9) {
        phaseTransitionAlert = `${limit - followerCount} followers to ${label}`;
        break;
      }
    }

    followersNeeded = Math.max(0, followerTarget - followerCount);

    // Calculate months remaining to target_date
    if (targetDate && targetDate !== 'null' && targetDate !== '""') {
      const [tYear, tMonth] = targetDate.split('-').map(Number);
      const now = new Date();
      monthsRemaining = (tYear - now.getFullYear()) * 12 + (tMonth - (now.getMonth() + 1));
      if (monthsRemaining < 1) monthsRemaining = 1;
      ratePerMonth = Math.floor(followersNeeded / monthsRemaining);
    }

    // Schedule status
    if (followerCount >= followerTarget) {
      milestoneStatus = 'ACHIEVED';
    } else if (growthRateNeeded > 0 && monthsRemaining > 0) {
      if (ratePerMonth > growthRateNeeded * 2) milestoneStatus = 'SIGNIFICANTLY BEHIND';
      else if (ratePerMonth > growthRateNeeded * 1.2) milestoneStatus = 'BEHIND';
      else if (ratePerMonth < growthRateNeeded * 0.8) milestoneStatus = 'AHEAD';
      else milestoneStatus = 'ON TRACK';
    } else if (followerCount >= followerTarget) {
      milestoneStatus = 'ACHIEVED';
    } else {
      milestoneStatus = 'TRACKING';
    }
  }

  // Week rollover — auto-reset posts_this_week on week change
  const actualWeek = isoWeek();
  let weekResetNote = '';
  try {
    const rollover = applyWeekRollover(stateContent, currentWeek, actualWeek);
    if (rollover) {
      writeFileSync(STATE_FILE, rollover.content, 'utf-8');
      weekResetNote = rollover.message;
    }
  } catch (err) {
    weekResetNote = `Warning: Week rollover failed (${err.message}). Manual reset may be needed.`;
  }

  // Auto-prune Recent Posts entries older than 90 days
  try {
    const currentState = readFileSync(STATE_FILE, 'utf-8');
    const { pruneContentHistory } = await import('./state-updater.mjs');
    const pruneResult = pruneContentHistory(currentState, 90);
    if (pruneResult && pruneResult.pruned > 0) {
      writeFileSync(STATE_FILE, pruneResult.content, 'utf-8');
      weekResetNote += (weekResetNote ? ' ' : '') + `Auto-pruned ${pruneResult.pruned} posts older than 90 days from Recent Posts.`;
    }
  } catch {
    // Non-critical: don't block session start on pruning failure
  }

  // Count published posts for progressive onboarding
  const recentPostsSection = stateContent.match(/^## Recent Posts\n([\s\S]*?)(?=\n## [^R]|\n## $|$)/m);
  let publishedPostCount = 0;
  if (recentPostsSection) {
    publishedPostCount = (recentPostsSection[1].match(/^\s*[-\[]/gm) || []).length;
  }

  // Build status line
  let statusLine = `LinkedIn: ${postsThisWeek}/${weeklyGoal} posts this week | Streak: ${currentStreak} days`;
  if (lastPostDate && lastPostDate !== 'null') {
    statusLine += ` | Last: ${lastPostDate}`;
    if (daysSincePost !== null) statusLine += ` (${daysSincePost} days ago)`;
  }
  if (lastImportDate && lastImportDate !== 'null' && daysSinceImport !== null) {
    statusLine += ` | Import: ${daysSinceImport}d ago`;
  } else {
    statusLine += ' | Import: never';
  }
  if (milestonePhase && followerCount > 0) {
    statusLine += ` | ${followerCount}/${followerTarget} followers (${milestonePhase})`;
  }

  // Personalization score (only show after 3+ published posts — progressive onboarding)
  let pScore = null;
  try {
    const { score } = calculateScore(PLUGIN_ROOT);
    pScore = score;
    if (publishedPostCount >= 3) {
      statusLine += ` | Personalization: ${score}%`;
    }
  } catch { /* ignore */ }

  // New creator window
  if (boostWindowStatus === 'ACTIVE') {
    statusLine += ` | NEW CREATOR: ${boostDaysRemaining}d left`;
  }

  // Load queue data
  let queueTodayEntries = [];
  let queueOverdueEntries = [];
  let queueUpcomingCount = 0;
  try {
    queueTodayEntries = queueToday();
    queueOverdueEntries = queueOverdue();
    queueUpcomingCount = queueUpcoming(7).length;
  } catch { /* ignore */ }

  const queueTodayCount = queueTodayEntries.length;
  const queueOverdueCount = queueOverdueEntries.length;

  let queueTodayText = '';
  if (queueTodayCount > 0) {
    queueTodayText = queueTodayEntries.map(e => {
      const t = e.scheduled_time || '?';
      const hook = (e.hook_preview || '').slice(0, 50);
      const pillar = e.pillar || '?';
      const fmt = e.format || '?';
      return `  ${t}: "${hook}..." — ${pillar} (${fmt})`;
    }).join('\n');
  }

  let queueOverdueText = '';
  if (queueOverdueCount > 0) {
    queueOverdueText = queueOverdueEntries.map(e => {
      const d = e.scheduled_date || '?';
      const hook = (e.hook_preview || '').slice(0, 50);
      const pillar = e.pillar || '?';
      return `  ${d}: "${hook}..." — ${pillar}`;
    }).join('\n');
  }

  // Build context output
  context = 'LinkedIn Studio session context loaded.\\n\\n';
  context += `## Status\\n\`\`\`\\n${statusLine}\\n\`\`\`\\n\\n`;

  if (weekResetNote) context += `**${weekResetNote}**\\n\\n`;
  if (nextPlannedTopic) context += `**Planned next topic:** ${nextPlannedTopic}\\n\\n`;
  if (lastPostTopic) context += `**Last post topic:** ${lastPostTopic}\\n\\n`;

  // Recent posts section
  const recentMatch = stateContent.match(/^## Recent Posts\n([\s\S]*?)(?=\n## [^R]|\n## $|$)/m);
  if (recentMatch) {
    const recentPosts = recentMatch[1].split('\n').slice(0, 10).join('\n');
    if (recentPosts.trim()) context += `## Recent Posts\\n${recentPosts.replace(/\n/g, '\\n')}\\n\\n`;
  }

  // Today's scheduled posts
  if (queueTodayText) {
    context += `## Today's Scheduled Posts\\n${queueTodayText.replace(/\n/g, '\\n')}\\nRun /linkedin:calendar after posting to mark as published.\\n\\n`;
  }

  // Overdue posts
  if (queueOverdueText) {
    context += `## OVERDUE Posts\\n${queueOverdueText.replace(/\n/g, '\\n')}\\nRun /linkedin:calendar to mark as posted or reschedule.\\n\\n`;
  }

  // Posting reminders
  let reminders = '';
  if (daysSincePost !== null) {
    if (daysSincePost >= 3) {
      reminders += `- No LinkedIn post in ${daysSincePost} days. Posting gaps >5 days reduce reach by 15-25%. Consider /linkedin:quick or /linkedin:pipeline.\\n`;
    }
    if (daysSincePost >= 2 && currentStreak > 3) {
      reminders += `- Your ${currentStreak}-day posting streak is at risk! Post today to keep momentum.\\n`;
    }
  }

  // First-post nudge
  if ((!firstPostDate || firstPostDate === 'null') && postsThisWeek === 0) {
    reminders += '- First post not yet created! Run /linkedin:first-post to publish your first LinkedIn post in under 10 minutes.\\n';
  }

  // Weekly goal check
  const weekRemaining = weeklyGoal - postsThisWeek;
  const dow = dayOfWeek();
  if (weekRemaining > 0 && dow >= 4) {
    reminders += `- ${weekRemaining} posts remaining to hit weekly goal of ${weeklyGoal}. It's late in the week.\\n`;
  }

  // Personalization score check (only after 3+ posts — progressive onboarding)
  if (pScore !== null && pScore < 50 && publishedPostCount >= 3) {
    reminders += `- Personalization score is ${pScore}%. Run /linkedin:setup to improve content quality with your real voice, case studies, and audience data.\\n`;
  }

  // Import staleness
  if (daysSinceImport !== null) {
    if (daysSinceImport >= 14) {
      reminders += `- Analytics data is ${daysSinceImport} days stale. Strategy recommendations may be inaccurate. Run /linkedin:import.\\n`;
    } else if (daysSinceImport >= 7) {
      reminders += `- Last analytics import was ${daysSinceImport} days ago. Consider /linkedin:import for fresh data.\\n`;
    }
  } else if (!lastImportDate || lastImportDate === 'null') {
    reminders += '- No analytics data imported yet. Run /linkedin:import to start tracking performance.\\n';
  }

  // Trend-store staleness (B-S3): warn-only, ≥7 days. Fires ONLY when the store
  // already holds captures (newest === null ⇒ no nudge), so a never-scanned user
  // is never nagged. Neutral wording — "scan for trends" hits trend-spotter's own
  // trigger; no hardcoded beat (de-niche invariant, §17 guard).
  const newestTrendCapture = trendsNewestCapture(join(getDataRoot('trends'), 'trends.json'));
  const daysSinceTrend = daysSince(newestTrendCapture);
  if (daysSinceTrend !== null && daysSinceTrend >= 7) {
    reminders += `- Trend signals are ${daysSinceTrend} days old. Scan for trends to refresh the digest before planning content.\\n`;
  }

  // Brain consolidation-due (SB-S2): fires only when published gold records exist
  // and the loop is stale/never-run (a never-used brain never nags). Counts files
  // + reads the sidecar last_run — no profile.md parse. Zero-dep.
  const publishedCount = countPublished(join(getDataRoot('ingest'), 'published'));
  const brainLastRunDate = brainLastRun(join(getDataRoot('brain'), 'consolidation-state.json'));
  const daysSinceConsolidation = daysSince(brainLastRunDate);
  if (publishedCount > 0 && (brainLastRunDate === null || (daysSinceConsolidation !== null && daysSinceConsolidation >= 7))) {
    const since = brainLastRunDate === null ? 'never run' : `${daysSinceConsolidation}d ago`;
    reminders += `- ${publishedCount} published post(s) captured, last brain consolidation ${since}. Run \`brain consolidate\` to evolve your profile.\\n`;
  }

  // Milestone reminders
  if (milestonePhase && followerCount > 0) {
    if (milestoneStatus === 'SIGNIFICANTLY BEHIND') {
      reminders += `- 10K milestone: SIGNIFICANTLY BEHIND schedule. Need ~${ratePerMonth} followers/month (2x+ original rate). Run /linkedin:strategy for corrective adjustments — current approach needs a fundamental shift.\\n`;
    } else if (milestoneStatus === 'BEHIND') {
      reminders += `- 10K milestone: BEHIND schedule. Need ~${ratePerMonth} followers/month. Consider /linkedin:strategy for trajectory-based adjustments.\\n`;
    } else if (milestoneStatus === 'AHEAD') {
      reminders += '- 10K milestone: AHEAD of schedule. Consider raising target or shifting focus to monetization (/linkedin:monetize).\\n';
    }
  } else if (!followerCount || followerCount === 0) {
    reminders += '- No follower count tracked yet. Update follower_count in state file to enable 10K milestone tracking.\\n';
  }

  // Phase transition proximity
  if (phaseTransitionAlert) {
    reminders += `- PHASE TRANSITION: ${phaseTransitionAlert}. Run /linkedin:strategy to prepare.\\n`;
  }

  // New creator advantage window
  if (boostWindowStatus === 'ACTIVE') {
    if (boostDaysRemaining < 14) {
      reminders += `- NEW CREATOR WINDOW CLOSING: Only ${boostDaysRemaining} days left! Maximize posting frequency (4-5x/week) and engagement (15-20 comments/day) now.\\n`;
    } else if (boostDaysRemaining < 30) {
      reminders += `- New creator window: ${boostDaysRemaining} days remaining. Maintain high frequency (4-5x/week) to lock in algorithmic momentum.\\n`;
    } else {
      reminders += `- New creator advantage active (${boostDaysRemaining}d left). Higher posting frequency pays outsized returns during this window.\\n`;
    }
  } else if (boostWindowStatus === 'TRANSITION') {
    reminders += `- New creator window ended ${daysSinceFirstPost} days ago. Transition to sustainable posting rhythm (3-4x/week) and optimize based on analytics.\\n`;
  }

  // Queue-related reminders
  if (queueTodayCount > 0) {
    reminders += `- You have ${queueTodayCount} post(s) scheduled for today. Run /linkedin:calendar after posting to mark as published.\\n`;
  }
  if (queueOverdueCount > 0) {
    reminders += `- ${queueOverdueCount} overdue post(s) in queue. Run /linkedin:calendar to mark as posted or reschedule.\\n`;
  }

  if (reminders) context += `## Posting Reminders\\n${reminders}\\n`;

  // 10K Milestone Tracker section
  if (milestonePhase && followerCount > 0) {
    context += '## 10K Milestone Tracker\\n';
    context += `- Current: ${followerCount} followers (Phase: ${milestonePhase})\\n`;
    if (monthsRemaining > 0 && followersNeeded > 0) {
      context += `- Required rate: ~${ratePerMonth} followers/month to hit ${followerTarget} by ${targetDate}\\n`;
    }
    if (milestoneStatus) context += `- Status: ${milestoneStatus}\\n`;
    if (projected10kDate && projected10kDate !== 'null' && projected10kDate !== '""') {
      context += `- Projected: ${projected10kDate} (at current rate)\\n`;
    }
    if (phaseTransitionAlert) context += `- PHASE TRANSITION: ${phaseTransitionAlert}\\n`;
    if (milestoneStatus === 'SIGNIFICANTLY BEHIND') {
      context += '- Trajectory hint: Current approach needs fundamental adjustment. Run /linkedin:strategy for corrective plan.\\n';
    } else if (milestoneStatus === 'BEHIND') {
      context += '- Trajectory hint: Consider /linkedin:strategy for trajectory-based adjustments to close the gap.\\n';
    } else if (milestoneStatus === 'AHEAD') {
      context += '- Trajectory hint: Strong momentum. Consider raising target or shifting to monetization (/linkedin:monetize).\\n';
    }
    context += '\\n';
  }

  // New creator advantage window context
  if (boostWindowStatus === 'ACTIVE') {
    context += '## New Creator Advantage Window\\n';
    context += `- Status: ACTIVE (day ${daysSinceFirstPost} of 90, ${boostDaysRemaining} days remaining)\\n`;
    context += `- First post: ${firstPostDate}\\n`;
    context += '- Recommended frequency: 4-5x/week (vs standard 3x)\\n';
    context += '- Recommended engagement: 15-20 strategic comments/day\\n';
    context += '- Priority: Save-worthy content (frameworks, checklists, templates)\\n\\n';
  } else if (boostWindowStatus === 'TRANSITION') {
    context += '## New Creator Advantage Window\\n';
    context += `- Status: TRANSITION (day ${daysSinceFirstPost}, window closed at day 90)\\n`;
    context += '- Shift to sustainable rhythm: 3-4x/week, optimize based on analytics data\\n\\n';
  }

  // Queue summary
  if (queueUpcomingCount > 0) {
    context += '## Queue Summary\\n';
    context += `- Queued posts (next 7 days): ${queueUpcomingCount}\\n`;
    if (queueTodayCount > 0) context += `- Today: ${queueTodayCount} post(s)\\n`;
    if (queueOverdueCount > 0) context += `- Overdue: ${queueOverdueCount} post(s)\\n`;
    context += '- Manage + publish: /linkedin:calendar\\n\\n';
  }

  context += `State file: ${STATE_FILE}\\n`;

} else {
  // Auto-initialize state file from template
  const templateFile = join(PLUGIN_ROOT, 'config', 'state-file.template.md');
  if (existsSync(templateFile)) {
    mkdirSync(dirname(STATE_FILE), { recursive: true });
    copyFileSync(templateFile, STATE_FILE);
    const actualWeek = isoWeek();
    let content = readFileSync(STATE_FILE, 'utf-8');
    content = content.replace(/^current_week: .*/m, `current_week: "${actualWeek}"`);
    writeFileSync(STATE_FILE, content);
    context = `LinkedIn state file auto-initialized from template at ${STATE_FILE}.\\n`;
    context += `Current ISO week set to ${actualWeek}.\\n`;
    context += 'Edit the file to set your expertise_areas and weekly_goal.\\n';
  } else {
    context = `No LinkedIn state file found at ${STATE_FILE} and template missing.\\n`;
    context += `Expected template at: ${templateFile}\\n`;
  }
}

// SB-S2: nudge to seed the brain if it isn't initialised (after the if/else, so
// the fresh-install branch's `context = …` reassignment can't clobber it).
if (brainProfileMissing) {
  context += '\\n## Brain\\n- Brain not initialised. Run `brain init` to seed your evolving profile.\\n';
}

// RE-R2b: surface the latest dated morning brief (zero-tsx; summary is a
// pre-rendered single line from the brief's frontmatter). Unconditional on a brief
// existing, like the brain nudge above (so the fresh-install branch surfaces it too).
const latestBrief = latestMorningBrief(join(getDataRoot('trends'), 'morning-brief'));
if (latestBrief && latestBrief.summary) {
  context += `\\n## Morning Brief (${latestBrief.date})\\n${latestBrief.summary}\\n→ Full brief: ${latestBrief.file}\\n`;
}

// N13 (A1-10/A1-11): the week's production picture — long-form editions in
// flight, and publishing slots nothing covers yet. Unconditional like the brain
// nudge above (the fresh-install branch reassigns `context`, so an earlier
// append would be clobbered). Zero-tsx: slots.mjs reads the register, the queue
// and the grid as plain JSON, the same discipline as trendsNewestCapture.
// Both blocks are silent when their data is absent — an operator who has
// configured no slots and started no edition is never nagged.
const SLOT_HORIZON_DAYS = 14;
const SLOT_IMMINENT_DAYS = 3;
try {
  const inFlight = activeEditions();
  if (inFlight.length > 0) {
    context += '\\n## Editions in Flight\\n';
    for (const e of inFlight) {
      const age = e.daysInFlight === null ? '' : ` | ${e.daysInFlight}d in flight`;
      context += `- ${e.series} #${e.editionId} — ${e.title || 'untitled'} | phase: ${e.currentPhase}`;
      context += ` | next: ${e.nextAction || 'unset'} | slot: ${e.slot || 'unclaimed'}${age}\\n`;
    }
    context += '- Resume an edition with /linkedin:newsletter (it reads edition-state.json, not this view).\\n';
  }

  const { slots } = readSlots();
  if (slots.length > 0) {
    const today = new Date().toISOString().slice(0, 10);
    const planned = slotOccurrences(slots, today, SLOT_HORIZON_DAYS).length;
    const vacancies = slotVacancies({ from: today, days: SLOT_HORIZON_DAYS });
    context += '\\n## Publishing Slots\\n';
    if (vacancies.length > 0) {
      const until = daysUntil(today, vacancies[0].date);
      context += `- Next open slot: ${formatSlot(vacancies[0], today)}\\n`;
      context += `- Open in the next ${SLOT_HORIZON_DAYS} days: ${vacancies.length} of ${planned} slot(s)\\n`;
      if (until !== null && until <= SLOT_IMMINENT_DAYS) {
        context += '- Fill it: /linkedin:batch (short-form) or /linkedin:newsletter (long-form).\\n';
      }
    } else {
      context += `- All ${planned} slot(s) in the next ${SLOT_HORIZON_DAYS} days are covered.\\n`;
    }
    context += `- Grid: ${defaultSlotsPath()}\\n`;
  }
} catch {
  // Non-critical: a broken grid or register must never block session start.
}

// Read REMEMBER.md for user session context
const rememberFile = join(getDataRoot(), 'REMEMBER.md');
const rememberTemplate = join(PLUGIN_ROOT, 'config', 'REMEMBER.template.md');

if (!existsSync(rememberFile) && existsSync(rememberTemplate)) {
  mkdirSync(dirname(rememberFile), { recursive: true });
  copyFileSync(rememberTemplate, rememberFile);
  let rememberContent = readFileSync(rememberFile, 'utf-8');
  const today = new Date().toISOString().slice(0, 10);
  rememberContent = rememberContent.replace('[Auto-filled by session-start.sh]', today);
  writeFileSync(rememberFile, rememberContent);
  context += '\\n## Session State\\nREMEMBER.md auto-initialized from template. Update after your first session.\\n';
} else if (existsSync(rememberFile)) {
  const rememberContent = readFileSync(rememberFile, 'utf-8');
  const rememberSummary = rememberContent.split('\n').slice(0, 50).join('\n');
  context += `\\n## Session Context (from REMEMBER.md)\\n${rememberSummary.replace(/\n/g, '\\n')}\\n`;
}

// M0: log one line when the migration actually relocated data (D5).
if (m0Migration.status === 'migrated' && (m0Migration.moved.length || m0Migration.copied.length)) {
  context += `\\nLinkedIn Studio: migrated ${m0Migration.moved.length} file(s) + ${m0Migration.copied.length} scaffold(s) to the external data root.\\n`;
}

// Output JSON for Claude Code
const output = {
  continue: true,
  hookSpecificOutput: {
    hookEventName: 'SessionStart',
    additionalContext: context.replace(/\\n/g, '\n')
  }
};

process.stdout.write(JSON.stringify(output));
