/**
 * OmegaLispPolicyEngine — wraps MembraneStepper for backward compatibility.
 *
 * ADR-025: Power-user escape hatch. Runs omega-lisp CESK programs for
 * per-actor policy evaluation. Supports inherited zone composition.
 *
 * This moves zone composition logic (previously in MatrixActor._runMembraneInner)
 * into the engine, where it belongs.
 */
import { POLICY_ALLOW, type IPolicyEngine, type IPolicyRequest, type IPolicyDecision } from './IPolicyEngine.js';
import { MembraneStepper, DEFAULT_MEMBRANE_SOURCE } from '../membrane/MembraneStepper.js';
import type { Expr } from '@open-matrix/omega-core';

function isMergeablePayload(value: unknown): value is Record<string, unknown> {
  return value != null && typeof value === 'object' && !Array.isArray(value);
}

export class OmegaLispPolicyEngine implements IPolicyEngine {
  readonly name = 'omega-lisp';
  private _stepper: MembraneStepper;
  private _inheritedPrograms: Expr[] = [];
  private _inheritedSources: string[] = [];
  private _localProgram: Expr | null = null;
  private _localSource: string | null = null;

  constructor(stepper?: MembraneStepper) {
    this._stepper = stepper ?? MembraneStepper.create();
  }

  async evaluate(request: IPolicyRequest): Promise<IPolicyDecision> {
    // No programs compiled — allow everything (same as default membrane)
    if (this._inheritedPrograms.length === 0 && !this._localProgram) {
      return POLICY_ALLOW;
    }

    const bindings = {
      op: request.op,
      payload: request.payload,
      sender: request.sender,
      meta: request.meta,
    };

    // Track accumulated payload mutations across zones
    let mutatedPayload: unknown = undefined;
    let hasMutation = false;

    // Run inherited programs in order (ancestors → parent)
    for (const inherited of this._inheritedPrograms) {
      const result = await this._stepper.run(inherited, bindings);
      if (result.action === 'deny') {
        return { action: 'deny', error: result.error ?? 'Denied by inherited membrane' };
      }
      if (result.action === 'mutate') {
        bindings.payload = result.payload;
        mutatedPayload = result.payload;
        hasMutation = true;
      }
    }

    // Run local program (if set)
    if (this._localProgram) {
      const localResult = await this._stepper.run(this._localProgram, bindings);
      if (localResult.action === 'deny') {
        return { action: 'deny', error: localResult.error ?? 'Denied by local membrane' };
      }
      if (localResult.action === 'mutate') {
        // Compose: shallow-merge when both are plain objects, local wins otherwise
        if (hasMutation) {
          if (isMergeablePayload(mutatedPayload) && isMergeablePayload(localResult.payload)) {
            return { action: 'mutate', payload: { ...mutatedPayload, ...localResult.payload } };
          }
        }
        return { action: 'mutate', payload: localResult.payload };
      }
      // Local allows — carry forward inherited mutation if any
      if (hasMutation) {
        return { action: 'mutate', payload: mutatedPayload };
      }
      return POLICY_ALLOW;
    }

    // No local program — return inherited mutation or allow
    if (hasMutation) {
      return { action: 'mutate', payload: mutatedPayload };
    }
    return POLICY_ALLOW;
  }

  // ─── Membrane-specific methods ──────────────────────────────────────────

  /** Compile a Lisp source string into a program */
  compile(source: string): Expr {
    return this._stepper.compile(source);
  }

  /** Set inherited programs (from ancestor membranes) */
  setInherited(sources: string[]): void {
    this._inheritedSources = sources;
    this._inheritedPrograms = sources.map(s => this._stepper.compile(s));
  }

  /** Set local membrane program */
  setLocal(source: string): void {
    this._localSource = source;
    this._localProgram = this._stepper.compile(source);
  }

  /** Remove local membrane program */
  clearLocal(): void {
    this._localSource = null;
    this._localProgram = null;
  }

  /** Get current state for introspection */
  getState(): { inherited: string[]; local: string | null } {
    return {
      inherited: this._inheritedSources,
      local: this._localSource,
    };
  }

  /** Access the underlying stepper (for tests and advanced use) */
  get stepper(): MembraneStepper {
    return this._stepper;
  }
}
