package solutions.tretter.mindmachine.session

import solutions.tretter.mindmachine.domain.SessionPreset
import solutions.tretter.mindmachine.domain.VisualPattern

object TimelineProgramFactory {
    fun fromPreset(preset: SessionPreset): TimelineEditorState {
        return builtInTimelineForPresetId(preset.id) ?: constantTimelineFromPreset(preset)
    }

    fun builtInTimelineForPresetId(id: String): TimelineEditorState? = builtInPrograms[id]

    fun toPreset(
        id: String,
        name: String,
        description: String,
        timeline: TimelineEditorState,
        sortOrder: Int,
        isUserProgram: Boolean,
    ): SessionPreset {
        val t0 = 0f
        val flashMs = ParameterMapping.logMap01(
            timeline.visualLeft.valueAt(t0),
            ParameterRanges.FLASH_INTERVAL_MS_MIN,
            ParameterRanges.FLASH_INTERVAL_MS_MAX,
        ).toInt().coerceIn(ParameterRanges.FLASH_INTERVAL_MS_MIN.toInt(), ParameterRanges.FLASH_INTERVAL_MS_MAX.toInt())
        val carrierHz = ParameterMapping.logMap01(
            timeline.audioLeft.valueAt(t0),
            ParameterRanges.CARRIER_HZ_MIN,
            ParameterRanges.CARRIER_HZ_MAX,
        )
        val binauralHz = ParameterMapping.logMap01(
            timeline.audioRight.valueAt(t0),
            ParameterRanges.BINAURAL_HZ_MIN,
            ParameterRanges.BINAURAL_HZ_MAX,
        )

        return SessionPreset(
            id = id,
            name = name,
            description = description,
            defaultDurationSec = timeline.durationSec,
            visualPatternType = VisualPattern.FLASH,
            flashIntervalMs = flashMs,
            intensityPercent = 60,
            carrierFrequencyHz = carrierHz,
            binauralDifferenceHz = binauralHz,
            cautionNote = "Stop if discomfort occurs.",
            sortOrder = sortOrder,
            isUserProgram = isUserProgram,
            flashSequence = timeline.flashSequence,
        )
    }

    private fun constantTimelineFromPreset(preset: SessionPreset): TimelineEditorState {
        val dur = preset.defaultDurationSec.coerceIn(60, 8 * 60 * 60)

        val flash01 = ParameterMapping.invLog01(
            value = preset.flashIntervalMs.toFloat(),
            min = ParameterRanges.FLASH_INTERVAL_MS_MIN,
            max = ParameterRanges.FLASH_INTERVAL_MS_MAX,
        )
        val blank01 = flash01
        val carrier01 = ParameterMapping.invLog01(
            value = preset.carrierFrequencyHz,
            min = ParameterRanges.CARRIER_HZ_MIN,
            max = ParameterRanges.CARRIER_HZ_MAX,
        )
        val binaural01 = ParameterMapping.invLog01(
            value = preset.binauralDifferenceHz,
            min = ParameterRanges.BINAURAL_HZ_MIN,
            max = ParameterRanges.BINAURAL_HZ_MAX,
        )

        return TimelineEditorState(
            durationSec = dur,
            curveGranularitySec = TIMELINE_DEFAULT_GRANULARITY_SEC,
            flashSequence = preset.flashSequence,
            selection = TimelineSelection(0, dur),
            viewport = TimelineViewport(startSec = 0f, secondsPerScreen = minOf(10f * 60f, dur.toFloat())),
            activeCurve = TimelineEditorState.ActiveCurve.VISUAL_LEFT,
            visualLeft = TimelineCurve.constant(flash01, dur),
            visualRight = TimelineCurve.constant(blank01, dur),
            audioLeft = TimelineCurve.constant(carrier01, dur),
            audioRight = TimelineCurve.constant(binaural01, dur),
            isPlaying = false,
            playheadSec = 0f,
        )
    }

