import * as fsPromises from "node:fs/promises";
import path from "node:path";
import { openTaskList } from "@poe-code/task-list";
import { hasOwnErrorCode } from "./error-codes.js";
import { resolveWorkflowPath } from "./paths.js";
const PLAN_LIST_NAME = "plans";
const MARKDOWN_EXTENSION = ".md";
function defaultFs() {
    return fsPromises;
}
async function directoryExists(fs, directoryPath) {
    try {
        return (await fs.stat(directoryPath)).isDirectory();
    }
    catch (error) {
        if (hasOwnErrorCode(error, "ENOENT") || hasOwnErrorCode(error, "ENOTDIR")) {
            return false;
        }
        throw error;
    }
}
function stripMarkdownExtension(fileName) {
    if (!fileName.endsWith(MARKDOWN_EXTENSION)) {
        return undefined;
    }
    return fileName.slice(0, -MARKDOWN_EXTENSION.length);
}
function idFromPlanFileName(fileName) {
    const stem = stripMarkdownExtension(fileName);
    if (stem === undefined) {
        return undefined;
    }
    let index = 0;
    while (index < stem.length && stem.charCodeAt(index) >= 48 && stem.charCodeAt(index) <= 57) {
        index += 1;
    }
    if (index > 0 && stem[index] === "-" && index < stem.length - 1) {
        return stem.slice(index + 1);
    }
    return stem;
}
async function readPlanPaths(fs, directoryPath) {
    const fileNames = await fs.readdir(directoryPath);
    const pathsById = new Map();
    for (const fileName of fileNames) {
        const id = idFromPlanFileName(fileName);
        if (id === undefined) {
            continue;
        }
        const absolutePath = path.join(directoryPath, fileName);
        const stat = await fs.stat(absolutePath);
        if (stat.isFile()) {
            const existingPath = pathsById.get(id);
            if (existingPath !== undefined) {
                throw new Error(`Duplicate active plan identifier "${id}": ${existingPath} and ${absolutePath}`);
            }
            pathsById.set(id, absolutePath);
        }
    }
    return pathsById;
}
function isPathWithin(basePath, targetPath) {
    const relativePath = path.relative(basePath, targetPath);
    return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath));
}
function displayPlanPath(absolutePath, cwd, homeDir) {
    if (isPathWithin(cwd, absolutePath)) {
        return path.relative(cwd, absolutePath);
    }
    if (isPathWithin(homeDir, absolutePath)) {
        const relativeToHome = path.relative(homeDir, absolutePath);
        return relativeToHome.length === 0 ? "~" : `~/${relativeToHome}`;
    }
    return absolutePath;
}
function resolvePlanDirectory(options) {
    return resolveWorkflowPath(options.planDirectory, options.cwd, options.homeDir);
}
function planKind(metadata) {
    return String(metadata.kind ?? "plan");
}
export async function discoverPlans(options) {
    const resolvedDirectory = resolvePlanDirectory(options);
    const fs = options.fs ?? defaultFs();
    if (!(await directoryExists(fs, resolvedDirectory))) {
        return [];
    }
    const [taskList, pathsById] = await Promise.all([
        openPlanList(options),
        readPlanPaths(fs, resolvedDirectory)
    ]);
    const kindFilter = options.kinds === undefined ? undefined : new Set(options.kinds);
    const tasks = await taskList.list(PLAN_LIST_NAME).all();
    return tasks
        .map((task) => ({
        id: task.id,
        name: task.name,
        kind: planKind(task.metadata),
        absolutePath: pathsById.get(task.id) ?? path.join(resolvedDirectory, `${task.id}.md`)
    }))
        .filter((plan) => kindFilter === undefined || kindFilter.has(plan.kind))
        .map((plan) => ({
        ...plan,
        displayPath: displayPlanPath(plan.absolutePath, options.cwd, options.homeDir)
    }));
}
export const archivePlan = async (options) => {
    const taskList = await openPlanList(options);
    const plans = taskList.list(PLAN_LIST_NAME);
    await plans.fire(options.id, "archive");
};
export function openPlanList(options) {
    return openTaskList({
        type: "markdown-dir",
        path: resolvePlanDirectory(options),
        singleList: PLAN_LIST_NAME,
        frontmatterMode: "passthrough",
        fs: options.fs
    });
}
