// ampli.go
//
// Ampli - A strong typed wrapper for your Analytics
//
// This file is generated by Amplitude.
// To update run 'ampli pull ddev'
//
// Required dependencies: github.com/amplitude/analytics-go@latest
// Tracking Plan Version: 12
// Build: 1.0.0
// Runtime: go-ampli
//
// View Tracking Plan: https://data.amplitude.com/ddev/DDEV/events/main/latest
//
// Full Setup Instructions: https://data.amplitude.com/ddev/DDEV/implementation/main/latest/getting-started/ddev
//

package ampli

import (
	"log"
	"sync"

	"github.com/amplitude/analytics-go/amplitude"
)

type (
	EventOptions  = amplitude.EventOptions
	ExecuteResult = amplitude.ExecuteResult
)

const (
	IdentifyEventType      = amplitude.IdentifyEventType
	GroupIdentifyEventType = amplitude.GroupIdentifyEventType

	ServerZoneUS = amplitude.ServerZoneUS
	ServerZoneEU = amplitude.ServerZoneEU
)

var (
	NewClientConfig = amplitude.NewConfig
	NewClient       = amplitude.NewClient
)

var Instance = Ampli{}

type Environment string

const (
	EnvironmentDevelopment Environment = `development`

	EnvironmentProduction Environment = `production`
)

var APIKey = map[Environment]string{
	EnvironmentDevelopment: ``,

	EnvironmentProduction: ``,
}

// LoadClientOptions is Client options setting to initialize Ampli client.
//
// Params:
//   - APIKey: the API key of Amplitude project
//   - Instance: the core SDK instance used by Ampli client
//   - Configuration: the core SDK client configuration instance
type LoadClientOptions struct {
	APIKey        string
	Instance      amplitude.Client
	Configuration amplitude.Config
}

// LoadOptions is options setting to initialize Ampli client.
//
// Params:
//   - Environment: the environment of Amplitude Data project
//   - Disabled: the flag of disabled Ampli client
//   - Client: the LoadClientOptions struct
type LoadOptions struct {
	Environment Environment
	Disabled    bool
	Client      LoadClientOptions
}

type baseEvent struct {
	eventType  string
	properties map[string]interface{}
}

type Event interface {
	ToAmplitudeEvent() amplitude.Event
}

func newBaseEvent(eventType string, properties map[string]interface{}) baseEvent {
	return baseEvent{
		eventType:  eventType,
		properties: properties,
	}
}

func (event baseEvent) ToAmplitudeEvent() amplitude.Event {
	return amplitude.Event{
		EventType:       event.eventType,
		EventProperties: event.properties,
	}
}

var Identify = struct {
	Builder func() interface {
		DdevEnvironment(ddevEnvironment string) interface {
			DockerPlatform(dockerPlatform string) interface {
				DockerVersion(dockerVersion string) interface {
					Timezone(timezone string) IdentifyBuilder
				}
			}
		}
	}
}{
	Builder: func() interface {
		DdevEnvironment(ddevEnvironment string) interface {
			DockerPlatform(dockerPlatform string) interface {
				DockerVersion(dockerVersion string) interface {
					Timezone(timezone string) IdentifyBuilder
				}
			}
		}
	} {
		return &identifyBuilder{
			properties: map[string]interface{}{},
		}
	},
}

type IdentifyEvent interface {
	Event
	identify()
}

type identifyEvent struct {
	baseEvent
}

func (e identifyEvent) identify() {
}

type IdentifyBuilder interface {
	Build() IdentifyEvent
	User(user string) IdentifyBuilder
	WslDistro(wslDistro string) IdentifyBuilder
}

type identifyBuilder struct {
	properties map[string]interface{}
}

func (b *identifyBuilder) DdevEnvironment(ddevEnvironment string) interface {
	DockerPlatform(dockerPlatform string) interface {
		DockerVersion(dockerVersion string) interface {
			Timezone(timezone string) IdentifyBuilder
		}
	}
} {
	b.properties[`DDEV Environment`] = ddevEnvironment

	return b
}

func (b *identifyBuilder) DockerPlatform(dockerPlatform string) interface {
	DockerVersion(dockerVersion string) interface {
		Timezone(timezone string) IdentifyBuilder
	}
} {
	b.properties[`Docker Platform`] = dockerPlatform

	return b
}

func (b *identifyBuilder) DockerVersion(dockerVersion string) interface {
	Timezone(timezone string) IdentifyBuilder
} {
	b.properties[`Docker Version`] = dockerVersion

	return b
}

func (b *identifyBuilder) Timezone(timezone string) IdentifyBuilder {
	b.properties[`Timezone`] = timezone

	return b
}

