#!/usr/bin/env node
// M0 migration: relocate per-user data from the plugin tree to the external data
// root (getDataRoot()). Created at Step 4, WIRED into session-start at Step 12 —
// this module only acts when called. Atomic, locked, idempotent, and
// external-canonical (never overwrites an existing external file).
//
// Two move-classes:
//   (a) gitignored runtime data → atomic MOVE (source removed only after a
//       verified copy).
//   (b) tracked D2 scaffolds → COPY (tracked source preserved; Step 13 later
//       templatizes the in-plugin file).
//
// MOVE_FILES/COPY_FILES below are the SINGLE SOURCE OF TRUTH for external paths.
// Step 7's personalization-score split MUST resolve the same scaffold dests
// (import COPY_FILES from here, or mirror it) — keep them in lockstep.
// External dest = in-plugin path minus 'assets/' (the brief 7.1 convention).

import {
  existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync,
  renameSync, unlinkSync, rmSync, readdirSync,
  openSync, fsyncSync, closeSync,
} from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { getDataRoot } from './data-root.mjs';

const __dirname = dirname(fileURLToPath(import.meta.url));

// [in-plugin (rel pluginRoot), external (rel dataRoot)] — atomic MOVE.
const MOVE_FILES = [
  ['assets/voice-samples/authentic-voice-samples.local.md', 'voice-samples/authentic-voice-samples.md'], // D6: drop .local
  ['assets/drafts/queue.json', 'drafts/queue.json'],
  ['config/user-profile.local.md', 'profile/user-profile.md'], // D1 (expected-absent today)
];

// Whole-directory MOVE: every file under src → matching path under dest.
const MOVE_DIRS = [
  ['assets/analytics/exports', 'analytics/exports'],
  ['assets/analytics/posts', 'analytics/posts'],
  ['assets/analytics/weekly-reports', 'analytics/weekly-reports'],
  ['assets/analytics/monthly-reports', 'analytics/monthly-reports'], // expected-absent
];

// drafts/week-* runtime subdirs → external drafts/week-*.
const DRAFTS_WEEK = ['assets/drafts', 'drafts', /^week-/];

// move-class (b): tracked scaffolds → external instance (COPY, source kept).
const COPY_FILES = [
  ['assets/examples/high-engagement-posts.md', 'examples/high-engagement-posts.md'],
  ['assets/audience-insights/demographics.md', 'audience-insights/demographics.md'],
  ['assets/audience-insights/engagement-patterns.md', 'audience-insights/engagement-patterns.md'],
  ['assets/templates/my-post-templates.md', 'templates/my-post-templates.md'],
];

export { MOVE_FILES, COPY_FILES };

// Atomic copy: tmp in dest dir → fsync → byte-verify → rename. Returns false
// (without touching anything) if dest already exists — external is canonical.
function atomicCopy(src, dest) {
  if (existsSync(dest)) return false;
  mkdirSync(dirname(dest), { recursive: true });
  const tmp = `${dest}.migrating.${process.pid}`;
  copyFileSync(src, tmp);
  const fd = openSync(tmp, 'r+');
  try { fsyncSync(fd); } finally { closeSync(fd); }
  if (!readFileSync(src).equals(readFileSync(tmp))) {
    unlinkSync(tmp);
    throw new Error(`migrate verify failed: ${src}`);
  }
  renameSync(tmp, dest);
  return true;
}

function moveFile(srcAbs, destAbs, log) {
  if (!existsSync(srcAbs)) return;            // expected-absent → no-op
  if (atomicCopy(srcAbs, destAbs)) {
    unlinkSync(srcAbs);                        // remove source only after verified copy
    log.moved.push(destAbs);
  } else {
    log.skipped.push(destAbs);                // collision: external kept, source untouched
  }
}

function copyFile(srcAbs, destAbs, log) {
  if (!existsSync(srcAbs)) return;
  if (atomicCopy(srcAbs, destAbs)) log.copied.push(destAbs);
  else log.skipped.push(destAbs);             // source always preserved for COPY class
}

function walkFiles(dir) {
  const out = [];
  for (const e of readdirSync(dir, { withFileTypes: true })) {
    const p = join(dir, e.name);
    if (e.isDirectory()) out.push(...walkFiles(p));
    else if (e.isFile()) out.push(p);
  }
  return out;
}

function moveDir(srcDir, destDir, log) {
  if (!existsSync(srcDir)) return;
  for (const f of walkFiles(srcDir)) {
    moveFile(f, join(destDir, f.slice(srcDir.length + 1)), log);
  }
}

export function migrateData({ pluginRoot, dataRoot } = {}) {
  pluginRoot = pluginRoot || join(__dirname, '..', '..');
  dataRoot = dataRoot || getDataRoot();

  const marker = join(dataRoot, '.migrated');
  if (existsSync(marker)) return { status: 'already-migrated', moved: [], copied: [], skipped: [] };

  mkdirSync(dataRoot, { recursive: true });
  const lockDir = join(dataRoot, '.migrate.lock');
  try {
    mkdirSync(lockDir);                         // atomic; throws EEXIST if held
  } catch (e) {
    if (e.code === 'EEXIST') return { status: 'locked', moved: [], copied: [], skipped: [] };
    throw e;
  }

  const log = { status: 'migrated', moved: [], copied: [], skipped: [] };
  try {
    for (const [src, dest] of MOVE_FILES) moveFile(join(pluginRoot, src), join(dataRoot, dest), log);
    for (const [src, dest] of MOVE_DIRS) moveDir(join(pluginRoot, src), join(dataRoot, dest), log);

    const [wSrc, wDest, wRe] = DRAFTS_WEEK;
    const wSrcAbs = join(pluginRoot, wSrc);
    if (existsSync(wSrcAbs)) {
      for (const e of readdirSync(wSrcAbs, { withFileTypes: true })) {
        if (e.isDirectory() && wRe.test(e.name)) {
          moveDir(join(wSrcAbs, e.name), join(dataRoot, wDest, e.name), log);
        }
      }
    }

    for (const [src, dest] of COPY_FILES) copyFile(join(pluginRoot, src), join(dataRoot, dest), log);

    // Marker LAST → the whole run is idempotently skippable on re-entry.
    writeFileSync(marker, `linkedin-studio M0 data migration\nmoved=${log.moved.length} copied=${log.copied.length} skipped=${log.skipped.length}\n`);
  } finally {
    rmSync(lockDir, { recursive: true, force: true });
  }
  return log;
}

// Standalone: run against the real roots.
if (process.argv[1] === fileURLToPath(import.meta.url)) {
  const r = migrateData();
  console.log(`Migration: ${r.status} — moved ${r.moved.length}, copied ${r.copied.length}, skipped ${r.skipped.length}`);
}
