import { z } from 'zod'
import { ErrorCode } from '@activepieces/core-utils'
import { BaseModelSchema, Nullable } from '@activepieces/core-utils'
import { isNil, truncateString } from '@activepieces/core-utils'
import { ExecutionState, RunInternalError } from './execution/execution-output'
import { FlowRunStatus } from './execution/flow-execution'

export const FAILED_STEP_MESSAGE_MAX_LENGTH = 700

export function truncateFailedStepMessage(
    failedStep: FailedStep | undefined,
): FailedStep | undefined {
    if (isNil(failedStep) || isNil(failedStep.message)) {
        return failedStep
    }
    const truncated = truncateString({
        value: failedStep.message,
        maxLength: FAILED_STEP_MESSAGE_MAX_LENGTH,
    })
    if (truncated === failedStep.message) {
        return failedStep
    }
    return { ...failedStep, message: truncated }
}

export const PARENT_RUN_ID_HEADER = 'ap-parent-run-id'
export const FAIL_PARENT_ON_FAILURE_HEADER = 'ap-fail-parent-on-failure'
export const RAW_PAYLOAD_HEADER = 'ap-raw-payload'

export enum RunEnvironment {
    PRODUCTION = 'PRODUCTION',
    TESTING = 'TESTING',
}

export enum FlowRetryStrategy {
    ON_LATEST_VERSION = 'ON_LATEST_VERSION',
    FROM_FAILED_STEP = 'FROM_FAILED_STEP',
}

export type FlowRetryPayload = {
    strategy: FlowRetryStrategy
}

export const FailedStep = z.object({
    name: z.string(),
    displayName: z.string(),
    message: z.string().optional(),
})
export type FailedStep = z.infer<typeof FailedStep>

export const FlowRun = z.object({
    ...BaseModelSchema,
    projectId: z.string(),
    flowId: z.string(),
    parentRunId: z.string().optional(),
    failParentOnFailure: z.boolean(),
    triggeredBy: z.string().optional(),
    tags: z.array(z.string()).optional(),
    flowVersionId: z.string(),
    flowVersion: z.object({
        displayName: z.string().optional(),
    }).optional(),
    logsFileId: Nullable(z.string()),
    status: z.nativeEnum(FlowRunStatus),
    startTime: z.string().nullish(),
    finishTime: z.string().nullish(),
    environment: z.nativeEnum(RunEnvironment),
    // The steps data may be missing if the flow has not started yet,
    // or if the run is older than AP_EXECUTION_DATA_RETENTION_DAYS and its execution data has been purged.
    steps: Nullable(z.record(z.string(), z.unknown())),
    failedStep: FailedStep.optional(),
    stepNameToTest: z.string().optional(),
    archivedAt: Nullable(z.string()),
    stepsCount: z.number().optional(),
    // Populated only for platform admins on INTERNAL_ERROR runs; stripped from the response otherwise.
    internalError: RunInternalError.optional(),
})

export type FlowRun = z.infer<typeof FlowRun> & ExecutionState

export type FlowRunWithRetryError = FlowRun & {
    error?: { errorCode: ErrorCode, errorMessage: string }
}
