// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only
/**
 * @file lane-lifecycle-process.ts
 * WU-1748: Process-owned deferred lane lifecycle
 *
 * Lane design is an explicit lifecycle:
 *   unconfigured -> draft -> locked
 *
 * `init` should bootstrap tooling only. Lane artifacts are created/validated/
 * finalized through dedicated lane lifecycle commands.
 */
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
import path from 'node:path';
import YAML from 'yaml';
import { WORKSPACE_CONFIG_FILE_NAME, getConfig } from '@lumenflow/core/config';
import { WORKSPACE_V2_KEYS } from '@lumenflow/core/config-schema';
import { FILE_SYSTEM, LUMENFLOW_PATHS } from '@lumenflow/core/wu-constants';
import { DEFAULT_LANE_DEFINITIONS } from './init-templates.js';
const LANE_CONFIG_KEY = 'lanes';
const LANE_DEFINITIONS_KEY = 'definitions';
const LANE_LIFECYCLE_KEY = 'lifecycle';
const LANE_LIFECYCLE_STATUS_KEY = 'status';
const LANE_LIFECYCLE_UPDATED_AT_KEY = 'updated_at';
const LANE_LIFECYCLE_MIGRATED_AT_KEY = 'migrated_at';
const LANE_LIFECYCLE_MIGRATION_REASON_KEY = 'migration_reason';
const EMPTY_OBJECT = '{}';
const NEWLINE = '\n';
const WU_CREATE_PREFIX = '[wu:create]';
const INITIATIVE_CREATE_PREFIX = '[initiative:create]';
const INIT_PREFIX = '[lumenflow init]';
const LANE_SETUP_COMMAND = 'pnpm lane:setup';
const LANE_LOCK_COMMAND = 'pnpm lane:lock';
const LANE_READY_SENTINEL = 'lanes ready';
const SOFTWARE_DELIVERY_KEY = WORKSPACE_V2_KEYS.SOFTWARE_DELIVERY;
export const LANE_LIFECYCLE_STATUS = {
    UNCONFIGURED: 'unconfigured',
    DRAFT: 'draft',
    LOCKED: 'locked',
};
function asRecord(value) {
    return value && typeof value === 'object' && !Array.isArray(value)
        ? value
        : null;
}
function toIsoTimestamp() {
    return new Date().toISOString();
}
function isLaneLifecycleStatus(value) {
    return (value === LANE_LIFECYCLE_STATUS.UNCONFIGURED ||
        value === LANE_LIFECYCLE_STATUS.DRAFT ||
        value === LANE_LIFECYCLE_STATUS.LOCKED);
}
function getConfigPath(projectRoot) {
    return path.join(projectRoot, WORKSPACE_CONFIG_FILE_NAME);
}
function readWorkspaceDoc(projectRoot) {
    const configPath = getConfigPath(projectRoot);
    if (!existsSync(configPath)) {
        return {};
    }
    try {
        const content = readFileSync(configPath, FILE_SYSTEM.UTF8);
        const parsed = asRecord(YAML.parse(content));
        return parsed ?? {};
    }
    catch {
        return {};
    }
}
function readConfigDoc(projectRoot) {
    const workspace = readWorkspaceDoc(projectRoot);
    const softwareDelivery = asRecord(workspace[SOFTWARE_DELIVERY_KEY]);
    return softwareDelivery ?? {};
}
function writeConfigDoc(projectRoot, config) {
    const configPath = getConfigPath(projectRoot);
    const workspace = readWorkspaceDoc(projectRoot);
    workspace[SOFTWARE_DELIVERY_KEY] = config;
    const nextContent = YAML.stringify(workspace);
    writeFileSync(configPath, nextContent || EMPTY_OBJECT + NEWLINE, FILE_SYSTEM.UTF8);
}
function getLifecycleStatusFromDoc(config) {
    const status = config?.lanes?.lifecycle?.status;
    return isLaneLifecycleStatus(status) ? status : null;
}
function ensureLanesDoc(config) {
    if (!config[LANE_CONFIG_KEY] || typeof config[LANE_CONFIG_KEY] !== 'object') {
        config[LANE_CONFIG_KEY] = {};
    }
    return config[LANE_CONFIG_KEY];
}
function setLifecycleStatusInDoc(config, status, migrationReason) {
    const lanes = ensureLanesDoc(config);
    const existingLifecycle = lanes[LANE_LIFECYCLE_KEY] && typeof lanes[LANE_LIFECYCLE_KEY] === 'object'
        ? lanes[LANE_LIFECYCLE_KEY]
        : {};
    const now = toIsoTimestamp();
    const lifecycle = {
        ...existingLifecycle,
        [LANE_LIFECYCLE_STATUS_KEY]: status,
        [LANE_LIFECYCLE_UPDATED_AT_KEY]: now,
    };
    if (migrationReason) {
        lifecycle[LANE_LIFECYCLE_MIGRATED_AT_KEY] = now;
        lifecycle[LANE_LIFECYCLE_MIGRATION_REASON_KEY] = migrationReason;
    }
    lanes[LANE_LIFECYCLE_KEY] = lifecycle;
}
function hasLaneDefinitions(config) {
    const definitions = config?.lanes?.definitions;
    return Array.isArray(definitions) && definitions.length > 0;
}
function deepCloneDefaultLaneDefinitions() {
    return JSON.parse(JSON.stringify(DEFAULT_LANE_DEFINITIONS));
}
/**
 * Classify lifecycle status for projects that do not yet have explicit
 * lanes.lifecycle.status in config.
 */
