import type { DeepReadonly } from "ts-essentials"
import type { TranslationsRSIProps } from "./translationsRSIProps"
import type { Columns } from "./steps/MatchColumnsStep/types"
import type { StepState } from "./steps/UploadFlow"

// Meta type for row data with unique index
export type Meta = { __index: string }

export type SubmitOptions = {
  targetEnvironment: "dev" | "prod"
  useTestDataSource: boolean
  skipApiSubmission?: boolean
  showNewProduct?: boolean
}

export type RsiProps<T extends string> = {
  // Is modal visible.
  isOpen: boolean
  // callback when RSI is closed before final submit
  onClose: () => void
  // Field description for requested data
  fields: Fields<T>
  // Initial state of component that will be rendered on load
  initialStepState?: StepState
  // Runs after file upload step, receives and returns raw sheet data
  uploadStepHook?: (data: RawData[]) => Promise<RawData[]>
  // Runs after header selection step, receives and returns raw sheet data
  selectHeaderStepHook?: (headerValues: RawData, data: RawData[]) => Promise<{ headerValues: RawData; data: RawData[] }>
  // Runs once before validation step, used for data mutations and if you want to change how columns were matched
  matchColumnsStepHook?: (table: Data<T>[], rawData: RawData[], columns: Columns<T>) => Promise<Data<T>[]>
  // Runs after column matching and on entry change
  rowHook?: RowHook<T>
  // Runs after column matching and on entry change
  tableHook?: TableHook<T>
  // Function called after user finishes the flow. You can return a promise that will be awaited.
  onSubmit: (data: Result<T>, file: File, options: SubmitOptions) => void | Promise<any>
  // Allows submitting with errors. Default: true
  allowInvalidSubmit?: boolean
  // Enable navigation in stepper component and show back button. Default: false
  isNavigationEnabled?: boolean
  // Translations for each text
  translations?: TranslationsRSIProps
  // Specifies maximum number of rows for a single import
  maxRecords?: number
  // Maximum upload filesize (in bytes)
  maxFileSize?: number
  // Automatically map imported headers to specified fields if possible. Default: true
  autoMapHeaders?: boolean
  // Automatically map select values to options if possible. Default: false
  autoMapSelectValues?: boolean
  // Maximum distance for fuzzy string matching. Default: 2
  autoMapDistance?: number
  // Date format for parsing dates. Default: "yyyy-mm-dd"
  dateFormat?: string
  // Whether to parse raw values. Default: true
  parseRaw?: boolean
  // Use for right-to-left (RTL) support
  rtl?: boolean
}

export type RawData = (string | undefined)[]

export type DataValue = string | boolean | string[] | undefined

export type Data<T extends string> = Partial<Record<T, DataValue>> & {
  supplier?: string
  company?: string
  line?: string
  subline?: string
}

// Data model RSI uses for spreadsheet imports
export type Fields<T extends string> = DeepReadonly<Field<T>[]>

export type Field<T extends string> = {
  // UI-facing field label
  label: string
  // Field's unique identifier
  key: T
  // UI-facing additional information displayed via tooltip and ? icon
  description?: string
  // Alternate labels used for fields' auto-matching, e.g. "fname" -> "firstName"
  alternateMatches?: string[]
  // Validations used for field entries
  validations?: Validation[]
  // Field entry component
  fieldType: FieldType
  // UI-facing values shown to user as field examples pre-upload phase
  example?: string
  width?: number
  disabled?: boolean
  onChange?: (value: any, additionalData?: any) => void
}

export type FieldType =
  | {
      type: "input" | "multi-input";
      multiline?: boolean;
      price?: boolean;
      separator?: string;
    }
  | {
      type: "select" | "multi-select";
      options: readonly SelectOption[];
      separator?: string;
    }
  | {
      type: "checkbox";
      booleanMatches?: { readonly [key: string]: boolean };
    };

export type SelectOption = {
  // UI-facing option label
  label: string
  // Field entry matching criteria as well as select output
  value: string
}

export type Input = {
  type: "input"
}

export type MultiInput = {
  type: "multi-input"
  separator?: string // Optional separator for parsing multiple values, defaults to comma
}

export type MultiSelect = {
  type: "multi-select"
  options: SelectOption[]
  separator?: string // Optional separator for parsing multiple values, defaults to comma
}

export type Validation = RequiredValidation | UniqueValidation | RegexValidation

export type RequiredValidation = {
  rule: "required"
  errorMessage?: string
  level?: ErrorLevel
}

export type UniqueValidation = {
  rule: "unique"
  allowEmpty?: boolean
  errorMessage?: string
  level?: ErrorLevel
}

export type RegexValidation = {
  rule: "regex"
  value: string
  flags?: string
  errorMessage: string
  level?: ErrorLevel
}

export type RowHook<T extends string> = (
  row: Data<T>,
  rowIndex: number,
  allRows: Data<T>[],
) => Promise<Meta> | Meta

export type TableHook<T extends string> = (rows: Data<T>[]) => Promise<Meta[]> | Meta[]

export type ErrorLevel = "info" | "warning" | "error"

export type Info = {
  message: string
  level: ErrorLevel
}

export enum ErrorSources {
  Row = 'row',
  Table = 'table',
  Api = 'api',
  Upc = 'upc'
}

// Add a standardized error type enum
export enum ErrorType {
  Required = 'required',
  Regex = 'regex',
  Unique = 'unique',
  Custom = 'custom',
  Api = 'api'
}

// Add a standardized error interface
export interface ValidationError {
  message: string;
  level: 'info' | 'warning' | 'error';
  source?: ErrorSources;
  type: ErrorType;
}

/*
   Source determines whether the error is from the full table or row validation
   Table validation is tableHook and "unique" validation
   Row validation is rowHook and all other validations
   It is used to determine how errors should be stored in the validationErrors Map
*/
export type InfoWithSource = Info & {
  source: ErrorSources;
  type: ErrorType;
}

// Legacy error types used by dataMutations utility
export type FieldError = { [key: string]: InfoWithSource }
export type Errors = { [id: string]: FieldError }

export type Result<T extends string> = {
  validData: Data<T>[]
  invalidData: Data<T>[]
  all: Data<T>[]
}
