/**
 * Request Service — CRUD operations for V3 Request entities
 *
 * Requests represent user-level goals. They are stored as JSON files
 * in `{projectPath}/.crewly/requests/{id}.json`.
 *
 * @module services/v3/request.service
 */
import { type Request, type CreateRequestInput, type UpdateRequestInput } from '../../types/v2/index.js';
import { type WorkItem } from '../../types/v2/work-item.types.js';
import { type PlannedTask } from './v3-data.service.js';
import type { EventBusService } from '../event-bus/event-bus.service.js';
/**
 * Duck-typed interface RequestService uses to query child WorkItem
 * states for the {@link RequestService.update} → done gate.
 *
 * Defined as an interface (not a TaskPoolService import) so request.service
 * does NOT acquire a static dependency on task-pool.service. The dependency
 * direction stays one-way: TaskPool → Request (via Bug B's
 * `IRequestWorkItemLinker`), Request → TaskPool only via this duck-typed
 * setter (Bug C). This breaks the otherwise-circular type graph that would
 * form if either side imported the other concretely.
 *
 * Tests pass a fake; production wires the real TaskPoolService instance via
 * {@link RequestService.setTaskPoolService}.
 *
 * P1 Bug C (Pool umbrella WI 72ca743a-3a66-4d0e-a6cf-1a861f849dbd, sub-WI
 *           72ca743a Bug C): the F9 fixture closed a Request to status=done
 * with a non-terminal child WI in the pool. The gate fetches each child by
 * id and refuses the transition if any child is in a non-terminal state.
 */
export interface IWorkItemQueryable {
    /**
     * Look up a WorkItem by id. Returns `null` if no item has that id —
     * mirrors {@link TaskPoolService.findWorkItem}'s null-fallthrough idiom.
     *
     * @param workItemId - WorkItem id to look up
     * @returns The WorkItem, or `null` if no item has that id
     */
    findWorkItem(workItemId: string): Promise<WorkItem | null>;
}
/**
 * Structured error thrown when {@link RequestService.update} refuses to
 * transition a Request to `done` because at least one of its child
 * WorkItems is still in a non-terminal state.
 *
 * Distinct error class (not a generic `Error`) so callers can branch on
 * `instanceof RequestStillHasOpenChildrenError` and surface a meaningful
 * message — Slack bridge, REST consumers, audit, all want to differentiate
 * "still has open children" from generic transition-validation errors.
 *
 * The structured payload exposes `requestId`, `openChildIds`, and
 * `openChildCount` so consumers can render actionable messages without
 * re-querying the pool.
 */
export declare class RequestStillHasOpenChildrenError extends Error {
    /** The Request id that was refused. */
    readonly requestId: string;
    /** WorkItem ids of children that are still in non-terminal status. */
    readonly openChildIds: readonly string[];
    /** Convenience: openChildIds.length, included for log/format symmetry. */
    readonly openChildCount: number;
    constructor(args: {
        requestId: string;
        openChildIds: readonly string[];
    });
}
/**
 * Wire the EventBusService that {@link RequestService} should publish
 * lifecycle events to. Called once from the backend boot path before any
 * Request creation can fire.
 *
 * @param bus - The live EventBusService, or null to clear (tests)
 */
export declare function setRequestServiceEventBus(bus: EventBusService | null): void;
/**
 * Service for managing Request entities on disk.
 *
 * Uses singleton pattern for consistent state across the backend.
 * All persistence is file-based (one JSON file per Request).
 *
 * @example
 * ```typescript
 * const service = RequestService.getInstance('/path/to/project');
 * const req = await service.create({ title: 'Deploy staging', ... });
 * ```
 */
