import { parseJson, writeError } from "./registry.js";
export function registerPoliciesRoutes(routes, deps) {
    // ─── Policies (L2 "经验") ────────────────────────────────────────
    routes.set("GET /api/v1/policies", async (ctx) => {
        const params = ctx.url.searchParams;
        const parsedLimit = Number(params.get("limit"));
        const parsedOffset = Number(params.get("offset"));
        const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? parsedLimit : 50;
        const offset = Number.isFinite(parsedOffset) && parsedOffset >= 0 ? parsedOffset : 0;
        const statusRaw = params.get("status");
        const status = isValidPolicyStatus(statusRaw) ? statusRaw : undefined;
        const q = params.get("q") || undefined;
        const ownerAgentKind = params.get("ownerAgentKind") || undefined;
        const ownerProfileId = params.get("ownerProfileId") || undefined;
        const policies = await deps.core.listPolicies({
            status,
            limit,
            offset,
            q,
            ownerAgentKind,
            ownerProfileId,
        });
        const total = await deps.core.countPolicies({
            status,
            q,
            ownerAgentKind,
            ownerProfileId,
        });
        return {
            policies,
            limit,
            offset,
            total,
            nextOffset: policies.length === limit ? offset + limit : undefined,
        };
    });
    routes.setPattern("GET /api/v1/policies/:id", async (ctx) => {
        const id = ctx.params.id;
        if (!id) {
            writeError(ctx, 400, "invalid_argument", "id is required");
            return;
        }
        const policy = await deps.core.getPolicy(id);
        if (!policy) {
            writeError(ctx, 404, "not_found", `policy not found: ${id}`);
            return;
        }
        return policy;
    });
    /**
     * PATCH /api/v1/policies/:id — transition status and/or edit body
     * fields. Body shape (all optional, at least one required):
     *
     *   {
     *     status?: 'candidate' | 'active' | 'archived',
     *     title?, trigger?, procedure?, verification?, boundary?: string
     *   }
     *
     * Pre-existing callers that only sent `{status}` keep working — the
     * viewer's "启用 / 停用 / 归档" buttons still hit this endpoint with
     * just the status flag.
     */
    routes.setPattern("PATCH /api/v1/policies/:id", async (ctx) => {
        const id = ctx.params.id;
        if (!id) {
            writeError(ctx, 400, "invalid_argument", "id is required");
            return;
        }
        const body = parseJson(ctx);
        const contentPatch = {};
        if (typeof body.title === "string")
            contentPatch.title = body.title;
        if (typeof body.trigger === "string")
            contentPatch.trigger = body.trigger;
        if (typeof body.procedure === "string")
            contentPatch.procedure = body.procedure;
        if (typeof body.verification === "string")
            contentPatch.verification = body.verification;
        if (typeof body.boundary === "string")
            contentPatch.boundary = body.boundary;
        const hasContent = Object.keys(contentPatch).length > 0;
        const hasStatus = body.status !== undefined;
        if (!hasStatus && !hasContent) {
            writeError(ctx, 400, "invalid_argument", "provide at least one of status / title / trigger / procedure / verification / boundary");
            return;
        }
        if (hasStatus && !isValidPolicyStatus(body.status)) {
            writeError(ctx, 400, "invalid_argument", "status must be one of: candidate, active, archived");
            return;
        }
        let updated = hasContent
            ? await deps.core.updatePolicy(id, contentPatch)
            : await deps.core.getPolicy(id);
        if (!updated) {
            writeError(ctx, 404, "not_found", `policy not found: ${id}`);
            return;
        }
        if (hasStatus) {
            updated = await deps.core.setPolicyStatus(id, body.status);
            if (!updated) {
                writeError(ctx, 404, "not_found", `policy not found: ${id}`);
                return;
            }
        }
        return updated;
    });
    /**
     * `POST /api/v1/policies/:id/share` — set/clear the share state.
     * Same body shape as `POST /api/v1/traces/:id/share`.
     */
    routes.setPattern("POST /api/v1/policies/:id/share", async (ctx) => {
        const id = ctx.params.id;
        if (!id) {
            writeError(ctx, 400, "invalid_argument", "id is required");
            return;
        }
        const body = parseJson(ctx);
        const scope = body.scope === undefined ? "public" : body.scope;
        const updated = await deps.core.sharePolicy(id, {
            scope: scope ?? null,
            target: body.target ?? null,
            sharedAt: scope ? Date.now() : null,
        });
        if (!updated) {
            writeError(ctx, 404, "not_found", `policy not found: ${id}`);
            return;
        }
        return updated;
    });
    routes.setPattern("DELETE /api/v1/policies/:id", async (ctx) => {
        const id = ctx.params.id;
        if (!id) {
            writeError(ctx, 400, "invalid_argument", "id is required");
            return;
        }
        return await deps.core.deletePolicy(id);
    });
    /**
     * `POST /api/v1/policies/:id/guidance` — append decision guidance to
     * a policy. Body: `{ preference?: string[]; antiPattern?: string[] }`.
     * The viewer's PolicyDrawer uses this for manual guidance entry
     * when the feedback pipeline hasn't produced any yet. Duplicates
     * are de-duped server-side.
     */
    routes.setPattern("POST /api/v1/policies/:id/guidance", async (ctx) => {
        const id = ctx.params.id;
        if (!id) {
            writeError(ctx, 400, "invalid_argument", "id is required");
            return;
        }
        const body = parseJson(ctx);
        const pref = normalizeGuidanceList(body.preference);
        const avoid = normalizeGuidanceList(body.antiPattern);
        if (pref.length === 0 && avoid.length === 0) {
            writeError(ctx, 400, "invalid_argument", "provide at least one preference or antiPattern string");
            return;
        }
        const updated = await deps.core.editPolicyGuidance(id, {
            preference: pref,
            antiPattern: avoid,
        });
        if (!updated) {
            writeError(ctx, 404, "not_found", `policy not found: ${id}`);
            return;
        }
        return updated;
    });
    /**
     * `GET /api/v1/policies/:id/usage` — everything that references this
     * policy from the other tiers. The viewer's PolicyDrawer calls this
     * to render the "Related skills / In world models / Source episodes"
     * cross-link sections. Kept server-side so the drawer doesn't have
     * to pull every skill + world-model to filter client-side.
     */
    routes.setPattern("GET /api/v1/policies/:id/usage", async (ctx) => {
        const id = ctx.params.id;
        if (!id) {
            writeError(ctx, 400, "invalid_argument", "id is required");
            return;
        }
        const policy = await deps.core.getPolicy(id);
        if (!policy) {
            writeError(ctx, 404, "not_found", `policy not found: ${id}`);
            return;
        }
        const [skills, worldModels] = await Promise.all([
            deps.core.listSkills({ limit: 500 }),
            deps.core.listWorldModels({ limit: 500 }),
        ]);
        return {
            skills: skills
                .filter((s) => s.sourcePolicyIds.includes(id))
                .map((s) => ({ id: s.id, name: s.name, status: s.status, eta: s.eta })),
            worldModels: worldModels
                .filter((w) => w.policyIds.includes(id))
                .map((w) => ({ id: w.id, title: w.title })),
            // Policy rows carry their source episodes directly — no join
            // needed. Return the full list so the drawer can render every
            // task that contributed a supporting trace.
            sourceEpisodes: policy.sourceEpisodeIds ?? [],
        };
    });
    // ─── World models (L3 "世界环境知识") ────────────────────────────
    routes.set("GET /api/v1/world-models", async (ctx) => {
        const params = ctx.url.searchParams;
        const parsedLimit = Number(params.get("limit"));
        const parsedOffset = Number(params.get("offset"));
        const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? parsedLimit : 50;
        const offset = Number.isFinite(parsedOffset) && parsedOffset >= 0 ? parsedOffset : 0;
        const q = params.get("q") || undefined;
        const ownerAgentKind = params.get("ownerAgentKind") || undefined;
        const ownerProfileId = params.get("ownerProfileId") || undefined;
        const worldModels = await deps.core.listWorldModels({
            limit,
            offset,
            q,
            ownerAgentKind,
            ownerProfileId,
        });
        const total = await deps.core.countWorldModels({
            q,
            ownerAgentKind,
            ownerProfileId,
        });
        return {
            worldModels,
            limit,
            offset,
            total,
            nextOffset: worldModels.length === limit ? offset + limit : undefined,
        };
    });
    routes.setPattern("GET /api/v1/world-models/:id", async (ctx) => {
        const id = ctx.params.id;
        if (!id) {
            writeError(ctx, 400, "invalid_argument", "id is required");
            return;
        }
        const model = await deps.core.getWorldModel(id);
        if (!model) {
            writeError(ctx, 404, "not_found", `world model not found: ${id}`);
            return;
        }
        return model;
    });
    /**
     * `GET /api/v1/world-models/:id/usage` — cross-link payload for the
     * world-model drawer. Resolves `policyIds` to {id, title, status}
     * triples so the drawer can render clickable policy chips without
     * another round-trip per chip.
     */
    routes.setPattern("GET /api/v1/world-models/:id/usage", async (ctx) => {
        const id = ctx.params.id;
        if (!id) {
            writeError(ctx, 400, "invalid_argument", "id is required");
            return;
        }
        const wm = await deps.core.getWorldModel(id);
        if (!wm) {
            writeError(ctx, 404, "not_found", `world model not found: ${id}`);
            return;
        }
        const policies = await Promise.all(wm.policyIds.map(async (pid) => {
            const p = await deps.core.getPolicy(pid);
            return p
                ? { id: p.id, title: p.title, status: p.status, gain: p.gain }
                : { id: pid, title: null, status: null, gain: null };
        }));
        return { policies };
    });
    routes.setPattern("DELETE /api/v1/world-models/:id", async (ctx) => {
        const id = ctx.params.id;
        if (!id) {
            writeError(ctx, 400, "invalid_argument", "id is required");
            return;
        }
        return await deps.core.deleteWorldModel(id);
    });
    /**
     * PATCH /api/v1/world-models/:id — edit body and/or flip lifecycle.
     * Body shape (all optional, at least one required):
     *
     *   { title?: string; body?: string; status?: 'active' | 'archived' }
     */
    routes.setPattern("PATCH /api/v1/world-models/:id", async (ctx) => {
        const id = ctx.params.id;
        if (!id) {
            writeError(ctx, 400, "invalid_argument", "id is required");
            return;
        }
        const body = parseJson(ctx);
        const patch = {};
        if (typeof body.title === "string")
            patch.title = body.title;
        if (typeof body.body === "string")
            patch.body = body.body;
        if (body.status === "active" || body.status === "archived") {
            patch.status = body.status;
        }
        if (Object.keys(patch).length === 0) {
            writeError(ctx, 400, "invalid_argument", "provide at least one of title / body / status");
            return;
        }
        const updated = await deps.core.updateWorldModel(id, patch);
        if (!updated) {
            writeError(ctx, 404, "not_found", `world model not found: ${id}`);
            return;
        }
        return updated;
    });
    /**
     * `POST /api/v1/world-models/:id/share` — set/clear the share
     * state. Same body shape as `POST /api/v1/traces/:id/share`.
     */
    routes.setPattern("POST /api/v1/world-models/:id/share", async (ctx) => {
        const id = ctx.params.id;
        if (!id) {
            writeError(ctx, 400, "invalid_argument", "id is required");
            return;
        }
        const body = parseJson(ctx);
        const scope = body.scope === undefined ? "public" : body.scope;
        const updated = await deps.core.shareWorldModel(id, {
            scope: scope ?? null,
            target: body.target ?? null,
            sharedAt: scope ? Date.now() : null,
        });
        if (!updated) {
            writeError(ctx, 404, "not_found", `world model not found: ${id}`);
            return;
        }
        return updated;
    });
    /**
     * Soft-archive a world model (reversible). Mirrors the trace-level
     * "归档" affordance.
     */
    routes.setPattern("POST /api/v1/world-models/:id/archive", async (ctx) => {
        const id = ctx.params.id;
        if (!id) {
            writeError(ctx, 400, "invalid_argument", "id is required");
            return;
        }
        const updated = await deps.core.archiveWorldModel(id);
        if (!updated) {
            writeError(ctx, 404, "not_found", `world model not found: ${id}`);
            return;
        }
        return updated;
    });
    routes.setPattern("POST /api/v1/world-models/:id/unarchive", async (ctx) => {
        const id = ctx.params.id;
        if (!id) {
            writeError(ctx, 400, "invalid_argument", "id is required");
            return;
        }
        const updated = await deps.core.unarchiveWorldModel(id);
        if (!updated) {
            writeError(ctx, 404, "not_found", `world model not found: ${id}`);
            return;
        }
        return updated;
    });
}
function isValidPolicyStatus(v) {
    return v === "candidate" || v === "active" || v === "archived";
}
function normalizeGuidanceList(raw) {
    if (!Array.isArray(raw))
        return [];
    const out = [];
    for (const item of raw) {
        if (typeof item !== "string")
            continue;
        const t = item.trim();
        if (!t)
            continue;
        // Cap line length so a malformed client doesn't flood boundary.
        out.push(t.length > 400 ? t.slice(0, 400) : t);
        if (out.length >= 20)
            break;
    }
    return out;
}
//# sourceMappingURL=policies.js.map