package input

import (
	"fmt"
	"strconv"
	"strings"
)

type fixedWidthSplitter struct {
	// Valid increasing indexes
	indexes []int
}

// NewFixedWidthSplitter creates a new fixed-width field splitter based on the spec.
//
// The spec parameter supports these formats:
//
//	widths:w1,w2,w3...     : Split by explicit widths for each column. Omit last one for variable width ending (e.g., 4,4,5)
//	left-align             : Parse header with left-aligned fields
//	left-align-multi-word  : Parse header with multi-word support
//	right-align            : Parse header with right-aligned fields
//	right-align-multi-word : Parse header with right-aligned fields and multi-word support
//
// For mult-word cases, the column headers can be made up of multiple words
// e.g. "Seq No". Adjacent columns should be seperated by at least two spaces
func NewFixedWidthSplitter(spec, referenceRow string) (*fixedWidthSplitter, error) {
	var indexes []int

	if spec[:7] == "widths:" {
		var err error
		indexes, err = parseWidths(spec[7:])
		if err != nil {
			return nil, err
		}
	} else if spec == "left-align" {
		indexes = parseLeftAlign(referenceRow, false)
	} else if spec == "left-align-multi-word" {
		indexes = parseLeftAlign(referenceRow, true)
	} else if spec == "right-align" {
		indexes = parseRightAlign(referenceRow, false)
	} else if spec == "right-align-multi-word" {
		indexes = parseRightAlign(referenceRow, true)
	} else {
		return nil, fmt.Errorf("unknown spec: %v", spec)
	}
	return &fixedWidthSplitter{indexes: indexes}, nil

}

func (sp *fixedWidthSplitter) Split(line string) []string {
	return split(line, sp.indexes)
}

func parseWidths(widthsStr string) ([]int, error) {
	var indexes []int
	if len(widthsStr) == 0 {
		return indexes, nil
	}
	widths := strings.Split(widthsStr, ",")
	pos := 0
	for _, w := range widths {
		width, err := strconv.Atoi(strings.TrimSpace(w))
		if err != nil {
			return nil, fmt.Errorf("invalid width: %v, error: %w", w, err)
		}
		if width <= 0 {
			return nil, fmt.Errorf("not a positive width: %v", w)
		}
		pos += width
		indexes = append(indexes, pos)
	}
	return indexes, nil
}

func parseLeftAlign(referenceRow string, allowMultiWord bool) []int {
	var indexes []int
	inWord := true
	firstSpace := false //Used for multi word. True on the first space after a non-space
	for i, c := range referenceRow {
		if c != ' ' {
			if !inWord {
				indexes = append(indexes, i)
			}
			inWord = true
		} else {
			if allowMultiWord {
				if firstSpace {
					inWord = false
					firstSpace = false
				} else {
					firstSpace = true
					// inWord = true // already true
				}
			} else {
				inWord = false
			}
		}
	}
	return indexes
}

func parseRightAlign(referenceRow string, allowMultiWord bool) []int {
	var indexes []int
	inWord := false
	firstSpace := false //Used for multi word. True on the first space after a non-space
	for i, c := range referenceRow {
		if c != ' ' {
			inWord = true
			firstSpace = false
		} else {
			if inWord {
				if allowMultiWord {
					firstSpace = true
				} else {
					indexes = append(indexes, i)
				}
			} else {
				if allowMultiWord && firstSpace {
					indexes = append(indexes, i-1)
					firstSpace = false
				}
			}
			inWord = false
		}
	}
	return indexes
}

func split(line string, indexes []int) []string {
	var result []string
	if len(indexes) == 0 {
		result = append(result, line)
		return result
	}
	si := 0
	l := len(line)
	for _, idx := range indexes {
		if idx > l {
			break
		}
		result = append(result, line[si:idx])
		si = idx
	}
	rest := line[si:]
	if rest != "" {
		result = append(result, rest)
	}
	return result
}
