package executor

import (
	"context"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"webby-builder/internal/models"
)

// TypeCheckResult is the outcome of a tsc --noEmit run.
type TypeCheckResult struct {
	OK     bool
	Errors string // concise summary of `error TS` lines (file:line), empty when OK
}

// TypeChecker runs whole-program TypeScript type checking (incremental) so the
// agent gets the SAME signal the production `tsc && vite build` will enforce.
// The per-file esbuild validation in file.go is SYNTAX-ONLY (it transpiles by
// stripping types and never type-checks), so type errors like passing the wrong
// props to a component are invisible until the production build. This checker
// closes that gap by surfacing real type errors in-flight.
type TypeChecker struct {
	workspacePath string
	toolConfig    models.ToolExecutionConfig
}

// NewTypeChecker creates a TypeChecker scoped to a workspace.
func NewTypeChecker(workspacePath string, toolConfig models.ToolExecutionConfig) *TypeChecker {
	return &TypeChecker{workspacePath: workspacePath, toolConfig: toolConfig}
}

// Check runs `tsc --noEmit --incremental`. It SKIPS (returns OK=true) when there
// is no tsconfig.json or no node_modules — it must never block the agent on
// missing infrastructure (mirrors VerifyBuild's tolerance). The incremental
// build info is cached in .webby-tsbuildinfo so only the first run is whole-program.
func (e *TypeChecker) Check(ctx context.Context) (TypeCheckResult, error) {
	if _, err := os.Stat(filepath.Join(e.workspacePath, "tsconfig.json")); os.IsNotExist(err) {
		return TypeCheckResult{OK: true}, nil
	}
	if _, err := os.Stat(filepath.Join(e.workspacePath, "node_modules")); os.IsNotExist(err) {
		return TypeCheckResult{OK: true}, nil
	}

	timeout := time.Duration(e.toolConfig.Timeout) * time.Second
	if timeout < 120*time.Second {
		timeout = 120 * time.Second // match the build gate's minimum
	}
	ctx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	cmd := exec.CommandContext(ctx, "npx", "tsc", "--noEmit", "--incremental",
		"--tsBuildInfoFile", filepath.Join(e.workspacePath, ".webby-tsbuildinfo"))
	cmd.Dir = e.workspacePath
	output, err := cmd.CombinedOutput()
	if err == nil {
		return TypeCheckResult{OK: true}, nil
	}
	return TypeCheckResult{OK: false, Errors: summarizeTSErrors(string(output))}, nil
}

// summarizeTSErrors keeps only `error TS` lines (the `file(line,col): error TSxxxx`
// form tsc emits), capped at 15, to give the agent an actionable, non-overwhelming
// list. Falls back to the tail of the output if no recognizable error lines exist.
func summarizeTSErrors(output string) string {
	var errs []string
	for _, line := range strings.Split(output, "\n") {
		if strings.Contains(line, "error TS") {
			if t := strings.TrimSpace(line); t != "" {
				errs = append(errs, t)
			}
		}
	}
	if len(errs) == 0 {
		lines := strings.Split(strings.TrimRight(output, "\n"), "\n")
		if len(lines) > 20 {
			lines = lines[len(lines)-20:]
		}
		return strings.Join(lines, "\n")
	}
	if len(errs) > 15 {
		errs = append(errs[:15], "... (more type errors omitted)")
	}
	return strings.Join(errs, "\n")
}
