
import type { IMatrixContext } from '../../engine/core/IMatrixContext';
import type { IExitSignal } from '../../engine/mailbox/IMailbox';
import type { ChildManager } from '../children/ChildManager';

export type SupervisionStrategy = 'OneForOne' | 'OneForAll' | 'RestForOne';

export interface ISupervisorPolicy {
    strategy: SupervisionStrategy;
    maxRestarts: number;
    windowMs: number;
}

export const DEFAULT_SUPERVISION_POLICY: ISupervisorPolicy = {
    strategy: 'OneForOne',
    maxRestarts: 5,
    windowMs: 10000,
};

/**
 * Internal tracking for restart counts (separate from ChildManager).
 */
interface IRestartEntry {
    id: string;
    mount: string;
    restarts: number[]; // Timestamps of restarts
}

/**
 * SupervisorBehavior - Encapsulates Child Lifecycle & Failure Recovery.
 *
 * Vision:
 * - Uses ChildManager for blueprints and child lifecycle.
 * - Maintains own restart-count tracking (IRestartEntry).
 * - Implements OTP-style restart strategies.
 * - Handles 'cheating-free' introspection via async messaging.
 */
export class SupervisorBehavior {
    /** Restart tracking (separate from ChildManager's child tracking) */
    private readonly restartTracking = new Map<string, IRestartEntry>();
    private readonly context: IMatrixContext;
    private readonly childManager: ChildManager;
    private readonly policy: ISupervisorPolicy;

    constructor(
        context: IMatrixContext,
        childManager: ChildManager,
        policy: ISupervisorPolicy = DEFAULT_SUPERVISION_POLICY
    ) {
        this.context = context;
        this.childManager = childManager;
        this.policy = policy;
    }

    /**
     * Register a new child for restart tracking (AFTER it has been spawned by Runtime).
     * ChildManager handles the actual child data; this is for restart counts.
     */
    registerChild(id: string, mount: string): void {
        this.restartTracking.set(id, { id, mount, restarts: [] });
    }

    /**
     * Unregister a child from restart tracking (e.g. after deliberate removal).
     */
    unregisterChild(id: string): void {
        this.restartTracking.delete(id);
    }

    /**
     * Get child mount path. Delegates to ChildManager for authoritative data.
     */
    getChildMount(id: string): string | undefined {
        return this.childManager.getChildMount(id);
    }

    /**
     * Get all children. Delegates to ChildManager for authoritative data.
     */
    getAllChildren(): { id: string; mount: string }[] {
        return this.childManager.getAllChildren().map(c => ({ id: c.id, mount: c.mount }));
    }

    /**
     * Handle an EXIT signal from a child.
     * Creates a Plan of Action (which children to restart).
     */
    async handleExit(signal: IExitSignal): Promise<void> {
        const fromMount = signal.from;
        const entry = this.findRestartEntryByMount(fromMount);

        if (!entry) {
            // eslint-disable-next-line no-console -- supervisor must report exit signals from unknown children
            console.warn(`[Supervisor] Received EXIT from unknown child: ${fromMount}`);
            return;
        }

        // 1. Check Max Restarts (Escalation)
        if (this._tooManyRestarts(entry)) {
            // eslint-disable-next-line no-console -- supervisor escalation needs direct kernel diagnostics
            console.error(`[Supervisor] Child ${entry.id} exceeded max restarts. ESCALATING.`);
            // In a real actor system, we would now crash ourselves to propagate the error up.
            // For now, we will just give up on this child to prevent infinite loops.
            this.unregisterChild(entry.id);
            return; // TODO: Implement self-termination logic
        }

        // 2. Apply Strategy
        switch (this.policy.strategy) {
            case 'OneForOne':
                await this._restartChild(entry);
                break;

            case 'OneForAll':
                await this._restartAll();
                break;

            case 'RestForOne':
                await this._restartRest(entry);
                break;
        }
    }

    /**
     * Find restart tracking entry by mount path.
     */
    private findRestartEntryByMount(mount: string): IRestartEntry | undefined {
        for (const entry of this.restartTracking.values()) {
            if (entry.mount === mount) return entry;
        }
        return undefined;
    }

    /**
     * Check if child has exceeded max restart attempts within the window.
     */
    private _tooManyRestarts(entry: IRestartEntry): boolean {
        const now = Date.now();
        // Prune old timestamps
        entry.restarts = entry.restarts.filter(ts => now - ts < this.policy.windowMs);
        // Add new timestamp
        entry.restarts.push(now);
        return entry.restarts.length > this.policy.maxRestarts;
    }

    // ══════════════════════════════════════════════════════════════════════
    // RESTART LOGIC - Uses ChildManager for blueprints
    // ══════════════════════════════════════════════════════════════════════

    /**
     * Restart a single crashed child (OneForOne strategy).
     */
    private async _restartChild(entry: IRestartEntry): Promise<void> {
        // eslint-disable-next-line no-console -- supervisor restart progress is logged directly in kernel code
        console.log(`[Supervisor] Restarting child ${entry.id} (${entry.mount})...`);

        // 1. Get blueprint from ChildManager BEFORE killing
        const childData = this.childManager.getInternalChild(entry.id);
        if (!childData?.descriptor?.componentClass) {
            // eslint-disable-next-line no-console -- missing restart blueprints need direct supervisor diagnostics
            console.error(`[Supervisor] No blueprint for ${entry.id}, cannot restart`);
            this.unregisterChild(entry.id);
            return;
        }

        const { componentClass, props } = childData.descriptor;
        const slot = childData.slot;

        // 2. Kill the child (ensure it's dead)
        await this.context.runtime.remove(entry.mount);

        // 3. Unregister from ChildManager (clears subscriptions, removes from tracking)
        this.childManager.unregisterChild(entry.id);

        // 4. Recreate via ChildManager (delegates to runtime.createChild)
        const newMount = await this.childManager.addChild(entry.id, componentClass, { slot, props });

        // 5. Update our restart tracking with the new mount
        entry.mount = newMount;

        // eslint-disable-next-line no-console -- supervisor restart completion is logged directly in kernel code
        console.log(`[Supervisor] Restarted ${entry.id} at ${newMount}`);
    }

    /**
     * Restart ALL children (OneForAll strategy).
     */
    private async _restartAll(): Promise<void> {
        // eslint-disable-next-line no-console -- one-for-all restart progress is logged directly in kernel code
        console.log(`[Supervisor] OneForAll: Restarting all children...`);
        const entries = Array.from(this.restartTracking.values());
        for (const entry of entries) {
            await this._restartChild(entry);
        }
    }

    /**
     * Restart failed child and all children started after it (RestForOne strategy).
     */
    private async _restartRest(failedEntry: IRestartEntry): Promise<void> {
        // eslint-disable-next-line no-console -- rest-for-one restart progress is logged directly in kernel code
        console.log(`[Supervisor] RestForOne: Restarting ${failedEntry.id} and younger siblings...`);

        // Get children in order from ChildManager
        const allChildren = this.childManager.getAllChildren();
        const failedIdx = allChildren.findIndex(c => c.id === failedEntry.id);

        if (failedIdx < 0) {
            // Fallback to just restarting the failed one
            await this._restartChild(failedEntry);
            return;
        }

        // Restart failed child and all children after it (younger siblings)
        for (let i = failedIdx; i < allChildren.length; i++) {
            const entry = this.restartTracking.get(allChildren[i].id);
            if (entry) {
                await this._restartChild(entry);
            }
        }
    }
}
