costCentre :: HeaderParams -> Parser CostCentre
costCentre params = do
  name <- symbol; skipHorizontalSpace
  (modName, src, no, (entries, indTime, indAlloc, inhTime, inhAlloc, optInfo))
    <- validCostCentre params <|> jammedCostCentre
  return $! CostCentre
    { costCentreName = name
    , costCentreModule = modName
    , costCentreSrc = src
    , costCentreNo = no
    , costCentreEntries = entries
    , costCentreIndTime = indTime
    , costCentreIndAlloc = indAlloc
    , costCentreInhTime = inhTime
    , costCentreInhAlloc = inhAlloc
    , costCentreTicks = fst <$> optInfo
    , costCentreBytes = snd <$> optInfo
    }
  where
    validCostCentre HeaderParams {..} = do
      modName <- symbol; skipHorizontalSpace
      src <- if headerHasSrc
        then do
          !sym <- sourceSpan
          return $ Just sym
        else pure Nothing
      skipHorizontalSpace
      no <- decimal; skipHorizontalSpace
      vals <- metrics
      return (modName, src, no, vals)
    -- Workaround for https://ghc.haskell.org/trac/ghc/ticket/8811.
    -- This bug had been fixed before the SRC column was implemented so
    -- @sourceSpan@ isn't parsed here.
    -- Caveat: This parser can be confused if module name contains digits and
    -- the digits are jammed with the cost centre number. In such cases, all
    -- the digits are parsed as a number of entries.
    jammedCostCentre = do
      jammed <- symbol; skipHorizontalSpace
      let modName = T.dropWhileEnd isDigit jammed
      no <- either fail (return . fst) $ TR.decimal $ T.takeWhileEnd isDigit jammed
      vals <- metrics
      return (modName, Nothing, no, vals)
    metrics = do
      entries <- decimal; skipHorizontalSpace
      indTime <- scientific; skipHorizontalSpace
      indAlloc <- scientific; skipHorizontalSpace
      inhTime <- scientific; skipHorizontalSpace
      inhAlloc <- scientific; skipHorizontalSpace
      optInfo <- optional $ do
        !ticks <- decimal; skipHorizontalSpace
        !bytes <- decimal
        return (ticks, bytes)
      return (entries, indTime, indAlloc, inhTime, inhAlloc, optInfo)
costCentre :: HeaderParams -> Parser CostCentre
costCentre params = do
  name <- symbol; skipHorizontalSpace
  (modName, src, no, (entries, indTime, indAlloc, inhTime, inhAlloc, optInfo))
    <- validCostCentre params <|> jammedCostCentre
  return $! CostCentre
    { costCentreName = name
    , costCentreModule = modName
    , costCentreSrc = src
    , costCentreNo = no
    , costCentreEntries = entries
    , costCentreIndTime = indTime
    , costCentreIndAlloc = indAlloc
    , costCentreInhTime = inhTime
    , costCentreInhAlloc = inhAlloc
    , costCentreTicks = fst <$> optInfo
    , costCentreBytes = snd <$> optInfo
    }
  where
    validCostCentre HeaderParams {..} = do
      modName <- symbol; skipHorizontalSpace
      src <- if headerHasSrc
        then do
          !sym <- sourceSpan
          return $ Just sym
        else pure Nothing
      skipHorizontalSpace
      no <- decimal; skipHorizontalSpace
      vals <- metrics
      return (modName, src, no, vals)
    -- Workaround for https://ghc.haskell.org/trac/ghc/ticket/8811.
    -- This bug had been fixed before the SRC column was implemented so
    -- @sourceSpan@ isn't parsed here.
    -- Caveat: This parser can be confused if module name contains digits and
    -- the digits are jammed with the cost centre number. In such cases, all
    -- the digits are parsed as a number of entries.
    jammedCostCentre = do
      jammed <- symbol; skipHorizontalSpace
      let modName = T.dropWhileEnd isDigit jammed
      no <- either fail (return . fst) $ TR.decimal $ T.takeWhileEnd isDigit jammed
      vals <- metrics
      return (modName, Nothing, no, vals)
    metrics = do
      entries <- decimal; skipHorizontalSpace
      indTime <- scientific; skipHorizontalSpace
      indAlloc <- scientific; skipHorizontalSpace
      inhTime <- scientific; skipHorizontalSpace
      inhAlloc <- scientific; skipHorizontalSpace
      optInfo <- optional $ do
        !ticks <- decimal; skipHorizontalSpace
        !bytes <- decimal
        return (ticks, bytes)
      return (entries, indTime, indAlloc, inhTime, inhAlloc, optInfo)
costCentre params = do
  name <- symbol; skipHorizontalSpace
  (modName, src, no, (entries, indTime, indAlloc, inhTime, inhAlloc, optInfo))
    <- validCostCentre params <|> jammedCostCentre
  return $! CostCentre
    { costCentreName = name
    , costCentreModule = modName
    , costCentreSrc = src
    , costCentreNo = no
    , costCentreEntries = entries
    , costCentreIndTime = indTime
    , costCentreIndAlloc = indAlloc
    , costCentreInhTime = inhTime
    , costCentreInhAlloc = inhAlloc
    , costCentreTicks = fst <$> optInfo
    , costCentreBytes = snd <$> optInfo
    }
  where
    validCostCentre HeaderParams {..} = do
      modName <- symbol; skipHorizontalSpace
      src <- if headerHasSrc
        then do
          !sym <- sourceSpan
          return $ Just sym
        else pure Nothing
      skipHorizontalSpace
      no <- decimal; skipHorizontalSpace
      vals <- metrics
      return (modName, src, no, vals)
    -- Workaround for https://ghc.haskell.org/trac/ghc/ticket/8811.
    -- This bug had been fixed before the SRC column was implemented so
    -- @sourceSpan@ isn't parsed here.
    -- Caveat: This parser can be confused if module name contains digits and
    -- the digits are jammed with the cost centre number. In such cases, all
    -- the digits are parsed as a number of entries.
    jammedCostCentre = do
      jammed <- symbol; skipHorizontalSpace
      let modName = T.dropWhileEnd isDigit jammed
      no <- either fail (return . fst) $ TR.decimal $ T.takeWhileEnd isDigit jammed
      vals <- metrics
      return (modName, Nothing, no, vals)
    metrics = do
      entries <- decimal; skipHorizontalSpace
      indTime <- scientific; skipHorizontalSpace
      indAlloc <- scientific; skipHorizontalSpace
      inhTime <- scientific; skipHorizontalSpace
      inhAlloc <- scientific; skipHorizontalSpace
      optInfo <- optional $ do
        !ticks <- decimal; skipHorizontalSpace
        !bytes <- decimal
        return (ticks, bytes)
      return (entries, indTime, indAlloc, inhTime, inhAlloc, optInfo)