import { performance } from 'node:perf_hooks';
import { parentPort, workerData } from 'node:worker_threads';
import type {
  BehaviorInput,
  ClawclawBehavior,
} from '../../../../sdk/behavior-plugin/index.js';
import { createBehaviorReadApi } from '../../behavior-read-api.js';
import { normalizeBehaviorDecision } from '../../behavior-decision.js';
import {
  resolveBehaviorSource,
  validateClawclawBehaviorModule,
} from './behavior-module-loader.js';
import { deepFreezeBehaviorValue } from './behavior-manifest.js';
import type { ResolvedClawclawBehaviorSource } from './behavior-source.js';
import type { ClawclawBehaviorObservationSnapshot } from '../../behavior-observation-memory.js';

interface BehaviorThreadWorkerData {
  readonly poolId: string;
  readonly slotId: number;
  readonly source: ResolvedClawclawBehaviorSource;
  readonly sourceUrl: string;
}

interface BehaviorThreadRunRequest {
  readonly type: 'run';
  readonly poolId: string;
  readonly requestId: string;
  readonly operation: 'decide';
  readonly frame: BehaviorInput;
  readonly observationSnapshot?: Readonly<ClawclawBehaviorObservationSnapshot>;
}

interface BehaviorThreadStopRequest {
  readonly type: 'stop';
  readonly poolId: string;
  readonly requestId: string;
  readonly reason: 'switched' | 'scope_ended' | 'shutdown';
}

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

function errorCode(error: unknown, fallback: string): string {
  const explicit = error && typeof error === 'object' && typeof (error as any).code === 'string'
    ? (error as any).code.trim()
    : '';
  if (/^[A-Za-z][A-Za-z0-9_.-]{0,119}$/.test(explicit)) return explicit;
  const prefix = /^([A-Za-z][A-Za-z0-9_.-]{0,119})(?::|$)/.exec(errorMessage(error).trim())?.[1];
  return prefix ?? fallback;
}

const port = parentPort;
if (!port) throw new Error('behavior_thread_parent_missing');
const data = workerData as Partial<BehaviorThreadWorkerData> | undefined;
if (
  !data
  || typeof data.poolId !== 'string'
  || typeof data.slotId !== 'number'
  || !data.source
  || typeof data.sourceUrl !== 'string'
) throw new Error('behavior_thread_worker_data_invalid');

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

let loaded: ReturnType<typeof validateClawclawBehaviorModule>;
let execution: ResolvedClawclawBehaviorSource['execution'];
try {
  const behaviorModule = await import(data.sourceUrl) as Record<string, unknown>;
  // The parent validates immediately before spawn; repeat after the complete
  // module graph has loaded so an edit in that narrow window cannot enter the pool.
  resolveBehaviorSource(data.source);
  loaded = validateClawclawBehaviorModule(behaviorModule, data.source);
  execution = loaded.manifest.execution ?? 'parallel';
} catch (error) {
  send({
    type: 'error',
    code: errorCode(error, 'BEHAVIOR_THREAD_START_FAILED'),
    message: errorMessage(error),
  });
  throw error;
}

async function run(request: BehaviorThreadRunRequest | BehaviorThreadStopRequest): Promise<void> {
  if (
    !request
    || typeof request !== 'object'
    || request.poolId !== data!.poolId
    || typeof request.requestId !== 'string'
  ) throw new Error('behavior_thread_request_invalid');
  if (request.type === 'stop') {
    send({ type: 'stopped', requestId: request.requestId });
    return;
  }
  if (request.type !== 'run' || request.operation !== 'decide') {
    throw new Error('behavior_thread_request_invalid');
  }
  if (!request.frame || typeof request.frame !== 'object') {
    throw new Error('behavior_thread_frame_invalid');
  }
  const startedAt = performance.now();
  let queryCount = 0;
  let invocationActive = true;
  const input = deepFreezeBehaviorValue(structuredClone(request.frame));
  // Cross-tick computations are isolated. The module and constructor are
  // preloaded once per slot, but no mutable behavior `this` state crosses
  // frame boundaries or becomes dependent on random slot assignment.
  const instance: ClawclawBehavior = new loaded.BehaviorClass();
  if (!instance || typeof instance.decide !== 'function') {
    throw new Error('behavior_thread_instance_invalid');
  }
  const api = createBehaviorReadApi(input, {
    onQuery: () => { queryCount += 1; },
    isActive: () => invocationActive,
    ...(request.observationSnapshot === undefined
      ? {}
      : { observationSnapshot: request.observationSnapshot }),
  });
  try {
    if (execution !== 'parallel') throw new Error('behavior_thread_decide_not_available');
    const result = normalizeBehaviorDecision(await instance.decide(input, api), { finite: false });
    send({
      type: 'result',
      requestId: request.requestId,
      result,
      queryCount,
      durationMs: Math.round((performance.now() - startedAt) * 1_000) / 1_000,
    });
  } finally {
    invocationActive = false;
  }
}

let operationTail = Promise.resolve();
port.on('message', (request: BehaviorThreadRunRequest | BehaviorThreadStopRequest) => {
  operationTail = operationTail.then(() => run(request)).catch((error) => {
    send({
      type: 'error',
      ...(typeof request?.requestId === 'string' ? { requestId: request.requestId } : {}),
      code: errorCode(error, 'BEHAVIOR_THREAD_OPERATION_FAILED'),
      message: errorMessage(error),
    });
  });
});

send({
  type: 'ready',
  behaviorId: data.source.id,
  execution,
});
