export type Category = {
  category: string;
  description: string;
};

export enum BlockCostType {
  RUN = "run",
  BYTE = "byte",
  SECOND = "second",
}

export type BlockCost = {
  cost_amount: number;
  cost_type: BlockCostType;
  cost_filter: Record<string, any>;
};

/* Mirror of backend/blocks/_base.py:Block */
export type Block = {
  id: string;
  name: string;
  description: string;
  categories: Category[];
  inputSchema: BlockIORootSchema;
  outputSchema: BlockIORootSchema;
  staticOutput: boolean;
  uiType: BlockUIType;
  costs: BlockCost[];
};

export type BlockIORootSchema = {
  type: "object";
  properties: Record<string, BlockIOSubSchema>;
  required?: (keyof BlockIORootSchema["properties"])[];
  additionalProperties?: { type: string };
};

export type BlockIOSubSchema =
  | BlockIOSimpleTypeSubSchema
  | BlockIOCombinedTypeSubSchema;

export type BlockIOSubType = BlockIOSimpleTypeSubSchema["type"];

export type BlockIOSimpleTypeSubSchema =
  | BlockIOObjectSubSchema
  | BlockIOCredentialsSubSchema
  | BlockIOKVSubSchema
  | BlockIOArraySubSchema
  | BlockIOTableSubSchema
  | BlockIOStringSubSchema
  | BlockIONumberSubSchema
  | BlockIOBooleanSubSchema
  | BlockIONullSubSchema;

export enum DataType {
  SHORT_TEXT = "short-text",
  LONG_TEXT = "long-text",
  NUMBER = "number",
  DATE = "date",
  TIME = "time",
  DATE_TIME = "date-time",
  FILE = "file",
  SELECT = "select",
  MULTI_SELECT = "multi-select",
  BOOLEAN = "boolean",
  CREDENTIALS = "credentials",
  OBJECT = "object",
  KEY_VALUE = "key-value",
  ARRAY = "array",
  TABLE = "table",
  GOOGLE_DRIVE_PICKER = "google-drive-picker",
}

export type BlockIOSubSchemaMeta = {
  title?: string;
  description?: string;
  placeholder?: string;
  advanced?: boolean;
  depends_on?: string[];
  hidden?: boolean;
};

export type BlockIOObjectSubSchema = BlockIOSubSchemaMeta & {
  type: "object";
  properties: Record<string, BlockIOSubSchema>;
  const?: Record<keyof BlockIOObjectSubSchema["properties"], any>;
  default?: Record<keyof BlockIOObjectSubSchema["properties"], any>;
  required?: (keyof BlockIOObjectSubSchema["properties"])[];
  secret?: boolean;
};

export type BlockIOKVSubSchema = BlockIOSubSchemaMeta & {
  type: "object";
  additionalProperties?: { type: "string" | "number" | "integer" };
  const?: Record<string, string | number>;
  default?: Record<string, string | number>;
  secret?: boolean;
};

export type BlockIOArraySubSchema = BlockIOSubSchemaMeta & {
  type: "array";
  items?: BlockIOSimpleTypeSubSchema;
  const?: Array<string>;
  default?: Array<string>;
  secret?: boolean;
};

export type GoogleDriveFile = {
  id: string;
  name?: string;
  mimeType?: string;
  url?: string;
  iconUrl?: string;
  isFolder?: boolean;
};

/** Valid view types for Google Drive Picker - matches backend AttachmentView */
export type AttachmentView =
  | "DOCS"
  | "DOCUMENTS"
  | "SPREADSHEETS"
  | "PRESENTATIONS"
  | "DOCS_IMAGES"
  | "FOLDERS";

export type GoogleDrivePickerConfig = {
  multiselect?: boolean;
  allow_folder_selection?: boolean;
  allowed_views?: AttachmentView[];
  allowed_mime_types?: string[];
  scopes?: string[];
  /**
   * Auto-credentials configuration for combined picker + credentials fields.
   * When present, the picker will include _credentials_id in the output.
   */
  auto_credentials?: {
    provider: string;
    type: string;
    scopes?: string[];
    kwarg_name: string;
  };
};