export function classifyLaneLifecycleForProject(projectRoot) {
    const config = readConfigDoc(projectRoot);
    const configuredStatus = getLifecycleStatusFromDoc(config);
    if (configuredStatus) {
        return {
            status: configuredStatus,
            source: 'config',
            migrationReason: null,
            persisted: false,
        };
    }
    const definitionsPresent = hasLaneDefinitions(config);
    if (!definitionsPresent) {
        return {
            status: LANE_LIFECYCLE_STATUS.UNCONFIGURED,
            source: 'migration',
            migrationReason: 'no lane definitions',
            persisted: false,
        };
    }
    return {
        status: LANE_LIFECYCLE_STATUS.LOCKED,
        source: 'migration',
        migrationReason: 'lane definitions detected',
        persisted: false,
    };
}
/**
 * Ensure lifecycle status exists in config.
 *
 * Existing repositories are migrated deterministically from current
 * workspace lane definitions:
 * - lane definitions present => locked
 * - no lane definitions => unconfigured
 */
export function ensureLaneLifecycleForProject(projectRoot, options = {}) {
    const persist = options.persist ?? true;
    const classification = classifyLaneLifecycleForProject(projectRoot);
    if (!persist || classification.source === 'config') {
        return classification;
    }
    const config = readConfigDoc(projectRoot);
    setLifecycleStatusInDoc(config, classification.status, classification.migrationReason ?? undefined);
    writeConfigDoc(projectRoot, config);
    return {
        ...classification,
        persisted: true,
    };
}
export function setLaneLifecycleStatus(projectRoot, status) {
    const config = readConfigDoc(projectRoot);
    setLifecycleStatusInDoc(config, status);
    writeConfigDoc(projectRoot, config);
}
export function recommendLaneLifecycleNextStep(status) {
    if (status === LANE_LIFECYCLE_STATUS.UNCONFIGURED) {
        return LANE_SETUP_COMMAND;
    }
    if (status === LANE_LIFECYCLE_STATUS.DRAFT) {
        return LANE_LOCK_COMMAND;
    }
    return LANE_READY_SENTINEL;
}
export function buildWuCreateLaneLifecycleMessage(status) {
    return [
        `${WU_CREATE_PREFIX} Lane lifecycle status: ${status}`,
        'Cannot create delivery WU until lanes are locked.',
        `Next step: ${recommendLaneLifecycleNextStep(status)}`,
    ].join(NEWLINE);
}
export function buildInitiativeCreateLaneLifecycleMessage(status) {
    return [
        `${INITIATIVE_CREATE_PREFIX} Lane lifecycle: ${status}`,
        'Initiative creation is allowed before lane setup.',
        `When ready for delivery WUs, run: ${recommendLaneLifecycleNextStep(status)}`,
    ].join(NEWLINE);
}
export function buildInitLaneLifecycleMessage(status) {
    return [
        `${INIT_PREFIX} Lane lifecycle: ${status}`,
        'Lanes are configured after project context is defined (plan/architecture).',
        `Next step: ${recommendLaneLifecycleNextStep(status)}`,
    ].join(NEWLINE);
}
/**
 * WU-1755: Detect workspace structure and generate project-specific lane definitions.
 * Scans pnpm-workspace.yaml or package.json workspaces to build lanes from actual project layout.
 * Falls back to DEFAULT_LANE_DEFINITIONS if no workspace config is found.
 */
