export type MonitorEcgPathOptions = {
  segmentWidth: number
  height: number
  beatsPerSegment?: number
  segments?: number
  samplesPerBeat?: number
}

type Point = {
  x: number
  y: number
}

function clamp(value: number, min: number, max: number) {
  return Math.min(Math.max(value, min), max)
}

function beatAmplitude(progress: number) {
  const pWave = 1.2 * Math.exp(-Math.pow((progress - 0.18) / 0.035, 2))
  const qWave = -1.4 * Math.exp(-Math.pow((progress - 0.39) / 0.014, 2))
  const rWave = 7.6 * Math.exp(-Math.pow((progress - 0.43) / 0.01, 2))
  const sWave = -2.8 * Math.exp(-Math.pow((progress - 0.47) / 0.018, 2))
  const tWave = 2.3 * Math.exp(-Math.pow((progress - 0.7) / 0.07, 2))

  return pWave + qWave + rWave + sWave + tWave
}

function toPath(points: Point[]) {
  return points
    .map((point, index) => `${index === 0 ? 'M' : 'L'}${Number(point.x.toFixed(3))},${Number(point.y.toFixed(3))}`)
    .join(' ')
}

export function buildMonitorEcgPath(options: MonitorEcgPathOptions) {
  const segmentWidth = clamp(options.segmentWidth, 1, Number.MAX_SAFE_INTEGER)
  const height = clamp(options.height, 1, Number.MAX_SAFE_INTEGER)
  const beatsPerSegment = clamp(options.beatsPerSegment ?? 2, 1, 12)
  const segments = clamp(options.segments ?? 2, 1, 8)
  const samplesPerBeat = clamp(options.samplesPerBeat ?? 96, 12, 240)
  const baseline = height / 2
  const amplitudeScale = height / 16
  const points: Point[] = []

  for (let segmentIndex = 0; segmentIndex < segments; segmentIndex += 1) {
    for (let beatIndex = 0; beatIndex < beatsPerSegment; beatIndex += 1) {
      const beatStart = segmentIndex * segmentWidth + (beatIndex * segmentWidth) / beatsPerSegment
      const beatWidth = segmentWidth / beatsPerSegment

      for (let sampleIndex = 0; sampleIndex <= samplesPerBeat; sampleIndex += 1) {
        const progress = sampleIndex / samplesPerBeat
        const x = beatStart + beatWidth * progress
        const y = baseline - beatAmplitude(progress) * amplitudeScale

        if (points.length > 0) {
          const previous = points[points.length - 1]
          if (Math.abs(previous.x - x) < 0.0001) {
            points[points.length - 1] = { x, y }
            continue
          }
        }

        points.push({ x, y })
      }
    }
  }

  if (points.length === 0) {
    return `M0,${baseline} L${segmentWidth * segments},${baseline}`
  }

  const last = points[points.length - 1]
  points[points.length - 1] = { x: segmentWidth * segments, y: baseline }

  if (last.x < segmentWidth * segments) {
    points.push({ x: segmentWidth * segments, y: baseline })
  }

  return toPath(points)
}
