package audio

import (
	"encoding/binary"
	"fmt"
	"io"
	"os"
)

// WAVSource loads a PCM WAV file into memory and provides frame-by-frame access.
type WAVSource struct {
	frames     []Frame
	index      int
	SampleRate int
	Channels   int
}

// LoadWAVSource reads and decodes a WAV file. It properly scans for the "fmt "
// and "data" chunks, handling files with extra metadata chunks (LIST, INFO,
// bext, etc.) that appear between headers.
func LoadWAVSource(path string) (*WAVSource, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	// Read RIFF header (12 bytes)
	riffHeader := make([]byte, 12)
	if _, err := io.ReadFull(f, riffHeader); err != nil {
		return nil, fmt.Errorf("read riff header: %w", err)
	}
	if string(riffHeader[0:4]) != "RIFF" || string(riffHeader[8:12]) != "WAVE" {
		return nil, fmt.Errorf("not a RIFF/WAVE file")
	}

	var (
		audioFormat   uint16
		channels      uint16
		sampleRate    uint32
		bitsPerSample uint16
		dataBytes     []byte
		fmtFound      bool
		dataFound     bool
	)

	// Scan chunks
	for {
		var chunkID [4]byte
		var chunkSize uint32
		if _, err := io.ReadFull(f, chunkID[:]); err != nil {
			if err == io.EOF || err == io.ErrUnexpectedEOF {
				break
			}
			return nil, fmt.Errorf("read chunk id: %w", err)
		}
		if err := binary.Read(f, binary.LittleEndian, &chunkSize); err != nil {
			return nil, fmt.Errorf("read chunk size: %w", err)
		}

		switch string(chunkID[:]) {
		case "fmt ":
			if chunkSize < 16 {
				return nil, fmt.Errorf("fmt chunk too small: %d", chunkSize)
			}
			fmtData := make([]byte, chunkSize)
			if _, err := io.ReadFull(f, fmtData); err != nil {
				return nil, fmt.Errorf("read fmt chunk: %w", err)
			}
			audioFormat = binary.LittleEndian.Uint16(fmtData[0:2])
			channels = binary.LittleEndian.Uint16(fmtData[2:4])
			sampleRate = binary.LittleEndian.Uint32(fmtData[4:8])
			bitsPerSample = binary.LittleEndian.Uint16(fmtData[14:16])
			fmtFound = true

		case "data":
			dataBytes = make([]byte, chunkSize)
			if _, err := io.ReadFull(f, dataBytes); err != nil {
				return nil, fmt.Errorf("read data chunk: %w", err)
			}
			dataFound = true

		default:
			// Skip unknown chunks, respecting RIFF padding (chunks are word-aligned)
			skip := int64(chunkSize)
			if chunkSize%2 != 0 {
				skip++
			}
			if _, err := io.CopyN(io.Discard, f, skip); err != nil {
				// Could be EOF if this is the last chunk
				break
			}
		}

		if fmtFound && dataFound {
			break
		}
	}

	if !fmtFound {
		return nil, fmt.Errorf("no fmt chunk found")
	}
	if !dataFound {
		return nil, fmt.Errorf("no data chunk found")
	}
	if audioFormat != 1 {
		return nil, fmt.Errorf("only PCM wav supported (format=%d)", audioFormat)
	}
	if bitsPerSample != 16 {
		return nil, fmt.Errorf("only 16-bit PCM wav supported (bits=%d)", bitsPerSample)
	}
	if channels != 1 && channels != 2 {
		return nil, fmt.Errorf("only mono/stereo wav supported (channels=%d)", channels)
	}
	if sampleRate == 0 {
		return nil, fmt.Errorf("invalid wav sample rate")
	}

	step := int(channels) * 2
	frames := make([]Frame, 0, len(dataBytes)/step)
	for i := 0; i+step <= len(dataBytes); i += step {
		l := pcm16ToSample(int16(binary.LittleEndian.Uint16(dataBytes[i : i+2])))
		r := l
		if channels == 2 {
			r = pcm16ToSample(int16(binary.LittleEndian.Uint16(dataBytes[i+2 : i+4])))
		}
		frames = append(frames, NewFrame(l, r))
	}

	return &WAVSource{
		frames:     frames,
		SampleRate: int(sampleRate),
		Channels:   int(channels),
	}, nil
}

// NextFrame returns the next audio frame, looping at the end.
func (s *WAVSource) NextFrame() Frame {
	if len(s.frames) == 0 {
		return NewFrame(0, 0)
	}
	frame := s.frames[s.index]
	s.index++
	if s.index >= len(s.frames) {
		s.index = 0
	}
	return frame
}

func pcm16ToSample(v int16) Sample {
	return Sample(float64(v) / 32768.0).Clamp()
}