/**
 * Schema for Google Drive Picker input fields.
 * When multiselect=false: type="object" (single GoogleDriveFile)
 * When multiselect=true: type="array" with items={ type="object" } (array of GoogleDriveFile)
 */
export type GoogleDrivePickerSchema = BlockIOSubSchemaMeta & {
  type: "object" | "array";
  format: "google-drive-picker";
  google_drive_picker_config?: GoogleDrivePickerConfig;
};

// Table cell values are typically primitives
export type TableCellValue = string | number | boolean | null;

export type TableRow = Record<string, TableCellValue>;

export type BlockIOTableSubSchema = BlockIOSubSchemaMeta & {
  type: "array";
  format: "table";
  items: BlockIOObjectSubSchema;
  const?: TableRow[];
  default?: TableRow[];
  secret?: boolean;
};

export type BlockIOStringSubSchema = BlockIOSubSchemaMeta & {
  type: "string";
  enum?: string[];
  secret?: true;
  const?: string;
  default?: string;
  format?: string;
  maxLength?: number;
};

export type BlockIONumberSubSchema = BlockIOSubSchemaMeta & {
  type: "integer" | "number";
  const?: number;
  default?: number;
  secret?: boolean;
};

export type BlockIOBooleanSubSchema = BlockIOSubSchemaMeta & {
  type: "boolean";
  const?: boolean;
  default?: boolean;
  secret?: boolean;
};

export type CredentialsType =
  | "api_key"
  | "oauth2"
  | "user_password"
  | "host_scoped";

export type Credentials =
  | APIKeyCredentials
  | OAuth2Credentials
  | UserPasswordCredentials
  | HostScopedCredentials;

// --8<-- [start:BlockIOCredentialsSubSchema]
// Provider names are now dynamic and fetched from the API
// This allows for SDK-registered providers without hardcoding
export type CredentialsProviderName = string;

// For backward compatibility, we'll keep PROVIDER_NAMES but it should be
// populated dynamically from the API. This is a placeholder that will be
// replaced with actual values from the /api/integrations/providers endpoint
export const PROVIDER_NAMES = {} as Record<string, string>;
// --8<-- [end:BlockIOCredentialsSubSchema]

export type BlockIOCredentialsSubSchema = BlockIOObjectSubSchema & {
  /* Mirror of backend/data/model.py:CredentialsFieldSchemaExtra */
  credentials_provider: CredentialsProviderName[];
  credentials_scopes?: string[];
  credentials_types: Array<CredentialsType>;
  discriminator?: string;
  discriminator_mapping?: Record<string, CredentialsProviderName>;
  discriminator_values?: any[];
  secret?: boolean;
};

export type BlockIONullSubSchema = BlockIOSubSchemaMeta & {
  type: "null";
  const?: null;
  secret?: boolean;
};

// At the time of writing, combined schemas only occur on the first nested level in a
// block schema. It is typed this way to make the use of these objects less tedious.
type BlockIOCombinedTypeSubSchema = BlockIOSubSchemaMeta & {
  type?: never;
  const?: never;
} & (
    | {
        allOf: [BlockIOSimpleTypeSubSchema];
        secret?: boolean;
      }
    | {
        anyOf: BlockIOSimpleTypeSubSchema[];
        default?: string | number | boolean | null;
        secret?: boolean;
        format?: string; // For table format and other formats on anyOf schemas
      }
    | BlockIOOneOfSubSchema
    | BlockIODiscriminatedOneOfSubSchema
  );

export type BlockIOOneOfSubSchema = {
  oneOf: BlockIOSimpleTypeSubSchema[];
  default?: string | number | boolean | null;
  secret?: boolean;
};

export type BlockIODiscriminatedOneOfSubSchema = {
  oneOf: BlockIOObjectSubSchema[];
  discriminator: {
    propertyName: string;
    mapping: Record<string, BlockIOObjectSubSchema>;
  };
  default?: Record<string, any>;
  secret?: boolean;
};

export type NodeCreatable = {
  id: string;
  block_id: string;
  input_default: Record<string, any>;
  metadata: {
    position: { x: number; y: number };
    [key: string]: any;
  };
};

