/**
 * Phase 2 — executes a rule's linear-chain AST against a single day. Walks the chain from the
 * base, applying each stage's transform **in order**, and annotates every node with its result
 * (`resultIntervals` / `resultHours`, and the resolved `thresholdMs` for thresholds) for the explain.
 *
 * Transforms:
 * - `THRESHOLD_GT` → `trimFromStart` (keeps the excess past the threshold).
 * - `THRESHOLD_LT` → `keepFromStart` (keeps only up to the threshold).
 * - `FILTER` `INTERSECT` / `DIFFERENCE` → intersect / subtract the `TIME_RANGE` slot.
 * - `FILTER` `GATE` → pass the input through, or empty it, by the `DAY_LIST` / `DAY_TYPE` predicate.
 *
 * Thresholds compose per stage (two GTs trim additively): this is the literal-order semantics.
 *
 * `annotate` controls whether each node is decorated with its `resultIntervals`/`resultHours`
 * (and `thresholdMs`) for the explain/log-trace. The calculation hot path passes `false` — it
 * only needs the final `intervals`, so it skips the per-node `intervalsToTuples`/`totalDurationInHours`.
 */

import { Interval } from 'luxon';
import { TimeTrackingDaySummary } from '../models/daySummary';
import { RuleAstNode, StageNode } from '../models/timeTracking/ruleAst';
import { resolveThresholdDuration, timeRangeConditionSlot, workedHoursFilterPasses } from './conditionEvaluators';
import { differenceIntervalSets, intersectIntervalSets, keepFromStart, totalDurationInHours, trimFromStart } from './timeUtils';
import { intervalsToTuples } from './traceBuilder';

export function executeRuleAst(
    rule: RuleAstNode,
    daySummary: TimeTrackingDaySummary,
    workedIntervals: Interval[],
    timezone: string,
    annotate = false,
): { intervals: Interval[]; annotatedNode: RuleAstNode } {
    // A rule with no stages (no conditions, or only a skipped EQUALS/NOT_EQUALS threshold) matches
    // nothing — mirrors the legacy engine (`filterResult ?? [] === []`), not the raw worked intervals.
    if (rule.child.kind === 'BASE') {
        return { intervals: [], annotatedNode: rule };
    }

    const intervals = evalStage(rule.child, daySummary, workedIntervals, timezone, annotate);
    if (annotate) {
        rule.resultIntervals = intervalsToTuples(intervals, timezone);
        rule.resultHours = totalDurationInHours(intervals);
    }
    return { intervals, annotatedNode: rule };
}

function evalStage(
    stage: StageNode,
    daySummary: TimeTrackingDaySummary,
    workedIntervals: Interval[],
    timezone: string,
    annotate: boolean,
): Interval[] {
    if (stage.kind === 'BASE') return workedIntervals;

    const input = evalStage(stage.input, daySummary, workedIntervals, timezone, annotate);
    let result: Interval[];

    switch (stage.kind) {
        case 'FILTER':
            if (stage.op === 'GATE') {
                result = workedHoursFilterPasses(stage.condition, daySummary) ? input : [];
            } else if (stage.op === 'EMPTY') {
                result = [];
            } else {
                const slot = timeRangeConditionSlot(stage.condition, daySummary, timezone);
                result = stage.op === 'INTERSECT' ? intersectIntervalSets(input, slot) : differenceIntervalSets(input, slot);
            }
            break;

        case 'THRESHOLD_GT': {
            const duration = resolveThresholdDuration(stage.condition, daySummary);
            if (annotate) stage.thresholdMs = duration.as('milliseconds');
            result = trimFromStart(input, duration);
            break;
        }

        case 'THRESHOLD_LT': {
            const duration = resolveThresholdDuration(stage.condition, daySummary);
            if (annotate) stage.thresholdMs = duration.as('milliseconds');
            result = keepFromStart(input, duration);
            break;
        }

        default: {
            const exhaustive: never = stage;
            throw new Error(`Unknown rule AST stage: ${JSON.stringify(exhaustive)}`);
        }
    }

    if (annotate) {
        stage.resultIntervals = intervalsToTuples(result, timezone);
        stage.resultHours = totalDurationInHours(result);
    }
    return result;
}
