/**
 * Linear-chain AST for a single hour-categorization rule under literal-order evaluation.
 *
 * Each condition in the rule's `conditions` array becomes exactly one stage node, in array
 * order. A stage points to its `input` — the previous stage, or the `BaseNode` (the day's
 * worked intervals) when it is the first condition:
 *
 *   BASE → <stage> → <stage> → ... → (RuleAstNode wraps the last stage)
 *
 * `buildRuleAst` constructs the chain (pure — structural fields only). `executeRuleAst`
 * walks it from the base applying each transform, resolving runtime thresholds, and filling
 * the `result*` (and `thresholdMs`) annotations consumed by the explain.
 *
 * This is the primary representation for both calculation and explain. It supersedes the
 * fixed-pipeline model in `categorizationAST.ts` (FILTER / INTERSECTION / THRESHOLD_GT /
 * THRESHOLD_LT reconstructed from a `TraceEvent` stream), which is removed at the engine swap.
 */

import { RuleCondition } from '../hourCategorization';
import { CategoryInput, TraceScope } from './categorizationTrace';

/**
 * Per-stage result annotation. Empty (`[]` / `0`) on the plan produced by `buildRuleAst`;
 * populated by `executeRuleAst`.
 */
export interface StageResult {
    resultIntervals: [string, string][];
    resultHours: number;
}

/** Chain seed: the day's worked intervals, before any condition is applied. */
export interface BaseNode {
    kind: 'BASE';
}

/**
 * A filter condition applied to its `input`:
 * - `INTERSECT` — keep the overlap with the condition's interval set (`TIME_RANGE` EQUALS).
 * - `DIFFERENCE` — keep what falls outside it (`TIME_RANGE` NOT_EQUALS).
 * - `GATE` — all-or-nothing day match (`DAY_LIST` / `DAY_TYPE`): input passes through unchanged
 *   when the day matches, otherwise becomes empty.
 * - `EMPTY` — always yields `[]`. Used for a malformed condition the legacy engine resolved to
 *   `[]` (e.g. `field=TIME_RANGE` with a non-`TIME_RANGE` valueType), preserving that parity.
 */
export interface FilterNode extends StageResult {
    kind: 'FILTER';
    op: 'INTERSECT' | 'DIFFERENCE' | 'GATE' | 'EMPTY';
    condition: RuleCondition;
    input: StageNode;
}

/** `GREATER_THAN` threshold: `trimFromStart(input, thresholdMs)` — keeps the excess past the threshold. */
export interface ThresholdGtNode extends StageResult {
    kind: 'THRESHOLD_GT';
    condition: RuleCondition;
    /** Resolved by `executeRuleAst` (a `SCHEDULED_HOURS` threshold depends on the day summary). */
    thresholdMs: number;
    input: StageNode;
}

/** `LESS_THAN` threshold: `keepFromStart(input, thresholdMs)` — keeps only up to the threshold. */
export interface ThresholdLtNode extends StageResult {
    kind: 'THRESHOLD_LT';
    condition: RuleCondition;
    /** Resolved by `executeRuleAst` (a `SCHEDULED_HOURS` threshold depends on the day summary). */
    thresholdMs: number;
    input: StageNode;
}

/** Any node that can appear as the `input` of another stage. */
export type StageNode = BaseNode | FilterNode | ThresholdGtNode | ThresholdLtNode;

/** Root of a single rule's chain: points to the last stage applied (`BaseNode` for an empty rule). */
export interface RuleAstNode extends StageResult {
    kind: 'RULE';
    ruleId: number;
    child: StageNode;
}

// ── Explain wrappers: the `RUN → CATEGORY → RULE` tree returned by the explain endpoint ──
// (and serialized to the prod log-trace). Built directly from the executed rule ASTs — no
// reconstruction from a TraceEvent stream. `TraceScope`/`CategoryInput` are reused as-is.

export interface CategoryNode {
    type: 'CATEGORY';
    scope: TraceScope;
    input: CategoryInput;
    totalHours: number;
    mergedIntervals: [string, string][];
    rules: RuleAstNode[];
}

export interface CategorySkippedNode {
    type: 'CATEGORY_SKIPPED';
    scope: TraceScope;
    reason: 'NO_ACTIVITY';
}

export interface RunNode {
    type: 'RUN';
    scope: TraceScope;
    startedAt: string;
    completedAt: string;
    children: (CategoryNode | CategorySkippedNode)[];
}