/* Mirror of backend/data/graph.py:Node */
export type Node = NodeCreatable & {
  input_links: Link[];
  output_links: Link[];
  webhook_id?: string | null;
};

/* Mirror of backend/data/graph.py:Link */
export type Link = {
  id: string;
  source_id: string;
  sink_id: string;
  source_name: string;
  sink_name: string;
  is_static: boolean;
};

export type LinkCreatable = Omit<Link, "id" | "is_static"> & {
  id?: string;
};

/* Mirror of backend/data/execution.py:GraphExecutionMeta */
export type GraphExecutionMeta = {
  id: GraphExecutionID;
  user_id: UserID;
  graph_id: GraphID;
  graph_version: number;
  inputs: Record<string, any> | null;
  credential_inputs: Record<string, CredentialsMetaInput> | null;
  nodes_input_masks: Record<string, Record<string, any>> | null;
  preset_id: LibraryAgentPresetID | null;
  status:
    | "QUEUED"
    | "RUNNING"
    | "COMPLETED"
    | "TERMINATED"
    | "FAILED"
    | "INCOMPLETE"
    | "REVIEW";
  started_at: Date | null;
  ended_at: Date | null;
  stats: {
    error: string | null;
    cost: number;
    duration: number;
    duration_cpu_only: number;
    node_exec_time: number;
    node_exec_time_cpu_only: number;
    node_exec_count: number;
    activity_status: string | null;
    [key: string]: any;
  } | null;
};

export type GraphExecutionID = Brand<string, "GraphExecutionID">;

/* Mirror of backend/data/execution.py:GraphExecution */
export type GraphExecution = Omit<GraphExecutionMeta, "inputs"> & {
  inputs: Record<string, any>;
  outputs: Record<string, Array<any>>;
  node_executions?: NodeExecutionResult[];
};

export type GraphExecutionsResponse = {
  executions: GraphExecutionMeta[];
  pagination: Pagination;
};

/* Mirror of backend/data/graph.py:GraphMeta */
export type GraphMeta = {
  id: GraphID;
  user_id: UserID;
  version: number;
  is_active: boolean;
  created_at: Date;
  name: string;
  description: string;
  instructions?: string | null;
  recommended_schedule_cron: string | null;
  forked_from_id?: GraphID | null;
  forked_from_version?: number | null;
};

export type GraphID = Brand<string, "GraphID">;

/* Derived from backend/data/graph.py:Graph._generate_schema() */
export type GraphInputSchema = {
  type: "object";
  properties: Record<string, GraphInputSubSchema>;
  required: (keyof GraphInputSchema["properties"])[];
};
export type GraphInputSubSchema = GraphOutputSubSchema &
  (
    | { type?: never; default: any | null } // AgentInputBlock (generic Any type)
    | { type: "string"; format: "short-text"; default: string | null } // AgentShortTextInputBlock
    | { type: "string"; format: "long-text"; default: string | null } // AgentLongTextInputBlock
    | { type: "integer"; default: number | null } // AgentNumberInputBlock
    | { type: "string"; format: "date"; default: string | null } // AgentDateInputBlock
    | { type: "string"; format: "time"; default: string | null } // AgentTimeInputBlock
    | { type: "string"; format: "file"; default: string | null } // AgentFileInputBlock
    | { type: "string"; enum: string[]; default: string | null } // AgentDropdownInputBlock
    | { type: "boolean"; default: boolean } // AgentToggleInputBlock
    | {
        // AgentTableInputBlock
        type: "array";
        format: "table";
        items: {
          type: "object";
          properties: Record<string, { type: "string" }>;
        };
        default: Array<Record<string, string>> | null;
      }
    | {
        // AgentGoogleDriveFileInputBlock
        type: "object";
        format: "google-drive-picker";
        google_drive_picker_config?: GoogleDrivePickerConfig;
        default: GoogleDriveFile | null;
      }
  );
export type GraphOutputSchema = {
  type: "object";
  properties: Record<string, GraphOutputSubSchema>;
  required: (keyof GraphOutputSchema["properties"])[];
};
export type GraphOutputSubSchema = {
  // TODO: typed outputs based on the incoming edges?
  title: string;
  description?: string;
  advanced: boolean;
  secret: boolean;
};