    private fun timeline(
        durationSec: Int,
        visualLeftMs: List<Int>,
        visualRightMs: List<Int>,
        audioLeftHz: List<Float>,
        audioRightHz: List<Float>,
    ): TimelineEditorState {
        val minutes = durationSec / 60
        require(visualLeftMs.size == minutes + 1)
        require(visualRightMs.size == minutes + 1)
        require(audioLeftHz.size == minutes + 1)
        require(audioRightHz.size == minutes + 1)

        fun curveFromMs(values: List<Int>): TimelineCurve = TimelineCurve(
            values.mapIndexed { index, value ->
                TimelinePoint(
                    tSec = index * 60,
                    value01 = ParameterMapping.invLog01(
                        value = value.toFloat(),
                        min = ParameterRanges.FLASH_INTERVAL_MS_MIN,
                        max = ParameterRanges.FLASH_INTERVAL_MS_MAX,
                    )
                )
            }
        )

        fun curveFromHz(values: List<Float>, min: Float, max: Float): TimelineCurve = TimelineCurve(
            values.mapIndexed { index, value ->
                TimelinePoint(
                    tSec = index * 60,
                    value01 = ParameterMapping.invLog01(value, min, max)
                )
            }
        )

        return TimelineEditorState(
            durationSec = durationSec,
            curveGranularitySec = TIMELINE_DEFAULT_GRANULARITY_SEC,
            flashSequence = defaultFlashSequence(),
            selection = TimelineSelection(0, durationSec),
            viewport = TimelineViewport(startSec = 0f, secondsPerScreen = minOf(10f * 60f, durationSec.toFloat())),
            activeCurve = TimelineEditorState.ActiveCurve.VISUAL_LEFT,
            visualLeft = curveFromMs(visualLeftMs),
            visualRight = curveFromMs(visualRightMs),
            audioLeft = curveFromHz(audioLeftHz, ParameterRanges.CARRIER_HZ_MIN, ParameterRanges.CARRIER_HZ_MAX),
            audioRight = curveFromHz(audioRightHz, ParameterRanges.BINAURAL_HZ_MIN, ParameterRanges.BINAURAL_HZ_MAX),
            isPlaying = false,
            playheadSec = 0f,
        )
    }

