content: module Main (main) where
  
  import qualified Backend.Python as Python
  import qualified Backend.Rust as Rust
  import qualified Config
  import qualified Convex.Parser as P
  import Data.List (intercalate)
  import Dev (runDevMode)
  import qualified Dev
  import qualified Init
  import Options.Applicative
  import System.Directory (doesFileExist)
  import System.Exit (exitFailure)
  
  data Command
    = Generate GenerateOptions
    | Dev DevCliOptions
    | Init InitOptions
  
  data GenerateOptions = GenerateOptions
    { schemaPath :: FilePath,
      declarationsDir :: FilePath,
      target :: Config.GenTarget,
      outputFile :: Maybe FilePath
    }
  
  data DevCliOptions = DevCliOptions
    { devCliConfigPath :: FilePath
    }
  
  data InitOptions = InitOptions
    { initConfigPath :: FilePath
    }
  
  -- We can reuse the GenTarget Read instance from Config.hs if it were exported,
  -- but redefining it here keeps Main self-contained for parsing.
  instance Read Config.GenTarget where
    readsPrec _ "Python" = [(Config.Python, "")]
    readsPrec _ "Rust" = [(Config.Rust, "")]
    readsPrec _ _ = []
  
  availableTargets :: String
  availableTargets = "Available: " ++ intercalate ", " (map show [(minBound :: Config.GenTarget) ..])
  
  generateOptionsParser :: Parser GenerateOptions
  generateOptionsParser =
    GenerateOptions
      <$> strOption
        ( long "schema"
            <> metavar "SCHEMA_PATH"
            <> help "Path to the source schema.ts file"
        )
      <*> strOption
        ( long "declarations"
            <> metavar "DECL_DIR"
            <> help "Path to the root of the tsc-generated declarations directory"
        )
      <*> option
        auto
        ( long "target"
            <> metavar "TARGET"
            <> value Config.Python
            <> showDefault
            <> help ("Target language for code generation. " ++ availableTargets)
        )
      <*> optional
        ( strOption
            ( long "output"
                <> short 'o'
                <> metavar "OUTPUT_FILE"
                <> help "File to write the generated code to (prints to stdout if omitted)"
            )
        )
  
  devCliOptionsParser :: Parser DevCliOptions
  devCliOptionsParser =
    DevCliOptions
      <$> strOption
        ( long "config"
            <> metavar "CONFIG_FILE"
            <> value "convex-parser.yaml"
            <> showDefault
            <> help "Path to the YAML config file"
        )
  
  initOptionsParser :: Parser InitOptions
  initOptionsParser =
    InitOptions
      <$> strOption
        ( long "config"
            <> metavar "CONFIG_FILE"
            <> value "convex-parser.yaml"
            <> showDefault
            <> help "Path where the default YAML config file will be created"
        )
  
  commandParser :: Parser Command
  commandParser =
    subparser
      ( command "generate" (info (Generate <$> generateOptionsParser <**> helper) (progDesc "Generate a client once and exit"))
          <> command "dev" (info (Dev <$> devCliOptionsParser <**> helper) (progDesc "Watch for changes and regenerate clients from a config file"))
          <> command "init" (info (Init <$> initOptionsParser <**> helper) (progDesc "Initialize a descriptive default config"))
      )
  
  main :: IO ()
  main = do
    cmd <- customExecParser p optsParserInfo
    case cmd of
      Generate opts -> runGenerate opts
      Dev opts -> runDev opts
      Init opts -> Init.runInit (initConfigPath opts)
    where
      optsParserInfo =
        info
          (commandParser <**> helper)
          ( fullDesc
              <> progDesc "Generate typed clients from a Convex schema and function definitions."
              <> header "convex-parser - A code generator for Convex"
          )
      p = prefs showHelpOnError
  
  runGenerate :: GenerateOptions -> IO ()
  runGenerate opts = do
    projectResult <- P.parseProject (schemaPath opts) (declarationsDir opts)
    case projectResult of
      Left err -> do
        putStrLn $ "Error parsing project: " ++ err
        exitFailure
      Right project ->
        let generatedCode = case target opts of
              Config.Python -> Python.generatePythonCode project
              Config.Rust -> Rust.generateRustCode project
         in case outputFile opts of
              Just path -> writeFile path generatedCode
              Nothing -> putStrLn generatedCode
  
  runDev :: DevCliOptions -> IO ()
  runDev opts = do
    configExists <- doesFileExist (devCliConfigPath opts)
    if not configExists
      then do
        putStrLn $ "Error: Config file not found at " ++ devCliConfigPath opts
        exitFailure
      else do
        configResult <- Config.loadConfig (devCliConfigPath opts)
        case configResult of
          Left err -> do
            putStrLn "Error parsing config file:"
            print err
            exitFailure
          Right config ->
            let declarationsDir = Config.declarationsDir config
                devOpts = Dev.DevOptions (Config.projectPath config) declarationsDir config
             in runDevMode config
  