export type CredentialsInputSchema = {
  type: "object";
  properties: Record<string, BlockIOCredentialsSubSchema>;
  required?: (keyof CredentialsInputSchema["properties"])[];
};

/* Mirror of backend/data/graph.py:GraphTriggerInfo */
export type GraphTriggerInfo = {
  provider: CredentialsProviderName;
  config_schema: BlockIORootSchema;
  credentials_input_name: string | null;
};

/* Mirror of backend/data/graph.py:Graph */
export type Graph = GraphMeta & {
  nodes: Node[];
  links: Link[];
  sub_graphs: Omit<Graph, "sub_graphs">[]; // Flattened sub-graphs
  input_schema: GraphInputSchema;
  output_schema: GraphOutputSchema;
  credentials_input_schema: CredentialsInputSchema;
} & (
    | {
        has_external_trigger: true;
        trigger_setup_info: GraphTriggerInfo;
      }
    | {
        has_external_trigger: false;
        trigger_setup_info: null;
      }
  );

export type GraphUpdateable = Omit<
  Graph,
  | "user_id"
  | "version"
  | "created_at"
  | "is_active"
  | "nodes"
  | "links"
  | "sub_graphs"
  | "input_schema"
  | "output_schema"
  | "credentials_input_schema"
  | "has_external_trigger"
  | "trigger_setup_info"
> & {
  version?: number;
  is_active?: boolean;
  nodes: NodeCreatable[];
  links: LinkCreatable[];
  input_schema?: GraphInputSchema;
  output_schema?: GraphOutputSchema;
};

export type GraphCreatable = _GraphCreatableInner & {
  sub_graphs?: _GraphCreatableInner[]; // Flattened sub-graphs
};
type _GraphCreatableInner = Omit<GraphUpdateable, "id"> & { id?: string };

/* Mirror of backend/data/execution.py:NodeExecutionResult */
export type NodeExecutionResult = {
  graph_id: GraphID;
  graph_version: number;
  graph_exec_id: GraphExecutionID;
  node_exec_id: string;
  node_id: string;
  block_id: string;
  status:
    | "INCOMPLETE"
    | "QUEUED"
    | "RUNNING"
    | "COMPLETED"
    | "TERMINATED"
    | "FAILED"
    | "REVIEW";
  input_data: Record<string, any>;
  output_data: Record<string, Array<any>>;
  add_time: Date;
  queue_time?: Date;
  start_time?: Date;
  end_time?: Date;
};

/* Structured validation error types for graph execution */
export type GraphValidationErrorResponse = {
  detail: {
    type: "validation_error";
    message: string;
    node_errors: Record<string, Record<string, string>>;
  };
};

/* *** LIBRARY *** */

/* Mirror of backend/api/features/library/model.py:LibraryAgent */
export type LibraryAgent = {
  id: LibraryAgentID;
  graph_id: GraphID;
  graph_version: number;
  image_url: string | null;
  creator_name: string;
  creator_image_url: string;
  status: AgentStatus;
  updated_at: Date;
  name: string;
  description: string;
  instructions?: string | null;
  input_schema: GraphInputSchema;
  output_schema: GraphOutputSchema;
  credentials_input_schema: CredentialsInputSchema;
  new_output: boolean;
  can_access_graph: boolean;
  is_favorite: boolean;
  is_latest_version: boolean;
  recommended_schedule_cron: string | null;
} & (
  | {
      has_external_trigger: true;
      trigger_setup_info: GraphTriggerInfo;
    }
  | {
      has_external_trigger: false;
      trigger_setup_info: null;
    }
);

export type LibraryAgentID = Brand<string, "LibraryAgentID">;

export enum AgentStatus {
  COMPLETED = "COMPLETED",
  HEALTHY = "HEALTHY",
  WAITING = "WAITING",
  ERROR = "ERROR",
}

export type LibraryAgentResponse = {
  agents: LibraryAgent[];
  pagination: Pagination;
};

