// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

package main

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"runtime/debug"
	"strings"

	"github.com/charmbracelet/fang"
	"github.com/spf13/cobra"
)

var (
	commitType     string
	message        string
	trailers       []string
	body           string
	scope          string
	breakingChange string
	add            bool
	amend          bool
)

var rootCmd = &cobra.Command{
	Use:   "formatted-commit",
	Short: "Create conventionally formatted Git commits",
	Long: `formatted-commit helps you create well-formatted Git commits that follow
the Conventional Commits specification with proper subject length validation,
body wrapping, and trailer formatting.`,
	Example: `
# With Assisted-by
formatted-commit -t feat -m "do a thing" -T "Assisted-by: GLM 4.6 via Crush"

# Breaking change with description
formatted-commit -t feat -m "remove deprecated API" \
  -B "The old /v1/users endpoint is removed. Use /v2/users instead."

# Breaking change with multi-line description using heredoc
formatted-commit -t feat -m "restructure config format" -B "$(cat <<'EOF'
Configuration format changed from JSON to TOML.
Migrate by running: ./migrate-config.sh
EOF
)" -b "Improves readability and supports comments"

# Including scope for more precise changes
formatted-commit -t refactor -s "web/git-bug" -m "fancy shmancy" \
  -b "Had to do a weird thing because..."

# Check for upgrades
formatted-commit upgrade

# Then apply
formatted-commit upgrade -a
`,
	RunE: func(cmd *cobra.Command, args []string) error {
		subject, err := buildAndValidateSubject(commitType, scope, message, breakingChange)
		if err != nil {
			return err
		}

		var commitMsg strings.Builder
		commitMsg.WriteString(subject)

		if body != "" {
			formattedBody, err := formatBody(body)
			if err != nil {
				return fmt.Errorf("failed to format body: %w", err)
			}
			commitMsg.WriteString("\n\n")
			commitMsg.WriteString(formattedBody)
		}

		if breakingChange != "" {
			formattedBreaking, err := formatBody("BREAKING CHANGE: " + breakingChange)
			if err != nil {
				return fmt.Errorf("failed to format breaking change: %w", err)
			}
			commitMsg.WriteString("\n\n")
			commitMsg.WriteString(formattedBreaking)
		}

		if len(trailers) > 0 {
			trailersBlock, err := buildTrailersBlock(trailers)
			if err != nil {
				return fmt.Errorf("failed to build trailers: %w", err)
			}
			commitMsg.WriteString("\n\n")
			commitMsg.WriteString(trailersBlock)
		}

		gitArgs := []string{"commit"}
		if add {
			gitArgs = append(gitArgs, "-a")
		}
		if amend {
			gitArgs = append(gitArgs, "--amend")
		}
		gitArgs = append(gitArgs, "-F", "-")

		gitCmd := exec.Command("git", gitArgs...)
		gitCmd.Stdout = os.Stdout
		gitCmd.Stderr = os.Stderr

		stdin, err := gitCmd.StdinPipe()
		if err != nil {
			return fmt.Errorf("failed to create stdin pipe: %w", err)
		}

		if err := gitCmd.Start(); err != nil {
			return fmt.Errorf("failed to start git command: %w", err)
		}

		if _, err := stdin.Write([]byte(commitMsg.String())); err != nil {
			return fmt.Errorf("failed to write to git stdin: %w", err)
		}

		if err := stdin.Close(); err != nil {
			return fmt.Errorf("failed to close stdin: %w", err)
		}

		if err := gitCmd.Wait(); err != nil {
			return fmt.Errorf("git commit failed: %w", err)
		}

		return nil
	},
}

func init() {
	rootCmd.Flags().StringVarP(&commitType, "type", "t", "", "commit type (required)")
	rootCmd.Flags().StringVarP(&message, "message", "m", "", "commit message (required)")
	rootCmd.Flags().StringArrayVarP(&trailers, "trailer", "T", []string{}, "trailer in 'Sentence-case-key: value' format (optional, repeatable)")
	rootCmd.Flags().StringVarP(&body, "body", "b", "", "commit body (optional)")
	rootCmd.Flags().StringVarP(&scope, "scope", "s", "", "commit scope (optional)")
	rootCmd.Flags().StringVarP(&breakingChange, "breaking", "B", "", "breaking change description (optional)")
	rootCmd.Flags().BoolVarP(&add, "add", "a", false, "stage all modified files before committing (optional)")
	rootCmd.Flags().BoolVar(&amend, "amend", false, "amend the previous commit (optional)")

	if err := rootCmd.MarkFlagRequired("type"); err != nil {
		panic(err)
	}
	if err := rootCmd.MarkFlagRequired("message"); err != nil {
		panic(err)
	}
}

func buildAndValidateSubject(commitType, scope, message string, breaking string) (string, error) {
	var subject strings.Builder

	subject.WriteString(commitType)

	if scope != "" {
		subject.WriteString("(")
		subject.WriteString(scope)
		subject.WriteString(")")
	}

	if breaking != "" {
		subject.WriteString("!")
	}

	subject.WriteString(": ")
	subject.WriteString(message)

	result := subject.String()
	length := len(result)

	if length > 50 {
		exceededBy := length - 50
		truncated := result[:50] + "…"
		return "", fmt.Errorf("subject exceeds 50 character limit by %d:\n%s", exceededBy, truncated)
	}

	return result, nil
}

func main() {
	ctx := context.Background()

	var version string
	if info, ok := debug.ReadBuildInfo(); ok {
		version = info.Main.Version
	}
	if version == "" || version == "(devel)" {
		version = "dev"
	}

	if err := fang.Execute(ctx, rootCmd,
		fang.WithVersion(version),
		fang.WithoutCompletions(),
	); err != nil {
		os.Exit(1)
	}
}
