WITH expected AS (
  SELECT 'ULC1_1' AS Name
  UNION ALL SELECT 'ULC2_1'
  UNION ALL SELECT 'ULC3_1'
  UNION ALL SELECT 'ULC4_1'
  UNION ALL SELECT 'ULC5_1'
  UNION ALL SELECT 'ULC6_1'
  UNION ALL SELECT 'ULC7_1'
  UNION ALL SELECT 'ULC8_1'
),

/* Build event stream with previous AND next values - single scan */
ev AS (
  SELECT
    t.Name,
    t.t_stamp,
    t.Dumping,
    t.Tipper_Faulted,
    LAG(t.Dumping) OVER (PARTITION BY t.Name ORDER BY t.t_stamp) AS prev_dumping,
    LAG(t.Tipper_Faulted) OVER (PARTITION BY t.Name ORDER BY t.t_stamp) AS prev_faulted,
    LEAD(t.t_stamp) OVER (PARTITION BY t.Name ORDER BY t.t_stamp) AS next_stamp,
    LEAD(t.Dumping) OVER (PARTITION BY t.Name ORDER BY t.t_stamp) AS next_dumping,
    LEAD(t.Tipper_Faulted) OVER (PARTITION BY t.Name ORDER BY t.t_stamp) AS next_faulted
  FROM tipper_status t
  JOIN expected e ON e.Name = t.Name
  WHERE t.t_stamp BETWEEN DATE_SUB(:startTime, INTERVAL 1 DAY) AND DATE_ADD(:endTime, INTERVAL 1 DAY)
),

/* ----- CYCLES: Direct pairing using LEAD ----- */
dump_cycles_raw AS (
  SELECT
    Name,
    t_stamp AS start_time,
    -- Find next transition to 0
    (SELECT MIN(ev2.t_stamp)
     FROM ev ev2
     WHERE ev2.Name = ev.Name 
       AND ev2.t_stamp > ev.t_stamp
       AND ev2.Dumping = 0 
       AND IFNULL(ev2.prev_dumping,1) = 1
    ) AS end_time_raw
  FROM ev
  WHERE Dumping = 1 
    AND IFNULL(prev_dumping,0) = 0
    AND t_stamp BETWEEN :startTime AND :endTime
),

dump_cycles_clip AS (
  SELECT
    Name,
    GREATEST(start_time, :startTime) AS start_time,
    LEAST(COALESCE(end_time_raw, :endTime), :endTime) AS end_time,
    TIMESTAMPDIFF(SECOND,
      GREATEST(start_time, :startTime),
      LEAST(COALESCE(end_time_raw, :endTime), :endTime)) AS seconds_in_cycle
  FROM dump_cycles_raw
  WHERE LEAST(COALESCE(end_time_raw, :endTime), :endTime) > GREATEST(start_time, :startTime)
),

cycle_rollup AS (
  SELECT
    Name,
    COUNT(*) AS cycle_count,
    ROUND(SUM(seconds_in_cycle)/60, 2) AS total_cycle_min,
    ROUND(AVG(NULLIF(seconds_in_cycle,0))/60, 2) AS avg_cycle_min
  FROM dump_cycles_clip
  GROUP BY Name
),

/* ----- FAULTS: Direct pairing using LEAD ----- */
fault_intervals_raw AS (
  SELECT
    Name,
    t_stamp AS start_time,
    (SELECT MIN(ev2.t_stamp)
     FROM ev ev2
     WHERE ev2.Name = ev.Name 
       AND ev2.t_stamp > ev.t_stamp
       AND ev2.Tipper_Faulted = 0 
       AND IFNULL(ev2.prev_faulted,1) = 1
    ) AS end_time_raw
  FROM ev
  WHERE Tipper_Faulted = 1 
    AND IFNULL(prev_faulted,0) = 0
    AND t_stamp BETWEEN :startTime AND :endTime
),