export type LibraryAgentPreset = {
  id: LibraryAgentPresetID;
  created_at: Date;
  updated_at: Date;
  graph_id: GraphID;
  graph_version: number;
  inputs: Record<string, any>;
  credentials: Record<string, CredentialsMetaInput>;
  name: string;
  description: string;
  is_active: boolean;
} & (
  | {
      webhook_id: string;
      webhook: Webhook;
    }
  | {
      webhook_id?: undefined;
      webhook?: undefined;
    }
);

export type LibraryAgentPresetID = Brand<string, "LibraryAgentPresetID">;

export type LibraryAgentPresetResponse = {
  presets: LibraryAgentPreset[];
  pagination: Pagination;
};

export type LibraryAgentPresetCreatable = Omit<
  LibraryAgentPreset,
  "id" | "created_at" | "updated_at" | "is_active"
> & {
  is_active?: boolean;
};

export type LibraryAgentPresetCreatableFromGraphExecution = Omit<
  LibraryAgentPresetCreatable,
  "graph_id" | "graph_version" | "inputs" | "credentials"
> & {
  graph_execution_id: GraphExecutionID;
};

export type LibraryAgentPresetUpdatable = Partial<
  Omit<LibraryAgentPresetCreatable, "graph_id" | "graph_version">
>;

export enum LibraryAgentSortEnum {
  CREATED_AT = "createdAt",
  UPDATED_AT = "updatedAt",
}

/* *** CREDENTIALS *** */

/* Mirror of backend/api/features/integrations/router.py:CredentialsMetaResponse */
export type CredentialsMetaResponse = {
  id: string;
  provider: CredentialsProviderName;
  type: CredentialsType;
  title?: string;
  scopes?: Array<string>;
  username?: string;
  host?: string;
  is_system?: boolean;
  is_managed?: boolean;
};

/* Mirror of backend/api/features/integrations/router.py:CredentialsDeletionResponse */
export type CredentialsDeleteResponse = {
  deleted: true;
  revoked: boolean | null;
};

/* Mirror of backend/api/features/integrations/router.py:CredentialsDeletionNeedsConfirmationResponse */
export type CredentialsDeleteNeedConfirmationResponse = {
  deleted: false;
  need_confirmation: true;
  message: string;
};

/* Mirror of backend/data/model.py:CredentialsMetaInput */
export type CredentialsMetaInput = {
  id: string;
  type: CredentialsType;
  title?: string | null;
  provider: string;
};

/* Mirror of backend/backend/data/model.py:_BaseCredentials */
type BaseCredentials = {
  id: string;
  type: CredentialsType;
  title?: string;
  provider: CredentialsProviderName;
};

/* Mirror of backend/backend/data/model.py:OAuth2Credentials */
export type OAuth2Credentials = BaseCredentials & {
  type: "oauth2";
  scopes: string[];
  username?: string;
  access_token: string;
  access_token_expires_at?: number;
  refresh_token?: string;
  refresh_token_expires_at?: number;
  metadata: Record<string, any>;
};

/* Mirror of backend/backend/data/model.py:APIKeyCredentials */
export type APIKeyCredentials = BaseCredentials & {
  type: "api_key";
  title: string;
  api_key: string;
  expires_at?: number;
};

export type UserPasswordCredentials = BaseCredentials & {
  type: "user_password";
  title: string;
  username: string;
  password: string;
};

/* Mirror of backend/backend/data/model.py:HostScopedCredentials */
export type HostScopedCredentials = BaseCredentials & {
  type: "host_scoped";
  title: string;
  host: string;
  headers: Record<string, string>;
};

// Mirror of backend/backend/data/notifications.py:NotificationType
export type NotificationType =
  | "AGENT_RUN"
  | "ZERO_BALANCE"
  | "LOW_BALANCE"
  | "BLOCK_EXECUTION_FAILED"
  | "CONTINUOUS_AGENT_ERROR"
  | "DAILY_SUMMARY"
  | "WEEKLY_SUMMARY"
  | "MONTHLY_SUMMARY"
  | "AGENT_APPROVED"
  | "AGENT_REJECTED";

