import os from "node:os";
import path from "node:path";
import { resolveScope } from "@poe-code/poe-code-config";
import { validateStateDefinitions } from "./validate.js";
const maestroConfigScope = {
    polling: {
        type: "json",
        default: { interval_ms: 30_000 },
        parse: parseRecord,
        doc: "Polling options."
    },
    workspace: {
        type: "json",
        default: { root: path.join(os.tmpdir(), "poe-code-maestro") },
        parse: parseRecord,
        doc: "Workspace options."
    },
    agent: {
        type: "json",
        default: {
            service: "codex",
            max_concurrent_agents: 1,
            max_retry_backoff_ms: 300_000
        },
        parse: parseRecord,
        doc: "Agent dispatch options."
    }
};
export function resolveConfig(raw, cwd) {
    const rawConfig = isRecord(raw) ? raw : {};
    const scoped = resolveScope(maestroConfigScope, rawConfig, process.env);
    const { states, stateOrder } = parseStates(getOwnEntry(rawConfig, "states"));
    const polling = scoped.polling;
    const workspace = scoped.workspace;
    const agent = scoped.agent;
    if (hasOwn(agent, "max_turns")) {
        throw new Error("agent.max_turns is not supported by agent spawning.");
    }
    return {
        tasks: resolveTasks(getOwnEntry(rawConfig, "tasks"), cwd),
        states,
        activeStateNames: stateOrder.filter((name) => states[name]?.prompt !== undefined),
        terminalStateNames: stateOrder.filter((name) => states[name]?.terminal === true),
        stateOrder,
        polling: {
            intervalMs: readPositiveInteger(getOwnEntry(polling, "interval_ms"), 30_000, "polling.interval_ms")
        },
        workspace: {
            root: resolvePathValue(readString(getOwnEntry(workspace, "root"), "workspace.root") ??
                path.join(os.tmpdir(), "poe-code-maestro"), cwd)
        },
        agent: {
            service: readString(getOwnEntry(agent, "service"), "agent.service") ?? "codex",
            list: readString(resolveStringValue(getOwnEntry(agent, "list")), "agent.list"),
            maxConcurrentAgents: readPositiveInteger(getOwnEntry(agent, "max_concurrent_agents"), 1, "agent.max_concurrent_agents"),
            maxRetryBackoffMs: readNonNegativeInteger(getOwnEntry(agent, "max_retry_backoff_ms"), 300_000, "agent.max_retry_backoff_ms")
        }
    };
}
function parseStates(value) {
    validateStateDefinitions(value);
    const states = Object.create(null);
    const stateOrder = [];
    const entries = value instanceof Map ? value.entries() : Object.entries(value);
    for (const [name, rawDefinition] of entries) {
        const stateName = String(name);
        const definition = rawDefinition;
        Object.defineProperty(states, stateName, {
            configurable: true,
            enumerable: true,
            value: parseStateDefinition(definition),
            writable: true
        });
        stateOrder.push(stateName);
    }
    return { states, stateOrder };
}
function parseStateDefinition(definition) {
    const state = {};
    if (hasOwn(definition, "prompt")) {
        state.prompt = readOptionalString(definition.prompt, "prompt");
    }
    if (hasOwn(definition, "agent")) {
        state.agent = readOptionalString(definition.agent, "agent");
    }
    if (hasOwn(definition, "model")) {
        state.model = readOptionalString(definition.model, "model");
    }
    if (hasOwn(definition, "mode")) {
        state.mode = readMode(definition.mode);
    }
    if (hasOwn(definition, "terminal")) {
        state.terminal = readOptionalBoolean(definition.terminal, "terminal");
    }
    return state;
}
function resolveTasks(value, cwd) {
    if (!isRecord(value)) {
        return undefined;
    }
    const resolved = resolveStringValues(value);
    const taskPath = getOwnEntry(resolved, "path");
    if (typeof taskPath === "string") {
        defineRecordEntry(resolved, "path", resolvePathValue(taskPath, cwd));
    }
    return resolved;
}
function parseRecord(value) {
    if (!isRecord(value)) {
        throw new Error("Expected an object.");
    }
    return { ...value };
}
function resolveStringValues(value) {
    const resolved = {};
    for (const [key, entry] of Object.entries(value)) {
        if (typeof entry === "string") {
            defineRecordEntry(resolved, key, resolveStringValue(entry));
            continue;
        }
        if (Array.isArray(entry)) {
            defineRecordEntry(resolved, key, entry.map((item) => (typeof item === "string" ? resolveStringValue(item) : item)));
            continue;
        }
        defineRecordEntry(resolved, key, isRecord(entry) ? resolveStringValues(entry) : entry);
    }
    return resolved;
}
function resolveStringValue(value) {
    if (typeof value !== "string" || !value.startsWith("$")) {
        return value;
    }
    const envName = value.slice(1);
    if (!isEnvName(envName)) {
        return value;
    }
    return process.env[envName] ?? "";
}
function resolvePathValue(value, cwd) {
    const expanded = expandHome(resolveStringValue(value));
    if (typeof expanded !== "string" || expanded.length === 0) {
        throw new Error("workspace.root must not resolve to an empty path");
    }
    return path.isAbsolute(expanded) ? expanded : path.resolve(cwd, expanded);
}
function expandHome(value) {
    if (typeof value !== "string") {
        return value;
    }
    if (value === "~") {
        return os.homedir();
    }
    if (value.startsWith("~/")) {
        return path.join(os.homedir(), value.slice(2));
    }
    return value;
}
function readString(value, field) {
    if (value === undefined) {
        return undefined;
    }
    if (typeof value !== "string") {
        throw new Error(`Expected "${field}" to be a string.`);
    }
    const trimmed = value.trim();
    return trimmed.length > 0 ? value : undefined;
}
function readNumber(value, fallback, field) {
    if (value === undefined) {
        return fallback;
    }
    if (typeof value !== "number" || !Number.isFinite(value)) {
        throw new Error(`Expected "${field}" to be a number.`);
    }
    return value;
}
function readPositiveInteger(value, fallback, field) {
    const numberValue = readNumber(value, fallback, field);
    if (!Number.isInteger(numberValue) || numberValue <= 0) {
        throw new Error(`Expected "${field}" to be a positive integer.`);
    }
    return numberValue;
}
function readNonNegativeInteger(value, fallback, field) {
    const numberValue = readNumber(value, fallback, field);
    if (!Number.isInteger(numberValue) || numberValue < 0) {
        throw new Error(`Expected "${field}" to be a non-negative integer.`);
    }
    return numberValue;
}
function readOptionalString(value, field) {
    if (value === undefined) {
        return undefined;
    }
    if (typeof value !== "string") {
        throw new Error(`Expected state "${field}" to be a string.`);
    }
    return value;
}
function readOptionalBoolean(value, field) {
    if (value === undefined) {
        return undefined;
    }
    if (typeof value !== "boolean") {
        throw new Error(`Expected state "${field}" to be a boolean.`);
    }
    return value;
}
function readMode(value) {
    if (value === undefined) {
        return undefined;
    }
    if (value === "yolo" || value === "edit" || value === "read") {
        return value;
    }
    throw new Error('Expected state "mode" to be one of "yolo", "edit", or "read".');
}
function isRecord(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
function hasOwn(value, key) {
    return Object.prototype.hasOwnProperty.call(value, key);
}
function getOwnEntry(value, key) {
    return hasOwn(value, key) ? value[key] : undefined;
}
function defineRecordEntry(record, key, value) {
    Object.defineProperty(record, key, {
        configurable: true,
        enumerable: true,
        value,
        writable: true
    });
}
function isEnvName(value) {
    if (value.length === 0) {
        return false;
    }
    for (const char of value) {
        const code = char.charCodeAt(0);
        const isUpper = code >= 65 && code <= 90;
        const isDigit = code >= 48 && code <= 57;
        if (!isUpper && !isDigit && char !== "_") {
            return false;
        }
    }
    return true;
}
