type StoredPricingState<TRow> = {
  detailRows?: TRow[]
  projectCount?: unknown
} | null | undefined

type AsyncVoid = () => Promise<void> | void

type ScalePaneIndustryOptions = {
  readIndustryCode: () => Promise<string>
  setIndustryCode: (code: string) => void
}

const refreshIndustryCode = async (options?: ScalePaneIndustryOptions) => {
  if (!options) return
  const industryCode = await options.readIndustryCode()
  options.setIndustryCode(industryCode)
}

export const loadPricingScalePaneRows = async <TRow>(options: {
  industry?: ScalePaneIndustryOptions
  setProjectCount: (count: number) => void
  ensureFactorDefaultsLoaded: AsyncVoid
  shouldForceDefaultLoad: () => boolean
  buildContractDefaultRows: (targetProjectCount: number) => Promise<TRow[]>
  loadStoredState: () => Promise<StoredPricingState<TRow>>
  isMutipleService: boolean
  normalizeProjectCount: (value: unknown) => number
  inferProjectCountFromRows: (rows: TRow[]) => number
  buildRowsFromStoredState: (rows: TRow[]) => TRow[]
  buildEmptyRows: (targetProjectCount: number) => TRow[]
  getTargetProjectCount: () => number
  applyRows: (rows: TRow[]) => void
  afterApplyRows: AsyncVoid
  onError?: (error: unknown) => void
}) => {
  try {
    await refreshIndustryCode(options.industry)
    options.setProjectCount(1)

    await options.ensureFactorDefaultsLoaded()

    if (options.shouldForceDefaultLoad()) {
      options.applyRows(await options.buildContractDefaultRows(options.getTargetProjectCount()))
      await options.afterApplyRows()
      return
    }

    const data = await options.loadStoredState()
    if (data) {
      const storedRows = Array.isArray(data.detailRows) ? data.detailRows : []
      if (options.isMutipleService) {
        const storedProjectCount = options.normalizeProjectCount(data.projectCount)
        options.setProjectCount(storedProjectCount || options.inferProjectCountFromRows(storedRows))
      }
      options.applyRows(options.buildRowsFromStoredState(storedRows))
      await options.afterApplyRows()
      return
    }

    options.applyRows(await options.buildContractDefaultRows(options.getTargetProjectCount()))
    await options.afterApplyRows()
  } catch (error) {
    options.onError?.(error)
    options.applyRows(options.buildEmptyRows(options.getTargetProjectCount()))
    await options.afterApplyRows()
  }
}

export const importPricingScalePaneRows = async <TRow>(options: {
  industry?: ScalePaneIndustryOptions
  getTargetProjectCount: () => number
  buildContractDefaultRows: (targetProjectCount: number) => Promise<TRow[]>
  applyRows: (rows: TRow[]) => void
  saveRows: AsyncVoid
  onError?: (error: unknown) => void
}) => {
  try {
    await refreshIndustryCode(options.industry)
    options.applyRows(await options.buildContractDefaultRows(options.getTargetProjectCount()))
    await options.saveRows()
  } catch (error) {
    options.onError?.(error)
  }
}

export const clearPricingScalePaneRows = async <TRow>(options: {
  getTargetProjectCount: () => number
  buildEmptyRows: (targetProjectCount: number) => TRow[]
  applyRows: (rows: TRow[]) => void
  saveRows: AsyncVoid
  onError?: (error: unknown) => void
}) => {
  try {
    options.applyRows(options.buildEmptyRows(options.getTargetProjectCount()))
    await options.saveRows()
  } catch (error) {
    options.onError?.(error)
  }
}

export const applyPricingScaleProjectCountChange = async <TRow>(options: {
  nextValue: unknown
  setProjectCount: (count: number) => void
  isMutipleService: boolean
  currentRows: TRow[]
  cloneRows: (rows: TRow[]) => TRow[]
  normalizeProjectCount: (value: unknown) => number
  inferProjectCountFromRows: (rows: TRow[]) => number
  buildRowsForReducedCount: (rows: TRow[], targetProjectCount: number) => TRow[]
  buildRowsFromImportDefaultSource: (targetProjectCount: number) => Promise<TRow[]>
  getRowKey: (row: Partial<TRow> | undefined) => string
  getRowProjectIndex: (row: Partial<TRow>) => number
  mergeExistingRow: (defaultRow: TRow, existingRow: TRow) => TRow
  applyRows: (rows: TRow[]) => void
  afterApplyRows: AsyncVoid
}) => {
  const normalized = options.normalizeProjectCount(options.nextValue)
  options.setProjectCount(normalized)
  if (!options.isMutipleService) return

  const previousRows = options.cloneRows(options.currentRows)
  const previousProjectCount = options.inferProjectCountFromRows(previousRows)
  if (normalized === previousProjectCount) return

  if (normalized < previousProjectCount) {
    options.applyRows(options.buildRowsForReducedCount(previousRows, normalized))
    await options.afterApplyRows()
    return
  }

  const defaultRows = await options.buildRowsFromImportDefaultSource(normalized)
  const existingMap = new Map<string, TRow>()
  for (const row of previousRows) {
    const key = options.getRowKey(row)
    if (!key) continue
    existingMap.set(key, row)
  }

  options.applyRows(defaultRows.map(defaultRow => {
    const key = options.getRowKey(defaultRow)
    const existingRow = key ? existingMap.get(key) : undefined
    if (!existingRow) return defaultRow
    if (options.getRowProjectIndex(existingRow) > previousProjectCount) return defaultRow
    return options.mergeExistingRow(defaultRow, existingRow)
  }))
  await options.afterApplyRows()
}
