package tsc

import (
	"fmt"
	"io"
	"strings"

	"github.com/microsoft/typescript-go/internal/ast"
	"github.com/microsoft/typescript-go/internal/core"
	"github.com/microsoft/typescript-go/internal/diagnosticwriter"
	"github.com/microsoft/typescript-go/internal/locale"
	"github.com/microsoft/typescript-go/internal/tspath"
)

func getFormatOptsOfSys(sys System, locale locale.Locale) *diagnosticwriter.FormattingOptions {
	return &diagnosticwriter.FormattingOptions{
		NewLine: "\n",
		ComparePathsOptions: tspath.ComparePathsOptions{
			CurrentDirectory:          sys.GetCurrentDirectory(),
			UseCaseSensitiveFileNames: sys.FS().UseCaseSensitiveFileNames(),
		},
		Locale: locale,
	}
}

type DiagnosticReporter = func(*ast.Diagnostic)

func QuietDiagnosticReporter(diagnostic *ast.Diagnostic) {}

func CreateDiagnosticReporter(sys System, w io.Writer, locale locale.Locale, options *core.CompilerOptions) DiagnosticReporter {
	if options.Quiet.IsTrue() {
		return QuietDiagnosticReporter
	}
	formatOpts := getFormatOptsOfSys(sys, locale)
	if shouldBePretty(sys, options) {
		return func(diagnostic *ast.Diagnostic) {
			diagnosticwriter.FormatDiagnosticWithColorAndContext(w, diagnosticwriter.WrapASTDiagnostic(diagnostic), formatOpts)
			fmt.Fprint(w, formatOpts.NewLine)
		}
	}
	return func(diagnostic *ast.Diagnostic) {
		diagnosticwriter.WriteFormatDiagnostic(w, diagnosticwriter.WrapASTDiagnostic(diagnostic), formatOpts)
	}
}

func defaultIsPretty(sys System) bool {
	if sys.GetEnvironmentVariable("NO_COLOR") != "" {
		return false
	}
	if sys.GetEnvironmentVariable("FORCE_COLOR") != "" {
		return true
	}
	return sys.WriteOutputIsTTY()
}

func shouldBePretty(sys System, options *core.CompilerOptions) bool {
	if options == nil || options.Pretty.IsUnknown() {
		return defaultIsPretty(sys)
	}
	return options.Pretty.IsTrue()
}

type colors struct {
	showColors bool

	isWindows            bool
	isWindowsTerminal    bool
	isVSCode             bool
	supportsRicherColors bool
}

func createColors(sys System) *colors {
	if !defaultIsPretty(sys) {
		return &colors{showColors: false}
	}

	os := sys.GetEnvironmentVariable("OS")
	isWindows := strings.Contains(strings.ToLower(os), "windows")
	isWindowsTerminal := sys.GetEnvironmentVariable("WT_SESSION") != ""
	isVSCode := sys.GetEnvironmentVariable("TERM_PROGRAM") == "vscode"
	supportsRicherColors := sys.GetEnvironmentVariable("COLORTERM") == "truecolor" || sys.GetEnvironmentVariable("TERM") == "xterm-256color"

	return &colors{
		showColors:           true,
		isWindows:            isWindows,
		isWindowsTerminal:    isWindowsTerminal,
		isVSCode:             isVSCode,
		supportsRicherColors: supportsRicherColors,
	}
}

func (c *colors) bold(str string) string {
	if !c.showColors {
		return str
	}
	return "\x1b[1m" + str + "\x1b[22m"
}

func (c *colors) blue(str string) string {
	if !c.showColors {
		return str
	}

	// Effectively Powershell and Command prompt users use cyan instead
	// of blue because the default theme doesn't show blue with enough contrast.
	if c.isWindows && !c.isWindowsTerminal && !c.isVSCode {
		return c.brightWhite(str)
	}
	return "\x1b[94m" + str + "\x1b[39m"
}

func (c *colors) blueBackground(str string) string {
	if !c.showColors {
		return str
	}
	if c.supportsRicherColors {
		return "\x1B[48;5;68m" + str + "\x1B[39;49m"
	} else {
		return "\x1b[44m" + str + "\x1B[39;49m"
	}
}

func (c *colors) brightWhite(str string) string {
	if !c.showColors {
		return str
	}
	return "\x1b[97m" + str + "\x1b[39m"
}

type DiagnosticsReporter = func(diagnostics []*ast.Diagnostic)

func QuietDiagnosticsReporter(diagnostics []*ast.Diagnostic) {}

func CreateReportErrorSummary(sys System, locale locale.Locale, options *core.CompilerOptions) DiagnosticsReporter {
	if shouldBePretty(sys, options) {
		formatOpts := getFormatOptsOfSys(sys, locale)
		return func(diagnostics []*ast.Diagnostic) {
			diagnosticwriter.WriteErrorSummaryText(sys.Writer(), diagnosticwriter.FromASTDiagnostics(diagnostics), formatOpts)
		}
	}
	return QuietDiagnosticsReporter
}

func CreateBuilderStatusReporter(sys System, w io.Writer, locale locale.Locale, options *core.CompilerOptions, testing CommandLineTesting) DiagnosticReporter {
	if options.Quiet.IsTrue() {
		return QuietDiagnosticReporter
	}

	formatOpts := getFormatOptsOfSys(sys, locale)
	writeStatus := core.IfElse(shouldBePretty(sys, options), diagnosticwriter.FormatDiagnosticsStatusWithColorAndTime, diagnosticwriter.FormatDiagnosticsStatusAndTime)
	return func(diagnostic *ast.Diagnostic) {
		writerDiagnostic := diagnosticwriter.WrapASTDiagnostic(diagnostic)
		if testing != nil {
			testing.OnBuildStatusReportStart(w)
			defer testing.OnBuildStatusReportEnd(w)
		}
		writeStatus(w, sys.Now().Format("03:04:05 PM"), writerDiagnostic, formatOpts)
		fmt.Fprint(w, formatOpts.NewLine, formatOpts.NewLine)
	}
}

func CreateWatchStatusReporter(sys System, locale locale.Locale, options *core.CompilerOptions, testing CommandLineTesting) DiagnosticReporter {
	formatOpts := getFormatOptsOfSys(sys, locale)
	writeStatus := core.IfElse(shouldBePretty(sys, options), diagnosticwriter.FormatDiagnosticsStatusWithColorAndTime, diagnosticwriter.FormatDiagnosticsStatusAndTime)
	return func(diagnostic *ast.Diagnostic) {
		writerDiagnostic := diagnosticwriter.WrapASTDiagnostic(diagnostic)
		writer := sys.Writer()
		if testing != nil {
			testing.OnWatchStatusReportStart()
			defer testing.OnWatchStatusReportEnd()
		}
		diagnosticwriter.TryClearScreen(writer, writerDiagnostic, options)
		writeStatus(writer, sys.Now().Format("03:04:05 PM"), writerDiagnostic, formatOpts)
		fmt.Fprint(writer, formatOpts.NewLine, formatOpts.NewLine)
	}
}
