import { parentPort, workerData } from 'node:worker_threads';
import type {
  ClawclawTrigger,
  ClawclawTriggerClass,
  TriggerInput,
  TriggerResetReason,
} from '../../../../sdk/trigger-plugin/index.js';
import type { BehaviorInput } from '../../../../sdk/behavior-plugin/index.js';
import { createBehaviorReadApi } from '../../behavior-read-api.js';
import { ClawclawBehaviorObservationMemory } from '../../behavior-observation-memory.js';
import { assertRuntimeModuleOutputSize } from '../../../framework/modules/runtime-module-output.js';
import {
  validateClawclawTriggerModule,
  type TriggerThreadModuleSpec,
} from './trigger-module-loader.js';

interface TriggerThreadWorkerData {
  readonly poolId: string;
  readonly laneId: number;
  readonly modules: readonly TriggerThreadModuleSpec[];
  readonly diagnosticsEnabled?: boolean;
}

interface TriggerEvaluateRequest {
  readonly type: 'evaluate';
  readonly poolId: string;
  readonly requestId: string;
  readonly triggerId: string;
  readonly input: TriggerInput;
  readonly readInput: BehaviorInput;
  readonly resetReason?: TriggerResetReason;
}

function errorMessage(error: unknown): string {
  return error instanceof Error ? error.message : String(error);
}

function errorCode(error: unknown): string {
  const message = errorMessage(error).trim();
  return /^([A-Za-z][A-Za-z0-9_.-]{0,119})(?::|$)/.exec(message)?.[1]
    ?? 'TRIGGER_THREAD_OPERATION_FAILED';
}

function deepFreeze<T>(value: T, seen = new WeakSet<object>()): T {
  if (!value || typeof value !== 'object' || Object.isFrozen(value)) return value;
  const object = value as object;
  if (seen.has(object)) return value;
  seen.add(object);
  for (const child of Object.values(object)) deepFreeze(child, seen);
  return Object.freeze(value);
}

const port = parentPort;
if (!port) throw new Error('trigger_thread_parent_missing');
const data = workerData as Partial<TriggerThreadWorkerData> | undefined;
if (
  !data
  || typeof data.poolId !== 'string'
  || typeof data.laneId !== 'number'
  || !Array.isArray(data.modules)
  || data.modules.length === 0
) throw new Error('trigger_thread_worker_data_invalid');

function diagnosticMemory(): Readonly<Record<string, number>> {
  return data?.diagnosticsEnabled === true
    ? { heapUsedBytes: process.memoryUsage().heapUsed }
    : {};
}

function send(value: Readonly<Record<string, unknown>>): void {
  port!.postMessage({ ...value, poolId: data!.poolId!, laneId: data!.laneId! });
}

const classes = new Map<string, ClawclawTriggerClass>();
const modules = new Map<string, ClawclawTrigger>();
const observationMemories = new Map<string, ClawclawBehaviorObservationMemory>();
try {
  for (const spec of data.modules) {
    const loaded = await import(spec.sourceUrl) as Record<string, unknown>;
    const ModuleClass = validateClawclawTriggerModule(loaded, spec.source);
    classes.set(spec.triggerId, ModuleClass);
    modules.set(spec.triggerId, new ModuleClass());
    observationMemories.set(spec.triggerId, new ClawclawBehaviorObservationMemory());
  }
} catch (error) {
  send({
    type: 'error',
    code: errorCode(error),
    message: errorMessage(error),
    ...diagnosticMemory(),
  });
  throw error;
}

async function evaluate(request: TriggerEvaluateRequest): Promise<void> {
  if (
    request.poolId !== data!.poolId
    || typeof request.requestId !== 'string'
    || typeof request.triggerId !== 'string'
    || !request.input
    || !request.readInput
  ) throw new Error('trigger_thread_request_invalid');
  const module = modules.get(request.triggerId);
  const ModuleClass = classes.get(request.triggerId);
  const observationMemory = observationMemories.get(request.triggerId);
  if (!module || !ModuleClass || !observationMemory) throw new Error('trigger_thread_module_unknown');
  let invocationActive = true;
  try {
    if (request.resetReason) {
      observationMemory.clear();
      await module.reset?.(request.resetReason);
    }
    const observationSnapshot = observationMemory.capture(request.readInput);
    const value = await module.evaluate(
      deepFreeze(request.input),
      createBehaviorReadApi(deepFreeze(request.readInput), {
        isActive: () => invocationActive,
        observationSnapshot,
      }),
    );
    const result = structuredClone(value);
    const size = assertRuntimeModuleOutputSize(result, 'trigger_output');
    send({
      type: 'result',
      requestId: request.requestId,
      triggerId: request.triggerId,
      result,
      outputBytes: size.bytes,
      ...diagnosticMemory(),
    });
  } catch (error) {
    modules.set(request.triggerId, new ModuleClass());
    observationMemory.clear();
    send({
      type: 'request_error',
      requestId: request.requestId,
      triggerId: request.triggerId,
      code: errorCode(error),
      message: errorMessage(error),
      ...diagnosticMemory(),
    });
  } finally {
    invocationActive = false;
  }
}

let operationTail = Promise.resolve();
port.on('message', (request: TriggerEvaluateRequest) => {
  operationTail = operationTail.then(() => evaluate(request)).catch((error) => {
    send({
      type: 'error',
      requestId: typeof request?.requestId === 'string' ? request.requestId : undefined,
      code: errorCode(error),
      message: errorMessage(error),
      ...diagnosticMemory(),
    });
  });
});

send({
  type: 'ready',
  triggerIds: [...modules.keys()],
  ...diagnosticMemory(),
});
