{-# LANGUAGE OverloadedStrings #-}
{- |
   Module      : Text.Pandoc.Readers.Xlsx.Cells
   Copyright   : © 2025 Anton Antic
   License     : GNU GPL, version 2 or above

   Maintainer  : Anton Antic <anton@everworker.ai>
   Stability   : alpha
   Portability : portable

Cell types and parsing for XLSX.
-}
module Text.Pandoc.Readers.Xlsx.Cells
  ( CellRef(..)
  , XlsxCell(..)
  , CellValue(..)
  , parseCellRef
  ) where

import qualified Data.Text as T
import Data.Text (Text)
import Data.Char (ord, isAlpha)
import Text.Read (readMaybe)

-- | Cell reference (A1 notation)
data CellRef = CellRef
  { CellRef -> Int
cellRefCol :: Int    -- 1-based (A=1, B=2, ..., AA=27)
  , CellRef -> Int
cellRefRow :: Int    -- 1-based
  } deriving (Int -> CellRef -> ShowS
[CellRef] -> ShowS
CellRef -> String
(Int -> CellRef -> ShowS)
-> (CellRef -> String) -> ([CellRef] -> ShowS) -> Show CellRef
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> CellRef -> ShowS
showsPrec :: Int -> CellRef -> ShowS
$cshow :: CellRef -> String
show :: CellRef -> String
$cshowList :: [CellRef] -> ShowS
showList :: [CellRef] -> ShowS
Show, CellRef -> CellRef -> Bool
(CellRef -> CellRef -> Bool)
-> (CellRef -> CellRef -> Bool) -> Eq CellRef
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: CellRef -> CellRef -> Bool
== :: CellRef -> CellRef -> Bool
$c/= :: CellRef -> CellRef -> Bool
/= :: CellRef -> CellRef -> Bool
Eq, Eq CellRef
Eq CellRef =>
(CellRef -> CellRef -> Ordering)
-> (CellRef -> CellRef -> Bool)
-> (CellRef -> CellRef -> Bool)
-> (CellRef -> CellRef -> Bool)
-> (CellRef -> CellRef -> Bool)
-> (CellRef -> CellRef -> CellRef)
-> (CellRef -> CellRef -> CellRef)
-> Ord CellRef
CellRef -> CellRef -> Bool
CellRef -> CellRef -> Ordering
CellRef -> CellRef -> CellRef
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: CellRef -> CellRef -> Ordering
compare :: CellRef -> CellRef -> Ordering
$c< :: CellRef -> CellRef -> Bool
< :: CellRef -> CellRef -> Bool
$c<= :: CellRef -> CellRef -> Bool
<= :: CellRef -> CellRef -> Bool
$c> :: CellRef -> CellRef -> Bool
> :: CellRef -> CellRef -> Bool
$c>= :: CellRef -> CellRef -> Bool
>= :: CellRef -> CellRef -> Bool
$cmax :: CellRef -> CellRef -> CellRef
max :: CellRef -> CellRef -> CellRef
$cmin :: CellRef -> CellRef -> CellRef
min :: CellRef -> CellRef -> CellRef
Ord)

-- | Cell value types
data CellValue
  = TextValue Text
  | NumberValue Double
  | EmptyValue
  deriving (Int -> CellValue -> ShowS
[CellValue] -> ShowS
CellValue -> String
(Int -> CellValue -> ShowS)
-> (CellValue -> String)
-> ([CellValue] -> ShowS)
-> Show CellValue
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> CellValue -> ShowS
showsPrec :: Int -> CellValue -> ShowS
$cshow :: CellValue -> String
show :: CellValue -> String
$cshowList :: [CellValue] -> ShowS
showList :: [CellValue] -> ShowS
Show, CellValue -> CellValue -> Bool
(CellValue -> CellValue -> Bool)
-> (CellValue -> CellValue -> Bool) -> Eq CellValue
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: CellValue -> CellValue -> Bool
== :: CellValue -> CellValue -> Bool
$c/= :: CellValue -> CellValue -> Bool
/= :: CellValue -> CellValue -> Bool
Eq)

-- | Parsed cell
data XlsxCell = XlsxCell
  { XlsxCell -> CellRef
cellRef :: CellRef
  , XlsxCell -> CellValue
cellValue :: CellValue
  , XlsxCell -> Bool
cellBold :: Bool
  , XlsxCell -> Bool
cellItalic :: Bool
  } deriving (Int -> XlsxCell -> ShowS
[XlsxCell] -> ShowS
XlsxCell -> String
(Int -> XlsxCell -> ShowS)
-> (XlsxCell -> String) -> ([XlsxCell] -> ShowS) -> Show XlsxCell
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> XlsxCell -> ShowS
showsPrec :: Int -> XlsxCell -> ShowS
$cshow :: XlsxCell -> String
show :: XlsxCell -> String
$cshowList :: [XlsxCell] -> ShowS
showList :: [XlsxCell] -> ShowS
Show)

-- | Parse cell reference (A1 → CellRef)
parseCellRef :: Text -> Either Text CellRef
parseCellRef :: Text -> Either Text CellRef
parseCellRef Text
ref = do
  let (Text
colStr, Text
rowStr) = (Char -> Bool) -> Text -> (Text, Text)
T.span Char -> Bool
isAlpha Text
ref

  Int
row <- case String -> Maybe Int
forall a. Read a => String -> Maybe a
readMaybe (Text -> String
T.unpack Text
rowStr) of
    Just Int
r | Int
r Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0 -> Int -> Either Text Int
forall a b. b -> Either a b
Right Int
r
    Maybe Int
_ -> Text -> Either Text Int
forall a b. a -> Either a b
Left (Text -> Either Text Int) -> Text -> Either Text Int
forall a b. (a -> b) -> a -> b
$ Text
"Invalid row: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
rowStr

  Int
col <- Text -> Either Text Int
parseColumn Text
colStr

  CellRef -> Either Text CellRef
forall a. a -> Either Text a
forall (m :: * -> *) a. Monad m => a -> m a
return (CellRef -> Either Text CellRef) -> CellRef -> Either Text CellRef
forall a b. (a -> b) -> a -> b
$ Int -> Int -> CellRef
CellRef Int
col Int
row

-- | Parse column (A=1, Z=26, AA=27, etc.)
parseColumn :: Text -> Either Text Int
parseColumn :: Text -> Either Text Int
parseColumn Text
colStr
  | Text -> Bool
T.null Text
colStr = Text -> Either Text Int
forall a b. a -> Either a b
Left Text
"Empty column"
  | Bool
otherwise = Int -> Either Text Int
forall a b. b -> Either a b
Right (Int -> Either Text Int) -> Int -> Either Text Int
forall a b. (a -> b) -> a -> b
$ (Int -> Char -> Int) -> Int -> Text -> Int
forall a. (a -> Char -> a) -> a -> Text -> a
T.foldl' (\Int
acc Char
c -> Int
acc Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
26 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ (Char -> Int
ord Char
c Int -> Int -> Int
forall a. Num a => a -> a -> a
- Char -> Int
ord Char
'A' Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)) Int
0 Text
colStr