// Mirror of backend/backend/data/notifications.py:NotificationPreference
export type NotificationPreferenceDTO = {
  email: string;
  preferences: { [key in NotificationType]: boolean };
  daily_limit: number;
};

export type NotificationPreference = NotificationPreferenceDTO & {
  user_id: UserID;
  emails_sent_today: number;
  last_reset_date: Date;
};

/* Mirror of backend/data/integrations.py:Webhook */
export type Webhook = {
  id: string;
  url: string;
  provider: CredentialsProviderName;
  credentials_id: string; // empty string if not appicable
  webhook_type: string;
  resource: string; // empty string if not appicable
  events: string[];
  secret: string;
  config: Record<string, any>;
  provider_webhook_id?: string;
};

export type User = {
  id: UserID;
  email: string;
};

export type UserID = Brand<string, "UserID">;

export enum BlockUIType {
  STANDARD = "Standard",
  INPUT = "Input",
  OUTPUT = "Output",
  NOTE = "Note",
  WEBHOOK = "Webhook",
  WEBHOOK_MANUAL = "Webhook (manual)",
  AGENT = "Agent",
  AI = "AI",
  AYRSHARE = "Ayrshare",
  MCP_TOOL = "MCP Tool",
}

export enum SpecialBlockID {
  AGENT = "e189baac-8c20-45a1-94a7-55177ea42565",
  MCP_TOOL = "a0a4b1c2-d3e4-4f56-a7b8-c9d0e1f2a3b4",
  TOOL_ORCHESTRATOR = "3b191d9f-356f-482d-8238-ba04b6d18381",
  OUTPUT = "363ae599-353e-4804-937e-b2ee3cef3da4",
}

export type AnalyticsMetrics = {
  metric_name: string;
  metric_value: number;
  data_string: string;
};

export type AnalyticsDetails = {
  type: string;
  data: Record<string, any>;
  index: string;
};

export type Pagination = {
  total_items: number;
  total_pages: number;
  current_page: number;
  page_size: number;
};

/* Mirror of backend/executor/scheduler.py:GraphExecutionJobInfo */
export type Schedule = {
  id: ScheduleID;
  name: string;
  cron: string;
  user_id: UserID;
  graph_id: GraphID;
  graph_version: number;
  input_data: Record<string, any>;
  input_credentials: Record<string, CredentialsMetaInput>;
  next_run_time: Date;
  timezone: string;
};

export type ScheduleID = Brand<string, "ScheduleID">;

/* Mirror of backend/api/features/v1.py:ScheduleCreationRequest */
export type ScheduleCreatable = {
  graph_id: GraphID;
  graph_version: number;
  name: string;
  cron: string;
  inputs: Record<string, any>;
  credentials?: Record<string, CredentialsMetaInput>;
};

// API Key Types

export enum APIKeyPermission {
  EXECUTE_GRAPH = "EXECUTE_GRAPH",
  READ_GRAPH = "READ_GRAPH",
  EXECUTE_BLOCK = "EXECUTE_BLOCK",
  READ_BLOCK = "READ_BLOCK",
}

export enum APIKeyStatus {
  ACTIVE = "ACTIVE",
  REVOKED = "REVOKED",
  SUSPENDED = "SUSPENDED",
}

export interface APIKey {
  id: string;
  name: string;
  prefix: string;
  postfix: string;
  status: APIKeyStatus;
  scopes: APIKeyPermission[];
  created_at: string;
  last_used_at?: string;
  revoked_at?: string;
  description?: string;
}

export interface CreateAPIKeyResponse {
  api_key: APIKey;
  plain_text_key: string;
}

export interface CreditTransaction {
  transaction_key: string;
  transaction_time: Date;
  transaction_type: CreditTransactionType;
  amount: number;
  running_balance: number;
  current_balance: number;
  description: string;
  usage_graph_id: GraphID;
  usage_execution_id: GraphExecutionID;
  usage_node_count: number;
  usage_starting_time: Date;
  user_id: string;
  user_email: string | null;
  reason: string | null;
  admin_email: string | null;
  extra_data: string | null;
}

export interface TransactionHistory {
  transactions: CreditTransaction[];
  next_transaction_time: Date | null;
}