func (b *identifyBuilder) User(user string) IdentifyBuilder {
	b.properties[`User`] = user

	return b
}

func (b *identifyBuilder) WslDistro(wslDistro string) IdentifyBuilder {
	b.properties[`WSL Distro`] = wslDistro

	return b
}

func (b *identifyBuilder) Build() IdentifyEvent {
	return &identifyEvent{
		newBaseEvent(IdentifyEventType, b.properties),
	}
}

func (e identifyEvent) ToAmplitudeEvent() amplitude.Event {
	identify := amplitude.Identify{}
	for name, value := range e.properties {
		identify.Set(name, value)
	}

	return amplitude.Event{
		EventType:      IdentifyEventType,
		UserProperties: identify.Properties,
	}
}

var Command = struct {
	Builder func() interface {
		Arguments(arguments []string) interface {
			CalledAs(calledAs string) interface {
				CommandName(commandName string) interface {
					CommandPath(commandPath string) CommandBuilder
				}
			}
		}
	}
}{
	Builder: func() interface {
		Arguments(arguments []string) interface {
			CalledAs(calledAs string) interface {
				CommandName(commandName string) interface {
					CommandPath(commandPath string) CommandBuilder
				}
			}
		}
	} {
		return &commandBuilder{
			properties: map[string]interface{}{},
		}
	},
}

type CommandEvent interface {
	Event
	command()
}

type commandEvent struct {
	baseEvent
}

func (e commandEvent) command() {
}

type CommandBuilder interface {
	Build() CommandEvent
}

type commandBuilder struct {
	properties map[string]interface{}
}

func (b *commandBuilder) Arguments(arguments []string) interface {
	CalledAs(calledAs string) interface {
		CommandName(commandName string) interface {
			CommandPath(commandPath string) CommandBuilder
		}
	}
} {
	b.properties[`Arguments`] = arguments

	return b
}

func (b *commandBuilder) CalledAs(calledAs string) interface {
	CommandName(commandName string) interface {
		CommandPath(commandPath string) CommandBuilder
	}
} {
	b.properties[`Called As`] = calledAs

	return b
}

func (b *commandBuilder) CommandName(commandName string) interface {
	CommandPath(commandPath string) CommandBuilder
} {
	b.properties[`Command Name`] = commandName

	return b
}

func (b *commandBuilder) CommandPath(commandPath string) CommandBuilder {
	b.properties[`Command Path`] = commandPath

	return b
}

func (b *commandBuilder) Build() CommandEvent {
	return &commandEvent{
		newBaseEvent(`Command`, b.properties),
	}
}

var Project = struct {
	Builder func() interface {
		Id(id string) interface {
			PerformanceMode(performanceMode string) interface {
				PhpVersion(phpVersion string) interface {
					ProjectType(projectType string) interface {
						WebserverType(webserverType string) ProjectBuilder
					}
				}
			}
		}
	}
}{
	Builder: func() interface {
		Id(id string) interface {
			PerformanceMode(performanceMode string) interface {
				PhpVersion(phpVersion string) interface {
					ProjectType(projectType string) interface {
						WebserverType(webserverType string) ProjectBuilder
					}
				}
			}
		}
	} {
		return &projectBuilder{
			properties: map[string]interface{}{},
		}
	},
}

type ProjectEvent interface {
	Event
	project()
}

type projectEvent struct {
	baseEvent
}

func (e projectEvent) project() {
}

type ProjectBuilder interface {
	Build() ProjectEvent
	AddOns(addOns []string) ProjectBuilder
	BindAllInterfaces(bindAllInterfaces bool) ProjectBuilder
	Ci(ci bool) ProjectBuilder
	Containers(containers []string) ProjectBuilder
	ContainersOmitted(containersOmitted []string) ProjectBuilder
	CorepackEnable(corepackEnable bool) ProjectBuilder
	DatabaseType(databaseType string) ProjectBuilder
	DatabaseVersion(databaseVersion string) ProjectBuilder
	DbImageExtraPackages(dbImageExtraPackages []string) ProjectBuilder
	DdevVersionConstraint(ddevVersionConstraint string) ProjectBuilder
	DisableSettingsManagement(disableSettingsManagement bool) ProjectBuilder
	FailOnHookFail(failOnHookFail bool) ProjectBuilder
	NoProjectMount(noProjectMount bool) ProjectBuilder
	NodejsVersion(nodejsVersion string) ProjectBuilder
	Router(router string) ProjectBuilder
	RouterDisabled(routerDisabled bool) ProjectBuilder
	WebExtraDaemonsDetails(webExtraDaemonsDetails []string) ProjectBuilder
	WebExtraDaemonsNames(webExtraDaemonsNames []string) ProjectBuilder
	WebExtraExposedPortsDetails(webExtraExposedPortsDetails []string) ProjectBuilder
	WebExtraExposedPortsNames(webExtraExposedPortsNames []string) ProjectBuilder
	WebimageExtraPackages(webimageExtraPackages []string) ProjectBuilder
}

