#!/usr/bin/env bun

import { Database } from 'bun:sqlite';
import { homedir } from 'os';
import { join, basename, dirname } from 'path';
import { existsSync, copyFileSync } from 'fs';
import { spawnSync } from 'child_process';

const DB_PATH = join(homedir(), '.claude-mem', 'claude-mem.db');
const APPLY = process.argv.includes('--apply');

type Classification =
  | { kind: 'main'; project: string }
  | { kind: 'worktree'; project: string; parent: string }
  | { kind: 'skip'; reason: string };

function git(cwd: string, args: string[]): string | null {
  const r = spawnSync('git', ['-C', cwd, ...args], { encoding: 'utf8' });
  if (r.status !== 0) {
    const stderr = (r.stderr ?? '').trim();
    if (stderr && !/not a git repository/i.test(stderr)) {
      console.error(`git ${args.join(' ')} failed in ${cwd}: ${stderr}`);
    }
    return null;
  }
  return r.stdout.trim();
}

function classify(cwd: string): Classification {
  if (!existsSync(cwd)) return { kind: 'skip', reason: 'cwd-missing' };

  const gitDir = git(cwd, ['rev-parse', '--absolute-git-dir']);
  if (!gitDir) return { kind: 'skip', reason: 'not-a-git-repo' };

  const commonDir = git(cwd, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
  if (!commonDir) return { kind: 'skip', reason: 'no-common-dir' };

  const toplevel = git(cwd, ['rev-parse', '--show-toplevel']);
  if (!toplevel) return { kind: 'skip', reason: 'no-toplevel' };
  const leaf = basename(toplevel);

  if (gitDir === commonDir) {
    return { kind: 'main', project: leaf };
  }

  const parentRepoDir = commonDir.endsWith('/.git')
    ? dirname(commonDir)
    : commonDir.replace(/\.git$/, '');
  const parent = basename(parentRepoDir);
  return { kind: 'worktree', project: `${parent}/${leaf}`, parent };
}

function main() {
  if (!existsSync(DB_PATH)) {
    console.error(`DB not found at ${DB_PATH}`);
    process.exit(1);
  }

  if (APPLY) {
    const backup = `${DB_PATH}.bak-cwd-remap-${Date.now()}`;
    copyFileSync(DB_PATH, backup);
    console.log(`Backup created: ${backup}`);
  }

  const db = new Database(DB_PATH);

  const cwdRows = db.prepare(`
    SELECT cwd, COUNT(*) AS messages
    FROM pending_messages
    WHERE cwd IS NOT NULL AND cwd != ''
    GROUP BY cwd
  `).all() as Array<{ cwd: string; messages: number }>;

  console.log(`Classifying ${cwdRows.length} distinct cwds via git...`);

  const byCwd = new Map<string, Classification>();
  const counts = { main: 0, worktree: 0, skip: 0 };
  for (const { cwd } of cwdRows) {
    const c = classify(cwd);
    byCwd.set(cwd, c);
    counts[c.kind]++;
  }
  console.log(`  main=${counts.main}  worktree=${counts.worktree}  skip=${counts.skip}`);

  const skipped = [...byCwd.entries()].filter(([, c]) => c.kind === 'skip') as Array<[string, Extract<Classification, { kind: 'skip' }>]>;
  if (skipped.length) {
    console.log('\nSkipped cwds:');
    for (const [cwd, c] of skipped) console.log(`  [${c.reason}] ${cwd}`);
  }

  const sessionRows = db.prepare(`
    SELECT s.id AS session_id, s.memory_session_id, s.content_session_id, s.project AS old_project, p.cwd
    FROM sdk_sessions s
    JOIN pending_messages p ON p.content_session_id = s.content_session_id
    WHERE p.cwd IS NOT NULL AND p.cwd != ''
      AND p.id = (
        SELECT MIN(p2.id) FROM pending_messages p2
        WHERE p2.content_session_id = s.content_session_id
          AND p2.cwd IS NOT NULL AND p2.cwd != ''
      )
  `).all() as Array<{ session_id: number; memory_session_id: string | null; content_session_id: string; old_project: string; cwd: string }>;

  type Target = { sessionId: number; memorySessionId: string | null; contentSessionId: string; oldProject: string; newProject: string; cwd: string };
  const perSession = new Map<number, Target>();

  for (const r of sessionRows) {
    const c = byCwd.get(r.cwd);
    if (!c || c.kind === 'skip') continue;
    perSession.set(r.session_id, {
      sessionId: r.session_id,
      memorySessionId: r.memory_session_id,
      contentSessionId: r.content_session_id,
      oldProject: r.old_project,
      newProject: c.project,
      cwd: r.cwd,
    });
  }

  const targets = [...perSession.values()].filter(t => t.oldProject !== t.newProject);

  console.log(`\nSessions linked to a classified cwd: ${perSession.size}`);
  console.log(`Sessions whose project would change: ${targets.length}`);

  const summary = new Map<string, number>();
  for (const t of targets) {
    const key = `${t.oldProject}  →  ${t.newProject}`;
    summary.set(key, (summary.get(key) ?? 0) + 1);
  }
  const rows = [...summary.entries()]
    .map(([mapping, n]) => ({ mapping, sessions: n }))
    .sort((a, b) => b.sessions - a.sessions);
  console.log('\nTop mappings:');
  console.table(rows.slice(0, 30));
  if (rows.length > 30) console.log(`  …and ${rows.length - 30} more mappings`);

  if (!APPLY) {
    console.log('\nDry-run only. Re-run with --apply to perform UPDATEs.');
    db.close();
    return;
  }

  const updSession = db.prepare('UPDATE sdk_sessions      SET project = ? WHERE id = ?');
  const updObs     = db.prepare('UPDATE observations      SET project = ? WHERE memory_session_id = ?');
  const updSum     = db.prepare('UPDATE session_summaries SET project = ? WHERE memory_session_id = ?');

  let sessionN = 0, obsN = 0, sumN = 0;
  const tx = db.transaction(() => {
    for (const t of targets) {
      sessionN += updSession.run(t.newProject, t.sessionId).changes;
      if (t.memorySessionId) {
        obsN += updObs.run(t.newProject, t.memorySessionId).changes;
        sumN += updSum.run(t.newProject, t.memorySessionId).changes;
      }
    }
  });
  tx();

  console.log(`\nApplied. sessions=${sessionN} observations=${obsN} session_summaries=${sumN}`);
  db.close();
}

main();
