// Emacs backend — all emacs.* effects
import type { EffectBackend } from "../../../src/core/effects/backend";
import type { State } from "../../../src/core/eval/machine";
import type { OpCall } from "../../../src/core/effects/opcall";
import type { Val } from "../../../src/core/eval/values";

export class EmacsBackend implements EffectBackend {
  readonly ops = [
    "emacs.health.op", "emacs.ensure.op", "emacs.eval.op",
    "emacs.search.op", "emacs.edit.op", "emacs.replace-all.op",
    "emacs.restart.op", "emacs.buffer-count.op",
  ] as const;

  async handle(_state: State, opcall: OpCall): Promise<State> {
    if (opcall.op === "emacs.health.op") {
      try {
        const { checkHealth, healthToVal } = await import("./effects");
        const health = await checkHealth();
        return opcall.resumption.invoke(healthToVal(health)) as State;
      } catch (e: any) {
        const result: Val = {
          tag: "Vector",
          items: [
            { tag: "Bool", b: false } as Val,
            { tag: "Str", s: e.message ?? "Emacs health check failed" } as Val,
          ],
        };
        return opcall.resumption.invoke(result) as State;
      }
    }

    if (opcall.op === "emacs.ensure.op") {
      try {
        const { ensureReady, healthToVal } = await import("./effects");
        const health = await ensureReady();
        return opcall.resumption.invoke(healthToVal(health)) as State;
      } catch (e: any) {
        throw new Error(`emacs.ensure.op failed: ${e.message}`);
      }
    }

    if (opcall.op === "emacs.eval.op") {
      const exprArg = opcall.args[0];
      const timeoutArg = opcall.args[1];

      if (!exprArg || exprArg.tag !== "Str") {
        throw new Error("emacs.eval.op: expected string expression");
      }

      const timeout = timeoutArg?.tag === "Num" ? timeoutArg.n : undefined;

      try {
        const { evalExpr } = await import("./effects");
        const result = await evalExpr(exprArg.s, timeout);
        return opcall.resumption.invoke({ tag: "Str", s: result } as Val) as State;
      } catch (e: any) {
        throw new Error(`emacs.eval.op failed: ${e.message}`);
      }
    }

    if (opcall.op === "emacs.search.op") {
      const patternArg = opcall.args[0];

      if (!patternArg || patternArg.tag !== "Str") {
        throw new Error("emacs.search.op: expected string pattern");
      }

      try {
        const { evalExpr } = await import("./effects");
        const resultRaw = await evalExpr(`(claude-search "${patternArg.s.replace(/"/g, '\\"')}")`);
        const matches = resultRaw.match(/"([^"]+)"/g) || [];
        const items: Val[] = matches.map(m => ({
          tag: "Str",
          s: m.slice(1, -1),
        } as Val));
        return opcall.resumption.invoke({ tag: "Vector", items } as Val) as State;
      } catch (e: any) {
        throw new Error(`emacs.search.op failed: ${e.message}`);
      }
    }

    if (opcall.op === "emacs.edit.op") {
      const fileArg = opcall.args[0];
      const oldArg = opcall.args[1];
      const newArg = opcall.args[2];

      if (!fileArg || fileArg.tag !== "Str") {
        throw new Error("emacs.edit.op: expected file path");
      }
      if (!oldArg || oldArg.tag !== "Str") {
        throw new Error("emacs.edit.op: expected old string");
      }
      if (!newArg || newArg.tag !== "Str") {
        throw new Error("emacs.edit.op: expected new string");
      }

      try {
        const { evalExpr } = await import("./effects");
        const escapedFile = fileArg.s.replace(/\\/g, "/").replace(/"/g, '\\"');
        const escapedOld = oldArg.s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
        const escapedNew = newArg.s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
        await evalExpr(`(claude-edit-file "${escapedFile}" "${escapedOld}" "${escapedNew}")`);
        return opcall.resumption.invoke({ tag: "Bool", b: true } as Val) as State;
      } catch (e: any) {
        throw new Error(`emacs.edit.op failed: ${e.message}`);
      }
    }

    if (opcall.op === "emacs.replace-all.op") {
      const oldArg = opcall.args[0];
      const newArg = opcall.args[1];

      if (!oldArg || oldArg.tag !== "Str") {
        throw new Error("emacs.replace-all.op: expected old string");
      }
      if (!newArg || newArg.tag !== "Str") {
        throw new Error("emacs.replace-all.op: expected new string");
      }

      try {
        const { evalExpr } = await import("./effects");
        const escapedOld = oldArg.s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
        const escapedNew = newArg.s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
        const result = await evalExpr(`(claude-replace-all "${escapedOld}" "${escapedNew}")`);
        const count = parseInt(result, 10) || 0;
        return opcall.resumption.invoke({ tag: "Num", n: count } as Val) as State;
      } catch (e: any) {
        throw new Error(`emacs.replace-all.op failed: ${e.message}`);
      }
    }

    if (opcall.op === "emacs.restart.op") {
      try {
        const { restart, healthToVal } = await import("./effects");
        const health = await restart();
        return opcall.resumption.invoke(healthToVal(health)) as State;
      } catch (e: any) {
        throw new Error(`emacs.restart.op failed: ${e.message}`);
      }
    }

    if (opcall.op === "emacs.buffer-count.op") {
      try {
        const { checkHealth, bufferCountToVal } = await import("./effects");
        const health = await checkHealth();
        if (health.bufferCount) {
          return opcall.resumption.invoke(bufferCountToVal(health.bufferCount)) as State;
        }
        return opcall.resumption.invoke(bufferCountToVal({ ts: 0, lisp: 0, other: 0 })) as State;
      } catch (e: any) {
        throw new Error(`emacs.buffer-count.op failed: ${e.message}`);
      }
    }

    throw new Error(`EmacsBackend: unhandled op ${opcall.op}`);
  }
}