type projectBuilder struct {
	properties map[string]interface{}
}

func (b *projectBuilder) Id(id string) interface {
	PerformanceMode(performanceMode string) interface {
		PhpVersion(phpVersion string) interface {
			ProjectType(projectType string) interface {
				WebserverType(webserverType string) ProjectBuilder
			}
		}
	}
} {
	b.properties[`ID`] = id

	return b
}

func (b *projectBuilder) PerformanceMode(performanceMode string) interface {
	PhpVersion(phpVersion string) interface {
		ProjectType(projectType string) interface {
			WebserverType(webserverType string) ProjectBuilder
		}
	}
} {
	b.properties[`Performance Mode`] = performanceMode

	return b
}

func (b *projectBuilder) PhpVersion(phpVersion string) interface {
	ProjectType(projectType string) interface {
		WebserverType(webserverType string) ProjectBuilder
	}
} {
	b.properties[`PHP Version`] = phpVersion

	return b
}

func (b *projectBuilder) ProjectType(projectType string) interface {
	WebserverType(webserverType string) ProjectBuilder
} {
	b.properties[`Project Type`] = projectType

	return b
}

func (b *projectBuilder) WebserverType(webserverType string) ProjectBuilder {
	b.properties[`Webserver Type`] = webserverType

	return b
}

func (b *projectBuilder) AddOns(addOns []string) ProjectBuilder {
	b.properties[`Add-ons`] = addOns

	return b
}

func (b *projectBuilder) BindAllInterfaces(bindAllInterfaces bool) ProjectBuilder {
	b.properties[`Bind All Interfaces`] = bindAllInterfaces

	return b
}

func (b *projectBuilder) Ci(ci bool) ProjectBuilder {
	b.properties[`CI`] = ci

	return b
}

func (b *projectBuilder) Containers(containers []string) ProjectBuilder {
	b.properties[`Containers`] = containers

	return b
}

func (b *projectBuilder) ContainersOmitted(containersOmitted []string) ProjectBuilder {
	b.properties[`Containers Omitted`] = containersOmitted

	return b
}

func (b *projectBuilder) CorepackEnable(corepackEnable bool) ProjectBuilder {
	b.properties[`Corepack Enable`] = corepackEnable

	return b
}

func (b *projectBuilder) DatabaseType(databaseType string) ProjectBuilder {
	b.properties[`Database Type`] = databaseType

	return b
}

func (b *projectBuilder) DatabaseVersion(databaseVersion string) ProjectBuilder {
	b.properties[`Database Version`] = databaseVersion

	return b
}

func (b *projectBuilder) DbImageExtraPackages(dbImageExtraPackages []string) ProjectBuilder {
	b.properties[`DB Image Extra Packages`] = dbImageExtraPackages

	return b
}

func (b *projectBuilder) DdevVersionConstraint(ddevVersionConstraint string) ProjectBuilder {
	b.properties[`DDEV Version Constraint`] = ddevVersionConstraint

	return b
}

func (b *projectBuilder) DisableSettingsManagement(disableSettingsManagement bool) ProjectBuilder {
	b.properties[`Disable Settings Management`] = disableSettingsManagement

	return b
}

func (b *projectBuilder) FailOnHookFail(failOnHookFail bool) ProjectBuilder {
	b.properties[`Fail On Hook Fail`] = failOnHookFail

	return b
}

func (b *projectBuilder) NoProjectMount(noProjectMount bool) ProjectBuilder {
	b.properties[`No Project Mount`] = noProjectMount

	return b
}

func (b *projectBuilder) NodejsVersion(nodejsVersion string) ProjectBuilder {
	b.properties[`Nodejs Version`] = nodejsVersion

	return b
}

func (b *projectBuilder) Router(router string) ProjectBuilder {
	b.properties[`Router`] = router

	return b
}

func (b *projectBuilder) RouterDisabled(routerDisabled bool) ProjectBuilder {
	b.properties[`Router Disabled`] = routerDisabled

	return b
}

func (b *projectBuilder) WebExtraDaemonsDetails(webExtraDaemonsDetails []string) ProjectBuilder {
	b.properties[`WebExtraDaemonsDetails`] = webExtraDaemonsDetails

	return b
}

