export function timelineTickSpacing(span) {
  if (span < 15) return { major: 1, minor: 0.2 };
  if (span < 30) return { major: 2, minor: 0.4 };
  if (span < 60) return { major: 5, minor: 1 };
  return { major: 10, minor: 2 };
}

export function timelineVisibleRange({ scroll, viewWidth, headerWidth, pixelsPerSecond }) {
  const start = Math.max(0, scroll) / pixelsPerSecond;
  return {
    start,
    end: start + (viewWidth - headerWidth) / pixelsPerSecond,
    scroll,
    viewW: viewWidth,
  };
}

export function timelineLoopRangeStyle(loop, pixelsPerSecond) {
  const start = Number(loop?.start);
  const end = Number(loop?.end);
  const scale = Number(pixelsPerSecond);
  const visible = Boolean(
    loop?.enabled
    && Number.isFinite(start)
    && Number.isFinite(end)
    && end > start
    && Number.isFinite(scale)
    && scale > 0
  );
  return {
    visible,
    left: visible ? start * scale : 0,
    width: visible ? (end - start) * scale : 0,
  };
}

export function createTimelineCanvasRenderer({
  viewport, content, rulerCanvas, waveCanvas, zoomInput,
  getDuration, getLoop, getWaveform, getPeakForTimeRange, formatTime,
  getDevicePixelRatio = () => window.devicePixelRatio,
  headerWidth = 140,
  onMetrics = () => {},
}) {
  let pixelsPerSecond = 4;
  let zoom = 1;
  const visibleRange = () => timelineVisibleRange({
    scroll: viewport.scrollLeft,
    viewWidth: viewport.clientWidth,
    headerWidth,
    pixelsPerSecond,
  });

  function alignRulerToViewport() {
    const width = Math.max(1, viewport.clientWidth - headerWidth);
    rulerCanvas.style.width = `${width}px`;
    // The ruler lives inside the horizontally scrolling timeline content.
    // Counter the content scroll so its viewport-sized drawing surface remains
    // over the visible time range rather than disappearing to the left.
    rulerCanvas.style.transform = `translateX(${Math.max(0, viewport.scrollLeft)}px)`;
    return width;
  }

  function layout() {
    zoom = Number.parseFloat(zoomInput.value);
    const viewWidth = viewport.clientWidth;
    const duration = getDuration();
    pixelsPerSecond = ((viewWidth - headerWidth) / Math.max(1e-6, duration)) * zoom;
    content.style.width = `${Math.max(viewWidth, duration * pixelsPerSecond + headerWidth)}px`;
    const dpr = Math.min(2, getDevicePixelRatio());
    const rulerWidth = alignRulerToViewport();
    rulerCanvas.width = Math.floor(rulerWidth * dpr);
    rulerCanvas.height = Math.floor(28 * dpr);
    const waveContainer = waveCanvas.parentElement;
    if (waveContainer) {
      const width = waveContainer.clientWidth || viewWidth;
      const height = waveContainer.clientHeight || 70;
      waveCanvas.width = Math.floor(width * dpr);
      waveCanvas.height = Math.floor(height * dpr);
      waveCanvas.style.width = `${width}px`;
      waveCanvas.style.height = `${height}px`;
    }
    onMetrics({ pixelsPerSecond, zoom });
  }

  function drawRuler() {
    const width = alignRulerToViewport();
    const context = rulerCanvas.getContext('2d');
    const dpr = rulerCanvas.width / width;
    context.setTransform(dpr, 0, 0, dpr, 0, 0);
    context.clearRect(0, 0, width, 28);
    context.fillStyle = 'rgba(45,45,50,1)';
    context.fillRect(0, 0, width, 28);
    const { start, end } = visibleRange();
    const loop = getLoop();
    if (loop.enabled) {
      const startX = (loop.start - start) * pixelsPerSecond;
      const endX = (loop.end - start) * pixelsPerSecond;
      context.fillStyle = 'rgba(255,150,80,0.15)';
      context.fillRect(startX, 0, endX - startX, 28);
      context.strokeStyle = 'rgba(255,150,80,0.6)';
      context.lineWidth = 2;
      context.beginPath();
      context.moveTo(startX, 0);
      context.lineTo(startX, 28);
      context.moveTo(endX, 0);
      context.lineTo(endX, 28);
      context.stroke();
      context.lineWidth = 1;
    }
    const { major, minor } = timelineTickSpacing(end - start);
    context.strokeStyle = 'rgba(255,255,255,0.18)';
    context.fillStyle = 'rgba(255,255,255,0.65)';
    context.font = '11px monospace';
    const first = Math.floor(start / major) * major;
    for (let time = first; time <= end + major; time += minor) {
      const x = (time - start) * pixelsPerSecond;
      const isMajor = Math.abs(time / major - Math.round(time / major)) < 1e-6;
      context.beginPath();
      context.moveTo(x, 28);
      context.lineTo(x, 28 - (isMajor ? 14 : 7));
      context.stroke();
      if (isMajor) context.fillText(formatTime(time), x + 3, 12);
    }
    if (loop.enabled) {
      const startX = (loop.start - start) * pixelsPerSecond;
      const endX = (loop.end - start) * pixelsPerSecond;
      context.fillStyle = 'rgba(255,150,80,0.9)';
      context.beginPath();
      context.moveTo(startX, 0);
      context.lineTo(startX + 10, 0);
      context.lineTo(startX, 10);
      context.closePath();
      context.fill();
      context.beginPath();
      context.moveTo(endX, 0);
      context.lineTo(endX - 10, 0);
      context.lineTo(endX, 10);
      context.closePath();
      context.fill();
    }
  }

  function drawWave() {
    const context = waveCanvas.getContext('2d');
    const width = waveCanvas.clientWidth || waveCanvas.width;
    const height = waveCanvas.clientHeight || 70;
    if (!width || !height) return;
    const dpr = waveCanvas.width / width;
    context.setTransform(dpr, 0, 0, dpr, 0, 0);
    context.clearRect(0, 0, width, height);
    context.fillStyle = 'rgba(0,0,0,0.35)';
    context.fillRect(0, 0, width, height);
    const { start } = visibleRange();
    const middle = height * 0.5;
    const loop = getLoop();
    if (loop.enabled) {
      context.fillStyle = 'rgba(255,150,80,0.08)';
      context.fillRect(
        (loop.start - start) * pixelsPerSecond,
        0,
        (loop.end - loop.start) * pixelsPerSecond,
        height,
      );
    }
    context.strokeStyle = 'rgba(255,255,255,0.12)';
    context.beginPath();
    context.moveTo(0, middle);
    context.lineTo(width, middle);
    context.stroke();
    const waveform = getWaveform();
    if (!waveform.peaks && !waveform.rawSamples) {
      context.fillStyle = 'rgba(255,255,255,0.25)';
      context.font = '11px ui-monospace, monospace';
      context.textAlign = 'center';
      context.fillText(waveform.hasAudio ? 'Loading waveform...' : 'Double click to add', width / 2, middle + 4);
      context.textAlign = 'left';
      return;
    }
    context.strokeStyle = 'rgba(120,180,255,0.85)';
    for (let x = 0; x < width; x += 1) {
      const time = start + x / pixelsPerSecond;
      const amplitude = waveform.rawSamples
        ? getPeakForTimeRange(time, time + 1 / pixelsPerSecond)
        : waveform.peaks[Math.floor(Math.min(0.999999, Math.max(0, time / waveform.duration)) * waveform.peaks.length)] || 0;
      const y = amplitude * (height * 0.44);
      context.beginPath();
      context.moveTo(x + 0.5, middle - y);
      context.lineTo(x + 0.5, middle + y);
      context.stroke();
    }
  }

  return {
    layout, drawRuler, drawWave,
    drawAll() { drawRuler(); drawWave(); },
    visibleRange,
    timeToX: time => time * pixelsPerSecond,
    xToTime: x => x / pixelsPerSecond,
    getPixelsPerSecond: () => pixelsPerSecond,
    getZoom: () => zoom,
  };
}
