import { existsSync } from "node:fs";
import { copyFile, cp, mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { defaultSeedAgentConfig } from "../config/defaults.js";
import { mergeSeedConfig } from "../config/merge.js";
import { createSeedAgents } from "../agents/index.js";
import { stripJsonComments } from "../config/loader.js";
import { createOpenCodeNativeAgentDisableConfig } from "../agents/opencode-isolation.js";
import { createSeedCommands } from "../plugin-handlers/config-handler.js";
import { SEED_DEFAULT_MODEL, SEED_DEFAULT_VARIANT, SEED_PACKAGE_NAME } from "./constants.js";
import { packageRootFromCli, packageRootPluginSpec, projectOpenCodeConfigPath, projectRootOpenCodeConfigPath, projectSeedConfigPath, resolveOpenCodeConfigPath, resolveUserSeedConfigPath, } from "./paths.js";
import { assertSeedSupportedPlatform } from "./platform.js";
export async function installSeedAgent(options = {}) {
    assertSeedSupportedPlatform(options.platform);
    const cwd = options.cwd ?? process.cwd();
    const opencodeConfigPath = options.config
        ? path.resolve(options.config)
        : options.project
            ? projectOpenCodeConfigPath(cwd)
            : resolveOpenCodeConfigPath(options);
    const rootOpenCodeConfigPath = options.project && !options.config
        ? projectRootOpenCodeConfigPath(cwd)
        : undefined;
    const seedConfigPath = options.seedConfig
        ? path.resolve(options.seedConfig)
        : options.project
            ? projectSeedConfigPath(cwd)
            : resolveUserSeedConfigPath(options);
    const pluginSpec = resolveInstallPluginSpec(options, opencodeConfigPath);
    const packageRoot = options.packageRoot ?? packageRootFromCli();
    const config = await readOpenCodeConfig(opencodeConfigPath);
    const plugins = Array.isArray(config.plugin) ? config.plugin : [];
    const normalized = normalizePluginList(plugins, pluginSpec, path.dirname(opencodeConfigPath));
    const pluginAdded = normalized.changed;
    const seedConfigCreated = !existsSync(seedConfigPath);
    const seedConfigNormalization = seedConfigCreated ? undefined : await normalizeExistingSeedConfig(seedConfigPath);
    const baseSeedConfigForInstallRaw = seedConfigCreated
        ? seedConfigDocument(defaultSeedAgentConfig)
        : seedConfigNormalization?.config ?? await readSeedConfig(seedConfigPath);
    const legacyModelNormalization = options.model
        ? { config: baseSeedConfigForInstallRaw, changed: false }
        : normalizeLegacyDefaultModelPins(baseSeedConfigForInstallRaw);
    const baseSeedConfigForInstall = legacyModelNormalization.config;
    const selectedModelConfig = options.model
        ? applyUnifiedModelToSeedConfig(baseSeedConfigForInstall, options.model, options.variant)
        : { config: baseSeedConfigForInstall, changed: false };
    const seedConfigForInstall = selectedModelConfig.config;
    const openCodeConfig = {
        ...config,
        plugin: normalized.plugins,
    };
    const projectedOpenCodeConfig = options.project
        ? applyProjectOpenCodeRuntimeDefaults(openCodeConfig, seedConfigForInstall)
        : { config: openCodeConfig, changed: false };
    const rootProjectedOpenCodeConfig = rootOpenCodeConfigPath
        ? await projectRootRuntimeConfig(rootOpenCodeConfigPath, seedConfigForInstall, options)
        : undefined;
    const rootOpenCodeConfigUpdated = Boolean(rootProjectedOpenCodeConfig?.updated);
    const opencodeConfigUpdated = pluginAdded
        || projectedOpenCodeConfig.changed
        || rootOpenCodeConfigUpdated
        || (!existsSync(opencodeConfigPath) && Object.keys(projectedOpenCodeConfig.config).length > 0);
    let backupPath;
    let rootBackupPath;
    let seedConfigBackupPath;
    if (!options.dryRun) {
        if (opencodeConfigUpdated || !existsSync(opencodeConfigPath)) {
            backupPath = await maybeBackup(opencodeConfigPath);
            await writeOpenCodeConfig(opencodeConfigPath, projectedOpenCodeConfig.config);
        }
        if (rootProjectedOpenCodeConfig?.updated) {
            rootBackupPath = await maybeBackup(rootProjectedOpenCodeConfig.path);
            await writeOpenCodeConfig(rootProjectedOpenCodeConfig.path, rootProjectedOpenCodeConfig.config);
        }
        if (seedConfigCreated) {
            await writeSeedConfig(seedConfigPath, seedConfigForInstall);
        }
        else if (seedConfigNormalization?.changed || legacyModelNormalization.changed || selectedModelConfig.changed) {
            seedConfigBackupPath = await maybeBackup(seedConfigPath);
            await writeFile(seedConfigPath, `${JSON.stringify(seedConfigForInstall, null, 2)}\n`, "utf8");
        }
        if (options.project) {
            await ensureBundledProjectSkills(cwd, packageRoot);
        }
        if (options.local && options.project && !options.skipOpenCodeDeps) {
            await ensureProjectOpenCodeDependencies(path.dirname(opencodeConfigPath));
        }
    }
    return {
        opencodeConfigPath,
        rootOpenCodeConfigPath,
        seedConfigPath,
        pluginSpec,
        pluginAdded,
        opencodeConfigUpdated,
        seedConfigCreated,
        seedConfigMigrated: Boolean(seedConfigNormalization?.changed),
        backupPath,
        rootBackupPath,
        seedConfigBackupPath,
    };
}
export async function readOpenCodePluginSpecs(file) {
    const config = await readOpenCodeConfig(file);
    if (!Array.isArray(config.plugin))
        return [];
    return config.plugin
        .map((item) => (typeof item === "string" ? item : Array.isArray(item) && typeof item[0] === "string" ? item[0] : undefined))
        .filter((item) => Boolean(item));
}
export function resolveInstallPluginSpec(options = {}, opencodeConfigPath) {
    if (options.pluginSpec)
        return options.pluginSpec;
    const cwd = options.cwd ?? process.cwd();
    const packageRoot = options.packageRoot ?? packageRootFromCli();
    if (options.local && options.project && opencodeConfigPath) {
        return relativePluginSpec(path.dirname(opencodeConfigPath), packageRoot);
    }
    if (options.local)
        return packageRootPluginSpec(packageRoot);
    if (options.project && opencodeConfigPath && isProjectNodeModulePackage(cwd, packageRoot)) {
        return relativePluginSpec(path.dirname(opencodeConfigPath), packageRoot);
    }
    return SEED_PACKAGE_NAME;
}
async function readOpenCodeConfig(file) {
    if (!existsSync(file))
        return {};
    const text = await readFile(file, "utf8");
    const trimmed = text.trim();
    if (!trimmed)
        return {};
    return JSON.parse(stripJsonComments(trimmed));
}
async function readSeedConfig(file) {
    if (!existsSync(file))
        return {};
    const text = await readFile(file, "utf8");
    const trimmed = text.trim();
    if (!trimmed)
        return {};
    return JSON.parse(stripJsonComments(trimmed));
}
function normalizePluginList(plugins, pluginSpec, configDir) {
    const pluginName = pluginIdentity(pluginSpec, configDir);
    const out = [];
    let found = false;
    let changed = false;
    for (const plugin of plugins) {
        const spec = pluginSpecifier(plugin);
        if (!spec) {
            out.push(plugin);
            continue;
        }
        if (pluginIdentity(spec, configDir) === pluginName || spec === pluginSpec) {
            if (found) {
                changed = true;
                continue;
            }
            found = true;
            if (spec === pluginSpec) {
                out.push(plugin);
            }
            else if (Array.isArray(plugin)) {
                out.push([pluginSpec, plugin[1]]);
                changed = true;
            }
            else {
                out.push(pluginSpec);
                changed = true;
            }
            continue;
        }
        out.push(plugin);
    }
    if (!found) {
        out.push(pluginSpec);
        changed = true;
    }
    return { plugins: out, changed };
}
function pluginSpecifier(plugin) {
    if (typeof plugin === "string")
        return plugin;
    if (Array.isArray(plugin) && typeof plugin[0] === "string")
        return plugin[0];
    return undefined;
}
function pluginIdentity(spec, configDir) {
    if (spec.startsWith("file://"))
        return path.resolve(fileURLToPath(spec));
    if (spec.startsWith("/") || spec.startsWith("."))
        return path.resolve(configDir, spec);
    const scoped = spec.match(/^(@[^/]+\/[^@/]+)(?:@.*)?$/);
    if (scoped)
        return scoped[1];
    const plain = spec.match(/^([^@/]+)(?:@.*)?$/);
    return plain?.[1] ?? spec;
}
function relativePluginSpec(configDir, packageRoot) {
    const relative = path.relative(configDir, path.resolve(packageRoot));
    if (!relative)
        return ".";
    if (relative.startsWith("."))
        return relative;
    return `.${path.sep}${relative}`;
}
function isProjectNodeModulePackage(cwd, packageRoot) {
    const nodeModules = path.resolve(cwd, "node_modules");
    const root = path.resolve(packageRoot);
    return root === nodeModules || root.startsWith(`${nodeModules}${path.sep}`);
}
async function maybeBackup(file) {
    if (!existsSync(file))
        return;
    const backup = `${file}.bak-${new Date().toISOString().replace(/[:.]/g, "-")}`;
    await copyFile(file, backup);
    return backup;
}
async function writeOpenCodeConfig(file, config) {
    await mkdir(path.dirname(file), { recursive: true });
    await writeFile(file, `${JSON.stringify(config, null, 2)}\n`, "utf8");
}
async function writeSeedConfig(file, config = seedConfigDocument(defaultSeedAgentConfig)) {
    await mkdir(path.dirname(file), { recursive: true });
    await writeFile(file, `${JSON.stringify(config, null, 2)}\n`, "utf8");
}
function seedConfigDocument(config) {
    return {
        $schema: "./schemas/seed-agent.schema.json",
        default_run_agent: config.default_run_agent,
        agent_order: config.agent_order,
        disabled_agents: config.disabled_agents,
        disabled_tools: config.disabled_tools,
        disabled_mcps: config.disabled_mcps,
        isolation: config.isolation,
        permissions: config.permissions,
        agents: config.agents,
        model_profiles: config.model_profiles,
        trace: config.trace,
        activity_journal: config.activity_journal,
        team_mode: config.team_mode,
        continuation: config.continuation,
        skills: config.skills,
        mcp: config.mcp,
        reporting: config.reporting,
        run: config.run,
        safety: config.safety,
    };
}
function applyProjectOpenCodeRuntimeDefaults(config, seedConfig) {
    const before = JSON.stringify(config);
    const next = { ...config };
    const mergedSeedConfig = mergeSeedConfig(seedConfig);
    next.default_agent = mergedSeedConfig.default_run_agent;
    const seedAgents = createSeedAgents(mergedSeedConfig);
    const existingAgents = isRecord(next.agent) ? next.agent : {};
    const seedAgentDisable = createOpenCodeNativeAgentDisableConfig();
    next.agent = mergedSeedConfig.isolation.allow_host_agents
        ? { ...existingAgents, ...seedAgents }
        : { ...seedAgentDisable, ...seedAgents };
    const seedCommands = createSeedCommands(mergedSeedConfig.default_run_agent, mergedSeedConfig);
    const existingCommands = isRecord(next.command) ? next.command : {};
    next.command = mergedSeedConfig.isolation.allow_host_commands
        ? { ...omitRetiredSeedCommands(existingCommands), ...seedCommands }
        : seedCommands;
    const disabledTools = Object.fromEntries(mergedSeedConfig.disabled_tools.map((name) => [name, false]));
    const existingTools = isRecord(next.tools) ? next.tools : {};
    next.tools = mergedSeedConfig.isolation.allow_host_tool_config
        ? { ...existingTools, ...disabledTools }
        : disabledTools;
    return { config: next, changed: JSON.stringify(next) !== before };
}
async function projectRootRuntimeConfig(file, seedConfig, options) {
    const config = await readOpenCodeConfig(file);
    const pluginSpec = resolveInstallPluginSpec(options, file);
    const plugins = Array.isArray(config.plugin) ? config.plugin : [];
    const normalized = normalizePluginList(plugins, pluginSpec, path.dirname(file));
    const projected = applyProjectOpenCodeRuntimeDefaults({
        ...config,
        plugin: normalized.plugins,
    }, seedConfig);
    const updated = normalized.changed
        || projected.changed
        || (!existsSync(file) && Object.keys(projected.config).length > 0);
    return { path: file, config: projected.config, updated };
}
async function normalizeExistingSeedConfig(file) {
    if (!existsSync(file))
        return;
    const raw = await readSeedConfig(file);
    const normalized = seedConfigDocument(mergeSeedConfig(raw));
    return { changed: JSON.stringify(raw) !== JSON.stringify(normalized), config: normalized };
}
function isRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function omitRetiredSeedCommands(commands) {
    const retired = new Set(["seed-status", "seed-lab-meeting", "seed-report", "seed-trace", "seed-stop", "seed-resume", "seed-intervene", "seed-meeting", "seed-meeting-now", "seed-meeting-status", "seed-meeting-enable"]);
    return Object.fromEntries(Object.entries(commands).filter(([name]) => !retired.has(name)));
}
function applyUnifiedModelToSeedConfig(input, model, variant) {
    const normalizedModel = normalizeProviderModel(model);
    const before = JSON.stringify(input);
    const config = { ...input };
    const profiles = isRecord(config.model_profiles) ? { ...config.model_profiles } : {};
    for (const profile of ["orchestrator", "reasoning", "research", "coding", "execution", "review", "reporting"]) {
        const current = isRecord(profiles[profile]) ? { ...profiles[profile] } : {};
        current.model = normalizedModel;
        if (variant && variant.trim())
            current.variant = variant.trim();
        else
            delete current.variant;
        profiles[profile] = current;
    }
    config.model_profiles = profiles;
    return { config, changed: JSON.stringify(config) !== before };
}
function normalizeLegacyDefaultModelPins(input) {
    if (!hasOnlyLegacyDefaultProfilePins(input))
        return { config: input, changed: false };
    const before = JSON.stringify(input);
    const config = { ...input };
    const profiles = isRecord(config.model_profiles) ? { ...config.model_profiles } : {};
    for (const [profileName, value] of Object.entries(profiles)) {
        if (!isRecord(value))
            continue;
        const next = { ...value };
        delete next.model;
        delete next.variant;
        profiles[profileName] = next;
    }
    config.model_profiles = profiles;
    return { config, changed: JSON.stringify(config) !== before };
}
function hasOnlyLegacyDefaultProfilePins(input) {
    const profiles = isRecord(input.model_profiles) ? input.model_profiles : undefined;
    if (!profiles)
        return false;
    const values = Object.values(profiles).filter(isRecord);
    if (!values.length)
        return false;
    const pinned = values.filter((profile) => typeof profile.model === "string" && profile.model.trim());
    if (!pinned.length)
        return false;
    return pinned.every((profile) => {
        const model = typeof profile.model === "string" ? profile.model.trim() : "";
        const variant = typeof profile.variant === "string" ? profile.variant.trim() : undefined;
        return model === SEED_DEFAULT_MODEL && (!variant || variant === SEED_DEFAULT_VARIANT);
    });
}
function normalizeProviderModel(model) {
    const trimmed = model.trim();
    const slash = trimmed.indexOf("/");
    if (slash <= 0 || slash === trimmed.length - 1) {
        throw new Error(`SEED install --model requires OpenCode provider/model format, for example openai/gpt-5.4 or anthropic/claude-sonnet-4-5: ${model}`);
    }
    return trimmed;
}
async function ensureProjectOpenCodeDependencies(configDir) {
    await mkdir(configDir, { recursive: true });
    const packageJsonPath = path.join(configDir, "package.json");
    const packageJson = {
        dependencies: {
            "@opencode-ai/plugin": "1.15.6",
        },
    };
    await writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf8");
    const rootNodeModules = path.resolve(configDir, "..", "node_modules");
    const opencodeNodeModules = path.join(configDir, "node_modules");
    if (existsSync(opencodeNodeModules))
        return;
    if (!existsSync(rootNodeModules)) {
        throw new Error(`Root node_modules not found at ${rootNodeModules}; run npm install before seed-agent install --local --project.`);
    }
    await cp(rootNodeModules, opencodeNodeModules, {
        recursive: true,
        dereference: true,
        preserveTimestamps: false,
    });
}
async function ensureBundledProjectSkills(cwd, packageRoot) {
    const sourceRoot = path.join(packageRoot, "assets", "skills");
    if (!existsSync(sourceRoot))
        return;
    const targetRoot = path.join(path.resolve(cwd), ".seed-agent", "skills");
    const bundledSkills = ["using-superpowers"];
    for (const skillName of bundledSkills) {
        const source = path.join(sourceRoot, skillName, "SKILL.md");
        const target = path.join(targetRoot, skillName, "SKILL.md");
        if (!existsSync(source) || existsSync(target))
            continue;
        await mkdir(path.dirname(target), { recursive: true });
        await copyFile(source, target);
    }
}
//# sourceMappingURL=opencode-config.js.map