package sources

import (
	"context"
	"io"
	"os"

	"github.com/salvacybersec/keyhunter/pkg/types"
)

// StdinSource reads content from an io.Reader (defaults to os.Stdin) and
// emits overlapping chunks. Used when a user runs `keyhunter scan stdin`
// or `keyhunter scan -`.
type StdinSource struct {
	Reader    io.Reader
	ChunkSize int
}

// NewStdinSource returns a StdinSource bound to os.Stdin.
func NewStdinSource() *StdinSource {
	return &StdinSource{Reader: os.Stdin, ChunkSize: defaultChunkSize}
}

// NewStdinSourceFrom returns a StdinSource bound to the given reader
// (used primarily by tests).
func NewStdinSourceFrom(r io.Reader) *StdinSource {
	return &StdinSource{Reader: r, ChunkSize: defaultChunkSize}
}

// Chunks reads the entire input, then emits it as overlapping chunks.
func (s *StdinSource) Chunks(ctx context.Context, out chan<- types.Chunk) error {
	if s.Reader == nil {
		s.Reader = os.Stdin
	}
	data, err := io.ReadAll(s.Reader)
	if err != nil {
		return err
	}
	if len(data) == 0 {
		return nil
	}
	return emitByteChunks(ctx, data, "stdin", s.ChunkSize, out)
}

// emitByteChunks emits overlapping chunks from an in-memory byte slice.
// Local helper for stdin/url/clipboard sources; mirrors the chunk-windowing
// logic in file.go so this plan does not depend on sibling plans' helpers.
func emitByteChunks(ctx context.Context, data []byte, source string, size int, out chan<- types.Chunk) error {
	if size <= 0 {
		size = defaultChunkSize
	}
	if len(data) == 0 {
		return nil
	}
	if len(data) <= size {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case out <- types.Chunk{Data: data, Source: source, Offset: 0}:
		}
		return nil
	}
	var offset int64
	for start := 0; start < len(data); start += size - chunkOverlap {
		end := start + size
		if end > len(data) {
			end = len(data)
		}
		chunk := types.Chunk{
			Data:   data[start:end],
			Source: source,
			Offset: offset,
		}
		select {
		case <-ctx.Done():
			return ctx.Err()
		case out <- chunk:
		}
		offset += int64(end - start)
		if end == len(data) {
			break
		}
	}
	return nil
}