export declare class RequestService {
    private readonly projectPath;
    private static instance;
    private readonly logger;
    private readonly requestsDir;
    /**
     * Optional TaskPool reference used by the {@link update} → done gate
     * to verify that all child WorkItems are in terminal state before the
     * Request itself can close. Wired via {@link setTaskPoolService} from
     * the backend boot path (P1 Bug C — Pool umbrella WI 72ca743a).
     *
     * When `null`, the gate degrades gracefully — closure proceeds with a
     * warning log. This preserves backward compatibility for tests and
     * boot orderings that happen to call `update` before the wiring step.
     * Production paths always wire the dependency before any user-facing
     * Request close request can land.
     */
    private taskPoolService;
    /**
     * Creates a new RequestService.
     *
     * @param projectPath - Absolute path to the project root
     */
    private constructor();
    /**
     * Gets the singleton instance.
     *
     * @param projectPath - Absolute path to the project root
     * @returns The singleton RequestService instance
     */
    static getInstance(projectPath?: string): RequestService;
    /**
     * Resets the singleton instance. For test isolation only.
     */
    static resetInstance(): void;
    /**
     * Wire the TaskPool-queryable used by the Request → done gate
     * (P1 Bug C — Pool umbrella WI 72ca743a).
     *
     * Called from the backend boot path after both services exist. Mirrors
     * Bug B's {@link TaskPoolService.setRequestService} — same setter shape,
     * idempotent, accepts `null` to clear in tests. Keeps the dependency
     * direction Request → TaskPool one-way and duck-typed, so neither
     * service needs a static import of the other.
     *
     * @param svc - The TaskPoolService instance (or any object satisfying
     *   {@link IWorkItemQueryable}), or null to clear.
     */
    setTaskPoolService(svc: IWorkItemQueryable | null): void;
    /**
     * Returns the file path for a request JSON file.
     *
     * @param requestId - The request ID
     * @returns Absolute path to the request JSON file
     */
    private getFilePath;
    /**
     * Loads a single Request from disk.
     *
     * @param requestId - The request ID
     * @returns The Request or null if not found
     */
    private load;
    /**
     * Saves a Request to disk atomically.
     *
     * @param request - The Request to persist
     */
    private save;
    /**
     * Creates a new Request.
     *
     * Publishes a `request:created` event after successful save so the
     * INBOUND-1 SLA subscriber (and any other in-process listener) can react
     * to inbound user goals. Event publication is best-effort — a failure to
     * publish does NOT roll back the persisted Request, but is logged.
     *
     * @param input - Creation input
     * @returns The created Request
     * @throws Error if validation fails
     */
    create(input: CreateRequestInput): Promise<Request>;
    /**
     * Publish the `request:created` event. Synchronous best-effort — failures
     * are logged but do not propagate to the caller (Request persistence is
     * already complete and must not be rolled back).
     *
     * @param request - The Request that was just persisted
     */
    private publishCreatedEvent;
    /**
     * Gets a Request by ID.
     *
     * @param id - Request ID
     * @returns The Request or null
     */
    getById(id: string): Promise<Request | null>;
    /**
     * Lists all Requests.
     *
     * @returns Array of all Requests, sorted by createdAt descending
     */
    listAll(): Promise<Request[]>;
    /**
     * Updates a Request with partial fields.
     *
     * @param id - Request ID
     * @param updates - Fields to update
     * @returns The updated Request
     * @throws Error if not found or invalid transition
     */
    update(id: string, updates: UpdateRequestInput): Promise<Request>;
    /**
     * P1 Bug C gate helper — verify that every WorkItem id in
     * `request.workItemIds[]` resolves to a WorkItem in terminal state.
     *
     * Throws {@link RequestStillHasOpenChildrenError} when at least one child
     * is non-terminal. Missing children (returned `null` by
     * {@link IWorkItemQueryable.findWorkItem}) are treated as **terminal /
     * harmless** — a deleted-or-purged WI does not block its parent's
     * closure. This matches the option-a "graceful degrade" stance ORC took
     * on historical orphan WIs and avoids a deadlock between data-cleanup
     * jobs and lifecycle gates.
     *
     * Semantics: non-terminal = anything not in
     * {@link TERMINAL_WORK_ITEM_STATUSES} (`done`, `verified`, `cancelled`).
     * `failed` and `rejected` are treated as non-terminal because the WI
     * state-machine in {@link WORK_ITEM_TRANSITIONS} explicitly allows them
     * to re-enter `queued` — work the user requested is not actually
     * complete.
     *
     * @param request - The Request whose children to verify
     * @throws {@link RequestStillHasOpenChildrenError} when any child is
     *   non-terminal.
     */
    private assertAllChildrenTerminal;
    /**
     * Deletes a Request from disk. Used for purging old completed Requests.
     *
     * @param id - The Request ID to delete
     */
    delete(id: string): Promise<void>;
    /**
     * Adds a WorkItem ID to a Request's workItemIds array.
     *
     * @param requestId - The Request ID
     * @param workItemId - The WorkItem ID to link
     * @returns The updated Request, or null if Request not found
     */
    linkWorkItem(requestId: string, workItemId: string): Promise<Request | null>;
    /**
     * Finds a Request by its source conversation item ID.
     * Used for deduplication — avoids creating duplicate Requests for the same message.
     *
     * @param sourceConversationItemId - The conversation item ID
     * @returns The matching Request, or null
     */
    findBySourceConversationItemId(sourceConversationItemId: string): Promise<Request | null>;
    /**
     * Decomposes a user message into a proposed list of ProjectTasks.
     *
     * This is the "TodoWrite-style" planning phase: given a raw user message,
     * it classifies the intent and returns structured tasks that can be
     * reviewed, approved, and then executed.
     *
     * Currently uses keyword-based decomposition (same logic as Mission
     * decomposition in V3DataService). Can be upgraded to AI-based planning
     * in the future without changing the API contract.
     *
     * @param message - The raw user message text
     * @param options - Optional planning configuration
     * @returns A RequestPlan with proposed tasks and metadata
     *
     * @example
     * ```typescript
     * const plan = await requestService.plan('Build a new auth system');
     * // plan.tasks → [{ title: 'Design...', ... }, { title: 'Implement...', ... }, ...]
     * ```
     */
    plan(message: string, options?: PlanOptions): Promise<RequestPlan>;
}
/**
 * Strategy used for task decomposition.
 */
export type PlanStrategy = 'build' | 'fix' | 'generic' | 'none';
/**
 * Optional configuration for the plan method.
 */
export interface PlanOptions {
    /** Maximum number of tasks to generate */
    maxTasks?: number;
    /** Preferred priority for generated tasks */
    defaultPriority?: 'high' | 'medium' | 'low';
}
/**
 * Result of planning a user request into tasks.
 */
export interface RequestPlan {
    /** The original user message */
    message: string;
    /** Proposed tasks derived from the message */
    tasks: PlannedTask[];
    /** Human-readable explanation of the decomposition */
    reasoning: string;
    /** Which planning strategy was applied */
    strategy: PlanStrategy;
}
//# sourceMappingURL=request.service.d.ts.map