// Apply a proposal to disk with Mem0-aligned ADD / UPDATE / NOOP
// semantics. Whole-file writes only — never deletes. All paths
// validated repo-relative + path-traversal-free.
//
// Three outcomes per file:
//   ADD    — file didn't exist, new content written (is_new must be true)
//   UPDATE — file existed, content differs from current, overwritten
//   NOOP   — file existed, content identical to current, nothing written
//
// The NOOP case is important: a cloud proposal that "re-derives" an
// existing file shouldn't churn the repo or burn test cycles.

import { writeFile, readFile, mkdir, access } from "node:fs/promises";
import { dirname, resolve, relative } from "node:path";
import type { ApplyOutcome, Proposal } from "./types.ts";

const REPO_ROOT = "/home/profit/lakehouse";

export async function applyProposal(p: Proposal): Promise<ApplyOutcome> {
  const added: string[] = [];
  const updated: string[] = [];
  const noop: string[] = [];
  const errors: string[] = [];

  for (const f of p.files) {
    const err = validatePath(f.path);
    if (err) {
      errors.push(`${f.path}: ${err}`);
      continue;
    }
    const abs = resolve(REPO_ROOT, f.path);
    try {
      const exists = await fileExists(abs);

      if (f.is_new && exists) {
        errors.push(`${f.path}: is_new=true but file exists`);
        continue;
      }
      if (!f.is_new && !exists) {
        // Proposal claims to update a file that doesn't exist.
        // Refuse rather than silently ADD — model is confused about
        // repo state, and we want that surfaced, not papered over.
        errors.push(`${f.path}: is_new=false but file does not exist`);
        continue;
      }

      if (!f.is_new) {
        // UPDATE candidate — compare to detect NOOP.
        const current = await readFile(abs, "utf8");
        if (current === f.content) {
          noop.push(f.path);
          continue;
        }
        await writeFile(abs, f.content);
        updated.push(f.path);
      } else {
        // ADD — file doesn't exist yet.
        await mkdir(dirname(abs), { recursive: true });
        await writeFile(abs, f.content);
        added.push(f.path);
      }
    } catch (e) {
      errors.push(`${f.path}: ${(e as Error).message}`);
    }
  }
  return { added, updated, noop, errors };
}

function validatePath(p: string): string | null {
  if (!p) return "empty path";
  if (p.startsWith("/")) return "must be repo-relative";
  if (p.startsWith("..") || p.includes("/../")) return "path traversal";
  const abs = resolve(REPO_ROOT, p);
  const rel = relative(REPO_ROOT, abs);
  if (rel.startsWith("..")) return "resolves outside repo";
  return null;
}

async function fileExists(abs: string): Promise<boolean> {
  try {
    await access(abs);
    return true;
  } catch {
    return false;
  }
}
