import { readFile } from 'node:fs/promises';
import { CliError } from '../errors/index.js';
import { atomicWriteJson } from '../fs/io.js';
import { normalizeToPosixPath } from '../fs/paths.js';
import { SyncStrategySchema } from '../shared/types.js';
import { z } from 'zod';
import { parseJsonConfig } from './json.js';
const ProviderConfigSchema = z.object({
    strategy: SyncStrategySchema.optional(),
    enabled: z.boolean().optional(),
});
export const SyncConfigSchema = z.object({
    version: z.literal(1),
    defaultStrategy: SyncStrategySchema,
    knownStrays: z.array(z.string()).optional(),
    providers: z.record(ProviderConfigSchema).optional(),
});
export const DEFAULT_SYNC_CONFIG = {
    version: 1,
    defaultStrategy: 'auto',
    knownStrays: [],
    providers: {},
};
function mergeProviderConfigs(base, override) {
    const merged = { ...base };
    for (const [name, config] of Object.entries(override)) {
        merged[name] = {
            ...(merged[name] ?? {}),
            ...config,
        };
    }
    return merged;
}
function normalizeKnownStrayPath(pathValue) {
    const trimmed = pathValue.trim();
    if (!trimmed) {
        return undefined;
    }
    const normalized = normalizeToPosixPath(trimmed)
        .replace(/\/+$/, '')
        .replace(/^\.\//, '');
    return normalized && normalized !== '.' ? normalized : undefined;
}
function normalizeKnownStrays(paths) {
    const normalized = paths
        ?.map((pathValue) => normalizeKnownStrayPath(pathValue))
        .filter((pathValue) => pathValue !== undefined);
    return [...new Set(normalized ?? [])].sort();
}
function normalizeConfig(config, defaults) {
    return {
        version: 1,
        defaultStrategy: config.defaultStrategy ?? defaults.defaultStrategy,
        knownStrays: normalizeKnownStrays([
            ...defaults.knownStrays,
            ...(config.knownStrays ?? []),
        ]),
        providers: mergeProviderConfigs(defaults.providers, config.providers ?? {}),
    };
}
function parseSyncConfig(raw, configPath) {
    let parsed;
    try {
        parsed = parseJsonConfig(raw, configPath);
    }
    catch {
        throw new CliError(`Sync config at ${configPath} is not valid JSON. Fix the file and retry.`);
    }
    const result = SyncConfigSchema.safeParse(parsed);
    if (!result.success) {
        throw new CliError(`Sync config at ${configPath} failed validation. Fix the file and retry.`);
    }
    return result.data;
}
export async function loadSyncConfig(configPath, defaults = DEFAULT_SYNC_CONFIG) {
    try {
        const raw = await readFile(configPath, 'utf8');
        return normalizeConfig(parseSyncConfig(raw, configPath), defaults);
    }
    catch (error) {
        if (typeof error === 'object' &&
            error !== null &&
            'code' in error &&
            error.code === 'ENOENT') {
            return {
                ...defaults,
                knownStrays: [...defaults.knownStrays],
                providers: { ...defaults.providers },
            };
        }
        if (error instanceof CliError) {
            throw error;
        }
        throw new CliError(`Unable to load sync config from ${configPath}: ${error instanceof Error ? error.message : 'unknown error'}`, 2);
    }
}
export async function saveSyncConfig(configPath, config) {
    const result = SyncConfigSchema.safeParse(config);
    if (!result.success) {
        throw new CliError(`Sync config at ${configPath} failed validation. Fix the config object and retry.`);
    }
    const normalized = normalizeConfig(result.data, DEFAULT_SYNC_CONFIG);
    await atomicWriteJson(configPath, normalized);
    return normalized;
}
export async function setProviderEnabled(configPath, providerName, enabled) {
    const current = await loadSyncConfig(configPath, DEFAULT_SYNC_CONFIG);
    const next = {
        ...current,
        providers: {
            ...current.providers,
            [providerName]: {
                ...(current.providers[providerName] ?? {}),
                enabled,
            },
        },
    };
    return saveSyncConfig(configPath, next);
}