export interface RefundRequest {
  id: string;
  user_id: UserID;
  transaction_key: string;
  amount: number;
  reason: string;
  result: string | null;
  status: string;
  created_at: Date;
  updated_at: Date;
}

export type OnboardingStep =
  // Introductory onboarding (Library)
  | "WELCOME"
  | "USAGE_REASON"
  | "INTEGRATIONS"
  | "AGENT_CHOICE"
  | "AGENT_NEW_RUN"
  | "AGENT_INPUT"
  | "CONGRATS"
  // First Wins
  | "VISIT_COPILOT"
  | "GET_RESULTS"
  | "MARKETPLACE_VISIT"
  | "MARKETPLACE_ADD_AGENT"
  | "MARKETPLACE_RUN_AGENT"
  | "BUILDER_SAVE_AGENT"
  // Consistency Challenge
  | "RE_RUN_AGENT"
  | "SCHEDULE_AGENT"
  | "RUN_AGENTS"
  | "RUN_3_DAYS"
  // The Pro Playground
  | "TRIGGER_WEBHOOK"
  | "RUN_14_DAYS"
  | "RUN_AGENTS_100"
  // No longer used but tracked
  | "BUILDER_OPEN"
  | "BUILDER_RUN_AGENT";

export interface UserOnboarding {
  completedSteps: OnboardingStep[];
  walletShown: boolean;
  notified: OnboardingStep[];
  rewardedFor: OnboardingStep[];
  usageReason: string | null;
  integrations: string[];
  otherIntegrations: string | null;
  selectedStoreListingVersionId: string | null;
  agentInput: Record<string, string | number> | null;
  onboardingAgentExecutionId: GraphExecutionID | null;
  lastRunAt: Date | null;
  consecutiveRunDays: number;
  agentRuns: number;
}

export interface OnboardingNotificationPayload {
  type: "onboarding";
  event: "step_completed" | "increment_runs";
  step: OnboardingStep | null;
}

export type WebSocketNotification =
  | OnboardingNotificationPayload
  | {
      type: string;
      event: string;
      [key: string]: unknown;
    };

/* *** UTILITIES *** */

/** Use branded types for IDs -> deny mixing IDs between different object classes */
export type Brand<T, Brand extends string> = T & {
  readonly [B in Brand as `__${B}_brand`]: never;
};

export interface OttoDocument {
  url: string;
  relevance_score: number;
}

export interface OttoResponse {
  answer: string;
  documents: OttoDocument[];
  success: boolean;
  error: boolean;
}

export interface OttoQuery {
  query: string;
  conversation_history: { query: string; response: string }[];
  message_id: string;
  include_graph_data: boolean;
  graph_id?: string;
}

export enum CreditTransactionType {
  TOP_UP = "TOP_UP",
  USAGE = "USAGE",
  GRANT = "GRANT",
  REFUND = "REFUND",
  CARD_CHECK = "CARD_CHECK",
}

export type UsersBalanceHistoryResponse = {
  history: CreditTransaction[];
  pagination: Pagination;
};

export type AddUserCreditsResponse = {
  new_balance: number;
  transaction_key: string;
};

const _stringFormatToDataTypeMap: Partial<Record<string, DataType>> = {
  date: DataType.DATE,
  time: DataType.TIME,
  file: DataType.FILE,
  "date-time": DataType.DATE_TIME,
  "short-text": DataType.SHORT_TEXT,
  "long-text": DataType.LONG_TEXT,
};

function _handleStringSchema(strSchema: BlockIOStringSubSchema): DataType {
  if (strSchema.format) {
    const type = _stringFormatToDataTypeMap[strSchema.format];
    if (type) return type;
  }
  if (strSchema.enum) return DataType.SELECT;
  if (strSchema.maxLength && strSchema.maxLength > 200)
    return DataType.LONG_TEXT;
  return DataType.SHORT_TEXT;
}

