library(shiny)
library(bslib)
library(DT)
library(readr)
library(amapGeocode)

# Define UI
ui <- page_sidebar(
  title = "amapGeocode Explorer",
  theme = bs_theme(version = 5, bootswatch = "flatly"),
  sidebar = sidebar(
    width = 300,
    title = "Navigation",
    navset_pill_list(
      id = "nav",
      well = FALSE,
      nav_panel("Geocoding", icon = icon("map-marker-alt"), value = "geocode"),
      nav_panel("Reverse Geocoding", icon = icon("globe-asia"), value = "reverse"),
      nav_panel("Convert Coords", icon = icon("exchange-alt"), value = "convert"),
      nav_panel("Settings", icon = icon("cogs"), value = "settings")
    ),
    hr(),
    div(
      class = "text-muted small",
      "Powered by amapGeocode"
    )
  ),
  
  tags$head(tags$style(HTML("
    /* Custom style to make navset_pill_list take full width in sidebar */
    .sidebar-content .row > [class*='col-']:first-child { 
      width: 100%; 
      flex: 0 0 100%; 
      max-width: 100%; 
    }
    .sidebar-content .row > [class*='col-']:last-child { 
      display: none; 
    }
    /* Improve nav link wrapping */
    .nav-pills .nav-link { 
      white-space: normal; 
      line-height: 1.2; 
      padding-top: 10px;
      padding-bottom: 10px;
    }
  "))),

  # Settings Panel
  conditionalPanel(
    condition = "input.nav == 'settings'",
    card(
      card_header("API Configuration"),
      card_body(
        passwordInput("api_key", "AutoNavi API Key", value = getOption("amap_key", "")),
        helpText("Enter your AutoNavi API Key. If you set 'amap_key' in options, it's pre-filled."),
        actionButton("save_key", "Save Key", class = "btn-primary"),
        hr(),
        h5("Rate Limiting"),
        numericInput("max_active", "Max Concurrent Requests", value = 3, min = 1, max = 20),
        numericInput("throttle_rate", "Throttle Rate (req/s)", value = 3, min = 1, max = 50),
        actionButton("save_config", "Update Config", class = "btn-info")
      )
    )
  ),
  
  # Geocoding Panel
  conditionalPanel(
    condition = "input.nav == 'geocode'",
    card(
      card_header("Geocoding (Address -> Coordinates)"),
      layout_sidebar(
        sidebar = sidebar(
          radioButtons("geo_mode", "Input Mode:", choices = c("Single Address", "Batch File (CSV)")),
          
          # Single Input
          conditionalPanel(
            condition = "input.geo_mode == 'Single Address'",
            textInput("geo_address", "Address", placeholder = "e.g., 四川省博物馆"),
            textInput("geo_city", "City (Optional)", placeholder = "e.g., Chengdu"),
            actionButton("btn_geo_single", "Get Coordinate", class = "btn-success")
          ),
          
          # Batch Input
          conditionalPanel(
            condition = "input.geo_mode == 'Batch File (CSV)'",
            fileInput("geo_file", "Upload CSV", accept = ".csv"),
            uiOutput("geo_col_select"),
            actionButton("btn_geo_batch", "Batch Process", class = "btn-success")
          )
        ),
        
        # Results
        card_body(
          DTOutput("tbl_geo"),
          uiOutput("dl_geo_btn")
        )
      )
    )
  ),
  
  # Reverse Geocoding Panel
  conditionalPanel(
    condition = "input.nav == 'reverse'",
    card(
      card_header("Reverse Geocoding (Coordinates -> Location)"),
      layout_sidebar(
        sidebar = sidebar(
          radioButtons("regeo_mode", "Input Mode:", choices = c("Single Point", "Batch File (CSV)")),
          
          # Single Input
          conditionalPanel(
            condition = "input.regeo_mode == 'Single Point'",
            numericInput("regeo_lng", "Longitude", value = 104.043284, step = 0.000001),
            numericInput("regeo_lat", "Latitude", value = 30.666864, step = 0.000001),
            actionButton("btn_regeo_single", "Get Location", class = "btn-success")
          ),
          
          # Batch Input
          conditionalPanel(
            condition = "input.regeo_mode == 'Batch File (CSV)'",
            fileInput("regeo_file", "Upload CSV", accept = ".csv"),
            uiOutput("regeo_col_select_lng"),
            uiOutput("regeo_col_select_lat"),
            actionButton("btn_regeo_batch", "Batch Process", class = "btn-success")
          )
        ),
        
        # Results
        card_body(
          DTOutput("tbl_regeo"),
          uiOutput("dl_regeo_btn")
        )
      )
    )
  ),
  
  # Convert Panel
  conditionalPanel(
    condition = "input.nav == 'convert'",
    card(
      card_header("Coordinate Conversion"),
      layout_sidebar(
        sidebar = sidebar(
          radioButtons("conv_mode", "Input Mode:", choices = c("Single Point", "Batch File (CSV)")),
          selectInput("conv_sys", "Coordinate System", choices = c("gps", "mapbar", "baidu"), selected = "gps"),
          
          # Single Input
          conditionalPanel(
            condition = "input.conv_mode == 'Single Point'",
            textInput("conv_coords", "Coordinates (lng,lat)", placeholder = "116.481499,39.990475"),
            actionButton("btn_conv_single", "Convert", class = "btn-success")
          ),
          
          # Batch Input
          conditionalPanel(
            condition = "input.conv_mode == 'Batch File (CSV)'",
            fileInput("conv_file", "Upload CSV", accept = ".csv"),
            uiOutput("conv_col_select"),
            actionButton("btn_conv_batch", "Batch Convert", class = "btn-success")
          )
        ),
        
        # Results
        card_body(
          DTOutput("tbl_conv"),
          uiOutput("dl_conv_btn")
        )
      )
    )
  )
)

# Define Server
server <- function(input, output, session) {
  
  # Settings Logic
  observeEvent(input$save_key, {
    options(amap_key = input$api_key)
    showNotification("API Key saved to options!", type = "message")
  })
  
  observeEvent(input$save_config, {
    amap_config(max_active = input$max_active, throttle = list(rate = input$throttle_rate))
    showNotification("Configuration updated!", type = "message")
  })
  
  # ---------------- Geocoding ----------------
  
  # Reactive Data for Geocoding
  geo_data <- reactiveVal(NULL)
  
  # Single Geocode
  observeEvent(input$btn_geo_single, {
    req(input$geo_address)
    tryCatch({
      withProgress(message = 'Geocoding...', {
        res <- getCoord(input$geo_address, city = if(nzchar(input$geo_city)) input$geo_city else NULL)
        geo_data(res)
      })
    }, error = function(e) {
      showNotification(paste("Error:", e$message), type = "error")
    })
  })
  
  # Batch Geocode - File Upload
  geo_file_data <- reactive({
    req(input$geo_file)
    read_csv(input$geo_file$datapath, show_col_types = FALSE)
  })
  
  output$geo_col_select <- renderUI({
    req(geo_file_data())
    selectInput("geo_col", "Select Address Column", choices = names(geo_file_data()))
  })
  
  observeEvent(input$btn_geo_batch, {
    req(geo_file_data(), input$geo_col)
    df <- geo_file_data()
    addresses <- df[[input$geo_col]]
    
    withProgress(message = 'Batch Geocoding...', value = 0, {
      # Use the package's batching capability
      res <- getCoord(as.character(addresses), batch = TRUE)
      
      # Bind result to original
      final <- dplyr::bind_cols(df, res)
      geo_data(final)
    })
  })
  
  output$tbl_geo <- renderDT({
    req(geo_data())
    datatable(geo_data(), options = list(scrollX = TRUE))
  })
  
  output$dl_geo_btn <- renderUI({
    req(geo_data())
    downloadButton("dl_geo_csv", "Download CSV")
  })
  
  output$dl_geo_csv <- downloadHandler(
    filename = function() { paste("geocoded_", Sys.Date(), ".csv", sep = "") },
    content = function(file) {
      write_csv(geo_data(), file)
    }
  )
  
  # ---------------- Reverse Geocoding ----------------
  
  regeo_data <- reactiveVal(NULL)
  
  observeEvent(input$btn_regeo_single, {
    req(input$regeo_lng, input$regeo_lat)
    tryCatch({
      withProgress(message = 'Reverse Geocoding...', {
        res <- getLocation(input$regeo_lng, input$regeo_lat)
        regeo_data(res)
      })
    }, error = function(e) {
      showNotification(paste("Error:", e$message), type = "error")
    })
  })
  
  regeo_file_data <- reactive({
    req(input$regeo_file)
    read_csv(input$regeo_file$datapath, show_col_types = FALSE)
  })
  
  output$regeo_col_select_lng <- renderUI({
    req(regeo_file_data())
    selectInput("regeo_col_lng", "Select Longitude Column", choices = names(regeo_file_data()))
  })
  
  output$regeo_col_select_lat <- renderUI({
    req(regeo_file_data())
    selectInput("regeo_col_lat", "Select Latitude Column", choices = names(regeo_file_data()))
  })
  
  observeEvent(input$btn_regeo_batch, {
    req(regeo_file_data(), input$regeo_col_lng, input$regeo_col_lat)
    df <- regeo_file_data()
    lngs <- df[[input$regeo_col_lng]]
    lats <- df[[input$regeo_col_lat]]
    
    withProgress(message = 'Batch Reverse Geocoding...', {
      res <- getLocation(lngs, lats, batch = TRUE)
      final <- dplyr::bind_cols(df, res)
      regeo_data(final)
    })
  })
  
  output$tbl_regeo <- renderDT({
    req(regeo_data())
    datatable(regeo_data(), options = list(scrollX = TRUE))
  })
  
  output$dl_regeo_btn <- renderUI({
    req(regeo_data())
    downloadButton("dl_regeo_csv", "Download CSV")
  })
  
  output$dl_regeo_csv <- downloadHandler(
    filename = function() { paste("reverse_geocoded_", Sys.Date(), ".csv", sep = "") },
    content = function(file) {
      write_csv(regeo_data(), file)
    }
  )
  
  # ---------------- Convert ----------------
  
  conv_data <- reactiveVal(NULL)
  
  observeEvent(input$btn_conv_single, {
    req(input$conv_coords)
    tryCatch({
      withProgress(message = 'Converting...', {
        res <- convertCoord(input$conv_coords, coordsys = input$conv_sys)
        conv_data(res)
      })
    }, error = function(e) {
      showNotification(paste("Error:", e$message), type = "error")
    })
  })
  
  conv_file_data <- reactive({
    req(input$conv_file)
    read_csv(input$conv_file$datapath, show_col_types = FALSE)
  })
  
  output$conv_col_select <- renderUI({
    req(conv_file_data())
    selectInput("conv_col", "Select Coords Column (lng,lat)", choices = names(conv_file_data()))
  })
  
  observeEvent(input$btn_conv_batch, {
    req(conv_file_data(), input$conv_col)
    df <- conv_file_data()
    coords_vec <- df[[input$conv_col]]
    
    withProgress(message = 'Batch Converting...', {
      # convertCoord doesn't formally support batch=TRUE in the same way, but let's check
      # If not, we map it manually or use the underlying support. 
      # Looking at convertCoord docs, it takes a single string "lng,lat;lng,lat".
      # But standard usage for multiple rows usually implies one-by-one or batching.
      # Let's assume input is vector of "lng,lat" strings.
      
      # The package convertCoord accepts a single string of coordinates separated by semi-colon for batch,
      # OR a vector if it handles vectorization.
      # Let's verify convertCoord implementation.
      # Assuming it vectorizes or we use amapGeocode built-in mechanisms.
      
      # For safety, let's map if the function isn't vectorized on vector input.
      # But amapGeocode generally supports vectorization.
      
      res <- convertCoord(as.character(coords_vec), coordsys = input$conv_sys)
      final <- dplyr::bind_cols(df, res)
      conv_data(final)
    })
  })
  
  output$tbl_conv <- renderDT({
    req(conv_data())
    datatable(conv_data(), options = list(scrollX = TRUE))
  })
  
  output$dl_conv_btn <- renderUI({
    req(conv_data())
    downloadButton("dl_conv_csv", "Download CSV")
  })
  
  output$dl_conv_csv <- downloadHandler(
    filename = function() { paste("converted_coords_", Sys.Date(), ".csv", sep = "") },
    content = function(file) {
      write_csv(conv_data(), file)
    }
  )
}

shinyApp(ui, server)