func (b *projectBuilder) WebExtraDaemonsNames(webExtraDaemonsNames []string) ProjectBuilder {
	b.properties[`WebExtraDaemonsNames`] = webExtraDaemonsNames

	return b
}

func (b *projectBuilder) WebExtraExposedPortsDetails(webExtraExposedPortsDetails []string) ProjectBuilder {
	b.properties[`WebExtraExposedPortsDetails`] = webExtraExposedPortsDetails

	return b
}

func (b *projectBuilder) WebExtraExposedPortsNames(webExtraExposedPortsNames []string) ProjectBuilder {
	b.properties[`WebExtraExposedPortsNames`] = webExtraExposedPortsNames

	return b
}

func (b *projectBuilder) WebimageExtraPackages(webimageExtraPackages []string) ProjectBuilder {
	b.properties[`Webimage Extra Packages`] = webimageExtraPackages

	return b
}

func (b *projectBuilder) Build() ProjectEvent {
	return &projectEvent{
		newBaseEvent(`Project`, b.properties),
	}
}

type Ampli struct {
	Disabled bool
	Client   amplitude.Client
	mutex    sync.RWMutex
}

// Load initializes the Ampli wrapper.
// Call once when your application starts.
func (a *Ampli) Load(options LoadOptions) {
	if a.Client != nil {
		log.Print("Warn: Ampli is already initialized. Ampli.Load() should be called once at application start up.")

		return
	}

	var apiKey string
	switch {
	case options.Client.APIKey != "":
		apiKey = options.Client.APIKey
	case options.Environment != "":
		apiKey = APIKey[options.Environment]
	default:
		apiKey = options.Client.Configuration.APIKey
	}

	if apiKey == "" && options.Client.Instance == nil {
		log.Print("Error: Ampli.Load() requires option.Environment, " +
			"and apiKey from either options.Instance.APIKey or APIKey[options.Environment], " +
			"or options.Instance.Instance")
	}

	clientConfig := options.Client.Configuration

	if clientConfig.Plan == nil {
		clientConfig.Plan = &amplitude.Plan{
			Branch:    `main`,
			Source:    `ddev`,
			Version:   `12`,
			VersionID: `fccb5108-a049-4205-a5af-a27817e8c5d0`,
		}
	}

	if clientConfig.IngestionMetadata == nil {
		clientConfig.IngestionMetadata = &amplitude.IngestionMetadata{
			SourceName:    `go-go-ampli`,
			SourceVersion: `2.0.0`,
		}
	}

	if options.Client.Instance != nil {
		a.Client = options.Client.Instance
	} else {
		clientConfig.APIKey = apiKey
		a.Client = amplitude.NewClient(clientConfig)
	}

	a.mutex.Lock()
	a.Disabled = options.Disabled
	a.mutex.Unlock()
}

// InitializedAndEnabled checks if Ampli is initialized and enabled.
func (a *Ampli) InitializedAndEnabled() bool {
	if a.Client == nil {
		log.Print("Error: Ampli is not yet initialized. Have you called Ampli.Load() on app start?")

		return false
	}

	a.mutex.RLock()
	defer a.mutex.RUnlock()

	return !a.Disabled
}

func (a *Ampli) setUserID(userID string, eventOptions *EventOptions) {
	if userID != "" {
		eventOptions.UserID = userID
	}
}

// Track tracks an event.
func (a *Ampli) Track(userID string, event Event, eventOptions ...EventOptions) {
	if !a.InitializedAndEnabled() {
		return
	}

	var options EventOptions
	if len(eventOptions) > 0 {
		options = eventOptions[0]
	}

	a.setUserID(userID, &options)

	baseEvent := event.ToAmplitudeEvent()
	baseEvent.EventOptions = options

	a.Client.Track(baseEvent)
}

// Identify identifies a user and set user properties.
func (a *Ampli) Identify(userID string, identify IdentifyEvent, eventOptions ...EventOptions) {
	a.Track(userID, identify, eventOptions...)
}

// Flush flushes events waiting in buffer.
func (a *Ampli) Flush() {
	if !a.InitializedAndEnabled() {
		return
	}

	a.Client.Flush()
}

// Shutdown disables and shutdowns Ampli Instance.
func (a *Ampli) Shutdown() {
	if !a.InitializedAndEnabled() {
		return
	}

	a.mutex.Lock()
	a.Disabled = true
	a.mutex.Unlock()

	a.Client.Shutdown()
}

func (a *Ampli) Command(userID string, event CommandEvent, eventOptions ...EventOptions) {
	a.Track(userID, event, eventOptions...)
}

func (a *Ampli) Project(userID string, event ProjectEvent, eventOptions ...EventOptions) {
	a.Track(userID, event, eventOptions...)
}