    private val builtInPrograms: Map<String, TimelineEditorState> = mapOf(
        "deep_release" to timeline(
            durationSec = 18 * 60,
            visualLeftMs = listOf(320, 300, 280, 260, 250, 240, 230, 220, 210, 200, 210, 220, 240, 260, 280, 300, 320, 340, 360),
            visualRightMs = listOf(360, 340, 320, 300, 280, 270, 260, 250, 240, 230, 240, 250, 270, 290, 310, 330, 350, 370, 390),
            audioLeftHz = listOf(190f, 188f, 186f, 184f, 182f, 180f, 178f, 176f, 174f, 172f, 170f, 168f, 166f, 164f, 162f, 160f, 158f, 156f, 154f),
            audioRightHz = listOf(7f, 6.6f, 6.2f, 5.8f, 5.4f, 5f, 4.8f, 4.6f, 4.4f, 4.2f, 4f, 3.8f, 3.6f, 3.4f, 3.2f, 3f, 2.8f, 2.6f, 2.4f),
        ),
        "laser_focus" to timeline(
            durationSec = 16 * 60,
            visualLeftMs = listOf(180, 170, 160, 150, 140, 130, 120, 110, 100, 95, 95, 95, 100, 105, 110, 115, 120),
            visualRightMs = listOf(200, 190, 180, 170, 160, 150, 140, 130, 120, 115, 115, 115, 120, 125, 130, 135, 140),
            audioLeftHz = listOf(205f, 208f, 212f, 216f, 220f, 224f, 228f, 232f, 236f, 240f, 242f, 244f, 246f, 248f, 250f, 248f, 245f),
            audioRightHz = listOf(8f, 8.5f, 9f, 9.5f, 10f, 10.5f, 11f, 11.5f, 12f, 12f, 11.5f, 11f, 10.5f, 10f, 9.5f, 9f, 8.5f),
        ),
        "creative_drift" to timeline(
            durationSec = 22 * 60,
            visualLeftMs = listOf(220, 200, 180, 160, 180, 210, 240, 270, 250, 220, 190, 170, 190, 220, 250, 280, 260, 230, 200, 180, 200, 230, 260),
            visualRightMs = listOf(260, 240, 220, 200, 220, 250, 280, 310, 290, 260, 230, 210, 230, 260, 290, 320, 300, 270, 240, 220, 240, 270, 300),
            audioLeftHz = listOf(200f, 204f, 208f, 212f, 216f, 220f, 224f, 228f, 224f, 220f, 216f, 212f, 208f, 204f, 200f, 196f, 192f, 196f, 200f, 204f, 208f, 212f, 216f),
            audioRightHz = listOf(6f, 6.8f, 7.6f, 8.4f, 9.2f, 10f, 9.4f, 8.8f, 8.2f, 7.6f, 7f, 6.4f, 5.8f, 5.2f, 4.6f, 4f, 4.8f, 5.6f, 6.4f, 7.2f, 8f, 8.8f, 9.6f),
        ),
        "reset_breath" to timeline(
            durationSec = 12 * 60,
            visualLeftMs = listOf(360, 340, 320, 300, 280, 260, 240, 260, 280, 300, 320, 340, 360),
            visualRightMs = listOf(390, 370, 350, 330, 310, 290, 270, 290, 310, 330, 350, 370, 390),
            audioLeftHz = listOf(175f, 173f, 171f, 169f, 167f, 165f, 163f, 165f, 167f, 169f, 171f, 173f, 175f),
            audioRightHz = listOf(6.5f, 6.2f, 5.9f, 5.6f, 5.3f, 5f, 4.7f, 5f, 5.3f, 5.6f, 5.9f, 6.2f, 6.5f),
        ),
        "night_rampdown" to timeline(
            durationSec = 28 * 60,
            visualLeftMs = listOf(300, 290, 280, 270, 260, 250, 240, 230, 220, 210, 205, 200, 205, 210, 220, 230, 240, 250, 260, 270, 280, 290, 300, 320, 340, 360, 380, 400, 420),
            visualRightMs = listOf(330, 320, 310, 300, 290, 280, 270, 260, 250, 240, 235, 230, 235, 240, 250, 260, 270, 280, 290, 300, 310, 320, 330, 350, 370, 390, 410, 430, 450),
            audioLeftHz = listOf(182f, 181f, 180f, 179f, 178f, 177f, 176f, 175f, 174f, 173f, 172f, 171f, 170f, 169f, 168f, 167f, 166f, 165f, 164f, 163f, 162f, 161f, 160f, 159f, 158f, 157f, 156f, 155f, 154f),
            audioRightHz = listOf(5f, 4.9f, 4.8f, 4.7f, 4.6f, 4.5f, 4.4f, 4.3f, 4.2f, 4.1f, 4f, 3.9f, 3.8f, 3.7f, 3.6f, 3.5f, 3.4f, 3.3f, 3.2f, 3.1f, 3f, 2.9f, 2.8f, 2.7f, 2.6f, 2.5f, 2.4f, 2.3f, 2.2f),
        ),
        "power_warmup" to timeline(
            durationSec = 9 * 60,
            visualLeftMs = listOf(140, 130, 120, 110, 100, 90, 85, 90, 100, 115),
            visualRightMs = listOf(160, 150, 140, 130, 120, 110, 105, 110, 120, 135),
            audioLeftHz = listOf(220f, 225f, 230f, 235f, 240f, 245f, 250f, 248f, 244f, 238f),
            audioRightHz = listOf(10f, 11f, 12f, 13f, 14f, 15f, 14f, 13f, 12f, 11f),
        ),
        "body_scan" to timeline(
            durationSec = 20 * 60,
            visualLeftMs = listOf(260, 250, 240, 230, 220, 210, 200, 190, 200, 210, 220, 230, 240, 250, 260, 250, 240, 230, 220, 210, 200),
            visualRightMs = listOf(290, 280, 270, 260, 250, 240, 230, 220, 230, 240, 250, 260, 270, 280, 290, 280, 270, 260, 250, 240, 230),
            audioLeftHz = listOf(195f, 194f, 193f, 192f, 191f, 190f, 189f, 188f, 187f, 186f, 185f, 186f, 187f, 188f, 189f, 190f, 191f, 192f, 193f, 194f, 195f),
            audioRightHz = listOf(7.2f, 7f, 6.8f, 6.6f, 6.4f, 6.2f, 6f, 5.8f, 5.6f, 5.4f, 5.2f, 5.4f, 5.6f, 5.8f, 6f, 6.2f, 6.4f, 6.6f, 6.8f, 7f, 7.2f),
        ),
        "storm_to_still" to timeline(
            durationSec = 24 * 60,
            visualLeftMs = listOf(140, 130, 120, 110, 100, 95, 90, 100, 120, 150, 180, 210, 240, 270, 300, 330, 340, 350, 360, 370, 380, 390, 400, 410, 420),
            visualRightMs = listOf(170, 160, 150, 140, 130, 125, 120, 130, 150, 180, 210, 240, 270, 300, 330, 360, 370, 380, 390, 400, 410, 420, 430, 440, 450),
            audioLeftHz = listOf(230f, 232f, 234f, 236f, 238f, 240f, 242f, 238f, 232f, 226f, 220f, 214f, 208f, 202f, 196f, 190f, 186f, 182f, 178f, 174f, 170f, 166f, 162f, 158f, 154f),
            audioRightHz = listOf(12f, 12.5f, 13f, 13.5f, 14f, 14.5f, 15f, 14f, 13f, 12f, 11f, 10f, 9f, 8f, 7f, 6f, 5.5f, 5f, 4.5f, 4f, 3.6f, 3.2f, 2.9f, 2.6f, 2.4f),
        ),
    )
}
