import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { basename, isAbsolute, join, relative, resolve } from 'node:path';
import { canonicalizeNotebook, findAppDocuments, findDashboardsForApp, loadAppDocument, loadDashboardDocument, parseAppDocument, parseDashboardDocument, } from '@duckcodeailabs/dql-core';
import { findProjectRoot } from '../local-runtime.js';
export async function runPromote(kind, rest, flags) {
    if (kind !== 'notebook' && kind !== 'app' && kind !== 'dashboard') {
        throw new Error('Usage: dql promote <notebook|app|dashboard> <path-or-id> --to shared');
    }
    if (flags.to !== 'shared') {
        throw new Error('Promotion currently supports only --to shared');
    }
    const target = rest[0];
    if (!target) {
        throw new Error(`Usage: dql promote ${kind} <path-or-id> --to shared`);
    }
    const projectRoot = findProjectRoot(process.cwd());
    const result = promoteArtifact(projectRoot, kind, target);
    if (flags.format === 'json') {
        console.log(JSON.stringify(result, null, 2));
        return;
    }
    console.log(`  ✓ Promoted ${kind} to shared source: ${result.path}`);
    if (result.removedLocalState.length > 0) {
        console.log(`    Removed local-only state: ${result.removedLocalState.join(', ')}`);
    }
}
export function promoteArtifact(projectRoot, kind, target) {
    if (kind === 'notebook')
        return promoteNotebook(projectRoot, target);
    if (kind === 'app')
        return promoteApp(projectRoot, target);
    return promoteDashboard(projectRoot, target);
}
function promoteNotebook(projectRoot, target) {
    const absPath = resolveProjectPath(projectRoot, target);
    if (!existsSync(absPath) || !absPath.endsWith('.dqlnb')) {
        throw new Error(`Notebook not found: ${target}`);
    }
    const source = readFileSync(absPath, 'utf-8');
    const raw = JSON.parse(source);
    const removed = new Set();
    const cleaned = stripLocalState({
        ...raw,
        metadata: {
            ...(isRecord(raw.metadata) ? raw.metadata : {}),
            visibility: 'shared',
            lifecycle: normalizeLifecycle(raw.metadata?.lifecycle),
        },
    }, removed);
    const text = canonicalizeNotebook(JSON.stringify(cleaned));
    writeFileSync(absPath, text, 'utf-8');
    return promoted('notebook', projectRoot, absPath, removed);
}
function promoteApp(projectRoot, appId) {
    const appPath = resolveAppJsonPath(projectRoot, appId);
    const source = readFileSync(appPath, 'utf-8');
    const raw = JSON.parse(source);
    const removed = new Set();
    const cleaned = stripLocalState({
        ...raw,
        visibility: 'shared',
        lifecycle: normalizeLifecycle(raw.lifecycle),
        notebooks: Array.isArray(raw.notebooks)
            ? raw.notebooks.filter((notebook) => isRecord(notebook) && (notebook.visibility === 'shared' || notebook.visibility === 'template'))
            : undefined,
    }, removed);
    const parsed = parseAppDocument(stableJson(cleaned), appPath);
    if (!parsed.document) {
        throw new Error(`Promoted app is invalid: ${parsed.errors.map((error) => error.message).join('; ')}`);
    }
    writeFileSync(appPath, stableJson(cleaned), 'utf-8');
    return promoted('app', projectRoot, appPath, removed);
}
function promoteDashboard(projectRoot, target) {
    const [appId, dashboardId] = target.split('/');
    if (!appId || !dashboardId) {
        throw new Error('Usage: dql promote dashboard <app-id>/<dashboard-id> --to shared');
    }
    const dashboardPath = resolveDashboardPath(projectRoot, appId, dashboardId);
    const source = readFileSync(dashboardPath, 'utf-8');
    const raw = JSON.parse(source);
    const removed = new Set();
    const layout = isRecord(raw.layout) ? raw.layout : {};
    const items = Array.isArray(layout.items)
        ? layout.items.flatMap((item) => {
            if (!isRecord(item))
                return [];
            if (item.aiPin) {
                removed.add('aiPin tiles');
                return [];
            }
            return [stripLocalState(promoteDashboardItemDisplay(item), removed)];
        })
        : [];
    const cleaned = stripLocalState({
        ...raw,
        metadata: {
            ...(isRecord(raw.metadata) ? raw.metadata : {}),
            visibility: 'shared',
            lifecycle: normalizeLifecycle(raw.metadata?.lifecycle),
        },
        layout: {
            ...layout,
            items,
        },
    }, removed);
    const parsed = parseDashboardDocument(stableJson(cleaned), dashboardPath);
    if (!parsed.document) {
        throw new Error(`Promoted dashboard is invalid: ${parsed.errors.map((error) => error.message).join('; ')}`);
    }
    writeFileSync(dashboardPath, stableJson(cleaned), 'utf-8');
    return promoted('dashboard', projectRoot, dashboardPath, removed);
}
function promoteDashboardItemDisplay(item) {
    if (item.display || !isRecord(item.viz))
        return item;
    const options = isRecord(item.viz.options) ? item.viz.options : {};
    if (!isRecord(options.dqlGenUi))
        return item;
    const genUi = options.dqlGenUi;
    const display = {
        mode: item.block ? 'block_hint' : 'ai_generated',
        component: typeof genUi.component === 'string' ? genUi.component : 'EvidenceTable',
        defaultVisualization: typeof genUi.defaultVisualization === 'string'
            ? genUi.defaultVisualization
            : typeof item.viz.type === 'string' ? item.viz.type : 'table',
        allowedVisualizations: Array.isArray(genUi.allowedVisualizations)
            ? genUi.allowedVisualizations.filter((value) => typeof value === 'string')
            : [typeof item.viz.type === 'string' ? item.viz.type : 'table'],
        ...(isRecord(genUi.fieldHints) ? { fieldHints: genUi.fieldHints } : {}),
        layoutIntent: typeof genUi.layoutIntent === 'string' ? genUi.layoutIntent : 'auto',
        rationale: typeof genUi.rationale === 'string' ? genUi.rationale : 'Promoted display metadata from legacy GenUI options.',
        trustState: genUi.trustState === 'certified' || genUi.trustState === 'draft_ready' ? genUi.trustState : 'review_required',
        reviewStatus: genUi.reviewStatus === 'certified' || genUi.reviewStatus === 'draft_ready' ? genUi.reviewStatus : 'review_required',
    };
    return { ...item, display };
}
function stripLocalState(value, removed) {
    if (Array.isArray(value))
        return value.map((item) => stripLocalState(item, removed));
    if (!isRecord(value))
        return value;
    const out = {};
    for (const [key, nested] of Object.entries(value)) {
        if (LOCAL_ONLY_KEYS.has(key) || key.startsWith('_local') || key.startsWith('local')) {
            removed.add(key);
            continue;
        }
        if (key === 'options' && isRecord(nested)) {
            const nextOptions = { ...nested };
            if ('dqlGenUi' in nextOptions) {
                delete nextOptions.dqlGenUi;
                removed.add('legacy dqlGenUi options');
            }
            out[key] = stripLocalState(nextOptions, removed);
            continue;
        }
        out[key] = stripLocalState(nested, removed);
    }
    return out;
}
const LOCAL_ONLY_KEYS = new Set([
    'aiPins',
    'execution',
    'executionCount',
    'executionState',
    'lastRunAt',
    'layoutOverrides',
    'modifiedAt',
    'outputs',
    'pins',
    'result',
    'results',
    'run',
    'runResult',
    'savedView',
    'savedViews',
    'selection',
    'snapshot',
    'transient',
    'uiState',
]);
function resolveProjectPath(projectRoot, target) {
    const abs = resolve(target);
    if (abs === projectRoot || abs.startsWith(`${projectRoot}/`))
        return abs;
    if (isAbsolute(target))
        throw new Error(`Path is outside this DQL project: ${target}`);
    return join(projectRoot, target);
}
function resolveAppJsonPath(projectRoot, appId) {
    for (const path of findAppDocuments(projectRoot)) {
        const loaded = loadAppDocument(path);
        if (loaded.document?.id === appId || basename(path.slice(0, -'/dql.app.json'.length)) === appId)
            return path;
    }
    throw new Error(`App not found: ${appId}`);
}
function resolveDashboardPath(projectRoot, appId, dashboardId) {
    const appPath = resolveAppJsonPath(projectRoot, appId);
    const appDir = appPath.slice(0, -'/dql.app.json'.length);
    for (const path of findDashboardsForApp(appDir)) {
        const loaded = loadDashboardDocument(path);
        if (loaded.document?.id === dashboardId || basename(path, '.dqld') === dashboardId)
            return path;
    }
    throw new Error(`Dashboard not found: ${appId}/${dashboardId}`);
}
function normalizeLifecycle(value) {
    return value === 'certified' || value === 'deprecated' ? value : 'review';
}
function promoted(kind, projectRoot, absPath, removed) {
    return {
        ok: true,
        kind,
        path: relative(projectRoot, absPath).replaceAll('\\', '/'),
        removedLocalState: Array.from(removed).sort(),
    };
}
function stableJson(value) {
    return JSON.stringify(value, stableReplacer(), 2) + '\n';
}
function stableReplacer() {
    return function replacer(_key, value) {
        if (!isRecord(value))
            return value;
        const sorted = {};
        for (const key of Object.keys(value).sort())
            sorted[key] = value[key];
        return sorted;
    };
}
function isRecord(value) {
    return typeof value === 'object' && value !== null && !Array.isArray(value);
}
//# sourceMappingURL=promote.js.map