{{#if isDevelopment}}
/**
 * Ajv is a peer dependency for development builds. It's used to apply run-time validation
 * to message payloads before passing them on to the underlying analytics instance.
 *
 * Note that the production bundle does not depend on Ajv.
 * 
 * You can install it with: `npm install --save-dev ajv`.
 */
import Ajv, { ErrorObject } from 'ajv'
{{/if}}

/**
 * You can install `@segment/analytics-node` by following instructions at: 
 * https://segment.com/docs/connections/sources/catalog/libraries/server/node
 */
import { Analytics, TrackParams } from '@segment/analytics-node'

/**
 * An ID associated with the user. Note: at least one of userId or anonymousId must be included!
 **/
type Identity =
  | { userId: string; anonymousId?: string }
  | { userId?: string; anonymousId: string }

/**
 * TrackMessage represents a message payload for an analytics `.track()` call.
 * See: https://segment.com/docs/spec/track/
 */
export type TrackMessage<PropertiesType> = Omit<
  TrackParams,
  'event' | 'properties'
> & { event?: string, properties: PropertiesType } & Identity

/** The callback exposed by analytics-node. */
export type Callback = Parameters<Analytics['track']>[1]

export type ViolationHandler = (
	message: TrackMessage<Record<string, any>>,
	{{!-- Swap the type definitions here so we don't want depend on ajv in production just for this type definition. --}}
	violations: {{#if isDevelopment}}ErrorObject{{else}}any{{/if}}[]
) => void

/**
 * The default handler that is fired if none is supplied with setTypewriterOptions.
 * If NODE_ENV="test", this handler will throw an error. Otherwise, it will log
 * a warning message to the console.
 */
 export const defaultValidationErrorHandler: ViolationHandler = (
	message,
	violations
) => {
	const msg = JSON.stringify(
		{
			type: 'Typewriter JSON Schema Validation Error',
			description:
				`You made an analytics call (${message.event}) using Typewriter that doesn't match the ` +
				'Tracking Plan spec.',
			errors: violations,
		},
		undefined,
		2
	)

	if (process.env.NODE_ENV === 'test') {
		throw new Error(msg)
	}
	console.warn(msg)
}

{{#if isDevelopment}}
let onViolation = defaultValidationErrorHandler
{{/if}}

const missingAnalyticsNodeError = new Error(`You must set an analytics-node instance:

>	import { Analytics } from '@segment/analytics-node'
>	import { setTypewriterOptions } from './analytics'
>
> const analytics = new Analytics({ writeKey: 'SEGMENT_WRITE_KEY' })
>	setTypewriterOptions({ analytics: analytics	})

For more information on @segment/analytics-node, see: https://segment.com/docs/sources/server/node/quickstart/
`)

let analytics: () => Analytics | undefined = () => {
	throw missingAnalyticsNodeError
}

/** Options to customize the runtime behavior of a Typewriter client. */
export interface TypewriterOptions {
	/**
	 * Underlying analytics instance where analytics calls are forwarded on to.
	 */
	analytics: Analytics
	/**
	 * Handler fired when if an event does not match its spec. This handler
	 * does not fire in production mode, because it requires inlining the full
	 * JSON Schema spec for each event in your Tracking Plan.
	 *
	 * By default, it will throw errors if NODE_ENV = "test" so that tests will fail
	 * if a message does not match the spec. Otherwise, errors will be logged to stderr.
	 */
	onViolation?: ViolationHandler
}

/**
 * Updates the run-time configuration of this Typewriter client.
 * This function must be called with a configured analytics-node instance before firing
 * any analytics calls, or else a `missingAnalyticsNodeError` error will be thrown.
 *
 * @param {TypewriterOptions} options - the options to upsert
 *
 * @typedef {Object} TypewriterOptions
 * @property {Analytics} analytics - Underlying analytics instance where analytics
 * 		calls are forwarded on to.
 * @property {Function} [onViolation] - Handler fired when if an event does not match its spec. This handler does not fire in
 * 		production mode, because it requires inlining the full JSON Schema spec for each event in your Tracking Plan. By default,
 * 		it will throw errors if NODE_ENV="test" so that tests will fail if a message does not match the spec. Otherwise, errors
 * 		will be logged to stderr.
 */
export function setTypewriterOptions(options: TypewriterOptions) {
	analytics = options.analytics ? () => options.analytics : analytics
  {{#if isDevelopment}}
	onViolation = options.onViolation || onViolation
	{{/if}}
}

{{#if isDevelopment}}
/**
	* Validates a message against a JSON Schema using Ajv. If the message
	* is invalid, the `onViolation` handler will be called.
	*/
function validateAgainstSchema(
	message: TrackMessage<Record<string, any>>,
	schema: object
) {
	const ajv = new Ajv({ allErrors: true, verbose: true })

	if (!ajv.validate(schema, message.properties) && ajv.errors) {
		onViolation(message, ajv.errors)
	}
}
{{/if}}

/**
 * Helper to attach metadata on Typewriter to outbound requests.
 * This is used for attribution and debugging by the Segment team.
 */
function withTypewriterContext<P extends Record<string, any>, T extends TrackMessage<P>>(
	message: T
) {
	return {
		...message,
		context: {
			...(message.context || {}),
			typewriter: {
				language: 'typescript',
				version: '{{version}}',
			},
		},
	}
}


{{#type}}
/**
 * Fires a '{{eventName}}' track call.
 * {{description}}
 *
 * @param {TrackMessage<{{typeName}}>} message - The analytics properties that will be sent to Segment.
 * @param {Function} [callback] - An optional callback called after a short timeout after the analytics
 * 		call is fired.
 */
 export function {{functionName}}(
	message: TrackMessage<{{typeName}}>,
	callback?: Callback
): void {
  const event = withTypewriterContext({
    ...message,
    event: '{{eventName}}',
    properties: {
      ...message.properties,
    },
  });
	{{#if ../isDevelopment}}
	const schema = {{{rawJSONSchema}}};
	validateAgainstSchema(event, schema);
	{{/if}}

	const a = analytics()
	if (a) {
		a.track(event,callback);
	} else {
		throw missingAnalyticsNodeError
	}
}
{{/type}}

const clientAPI = {
  /**
	 * Updates the run-time configuration of this Typewriter client.
	 * This function must be called with a configured analytics-node instance before firing
	 * any analytics calls, or else a `missingAnalyticsNodeError` error will be thrown.
	 *
	 * @param {TypewriterOptions} options - the options to upsert
	 *
	 * @typedef {Object} TypewriterOptions
	 * @property {Analytics} analytics - Underlying analytics instance where analytics
	 * 		calls are forwarded on to.
	 * @property {Function} [onViolation] - Handler fired when if an event does not match its spec. This handler does not fire in
	 * 		production mode, because it requires inlining the full JSON Schema spec for each event in your Tracking Plan. By default,
	 * 		it will throw errors if NODE_ENV="test" so that tests will fail if a message does not match the spec. Otherwise, errors
	 * 		will be logged to stderr.
	 */
	setTypewriterOptions,

  {{#type}}
  /**
   * Fires a '{{eventName}}' track call.
   * {{description}}
   * 
   * @param {{{typeName}}} props - The analytics properties that will be sent to Segment.
   * @param {Object} [options] - A dictionary of options. For example, enable or disable specific destinations for the call.
   * @param {Function} [callback] - An optional callback called after a short timeout after the analytics
   * 	call is fired.
   */
  {{functionName}},
  {{/type}}
};

export default new Proxy<typeof clientAPI>(clientAPI, {
  get(target, method) {
    if (typeof method === 'string' && target.hasOwnProperty(method)) {
      return target[method as keyof typeof clientAPI];
    }

    return () => {
      console.warn(`⚠️  You made an analytics call (${String(method)}) that can't be found. Either:
     a) Re-generate your typewriter client: \`npx typewriter\`
     b) Add it to your Tracking Plan: https://app.segment.com/segment-oscb/protocols/tracking-plans/rs_1zTHJU9fd5mt7cndWnd4PgJbMCE`);
     const a = analytics()
     if (a) {
       a.track(
         withTypewriterContext({
           event: 'Unknown Analytics Call Fired',
           properties: {
             method,
           },
           userId: 'typewriter',
         })
       )
     }
    };
  },
});
