// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bufio"
	"io"
)

// NamedScanner is a simple struct to pair a name with a Scanner.
type NamedScanner struct {
	Name    string
	Scanner *bufio.Scanner
}

// NamedReader is a simple struct to pair a name with a Reader,
// which will be converted to a Scanner using bufio.NewScanner.
type NamedReader struct {
	Name   string
	Reader io.Reader
}

// MultiScanner scans over multiple bufio.Scanners as if they were a single stream.
// It also keeps track of the name of the current scanner and the line number.
type MultiScanner struct {
	scanners   []NamedScanner
	scannerIdx int
	line       int
	totalLine  int
	err        error
}

// NewMultiScanner creates a new MultiScanner from slice of NamedScanners.
func NewMultiScanner(scanners []NamedScanner) *MultiScanner {
	return &MultiScanner{
		scanners:   scanners,
		scannerIdx: -1, // Start before the first scanner
	}
}

// MultiScannerFromReaders creates a new MultiScanner from a slice of NamedReaders.
func MultiScannerFromReaders(readers []NamedReader) *MultiScanner {
	var scanners []NamedScanner
	for _, r := range readers {
		scanners = append(scanners, NamedScanner{
			Name:    r.Name,
			Scanner: bufio.NewScanner(r.Reader),
		})
	}
	return NewMultiScanner(scanners)
}

// Scan advances the scanner to the next token, which will then be
// available through the Text method. It returns false when the scan stops,
// either by reaching the end of the input or an error.
// After Scan returns false, the Err method will return any error that
// occurred during scanning, except that if it was io.EOF, Err
// will return nil.
func (ms *MultiScanner) Scan() bool {
	if ms.scannerIdx == -1 {
		ms.scannerIdx = 0
	}

	for ms.scannerIdx < len(ms.scanners) {
		current := ms.scanners[ms.scannerIdx]
		if current.Scanner.Scan() {
			ms.line++
			ms.totalLine++
			return true
		}
		if err := current.Scanner.Err(); err != nil {
			ms.err = err
			return false
		}
		// Move to the next scanner
		ms.scannerIdx++
		ms.line = 0
	}

	return false
}

// Text returns the most recent token generated by a call to Scan.
func (ms *MultiScanner) Text() string {
	if ms.scannerIdx < 0 || ms.scannerIdx >= len(ms.scanners) {
		return ""
	}
	return ms.scanners[ms.scannerIdx].Scanner.Text()
}

// Err returns the first non-EOF error that was encountered by the MultiScanner.
func (ms *MultiScanner) Err() error {
	return ms.err
}

// Name returns the name of the current scanner.
func (ms *MultiScanner) Name() string {
	if ms.scannerIdx < 0 {
		return "<before first>"
	}
	if ms.scannerIdx >= len(ms.scanners) {
		return "<after last>"
	}
	return ms.scanners[ms.scannerIdx].Name
}

// Line returns the current line number within the current scanner.
func (ms *MultiScanner) Line() int {
	return ms.line
}

// TotalLine returns the total number of lines scanned across all scanners.
func (ms *MultiScanner) TotalLine() int {
	return ms.totalLine
}
