package main

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

func stripComment(line string) string {
	inSingle := false
	inDouble := false
	for i := 0; i < len(line); i++ {
		ch := line[i]
		switch {
		case ch == '\\' && !inSingle:
			i++ // skip escaped character (backslash escapes in double-quoted and unquoted contexts)
		case ch == '\'' && !inDouble:
			inSingle = !inSingle
		case ch == '"' && !inSingle:
			inDouble = !inDouble
		case ch == '#' && !inSingle && !inDouble:
			return strings.TrimRight(line[:i], " \t")
		}
	}
	return line
}

func parseExitCode(lines []string) ([]string, ExitCodeType, int) {
	if len(lines) == 0 {
		return lines, ExitCodeNone, 0
	}

	last := lines[len(lines)-1]
	if len(last) < 3 || last[0] != '[' || last[len(last)-1] != ']' {
		return lines, ExitCodeNone, 0
	}

	inner := last[1 : len(last)-1]
	if inner == "*" {
		return lines[:len(lines)-1], ExitCodeWildcard, 0
	}

	code, err := strconv.Atoi(inner)
	if err != nil {
		return lines, ExitCodeNone, 0
	}
	return lines[:len(lines)-1], ExitCodeExact, code
}

func trimTrailingEmpty(lines []string) []string {
	end := len(lines)
	for end > 0 && lines[end-1] == "" {
		end--
	}
	return lines[:end]
}

func parse(path, content string) (ShoutFile, error) {
	rawLines := strings.Split(content, "\n")

	// Remove trailing newline
	if len(rawLines) > 0 && rawLines[len(rawLines)-1] == "" {
		rawLines = rawLines[:len(rawLines)-1]
	}

	var commands []Command
	var directives []Directive
	var current *Command
	seenCommand := false

	for i, line := range rawLines {
		lineNum := i + 1

		if !seenCommand && strings.HasPrefix(line, "@") {
			if strings.HasPrefix(line, "@setup ") {
				setupPath := strings.TrimSpace(line[7:])
				if setupPath == "" {
					return ShoutFile{}, fmt.Errorf("%s:%d: @setup requires a file path", path, lineNum)
				}
				directives = append(directives, Directive{
					Type: "setup", Path: setupPath, Line: lineNum,
				})
			} else if strings.HasPrefix(line, "@env ") {
				rest := strings.TrimSpace(line[5:])
				eq := strings.Index(rest, "=")
				if eq <= 0 {
					return ShoutFile{}, fmt.Errorf("%s:%d: malformed @env directive (expected KEY=VALUE): %s", path, lineNum, line)
				}
				directives = append(directives, Directive{
					Type: "env", Key: rest[:eq], Value: rest[eq+1:], Line: lineNum,
				})
			} else {
				return ShoutFile{}, fmt.Errorf("%s:%d: unknown directive: %s", path, lineNum, line)
			}
			continue
		}

		if strings.HasPrefix(line, "$ ") {
			seenCommand = true
			if current != nil {
				trimmed := trimTrailingEmpty(current.Expected)
				remaining, ecType, ecVal := parseExitCode(trimmed)
				current.Expected = trimTrailingEmpty(remaining)
				current.ExitCodeType = ecType
				current.ExitCodeValue = ecVal
				commands = append(commands, *current)
			}

			current = &Command{
				Line:     lineNum,
				Raw:      line,
				Cmd:      stripComment(line[2:]),
				Expected: nil,
			}
		} else if current != nil {
			current.Expected = append(current.Expected, line)
		}
	}

	if current != nil {
		trimmed := trimTrailingEmpty(current.Expected)
		remaining, ecType, ecVal := parseExitCode(trimmed)
		current.Expected = trimTrailingEmpty(remaining)
		current.ExitCodeType = ecType
		current.ExitCodeValue = ecVal
		commands = append(commands, *current)
	}

	return ShoutFile{Path: path, Commands: commands, Directives: directives}, nil
}
