// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only

/**
 * Delegation Registry Store (ADR-014 follow-up, WU-2763)
 *
 * Event-sourced state store for tracking sub-agent delegations.
 * Stores events in .lumenflow/state/delegation-registry.jsonl (append-only, git-friendly).
 *
 * Core now re-exports this module as a compatibility shim.
 */

import fs from 'node:fs/promises';
import path from 'node:path';
import { createError, ErrorCodes } from '@lumenflow/kernel/primitives/error-handler';
import {
  validateDelegationEvent,
  generateDelegationId,
  DelegationStatus,
  DELEGATION_SCHEMA_VERSION_V2,
  type DelegationEvent,
  type DelegationBriefAttestation,
  type DelegationIntentValue,
  type DelegationRoleContract,
} from './delegation-registry-schema.js';

interface ValidationIssueLike {
  path: PropertyKey[];
  message: string;
}

/** Delegation registry file name constant */
export const DELEGATION_REGISTRY_FILE_NAME = 'delegation-registry.jsonl';

function formatValidationIssues(issues: readonly ValidationIssueLike[]): string {
  return issues.map((issue) => `${issue.path.join('.')}: ${issue.message}`).join(', ');
}

/**
 * Delegation Registry Store class
 */
export class DelegationRegistryStore {
  private readonly baseDir: string;
  private readonly registryFilePath: string;
  private readonly delegations: Map<string, DelegationEvent>;
  private readonly byParent: Map<string, string[]>;
  private readonly byTarget: Map<string, string>;

  /**
   * @param {string} baseDir - Directory containing .lumenflow/state/
   */
  constructor(baseDir: string) {
    this.baseDir = baseDir;
    this.registryFilePath = path.join(baseDir, DELEGATION_REGISTRY_FILE_NAME);
    this.delegations = new Map();
    this.byParent = new Map();
    this.byTarget = new Map();
  }

  /**
   * Loads and replays events from JSONL file into current state.
   */
  async load(): Promise<void> {
    this.delegations.clear();
    this.byParent.clear();
    this.byTarget.clear();

    let content: string;
    try {
      content = await fs.readFile(this.registryFilePath, 'utf-8');
    } catch (error) {
      if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
        return;
      }
      throw error;
    }

    const lines = content.split('\n');
    for (let i = 0; i < lines.length; i++) {
      const rawLine = lines[i];
      if (typeof rawLine !== 'string') {
        continue;
      }
      const line = rawLine.trim();
      if (!line) {
        continue;
      }

      let parsed;
      try {
        parsed = JSON.parse(line);
      } catch (error) {
        throw createError(
          ErrorCodes.PARSE_ERROR,
          `Malformed JSON on line ${i + 1}: ${error.message}`,
        );
      }

      const validation = validateDelegationEvent(parsed);
      if (!validation.success) {
        throw createError(
          ErrorCodes.VALIDATION_ERROR,
          `Validation error on line ${i + 1}: ${formatValidationIssues(validation.error.issues)}`,
        );
      }

      this.applyEvent(validation.data);
    }
  }

  /**
   * Applies an event to the in-memory state.
   */
  private applyEvent(event: DelegationEvent): void {
    const { id, parentWuId, targetWuId } = event;
    this.delegations.set(id, event);

    if (!this.byParent.has(parentWuId)) {
      this.byParent.set(parentWuId, []);
    }
    const parentDelegations = this.byParent.get(parentWuId);
    if (!parentDelegations) {
      return;
    }
    if (!parentDelegations.includes(id)) {
      parentDelegations.push(id);
    }

    this.byTarget.set(targetWuId, id);
  }

  /**
   * Appends an event to the registry file.
   */
  private async appendEvent(event: DelegationEvent): Promise<void> {
    const validation = validateDelegationEvent(event);
    if (!validation.success) {
      throw createError(
        ErrorCodes.VALIDATION_ERROR,
        `Validation error: ${formatValidationIssues(validation.error.issues)}`,
      );
    }

    await fs.mkdir(this.baseDir, { recursive: true });
    await fs.appendFile(this.registryFilePath, JSON.stringify(event) + '\n', 'utf-8');
  }

  /**
   * Records a new delegation event with pending status.
   */
  async record(
    parentWuId: string,
    targetWuId: string,
    lane: string,
    intent?: DelegationIntentValue,
    briefAttestation?: DelegationBriefAttestation,
    delegationId?: string,
    roleContract?: DelegationRoleContract,
  ): Promise<string> {
    const id = delegationId ?? generateDelegationId(parentWuId, targetWuId);

    const event: DelegationEvent = {
      id,
      parentWuId,
      targetWuId,
      lane,
      ...(intent ? { intent } : {}),
      delegatedAt: new Date().toISOString(),
      status: DelegationStatus.PENDING,
      completedAt: null,
      ...(briefAttestation ? { briefAttestation } : {}),
      ...(roleContract
        ? {
            schemaVersion: DELEGATION_SCHEMA_VERSION_V2,
            requested_role: roleContract.requested_role,
            actual_role: roleContract.actual_role,
            capabilities: roleContract.capabilities,
            ownership_scope: roleContract.ownership_scope,
          }
        : {}),
    } as DelegationEvent;

    await this.appendEvent(event);
    this.applyEvent(event);
    return id;
  }

  /**
   * Updates the status of a delegation.
   */
  async updateStatus(delegationId: string, status: string): Promise<void> {
    const existing = this.delegations.get(delegationId);
    if (!existing) {
      throw createError(ErrorCodes.DELEGATION_NOT_FOUND, `Delegation ID ${delegationId} not found`);
    }

    const event: DelegationEvent = {
      ...existing,
      status: status as DelegationEvent['status'],
      completedAt: new Date().toISOString(),
    };

    await this.appendEvent(event);
    this.applyEvent(event);
  }

  /**
   * Records claim-time pickup evidence for a delegation entry.
   */
  async recordPickup(delegationId: string, pickedUpBy: string, pickedUpAt?: string): Promise<void> {
    const existing = this.delegations.get(delegationId);
    if (!existing) {
      throw createError(ErrorCodes.DELEGATION_NOT_FOUND, `Delegation ID ${delegationId} not found`);
    }

    const event: DelegationEvent = {
      ...existing,
      pickedUpBy,
      pickedUpAt: pickedUpAt ?? new Date().toISOString(),
    };

    await this.appendEvent(event);
    this.applyEvent(event);
  }

  /**
   * Gets all delegations for a parent WU.
   */
  getByParent(parentWuId: string): DelegationEvent[] {
    const delegationIds = this.byParent.get(parentWuId) ?? [];
    return delegationIds
      .map((id) => this.delegations.get(id))
      .filter((event): event is DelegationEvent => event !== undefined);
  }

  /**
   * Gets delegation for a target WU.
   */
  getByTarget(targetWuId: string): DelegationEvent | null {
    const delegationId = this.byTarget.get(targetWuId);
    if (!delegationId) {
      return null;
    }
    return this.delegations.get(delegationId) ?? null;
  }

  /**
   * Gets all pending delegations.
   */
  getPendingDelegations(): DelegationEvent[] {
    return Array.from(this.delegations.values()).filter(
      (delegation) => delegation.status === DelegationStatus.PENDING,
    );
  }

  /**
   * Gets all delegations as an array.
   */
  getAllDelegations(): DelegationEvent[] {
    return Array.from(this.delegations.values());
  }

  /**
   * Gets delegation by ID.
   */
  getById(delegationId: string): DelegationEvent | null {
    return this.delegations.get(delegationId) ?? null;
  }
}