function detectWorkspaceLanes(projectRoot) {
    const lanes = [];
    // Try pnpm-workspace.yaml first
    const pnpmWorkspacePath = path.join(projectRoot, 'pnpm-workspace.yaml');
    let workspacePatterns = [];
    if (existsSync(pnpmWorkspacePath)) {
        try {
            const content = readFileSync(pnpmWorkspacePath, FILE_SYSTEM.ENCODING);
            const parsed = YAML.parse(content);
            workspacePatterns = parsed?.packages ?? [];
        }
        catch {
            // Fall through to package.json check
        }
    }
    // Fall back to package.json workspaces
    if (workspacePatterns.length === 0) {
        const pkgJsonPath = path.join(projectRoot, 'package.json');
        if (existsSync(pkgJsonPath)) {
            try {
                const content = readFileSync(pkgJsonPath, FILE_SYSTEM.ENCODING);
                const parsed = JSON.parse(content);
                if (Array.isArray(parsed?.workspaces)) {
                    workspacePatterns = parsed.workspaces;
                }
                else if (parsed?.workspaces?.packages) {
                    workspacePatterns = parsed.workspaces.packages;
                }
            }
            catch {
                // Fall through to defaults
            }
        }
    }
    if (workspacePatterns.length === 0) {
        return deepCloneDefaultLaneDefinitions();
    }
    // Scan workspace patterns for actual directories
    for (const pattern of workspacePatterns) {
        const baseDir = pattern.replace(/\/?\*$/, '');
        const fullPath = path.join(projectRoot, baseDir);
        if (!existsSync(fullPath))
            continue;
        try {
            const entries = readdirSync(fullPath, { withFileTypes: true });
            for (const entry of entries) {
                if (!entry.isDirectory())
                    continue;
                const pkgPath = path.join(fullPath, entry.name, 'package.json');
                if (!existsSync(pkgPath))
                    continue;
                // Determine lane category from directory structure
                const isApp = baseDir.includes('app');
                const category = isApp ? 'Experience' : 'Framework';
                const displayName = entry.name.replace(/^@[^/]+\//, '');
                lanes.push({
                    name: `${category}: ${displayName.charAt(0).toUpperCase() + displayName.slice(1)}`,
                    wip_limit: 1,
                    code_paths: [`${baseDir}/${entry.name}/**`],
                });
            }
        }
        catch {
            // Skip unreadable directories
        }
    }
    // Always include Content: Documentation if configured docs dir exists.
    const docsDir = getConfig({ projectRoot }).directories.docs.replace(/\/+$/, '');
    if (existsSync(path.join(projectRoot, docsDir))) {
        lanes.push({
            name: 'Content: Documentation',
            wip_limit: 1,
            code_paths: [`${docsDir}/**`, '*.md'],
        });
    }
    // Always include Operations: CI/CD if .github/ exists
    if (existsSync(path.join(projectRoot, '.github'))) {
        lanes.push({
            name: 'Operations: CI/CD',
            wip_limit: 1,
            code_paths: ['.github/workflows/**', '.github/actions/**'],
        });
    }
    return lanes.length > 0 ? lanes : deepCloneDefaultLaneDefinitions();
}
export function ensureDraftLaneArtifacts(projectRoot) {
    const config = readConfigDoc(projectRoot);
    const lanes = ensureLanesDoc(config);
    let createdDefinitions = false;
    if (!hasLaneDefinitions(config)) {
        // WU-1755: Detect workspace structure for project-specific lanes
        lanes[LANE_DEFINITIONS_KEY] = detectWorkspaceLanes(projectRoot);
        createdDefinitions = true;
    }
    setLifecycleStatusInDoc(config, LANE_LIFECYCLE_STATUS.DRAFT);
    writeConfigDoc(projectRoot, config);
    return {
        createdDefinitions,
        status: LANE_LIFECYCLE_STATUS.DRAFT,
    };
}
export function validateLaneArtifacts(projectRoot) {
    const configPath = getConfigPath(projectRoot);
    const warnings = [];
    const invalidLanes = [];
    if (!existsSync(configPath)) {
        warnings.push(`Missing ${WORKSPACE_CONFIG_FILE_NAME}. Run: ${LANE_SETUP_COMMAND}`);
        return {
            warnings,
            invalidLanes,
            missingDefinitions: true,
        };
    }
    try {
        getConfig({
            projectRoot,
            reload: true,
            strictWorkspace: true,
        });
    }
    catch (error) {
        warnings.push(`Invalid ${WORKSPACE_CONFIG_FILE_NAME}: ${error instanceof Error ? error.message : String(error)}`);
        return {
            warnings,
            invalidLanes,
            missingDefinitions: true,
        };
    }
    const config = readConfigDoc(projectRoot);
    const missingDefinitions = !hasLaneDefinitions(config);
    if (missingDefinitions) {
        warnings.push(`No lane definitions found in ${WORKSPACE_CONFIG_FILE_NAME} (${SOFTWARE_DELIVERY_KEY}.lanes). Run: ${LANE_SETUP_COMMAND}`);
    }
    return {
        warnings,
        invalidLanes,
        missingDefinitions,
    };
}
export function getLaneLifecycleStatusPath() {
    return path.join(LUMENFLOW_PATHS.STATE_DIR, 'lane-lifecycle.json');
}
//# sourceMappingURL=lane-lifecycle-process.js.map