-- Construct episodes across the complete, globally ordered input history.
-- A gap exactly equal to the configured threshold remains in the same episode.

CREATE OR REPLACE TABLE sequenced_events AS
WITH with_previous AS (
    SELECT
        *,
        lag(event_ts) OVER (
            PARTITION BY vehicle_token
            ORDER BY event_ts, internal_event_id
        ) AS previous_event_ts
    FROM clean_events
),
with_markers AS (
    SELECT
        *,
        CASE
            WHEN previous_event_ts IS NULL THEN 1
            WHEN event_ts - previous_event_ts
                 > (SELECT episode_gap_days FROM build_config)
                   * INTERVAL '1 day'
                THEN 1
            ELSE 0
        END AS starts_episode
    FROM with_previous
),
with_episode_number AS (
    SELECT
        *,
        sum(starts_episode) OVER (
            PARTITION BY vehicle_token
            ORDER BY event_ts, internal_event_id
            ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
        ) AS episode_number
    FROM with_markers
)
SELECT
    *,
    row_number() OVER (
        PARTITION BY vehicle_token, episode_number
        ORDER BY event_ts, internal_event_id
    ) AS attempt_number,
    row_number() OVER (
        PARTITION BY vehicle_token, episode_number
        ORDER BY event_ts DESC, internal_event_id DESC
    ) AS reverse_attempt_number
FROM with_episode_number;

CREATE OR REPLACE TABLE episode_daily_activity AS
SELECT
    vehicle_token,
    episode_number,
    cast(event_ts AS DATE) AS event_date,
    count(*) AS events_in_day
FROM sequenced_events
GROUP BY vehicle_token, episode_number, cast(event_ts AS DATE);

CREATE OR REPLACE TABLE episode_aggregates AS
SELECT
    e.vehicle_token,
    e.vehicle_bucket,
    e.episode_number,
    min(e.event_ts) AS episode_start,
    max(e.event_ts) AS episode_end,
    count(*) AS attempt_count,
    count(e.canonical_outcome) AS labeled_attempt_count,
    coalesce(bool_or(e.canonical_outcome = 'pass'), false) AS eventually_passed,
    max(d.events_in_day) AS max_events_in_day,
    (list(e.observed_make ORDER BY e.event_ts DESC, e.internal_event_id DESC)
        FILTER (WHERE e.observed_make IS NOT NULL))[1]
        AS episode_last_observed_make,
    (list(e.observed_model ORDER BY e.event_ts DESC, e.internal_event_id DESC)
        FILTER (WHERE e.observed_model IS NOT NULL))[1]
        AS episode_last_observed_model,
    (list(e.observed_model_year ORDER BY e.event_ts DESC, e.internal_event_id DESC)
        FILTER (WHERE e.observed_model_year IS NOT NULL))[1]
        AS episode_last_observed_model_year
FROM sequenced_events AS e
JOIN episode_daily_activity AS d
  ON d.vehicle_token = e.vehicle_token
 AND d.episode_number = e.episode_number
 AND d.event_date = cast(e.event_ts AS DATE)
GROUP BY e.vehicle_token, e.vehicle_bucket, e.episode_number;

CREATE OR REPLACE TABLE inspection_episodes AS
SELECT
    a.vehicle_token,
    a.vehicle_bucket,
    a.episode_number,
    a.episode_start,
    a.episode_end,
    f.internal_event_id AS first_internal_event_id,
    f.source_era,
    f.public_county,
    f.canonical_outcome AS first_outcome,
    f.outcome_label_source AS first_outcome_label_source,
    f.program_type AS first_program_type,
    f.test_type AS first_test_type,
    l.canonical_outcome AS final_outcome,
    a.attempt_count,
    a.labeled_attempt_count,
    a.eventually_passed,
    a.max_events_in_day,
    a.episode_last_observed_make,
    a.episode_last_observed_model,
    a.episode_last_observed_model_year
FROM episode_aggregates AS a
JOIN sequenced_events AS f
  ON f.vehicle_token = a.vehicle_token
 AND f.episode_number = a.episode_number
 AND f.attempt_number = 1
JOIN sequenced_events AS l
  ON l.vehicle_token = a.vehicle_token
 AND l.episode_number = a.episode_number
 AND l.reverse_attempt_number = 1;