function _handleSingleTypeSchema(subSchema: BlockIOSubSchema): DataType {
  if (subSchema.type === "string") {
    return _handleStringSchema(subSchema as BlockIOStringSubSchema);
  }
  if (subSchema.type === "boolean") {
    return DataType.BOOLEAN;
  }
  if (subSchema.type === "number" || subSchema.type === "integer") {
    return DataType.NUMBER;
  }
  if (subSchema.type === "array") {
    // Check for table format first
    if ("format" in subSchema && subSchema.format === "table") {
      return DataType.TABLE;
    }
    /** Commented code below since we haven't yet support rendering of a multi-select with array { items: enum } type */
    // if ("items" in subSchema && subSchema.items && "enum" in subSchema.items) {
    //   return DataType.MULTI_SELECT; // array + enum => multi-select
    // }
    return DataType.ARRAY;
  }
  if (subSchema.type === "object") {
    if (
      ("additionalProperties" in subSchema && subSchema.additionalProperties) ||
      !("properties" in subSchema)
    ) {
      return DataType.KEY_VALUE; // if additionalProperties / no properties => key-value
    }
    if (
      Object.values(subSchema.properties).every(
        (prop) => prop.type === "boolean",
      )
    ) {
      return DataType.MULTI_SELECT; // if all props are boolean => multi-select
    }
    return DataType.OBJECT;
  }
  return DataType.SHORT_TEXT;
}

export function determineDataType(schema: BlockIOSubSchema): DataType {
  if ("allOf" in schema) {
    // If this happens, that is because Pydantic wraps $refs in an allOf if the
    // $ref has sibling schema properties (which isn't technically allowed),
    // so there will only be one item in allOf[].
    // this should NEVER happen though, as $refs are resolved server-side.
    console.warn(
      `Detected 'allOf' wrapper: ${schema}. Normalizing use ${schema.allOf[0]} instead.`,
    );
    schema = schema.allOf[0];
  }

  // Credentials override
  if ("credentials_provider" in schema) {
    return DataType.CREDENTIALS;
  }

  if (
    "google_drive_picker_config" in schema ||
    ("format" in schema && schema.format === "google-drive-picker")
  ) {
    return DataType.GOOGLE_DRIVE_PICKER;
  }

  // enum == SELECT
  if ("enum" in schema) {
    return DataType.SELECT;
  }

  // Handle anyOf => optional types (string|null, number|null, etc.)
  if ("anyOf" in schema) {
    // e.g. schema.anyOf might look like [{ type: "string", ... }, { type: "null" }]
    const types = schema.anyOf.map((sub) =>
      "type" in sub ? sub.type : undefined,
    );

    // (string | null)
    if (types.includes("string") && types.includes("null")) {
      const strSchema = schema.anyOf.find(
        (s) => s.type === "string",
      ) as BlockIOStringSubSchema;
      return _handleStringSchema(strSchema);
    }

    // (number|integer) & null
    if (
      (types.includes("number") || types.includes("integer")) &&
      types.includes("null")
    ) {
      // Just reuse our single-type logic for whichever is not null
      const numSchema = schema.anyOf.find(
        (s) => s.type === "number" || s.type === "integer",
      );
      if (numSchema) {
        return _handleSingleTypeSchema(numSchema);
      }
      return DataType.NUMBER; // fallback
    }

    // (array | null)
    if (types.includes("array") && types.includes("null")) {
      // Check for table format on the parent schema (where anyOf is)
      if ("format" in schema && schema.format === "table") {
        return DataType.TABLE;
      }

      const arrSchema = schema.anyOf.find((s) => s.type === "array");
      if (arrSchema) return _handleSingleTypeSchema(arrSchema);
      return DataType.ARRAY;
    }

    // (object | null)
    if (types.includes("object") && types.includes("null")) {
      const objSchema = schema.anyOf.find(
        (s) => s.type === "object",
      ) as BlockIOObjectSubSchema;
      if (objSchema) return _handleSingleTypeSchema(objSchema);
      return DataType.OBJECT;
    }
  }

  // oneOf + discriminator => user picks which variant => SELECT
  if ("oneOf" in schema && "discriminator" in schema && schema.discriminator) {
    return DataType.SELECT;
  }

  // Direct type
  if ("type" in schema) {
    return _handleSingleTypeSchema(schema);
  }

  // Fallback
  return DataType.SHORT_TEXT;
}
