/**
 * Autonomous loop manager.
 *
 * Loads loop definitions from LOOP.md files (three-tier: bundled → personal → project)
 * and seeds them into the cron_jobs table on daemon startup (idempotent).
 *
 * Loops are seeded as disabled by default. Enable them via:
 *   nomos cron enable <name>
 */

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { getKysely } from "../db/client.ts";
import { parseFrontmatter } from "../skills/frontmatter.ts";
import { createLogger } from "../lib/logger.ts";

const log = createLogger("autonomous");

interface AutonomousLoop {
  name: string;
  description: string;
  schedule: string;
  scheduleType: "cron";
  prompt: string;
  sessionTarget: "main" | "isolated";
  deliveryMode: "none" | "announce";
  enabled: boolean;
  source: string;
}

/**
 * Load LOOP.md files from a single directory.
 * Each loop is a subdirectory containing a LOOP.md file.
 */
function loadLoopsFromDir(dir: string, source: string): AutonomousLoop[] {
  if (!fs.existsSync(dir)) return [];

  let entries: fs.Dirent[];
  try {
    entries = fs.readdirSync(dir, { withFileTypes: true });
  } catch {
    return [];
  }

  const loops: AutonomousLoop[] = [];

  for (const entry of entries) {
    if (!entry.isDirectory() || entry.name.startsWith(".")) continue;

    const loopMd = path.join(dir, entry.name, "LOOP.md");
    if (!fs.existsSync(loopMd)) continue;

    try {
      const raw = fs.readFileSync(loopMd, "utf-8");
      const { frontmatter, body } = parseFrontmatter(raw);

      if (!frontmatter.schedule) continue;

      loops.push({
        name: frontmatter.name ?? entry.name,
        description: frontmatter.description ?? "",
        schedule: frontmatter.schedule,
        scheduleType: "cron",
        prompt: body.trim(),
        sessionTarget: (frontmatter["session-target"] as "main" | "isolated") ?? "main",
        deliveryMode: (frontmatter["delivery-mode"] as "none" | "announce") ?? "none",
        enabled: frontmatter.enabled === "true",
        source,
      });
    } catch {
      // Skip malformed loop files
    }
  }

  return loops;
}

/**
 * Resolve the bundled autonomous/ directory.
 * Works in both dev (project root) and installed (relative to entry point) modes.
 */
function resolveBundledDir(): string | null {
  const candidates = [
    path.resolve("autonomous"),
    path.resolve(path.dirname(process.argv[1] ?? ""), "..", "autonomous"),
  ];

  for (const dir of candidates) {
    if (!fs.existsSync(dir)) continue;
    try {
      const entries = fs.readdirSync(dir, { withFileTypes: true });
      const hasLoop = entries.some((e) => {
        if (!e.isDirectory()) return false;
        return fs.existsSync(path.join(dir, e.name, "LOOP.md"));
      });
      if (hasLoop) return dir;
    } catch {
      continue;
    }
  }
  return null;
}

/**
 * Load all loops from three tiers.
 * Precedence (highest wins): project > personal > bundled
 */
export function loadAllLoops(): AutonomousLoop[] {
  const bundledDir = resolveBundledDir();
  const personalDir = path.join(os.homedir(), ".nomos", "autonomous");
  const projectDir = path.resolve(".nomos", "autonomous");

  const merged = new Map<string, AutonomousLoop>();

  // Load in precedence order (lowest first, later overwrites)
  if (bundledDir) {
    for (const loop of loadLoopsFromDir(bundledDir, "bundled")) {
      merged.set(loop.name, loop);
    }
  }
  for (const loop of loadLoopsFromDir(personalDir, "personal")) {
    merged.set(loop.name, loop);
  }
  for (const loop of loadLoopsFromDir(projectDir, "project")) {
    merged.set(loop.name, loop);
  }

  return Array.from(merged.values());
}

/**
 * Seed autonomous loop definitions into the cron_jobs table.
 *
 * Uses INSERT ... ON CONFLICT (name) DO NOTHING for idempotency.
 * Safe to call on every daemon start.
 */
/** Map a loader tier to a persisted provenance: bundled stays bundled, a user's
 * own personal/project LOOP.md is theirs (user). */
function tierToSource(tier: string): "bundled" | "user" {
  return tier === "bundled" ? "bundled" : "user";
}

export async function seedAutonomousLoops(): Promise<void> {
  const db = getKysely();
  const loops = loadAllLoops();
  let seeded = 0;

  for (const loop of loops) {
    const source = tierToSource(loop.source);
    const result = await db
      .insertInto("cron_jobs")
      .values({
        name: loop.name,
        schedule: loop.schedule,
        schedule_type: loop.scheduleType,
        session_target: loop.sessionTarget,
        delivery_mode: loop.deliveryMode,
        prompt: loop.prompt,
        enabled: loop.enabled,
        error_count: 0,
        source,
      })
      .onConflict((oc) => oc.column("name").doNothing())
      .returning("id")
      .execute();
    if (result.length > 0) {
      seeded++;
    }

    // Backfill provenance for installs that seeded this loop before the `source`
    // column existed (they default to 'system'). Only relabel rows still
    // untouched (source = 'system') so user/agent re-tagging is never clobbered.
    // The insert stays DO NOTHING so a user's enabled/schedule edits survive.
    await db
      .updateTable("cron_jobs")
      .set({ source })
      .where("name", "=", loop.name)
      .where("source", "=", "system")
      .execute();
  }

  if (seeded > 0) {
    log.info(`Seeded ${seeded} autonomous loop(s)`);
  } else {
    log.info(`All ${loops.length} autonomous loops already seeded`);
  }
}

/**
 * Get the loaded loop definitions for reference.
 */
export function getLoopDefinitions(): readonly AutonomousLoop[] {
  return loadAllLoops();
}