fault_clip AS (
  SELECT
    Name,
    GREATEST(start_time, :startTime) AS start_time,
    LEAST(COALESCE(end_time_raw, :endTime), :endTime) AS end_time,
    TIMESTAMPDIFF(SECOND,
      GREATEST(start_time, :startTime),
      LEAST(COALESCE(end_time_raw, :endTime), :endTime)) AS fault_seconds
  FROM fault_intervals_raw
  WHERE LEAST(COALESCE(end_time_raw, :endTime), :endTime) > GREATEST(start_time, :startTime)
),

fault_rollup AS (
  SELECT
    Name,
    COUNT(*) AS fault_count,
    ROUND(SUM(fault_seconds)/60, 2) AS total_fault_min,
    ROUND(AVG(NULLIF(fault_seconds,0))/60, 2) AS avg_fault_min
  FROM fault_clip
  GROUP BY Name
),

/* ----- SIMPLIFIED PAUSES ----- */
-- Materialize dump edges for pause calculation
dump_edges AS (
  SELECT Name, t_stamp, 'END' as edge_type
  FROM ev
  WHERE Dumping = 0 AND IFNULL(prev_dumping,1) = 1
    AND t_stamp BETWEEN :startTime AND :endTime
  UNION ALL
  SELECT Name, t_stamp, 'START' as edge_type
  FROM ev
  WHERE Dumping = 1 AND IFNULL(prev_dumping,0) = 0
    AND t_stamp BETWEEN :startTime AND :endTime
),

pause_gaps AS (
  SELECT
    de1.Name,
    de1.t_stamp AS gap_start,
    MIN(de2.t_stamp) AS gap_end
  FROM dump_edges de1
  JOIN dump_edges de2 
    ON de2.Name = de1.Name 
   AND de2.t_stamp > de1.t_stamp
   AND de2.edge_type = 'START'
  WHERE de1.edge_type = 'END'
  GROUP BY de1.Name, de1.t_stamp
),

pause_with_faults AS (
  SELECT
    p.Name,
    p.gap_start,
    p.gap_end,
    TIMESTAMPDIFF(SECOND, p.gap_start, p.gap_end) AS gap_seconds,
    COALESCE(SUM(
      GREATEST(0, TIMESTAMPDIFF(SECOND,
        GREATEST(p.gap_start, f.start_time),
        LEAST(p.gap_end, f.end_time)
      ))
    ), 0) AS fault_overlap_seconds
  FROM pause_gaps p
  LEFT JOIN fault_clip f
    ON f.Name = p.Name
   AND f.end_time > p.gap_start
   AND f.start_time < p.gap_end
  GROUP BY p.Name, p.gap_start, p.gap_end
),

pause_rollup AS (
  SELECT
    Name,
    COUNT(*) AS pause_count,
    SUM(GREATEST(gap_seconds - fault_overlap_seconds, 0)) AS pause_seconds
  FROM pause_with_faults
  WHERE gap_seconds > fault_overlap_seconds
  GROUP BY Name
)

SELECT
  :endTime AS `Time Stamp`,
  e.Name AS `Dumper`,
  COALESCE(cr.cycle_count,0) AS `Cycle Count`,
  COALESCE(cr.total_cycle_min,0) AS `Total Cycle Time (Minutes)`,
  COALESCE(cr.avg_cycle_min,0) AS `Average Cycle Time (Minutes)`,
  COALESCE(fr.fault_count,0) AS `Fault Count`,
  COALESCE(fr.total_fault_min,0) AS `Total Fault Time (Minutes)`,
  COALESCE(fr.avg_fault_min,0) AS `Average Fault Time (Minutes)`,
  COALESCE(pr.pause_count,0) AS `Pause Count`,
  ROUND(COALESCE(pr.pause_seconds,0)/60,2) AS `Total Pause Time (Minutes)`,
  ROUND(
    CASE WHEN COALESCE(pr.pause_count,0) > 0
         THEN (COALESCE(pr.pause_seconds,0)/60.0)/pr.pause_count
         ELSE 0 END, 2
  ) AS `Average Pause Time (Minutes)`
FROM expected e
LEFT JOIN cycle_rollup cr ON cr.Name = e.Name
LEFT JOIN fault_rollup fr ON fr.Name = e.Name
LEFT JOIN pause_rollup pr ON pr.Name = e.Name
ORDER BY e.Name;