#!/usr/bin/env node
// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only
/**
 * @file lane-remove.ts
 * WU-2540: Safe lane removal via audited CLI workflow
 */
import path from 'node:path';
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
import YAML from 'yaml';
import { findProjectRoot, getConfig, getWorkspaceInitCommand, WORKSPACE_CONFIG_FILE_NAME, } from '@lumenflow/core/config';
import { WORKSPACE_V2_KEYS } from '@lumenflow/core/config-schema';
import { createError, ErrorCodes, die } from '@lumenflow/core/error-handler';
import { WUStateStore } from '@lumenflow/core/wu-state-store';
import { FILE_SYSTEM } from '@lumenflow/core/wu-constants';
import { withMicroWorktree } from '@lumenflow/core/micro-worktree';
import { runCLI } from './cli-entry-point.js';
import { asRecord } from './object-guards.js';
import { validateLaneArtifacts, classifyLaneLifecycleForProject, LANE_LIFECYCLE_STATUS, } from './lane-lifecycle-process.js';
const LOG_PREFIX = '[lane:remove]';
const OPERATION_NAME = 'lane-remove';
const SOFTWARE_DELIVERY_KEY = WORKSPACE_V2_KEYS.SOFTWARE_DELIVERY;
const ARG_NAME = '--name';
const ARG_HELP = '--help';
const COMMIT_PREFIX = 'chore: lane:remove';
const NON_BLOCKING_WU_STATUSES = new Set(['done', 'cancelled']);
const HELP_TEXT = `Usage: pnpm lane:remove --name <lane>

Remove an existing lane definition in workspace.yaml via micro-worktree commit.

Required:
  ${ARG_NAME} <name>          Target lane name (exact match)

Safety checks:
  - Refuses removal when any non-done WU still references the lane
  - Re-runs lane validation before committing

Examples:
  pnpm lane:remove --name 'Framework: CLI'
  pnpm lane:remove --name 'Content: Framework Docs'
`;
export function parseLaneRemoveArgs(argv) {
    if (argv.includes(ARG_HELP)) {
        console.log(HELP_TEXT);
        process.exit(0);
    }
    let name;
    for (let i = 0; i < argv.length; i++) {
        const arg = argv[i];
        const next = argv[i + 1];
        if (arg === ARG_NAME) {
            name = next;
            i++;
        }
    }
    if (!name) {
        throw createError(ErrorCodes.INVALID_ARGUMENT, `${ARG_NAME} is required. Run with ${ARG_HELP} for usage.`);
    }
    return { name };
}
export function validateLaneRemovePreconditions(projectRoot) {
    const configPath = path.join(projectRoot, WORKSPACE_CONFIG_FILE_NAME);
    if (!existsSync(configPath)) {
        return {
            ok: false,
            error: `${LOG_PREFIX} Missing ${WORKSPACE_CONFIG_FILE_NAME}. Run \`${getWorkspaceInitCommand(projectRoot)}\` first.`,
        };
    }
    const classification = classifyLaneLifecycleForProject(projectRoot);
    const { status } = classification;
    if (status !== LANE_LIFECYCLE_STATUS.LOCKED && status !== LANE_LIFECYCLE_STATUS.DRAFT) {
        return {
            ok: false,
            error: `${LOG_PREFIX} Lane lifecycle status is "${status}". ` +
                `lane:remove requires lanes to be in locked or draft status. ` +
                `Run: pnpm lane:setup && pnpm lane:lock`,
        };
    }
    return { ok: true };
}
export function findBlockingLaneAssignments(wus, laneName) {
    return wus.filter((wu) => wu.lane === laneName && !NON_BLOCKING_WU_STATUSES.has(wu.status.toLowerCase()));
}
export function applyLaneRemove(definitions, name) {
    const targetIndex = definitions.findIndex((lane) => lane.name === name);
    if (targetIndex === -1) {
        return {
            ok: false,
            error: `${LOG_PREFIX} Lane "${name}" not found in definitions.`,
        };
    }
    return {
        ok: true,
        definitions: definitions.filter((lane) => lane.name !== name),
    };
}
function readConfigDoc(configPath) {
    const content = readFileSync(configPath, FILE_SYSTEM.UTF8);
    const workspace = asRecord(YAML.parse(content));
    if (!workspace) {
        return {};
    }
    return asRecord(workspace[SOFTWARE_DELIVERY_KEY]) ?? {};
}
function writeConfigDoc(configPath, config) {
    const content = readFileSync(configPath, FILE_SYSTEM.UTF8);
    const workspace = asRecord(YAML.parse(content)) ?? {};
    workspace[SOFTWARE_DELIVERY_KEY] = config;
    const nextContent = YAML.stringify(workspace);
    writeFileSync(configPath, nextContent, FILE_SYSTEM.UTF8);
}
function formatBlockingWUs(wus) {
    return wus.map((wu) => `  - ${wu.id} (${wu.status}): ${wu.title}`).join('\n');
}
async function loadLaneAssignments(projectRoot, laneName) {
    const config = getConfig({ projectRoot });
    const wuDir = path.join(projectRoot, config.directories.wuDir);
    const stateDir = path.join(projectRoot, config.state.stateDir);
    const stateStore = new WUStateStore(stateDir);
    await stateStore.load();
    if (!existsSync(wuDir)) {
        return [];
    }
    const assignments = [];
    for (const file of readdirSync(wuDir)) {
        if (!file.startsWith('WU-') || !file.endsWith('.yaml')) {
            continue;
        }
        const raw = readFileSync(path.join(wuDir, file), FILE_SYSTEM.UTF8);
        const parsed = asRecord(YAML.parse(raw));
        if (!parsed) {
            continue;
        }
        const id = typeof parsed.id === 'string' ? parsed.id : file.replace(/\.yaml$/, '');
        const yamlLane = typeof parsed.lane === 'string' ? parsed.lane : '';
        const lane = stateStore.getWUState(id)?.lane ?? yamlLane;
        if (lane !== laneName) {
            continue;
        }
        assignments.push({
            id,
            title: typeof parsed.title === 'string' ? parsed.title : '',
            lane,
            status: stateStore.getWUState(id)?.status ??
                (typeof parsed.status === 'string' ? parsed.status : 'ready'),
        });
    }
    return assignments;
}
async function ensureLaneHasNoBlockingWUs(projectRoot, laneName) {
    const laneWUs = await loadLaneAssignments(projectRoot, laneName);
    const blocking = findBlockingLaneAssignments(laneWUs, laneName);
    if (blocking.length > 0) {
        die(`${LOG_PREFIX} Cannot remove lane "${laneName}" while non-done WUs still reference it:\n` +
            `${formatBlockingWUs(blocking)}`);
    }
}
function buildCommitMessage(options) {
    return `${COMMIT_PREFIX} remove lane '${options.name}'`;
}
async function main() {
    const userArgs = process.argv.slice(2);
    const options = parseLaneRemoveArgs(userArgs);
    const projectRoot = findProjectRoot();
    const preconditions = validateLaneRemovePreconditions(projectRoot);
    if (!preconditions.ok) {
        die(preconditions.error);
    }
    await ensureLaneHasNoBlockingWUs(projectRoot, options.name);
    console.log(`${LOG_PREFIX} Removing lane "${options.name}" via micro-worktree isolation`);
    await withMicroWorktree({
        operation: OPERATION_NAME,
        id: `lane-remove-${Date.now()}`,
        logPrefix: LOG_PREFIX,
        pushOnly: true,
        async execute({ worktreePath }) {
            await ensureLaneHasNoBlockingWUs(worktreePath, options.name);
            const configRelPath = WORKSPACE_CONFIG_FILE_NAME;
            const configPath = path.join(worktreePath, configRelPath);
            if (!existsSync(configPath)) {
                die(`${LOG_PREFIX} Config file not found in micro-worktree: ${configRelPath}`);
            }
            const config = readConfigDoc(configPath);
            const definitions = (config.lanes?.definitions ?? []);
            const removeResult = applyLaneRemove(definitions, options.name);
            if (!removeResult.ok) {
                die(removeResult.error);
            }
            if (!config.lanes) {
                config.lanes = {};
            }
            config.lanes.definitions = removeResult.definitions;
            writeConfigDoc(configPath, config);
            const validation = validateLaneArtifacts(worktreePath);
            if (validation.warnings.length > 0 || validation.invalidLanes.length > 0) {
                const issues = [
                    ...validation.warnings.map((warning) => `  - ${warning}`),
                    ...validation.invalidLanes.map((lane) => `  - Invalid lane: ${lane}`),
                ].join('\n');
                die(`${LOG_PREFIX} lane:validate failed after remove. Changes NOT committed.\n${issues}`);
            }
            console.log(`${LOG_PREFIX} lane:validate passed after remove.`);
            return {
                commitMessage: buildCommitMessage(options),
                files: [configRelPath],
            };
        },
    });
    console.log(`${LOG_PREFIX} Lane "${options.name}" removed successfully.`);
}
if (import.meta.main) {
    void runCLI(main);
}
//# sourceMappingURL=lane-remove.js.map