is_web <- TextAnalysisR:::check_web_deployment()
is_docker <- TextAnalysisR:::check_docker_deployment()
is_remote <- is_web || is_docker

server_gemini_key <- Sys.getenv("GEMINI_API_KEY", "")
has_server_gemini <- nzchar(server_gemini_key)
server_gemini_model <- Sys.getenv("GEMINI_DEFAULT_MODEL", "gemini-2.5-flash")

shiny::enableBookmarking("disable")

.password_input <- function(inputId, label, value = "", placeholder = NULL) {
  if (isTRUE(has_server_gemini) && grepl("gemini", inputId, ignore.case = TRUE)) {
    return(tags$p(
      style = "color: #475569; font-size: 13px; margin-bottom: 8px;",
      "Usage is free here, supported by the Google Cloud Research program."
    ))
  }
  pw <- shiny::passwordInput(inputId, label, value = value, placeholder = placeholder)
  pw$children <- lapply(pw$children, function(ch) {
    if (inherits(ch, "shiny.tag") && identical(ch$name, "input")) {
      ch$attribs$autocomplete <- "new-password"
    }
    ch
  })
  pw
}

topic_modeling_ui_content <- function() {
      sidebarLayout(
        sidebarPanel(
          width = 3,
          class = "sidebar-panel",

          tags$h5(HTML("<strong>Topic modeling approach</strong>"), style = "color: #4269BF; margin-bottom: 10px;"),
          tags$div(
            id = "topic_modeling_path",
            class = "shiny-input-radiogroup shiny-input-container",
            role = "radiogroup",
            tags$div(
              class = "radio",
              style = "display: flex; align-items: center; margin-bottom: 8px;",
              tags$label(
                style = "margin-bottom: 0; display: flex; align-items: center; width: 100%;",
                tags$input(
                  type = "radio",
                  name = "topic_modeling_path",
                  value = "probability",
                  checked = "checked"
                ),
                tags$span("Structural Topic Model", style = "margin-left: 5px;"),
                actionLink("showSTMInfo", icon("info-circle"),
                          style = "color: #337ab7; font-size: 16px; margin-left: 8px;",
                          title = "Learn about STM",
                          `aria-label` = "Learn about STM")
              )
            ),
            tags$div(
              class = "radio",
              style = "display: flex; align-items: center; margin-bottom: 8px;",
              tags$label(
                style = "margin-bottom: 0; display: flex; align-items: center; width: 100%;",
                tags$input(
                  type = "radio",
                  name = "topic_modeling_path",
                  value = "embedding"
                ),
                tags$span("Embedding-based Topic Model", style = "margin-left: 5px;"),
                actionLink("showEmbeddingTopicsInfo", icon("info-circle"),
                          style = "color: #337ab7; font-size: 16px; margin-left: 8px;",
                          title = "Learn about Embedding-based Topics",
                          `aria-label` = "Learn about Embedding-based Topics")
              )
            )
          ),
          hr(),

          conditionalPanel(
            condition = "input.topic_modeling_path == 'probability' && input.conditioned3 == 4 && input.searchKSubtabs != 'ai_rec'",
            tags$h5(
              HTML("<strong>Evaluate optimal topic number (K)</strong> <a href='https://github.com/bstewart/stm' target='_blank' rel='noopener noreferrer' onclick='window.open(this.href); return false;' style='font-size: 16px;'>Source</a>"),
              style = "color: #4269BF; margin-bottom: 10px;"
            ),
            sliderInput(
              "stm_K_range",
              "Range of topic numbers",
              value = c(5, 10),
              min = 2,
              max = 50
            ),
            selectizeInput(
              "stm_categorical_var",
              "Categorical covariate(s)",
              choices = NULL,
              multiple = TRUE,
              options = list(placeholder = "Optional")
            ),
            selectizeInput(
              "stm_continuous_var",
              "Continuous covariate(s)",
              choices = NULL,
              multiple = TRUE,
              options = list(placeholder = "Optional")
            ),
            selectInput("stm_init_type_search", "Initialization type",
              choices = c("Spectral", "LDA", "Random", "Custom"),
              selected = "Spectral"
            ),
            radioButtons("stm_gamma_prior_search", "Gamma prior",
              choices = c("Pooled", "L1"),
              selected = "Pooled"
            ),
            radioButtons("stm_kappa_prior_search", "Kappa prior",
              choices = c("L1", "Jeffreys"),
              selected = "L1"
            ),
            numericInput("stm_max_em_its_search", "Max EM iterations",
              value = 500, min = 100, max = 2000, step = 50
            ),
            div(
              style = "margin-bottom: 15px;",
              actionButton("stm_search", "Search K", class = "btn-primary btn-block")
            )
          ),
          conditionalPanel(
            condition = "input.topic_modeling_path == 'probability' && input.conditioned3 == 5",
            tags$h5(strong("Structural topic model"), style = "color: #4269BF; margin-bottom: 10px;"),
            uiOutput("stm_k_selector_uiOutput"),
            selectInput(
              "stm_topic_measure",
              "Topic term measure",
              choices = c(
                "FREX" = "frex",
                "Lift" = "lift",
                "Score" = "score",
                "Probability" = "beta"
              ),
              selected = "frex"
            ),
            conditionalPanel(
              condition = "input.stm_topic_measure == 'frex'",
              sliderInput(
                "stm_top_term_number_frex",
                "Top terms (FREX)",
                value = 5,
                min = 0,
                max = 50
              )
            ),
            conditionalPanel(
              condition = "input.stm_topic_measure == 'lift'",
              sliderInput(
                "stm_top_term_number_lift",
                "Top terms (Lift)",
                value = 5,
                min = 0,
                max = 50
              )
            ),
            conditionalPanel(
              condition = "input.stm_topic_measure == 'score'",
              sliderInput(
                "stm_top_term_number_score",
                "Top terms (Score)",
                value = 5,
                min = 0,
                max = 50
              )
            ),
            conditionalPanel(
              condition = "input.stm_topic_measure == 'beta'",
              sliderInput(
                "stm_top_term_number_beta",
                "Top terms (Probability)",
                value = 5,
                min = 0,
                max = 50
              )
            ),
            sliderInput(
              "stm_ncol_top_terms",
              "Column numbers",
              value = 2,
              min = 1,
              max = 10
            ),
            selectizeInput(
              "stm_categorical_var_2",
              "Categorical covariate(s)",
              choices = NULL,
              multiple = TRUE,
              options = list(placeholder = "Optional")
            ),
            selectizeInput(
              "stm_continuous_var_2",
              "Continuous covariate(s)",
              choices = NULL,
              multiple = TRUE,
              options = list(placeholder = "Optional")
            ),
            selectInput("stm_init_type_K", "Initialization type",
              choices = c("Spectral", "LDA", "Random", "Custom"),
              selected = "Spectral"
            ),
            radioButtons("stm_gamma_prior_K", "Gamma prior",
              choices = c("Pooled", "L1"),
              selected = "Pooled"
            ),
            radioButtons("stm_kappa_prior_K", "Kappa prior",
              choices = c("L1", "Jeffreys"),
              selected = "L1"
            ),
            numericInput("stm_max_em_its_K", "Max EM iterations",
              value = 500, min = 100, max = 2000, step = 50
            ),
            div(
              style = "margin-bottom: 15px;",
              actionButton("stm_run", "Display", class = "btn-primary btn-block")
            ),
            tags$hr(),
            tags$h5(strong("Generate topic labels using AI"), style = "color: #4269BF; margin-bottom: 10px;"),
            sliderInput(
              "stm_top_term_number_labeling",
              "Top terms for labeling",
              value = 7,
              min = 3,
              max = 15
            ),
            radioButtons(
              "stm_label_provider",
              "AI Provider:",
              choices = c(
                "OpenAI (API Key Required)" = "openai",
                "Gemini (API Key Required)" = "gemini"
              ),
              selected = "openai",
              inline = FALSE
            ),
            conditionalPanel(
              condition = "input.stm_label_provider == 'openai'",
              selectizeInput(
                "stm_label_openai_model",
                "OpenAI Model:",
                choices = c("GPT-4.1 Mini (Default, fast)" = "gpt-4.1-mini", "GPT-4.1 (Accurate)" = "gpt-4.1", "GPT-4" = "gpt-4"),
                selected = NULL,
                options = list(create = TRUE, placeholder = "Type your model...", onInitialize = I("function() { this.setValue(\"\"); }"))
              ),
              .password_input("stm_label_openai_api_key", "API Key:", placeholder = "sk-..."),
              conditionalPanel(
                condition = "output.has_openai_key",
                tags$div(style = "color: #0C795A; font-size: 13px; margin-top: -8px; margin-bottom: 8px;",
                  icon("check-circle"), " Key stored. Enter new key to override.")
              )
            ),
            conditionalPanel(
              condition = "input.stm_label_provider == 'gemini'",
              selectizeInput(
                "stm_label_gemini_model",
                "Gemini Model:",
                choices = c("Gemini 2.5 Flash Lite (Default, economy)" = "gemini-2.5-flash-lite", "Gemini 2.5 Flash" = "gemini-2.5-flash", "Gemini 2.5 Pro (Accurate)" = "gemini-2.5-pro"),
                selected = NULL,
                options = list(create = TRUE, placeholder = "Type your model...", onInitialize = I("function() { this.setValue(\"\"); }"))
              ),
              .password_input("stm_label_gemini_api_key", "API Key:", placeholder = "AIza..."),
              conditionalPanel(
                condition = "output.has_gemini_key",
                tags$div(style = "color: #0C795A; font-size: 13px; margin-top: -8px; margin-bottom: 8px;",
                  icon("check-circle"), " Key stored. Enter new key to override.")
              )
            ),
            textAreaInput(
              "stm_system_prompt",
              "System prompt",
              value = "
You are a highly skilled data scientist specializing in generating concise and descriptive topic labels based on provided top terms for each topic.
Each topic consists of a list of terms ordered from most to least significant (by beta scores).

Your objective is to create precise labels that capture the essence of each topic by following these guidelines:

1. Use Person-First Language
   - Prioritize respectful and inclusive language.
   - Avoid terms that may be considered offensive or stigmatizing.
   - For example, use 'students with learning disabilities' instead of 'disabled students'.
   - Use 'students with visual impairments' instead of 'impaired students'
   - Use 'students with blindness' instead of 'blind students'.

1. Analyze Top Terms' Significance
   - Primary Focus: Emphasize high beta-score terms as they strongly define the topic.
   - Secondary Consideration: Include lower-scoring terms if they add essential context.

2. Synthesize the Topic Label
   - Clarity: Make sure the label is clear and easily understandable.
   - Conciseness: Aim for a short phrase of about 5-7 words.
   - Relevance: Reflect the collective meaning of the most influential terms.
   - Intelligent interpretation: Use your understanding to create meaningful labels that capture the topic's essence.

3. Maintain Consistency
   - Capitalize the first word of all topic labels.
   - Keep formatting and terminology uniform across all labels.
   - Avoid ambiguity or generic wording that does not fit the provided top terms.

4. Adhere to Style Guidelines
   - Capitalization: Use title case for labels.
   - Avoid Jargon: Maintain accessibility; only use technical terms if absolutely necessary.
   - Uniqueness: Ensure each label is distinct and does not overlap significantly with others.

5. Handle Edge Cases
   - Conflicting Top Terms: If the terms suggest different directions, prioritize those with higher beta scores.
   - Low-Scoring Terms: Include them only if they add meaningful context.

6. Iterative Improvement
   - If the generated label is insufficiently representative, re-check term significance and revise accordingly.
   - Always adhere to these guidelines.

Example
----------
Top Terms (highest to lowest beta score):
virtual manipulatives (.035)
manipulatives (.022)
mathematical (.014)
app (.013)
solving (.013)
learning disability (.012)
algebra (.012)
area (.011)
tool (.010)
concrete manipulatives (.010)

Generated Topic Label:
Mathematical learning tools for students with disabilities

Focus on incorporating the most significant keywords while following the guidelines above to produce a concise, descriptive topic label.
",
              rows = 10
            ),
            textAreaInput(
              "stm_user_prompt",
              "User prompt:",
              value = "You have a topic with keywords listed from most to least significant: [terms will be inserted here]. Please create a concise and descriptive label (5-7 words) that: 1. Reflects the collective meaning of these keywords. 2. Gives higher priority to the most significant terms. 3. Adheres to the style guidelines provided in the system message.",
              rows = 5
            ),
            sliderInput(
              "stm_temperature",
              "Temperature (creativity level)",
              min = 0, max = 1, value = 0.5, step = 0.1
            ),
            actionButton("topic_generate_labels", HTML("<i class=\"fas fa-wand-magic-sparkles\"></i> Generate Labels"), class = "btn-primary btn-block"),
            tags$hr(),
            textInput(
              "stm_label_topics",
              "Manually label topics",
              value = "",
              placeholder = "Type labels, use comma to separate"
            )
          ),
          conditionalPanel(
            condition = "input.topic_modeling_path == 'probability' && input.conditioned3 == 6",
            uiOutput("topic_number_uiOutput"),
            sliderInput(
              "stm_top_term_number_2",
              "Top terms per topic",
              value = 5,
              min = 1,
              max = 20
            ),
            actionButton("stm_display", "Display", class = "btn-primary btn-block")
          ),
          conditionalPanel(
            condition = "input.topic_modeling_path == 'probability' && input.conditioned3 == 7",
            tags$h5(strong("Explore example documents"), style = "color: #4269BF; margin-bottom: 10px;"),
            uiOutput("quote_topic_number_uiOutput"),
            selectInput("stm_topic_texts", "Example quotes to display", choices = NULL),
            actionButton("stm_quote", "Quote", class = "btn-primary btn-block")
          ),
          conditionalPanel(
            condition = "input.topic_modeling_path == 'probability' && input.conditioned3 == 8",
            tags$h5(
              strong("Estimate Covariate Effects on Topics"),
              style = "color: #4269BF; margin-bottom: 10px;"
            ),
            tags$div(
              class = "status-step-purple",
              style = "margin-bottom: 15px;",
              tags$i(class = "fa fa-check-circle status-icon status-icon-purple"),
              "Choose covariates in the Word-Topic tab"
            ),
            div(
              style = "display: flex; gap: 10px; margin-bottom: 15px;",
              div(
                style = "flex: 1;",
                actionButton("stm_effect", "Estimate", class = "btn-primary btn-block")
              ),
              div(
                style = "flex: 1;",
                downloadButton("stm_effect_download_table", class = "btn-secondary btn-block")
              )
            )
          ),
          conditionalPanel(
            condition = "input.topic_modeling_path == 'probability' && input.conditioned3 == 4 && input.searchKSubtabs == 'ai_rec'",
            tags$h5(strong("AI Configuration for K Selection"), style = "color: #4269BF; margin-bottom: 10px;"),

            radioButtons(
              "k_rec_provider",
              "AI Provider:",
              choices = c(
                "OpenAI (API Key Required)" = "openai",
                "Gemini (API Key Required)" = "gemini"
              ),
              selected = "openai",
              inline = FALSE
            ),

            conditionalPanel(
              condition = "input.k_rec_provider == 'openai'",
              selectizeInput(
                "k_rec_openai_model",
                "OpenAI Model:",
                choices = c(
                  "GPT-4.1 Mini (Default, fast)" = "gpt-4.1-mini",
                  "GPT-4.1 (Accurate)" = "gpt-4.1",
                  "GPT-4" = "gpt-4"
                ),
                selected = NULL,
                options = list(create = TRUE, placeholder = "Type your model...", onInitialize = I("function() { this.setValue(\"\"); }"))
              ),
              .password_input("k_rec_openai_api_key", "API Key:", placeholder = "sk-..."),
              conditionalPanel(
                condition = "output.has_openai_key",
                tags$div(style = "color: #0C795A; font-size: 13px; margin-top: -8px; margin-bottom: 8px;",
                  icon("check-circle"), " Key stored. Enter new key to override.")
              )
            ),

            conditionalPanel(
              condition = "input.k_rec_provider == 'gemini'",
              selectizeInput(
                "k_rec_gemini_model",
                "Gemini Model:",
                choices = c(
                  "Gemini 2.5 Flash Lite (Default, economy)" = "gemini-2.5-flash-lite",
                  "Gemini 2.5 Flash" = "gemini-2.5-flash",
                  "Gemini 2.5 Pro (Accurate)" = "gemini-2.5-pro"
                ),
                selected = NULL,
                options = list(create = TRUE, placeholder = "Type your model...", onInitialize = I("function() { this.setValue(\"\"); }"))
              ),
              .password_input("k_rec_gemini_api_key", "API Key:", placeholder = "AIza..."),
              conditionalPanel(
                condition = "output.has_gemini_key",
                tags$div(style = "color: #0C795A; font-size: 13px; margin-top: -8px; margin-bottom: 8px;",
                  icon("check-circle"), " Key stored. Enter new key to override.")
              )
            ),

            textAreaInput(
              "ai_system_search",
              "System prompt",
              value = "You are an expert in Structural Topic Modeling (STM) and topic model selection. Analyze the provided metrics to recommend the optimal number of topics (K).\n\nEvaluation criteria in order of importance:\n1. **Held-out likelihood**: Higher values indicate better generalization (most important for model selection)\n2. **Semantic coherence**: Higher values mean topics are more interpretable\n3. **Exclusivity**: Higher values mean topics are more distinct\n4. **Residuals**: Lower values indicate better model fit\n5. **Overall score**: A balanced metric combining all factors\n\nBest practice: Look for K values where:\n- Held-out likelihood plateaus or peaks\n- Semantic coherence and exclusivity are both reasonably high (upper-right quadrant)\n- There's an \"elbow\" in the residuals plot\n- The rate of improvement diminishes (diminishing returns)\n\nProvide a specific K recommendation with brief justification based on these metrics.",
              rows = 8
            ),
            textAreaInput(
              "ai_user_search",
              "User prompt",
              value = "Based on the Search K diagnostic plots and metrics table, what is the optimal number of topics (K)?\n\nKey observations:\n- Models with high coherence but low exclusivity may be too general\n- Models with high exclusivity but low coherence may be too specific\n- The ideal model balances both metrics while maximizing held-out likelihood\n- Consider practical interpretability: too few topics may oversimplify, too many may fragment meaningful themes\n\nPlease recommend a specific K value and explain which metrics most influenced your decision.",
              rows = 6
            ),
            actionButton(
              "generate_k_recommendation",
              HTML("<i class=\"fas fa-wand-magic-sparkles\"></i> Generate Recommendation"),
              class = "btn-primary btn-block"
            )
          ),
          conditionalPanel(
            condition = "input.topic_modeling_path == 'probability' && input.conditioned3 == 9",
            tags$h5(strong("Plot Topic Effects by Categorical Covariates"), style = "color: #4269BF; margin-bottom: 10px;"),
            tags$div(
              class = "status-step-purple",
              tags$i(class = "fa fa-check-circle status-icon status-icon-purple"),
              tags$strong("Step 1:"), " Select categorical covariates in Word-Topic tab"
            ),
            tags$div(
              class = "status-step-blue",
              tags$i(class = "fa fa-check-circle status-icon status-icon-info"),
              tags$strong("Step 2:"), " Estimate coefficients in Estimated Effects tab"
            ),
            selectizeInput(
              "stm_effect_cat_btn",
              "Categorical covariate",
              choices = NULL,
              multiple = FALSE
            ),
            sliderInput(
              "stm_ncol_cat",
              "Column numbers",
              value = 2,
              min = 1,
              max = 10
            ),
            actionButton("stm_display_cat", "Display", class = "btn-primary btn-block")
          ),
          conditionalPanel(
            condition = "input.topic_modeling_path == 'probability' && input.conditioned3 == 10",
            tags$h5(strong("Plot Topic Effects by Continuous Covariates"), style = "color: #4269BF; margin-bottom: 10px;"),
            tags$div(
              class = "status-step-purple",
              tags$i(class = "fa fa-check-circle status-icon status-icon-purple"),
              tags$strong("Step 1:"), " Select continuous covariates in Word-Topic tab"
            ),
            tags$div(
              class = "status-step-blue",
              tags$i(class = "fa fa-check-circle status-icon status-icon-info"),
              tags$strong("Step 2:"), " Estimate coefficients in Estimated Effects tab"
            ),
            selectizeInput(
              "stm_effect_con_btn",
              "Continuous covariate",
              choices = NULL,
              multiple = FALSE
            ),
            sliderInput(
              "stm_ncol_con",
              "Column numbers",
              value = 2,
              min = 1,
              max = 10
            ),
            actionButton("stm_display_con", "Display", class = "btn-primary btn-block")
          ),

          conditionalPanel(
            condition = "input.conditioned3 == 'ai_content'",
            tags$h5(HTML("<strong>Generate content using AI</strong>"), style = "color: #4269BF; margin-bottom: 15px;"),
            tags$div(
              class = "status-main-info",
              tags$i(class = "fas fa-robot status-icon status-icon-info"),
              "Generate survey items, research questions, or other content from topic terms."
            ),
            selectInput(
              "content_type",
              "Content type",
              choices = c(
                "Survey Item" = "survey_item",
                "Research Question" = "research_question",
                "Theme Description" = "theme_description",
                "Policy Recommendation" = "policy_recommendation",
                "Interview Question" = "interview_question",
                "Custom" = "custom"
              ),
              selected = "survey_item"
            ),
            sliderInput(
              "content_top_terms",
              "Top terms for content",
              value = 7,
              min = 3,
              max = 15
            ),
            sliderInput(
              "content_max_tokens",
              "Max tokens",
              min = 50, max = 500, value = 150, step = 25
            ),
            radioButtons(
              "content_provider",
              "AI Provider:",
              choices = c(
                "OpenAI (API Key Required)" = "openai",
                "Gemini (API Key Required)" = "gemini"
              ),
              selected = "openai",
              inline = FALSE
            ),
            conditionalPanel(
              condition = "input.content_provider == 'openai'",
              selectizeInput(
                "content_openai_model",
                "OpenAI Model:",
                choices = c("GPT-4.1 Mini (Default, fast)" = "gpt-4.1-mini", "GPT-4.1 (Accurate)" = "gpt-4.1", "GPT-4" = "gpt-4"),
                selected = NULL,
                options = list(create = TRUE, placeholder = "Type your model...", onInitialize = I("function() { this.setValue(\"\"); }"))
              ),
              .password_input("content_openai_api_key", "API Key:", placeholder = "sk-..."),
              conditionalPanel(
                condition = "output.has_openai_key",
                tags$div(style = "color: #0C795A; font-size: 13px; margin-top: -8px; margin-bottom: 8px;",
                  icon("check-circle"), " Key stored. Enter new key to override.")
              )
            ),
            conditionalPanel(
              condition = "input.content_provider == 'gemini'",
              selectizeInput(
                "content_gemini_model",
                "Gemini Model:",
                choices = c("Gemini 2.5 Flash Lite (Default, economy)" = "gemini-2.5-flash-lite", "Gemini 2.5 Flash" = "gemini-2.5-flash", "Gemini 2.5 Pro (Accurate)" = "gemini-2.5-pro"),
                selected = NULL,
                options = list(create = TRUE, placeholder = "Type your model...", onInitialize = I("function() { this.setValue(\"\"); }"))
              ),
              .password_input("content_gemini_api_key", "API Key:", placeholder = "AIza..."),
              conditionalPanel(
                condition = "output.has_gemini_key",
                tags$div(style = "color: #0C795A; font-size: 13px; margin-top: -8px; margin-bottom: 8px;",
                  icon("check-circle"), " Key stored. Enter new key to override.")
              )
            ),
            textAreaInput(
              "content_system_prompt",
              "System prompt",
              value = "",
              rows = 8
            ),
            textAreaInput(
              "content_user_prompt",
              "User prompt",
              value = "",
              rows = 5
            ),
            sliderInput(
              "content_temperature",
              "Temperature (creativity level)",
              min = 0, max = 1, value = 0.5, step = 0.1
            ),
            actionButton(
              "generate_topic_content",
              HTML("<i class=\"fas fa-pen-fancy\"></i> Generate Content"),
              class = "btn-primary btn-block",
              style = "margin-top: 15px;"
            )
          ),
          conditionalPanel(
            condition = "input.topic_modeling_path == 'embedding' && input.conditioned3 == 4",
            tags$h5(HTML("<strong>Configure Embedding-based Topic Modeling</strong>"), style = "color: #4269BF; margin-bottom: 10px;"),

            selectInput(
              "embedding_backend",
              "Backend:",
              choices = c(
                "Python (BERTopic, recommended)" = "python",
                "R (no Python required)" = "r"
              ),
              selected = "python"
            ),

            uiOutput("embedding_topic_provider_status"),

            conditionalPanel(
              condition = "input.embedding_backend == 'python'",
              uiOutput("embedding_topic_backend_status")
            ),

            radioButtons(
              "topic_embedding_provider",
              "AI Provider:",
              choices = c(
                "Sentence Transformers (Python)" = "sentence-transformers",
                "OpenAI (API Key Required)" = "openai",
                "Gemini (API Key Required)" = "gemini"
              ),
              selected = "sentence-transformers",
              inline = FALSE
            ),

            conditionalPanel(
              condition = "input.topic_embedding_provider == 'sentence-transformers'",
              selectizeInput(
                "topic_embedding_st_model",
                "Model:",
                choices = c(
                  "all-MiniLM-L6-v2 (Fast)" = "all-MiniLM-L6-v2",
                  "all-mpnet-base-v2 (Higher Quality)" = "all-mpnet-base-v2",
                  "BGE Small EN v1.5 (Fast + Strong Retrieval)" = "BAAI/bge-small-en-v1.5",
                  "BGE Base EN v1.5 (BERTopic Optimized)" = "BAAI/bge-base-en-v1.5",
                  "E5 Base v2 (Instructor-tuned)" = "intfloat/e5-base-v2",
                  "Nomic Embed Text v2 (Multilingual)" = "nomic-ai/nomic-embed-text-v2-moe",
                  "GTE Multilingual Base (Fast, Multilingual)" = "Alibaba-NLP/gte-multilingual-base"
                ),
                selected = NULL,
                options = list(create = TRUE, placeholder = "Type your model...", onInitialize = I("function() { this.setValue(\"\"); }"))
              ),
              tags$p(
                style = "font-size: 16px; color: #666;",
                "Requires Python + sentence-transformers"
              )
            ),

            conditionalPanel(
              condition = "input.topic_embedding_provider == 'openai'",
              selectizeInput(
                "topic_embedding_openai_model",
                "OpenAI Model:",
                choices = c(
                  "Text Embedding 3 Small (Default)" = "text-embedding-3-small",
                  "Text Embedding 3 Large (Higher Quality)" = "text-embedding-3-large"
                ),
                selected = NULL,
                options = list(create = TRUE, placeholder = "Type your model...", onInitialize = I("function() { this.setValue(\"\"); }"))
              ),
              .password_input("topic_embedding_openai_api_key", "API Key:", placeholder = "sk-..."),
              conditionalPanel(
                condition = "output.has_openai_key",
                tags$div(style = "color: #0C795A; font-size: 13px; margin-top: -8px; margin-bottom: 8px;",
                  icon("check-circle"), " Key stored. Enter new key to override.")
              )
            ),

            conditionalPanel(
              condition = "input.topic_embedding_provider == 'gemini'",
              selectizeInput(
                "topic_embedding_gemini_model",
                "Gemini Model:",
                choices = c("Gemini Embedding 001" = "gemini-embedding-001"),
                selected = NULL,
                options = list(create = TRUE, placeholder = "Type your model...", onInitialize = I("function() { this.setValue(\"\"); }"))
              ),
              .password_input("topic_embedding_gemini_api_key", "API Key:", placeholder = "AIza..."),
              conditionalPanel(
                condition = "output.has_gemini_key",
                tags$div(style = "color: #0C795A; font-size: 13px; margin-top: -8px; margin-bottom: 8px;",
                  icon("check-circle"), " Key stored. Enter new key to override.")
              )
            ),

            conditionalPanel(
              condition = "input.embedding_backend == 'python'",
              sliderInput(
                "embedding_umap_neighbors",
                "Neighbors:",
                value = 15,
                min = 5,
                max = 50
              ),
              sliderInput(
                "embedding_umap_n_components",
                "UMAP dimensions:",
                value = 5,
                min = 2,
                max = 50
              ),
              sliderInput(
                "embedding_umap_min_dist",
                "Min distance (0.0 = tight clusters for topic modeling):",
                value = 0.0,
                min = 0.0,
                max = 0.99,
                step = 0.01
              ),
              selectInput(
                "embedding_umap_metric",
                "Distance metric:",
                choices = c(
                  "Cosine (recommended for text)" = "cosine",
                  "Euclidean" = "euclidean"
                ),
                selected = "cosine"
              ),
              sliderInput(
                "embedding_min_topic_size",
                "Min cluster size:",
                value = 10,
                min = 2,
                max = 50
              ),
              selectInput(
                "embedding_cluster_selection",
                "Cluster selection method:",
                choices = c(
                  "EOM (broader topics, default)" = "eom",
                  "Leaf (smaller, focused topics)" = "leaf"
                ),
                selected = "eom"
              )
            ),

            conditionalPanel(
              condition = "input.embedding_backend == 'r'",
              selectInput(
                "embedding_dimred_method",
                "Dimensionality reduction:",
                choices = c(
                  "UMAP" = "umap",
                  "t-SNE" = "tsne",
                  "PCA" = "pca"
                ),
                selected = "umap"
              ),

              conditionalPanel(
                condition = "input.embedding_dimred_method == 'umap'",
                sliderInput(
                  "embedding_r_umap_neighbors",
                  "Neighbors:",
                  value = 15,
                  min = 5,
                  max = 50
                ),
                sliderInput(
                  "embedding_r_umap_n_components",
                  "UMAP dimensions:",
                  value = 5,
                  min = 2,
                  max = 50
                ),
                sliderInput(
                  "embedding_r_umap_min_dist",
                  "Min distance (0.0 = tight clusters for topic modeling):",
                  value = 0.0,
                  min = 0.0,
                  max = 0.5,
                  step = 0.01
                ),
                selectInput(
                  "embedding_r_umap_metric",
                  "Distance metric:",
                  choices = c(
                    "Cosine (recommended for text)" = "cosine",
                    "Euclidean" = "euclidean"
                  ),
                  selected = "cosine"
                )
              ),

              conditionalPanel(
                condition = "input.embedding_dimred_method == 'tsne'",
                sliderInput(
                  "embedding_tsne_perplexity",
                  "Perplexity:",
                  value = 30,
                  min = 5,
                  max = 100
                )
              ),

              conditionalPanel(
                condition = "input.embedding_dimred_method == 'pca'",
                sliderInput(
                  "embedding_pca_dims",
                  "Components:",
                  value = 50,
                  min = 10,
                  max = 200
                )
              ),

              selectInput(
                "embedding_method_r",
                "Clustering method:",
                choices = c(
                  "DBSCAN (density-based)" = "dbscan",
                  "K-means" = "kmeans",
                  "Hierarchical" = "hierarchical",
                  "HDBSCAN" = "hdbscan"
                ),
                selected = "dbscan"
              ),

              conditionalPanel(
                condition = "input.embedding_method_r == 'kmeans' || input.embedding_method_r == 'hierarchical'",
                numericInput(
                  "embedding_r_n_topics",
                  "Number of clusters:",
                  value = 5,
                  min = 2,
                  max = 50
                )
              ),

              conditionalPanel(
                condition = "input.embedding_method_r == 'dbscan'",
                sliderInput(
                  "embedding_dbscan_eps",
                  "Epsilon:",
                  value = 0.5,
                  min = 0.1,
                  max = 2.0,
                  step = 0.1
                ),
                sliderInput(
                  "embedding_dbscan_minpts",
                  "Min points:",
                  value = 5,
                  min = 2,
                  max = 20
                )
              ),

              conditionalPanel(
                condition = "input.embedding_method_r == 'hdbscan'",
                sliderInput(
                  "embedding_r_min_cluster_size",
                  "Min cluster size:",
                  value = 5,
                  min = 2,
                  max = 50
                )
              ),

              checkboxInput(
                "embedding_r_reduce_outliers",
                "Reduce outliers (for DBSCAN/HDBSCAN)",
                value = TRUE
              )
            ),

            actionButton("embedding_run", "Run Model", class = "btn-primary btn-block")
          ),

          conditionalPanel(
            condition = "input.topic_modeling_path == 'embedding' && input.conditioned3 == 5",
            uiOutput("embedding_topics_info"),
            actionButton("embedding_display", "Display", class = "btn-primary btn-block")
          ),

          conditionalPanel(
            condition = "input.topic_modeling_path == 'embedding' && input.conditioned3 == 6",
            selectInput(
              "embedding_viz_type",
              "Visualization type",
              choices = c(
                "Topic Distribution" = "distribution",
                "Document Clusters" = "documents",
                "Topic Similarity Heatmap" = "heatmap",
                "Intertopic Distance" = "distance"
              ),
              selected = "distribution"
            )
          ),

          conditionalPanel(
            condition = "input.topic_modeling_path == 'embedding' && input.conditioned3 == 7",
            sliderInput(
              "embedding_quote_number",
              "Quotes to display",
              value = 3,
              min = 1,
              max = 10
            ),
            selectInput(
              "embedding_quote_topic",
              "Topic",
              choices = NULL
            ),
            actionButton("embedding_quote", "Quote", class = "btn-primary btn-block")
          ),






        ),
        mainPanel(
          width = 9,
          tabsetPanel(
            id = "conditioned3",
            tabPanel(
              "1. Model Configuration",
              value = 4,
              shinyBS::bsCollapse(
                open = 0,
                shinyBS::bsCollapsePanel(
                  p(strong("Click to set plot dimensions"),
                    class = "plot-dimensions-text"
                  ),
                  value = 4,
                  style = "success",
                  p(strong("Dimensions of the plots")),
                  div(
                    style = "display:inline-block",
                    sliderInput(
                      inputId = "height_search_k",
                      post = " px",
                      label = "height",
                      min = 200,
                      max = 4000,
                      step = 5,
                      value = 600
                    )
                  ),
                  div(
                    style = "display:inline-block",
                    sliderInput(
                      inputId = "width_search_k",
                      post = " px",
                      label = "width",
                      min = 500,
                      max = 3000,
                      step = 5,
                      value = 1000
                    )
                  )
                )
              ),
              conditionalPanel(
                condition = "output.has_search_k_results == true",
                tabsetPanel(
                  id = "searchKSubtabs",
                  tabPanel(
                    "Diagnostic Plots",
                    value = "diagnostic",
                    br(),
                    uiOutput("topic_search_message"),
                    br(),
                    fluidRow(
                      column(6, uiOutput("quality_metrics_semcoh_uiOutput")),
                      column(6, uiOutput("quality_metrics_residual_uiOutput"))
                    ),
                    fluidRow(
                      column(6, uiOutput("quality_metrics_heldout_uiOutput")),
                      column(6, uiOutput("quality_metrics_lbound_uiOutput"))
                    )
                  ),
                  tabPanel(
                    "Quality Comparison",
                    value = "quality",
                    br(),
                    wellPanel(
                      style = "background-color: #f0f8ff; border: 1px solid #4682b4;",
                      p("Overall Score = Coherence(z) + Exclusivity(z) - Residual(z) + Heldout(z)",
                        style = "font-family: monospace; font-size: 16px; font-weight: bold;"),
                      tags$div(
                        style = "font-size: 16px; margin-top: 10px; line-height: 1.6;",
                        p(tags$b("Coherence:"), " How interpretable topics are based on co-occurring words", style = "margin: 5px 0;"),
                        p(tags$b("Exclusivity:"), " How distinctive topics are from each other", style = "margin: 5px 0;"),
                        p(tags$b("Residual:"), " Model fit to data (lower is better)", style = "margin: 5px 0;"),
                        p(tags$b("Heldout:"), " Model's ability to generalize to new data", style = "margin: 5px 0;")
                      )
                    ),
                    br(),
                    DT::dataTableOutput("quality_summary_table")
                  ),
                  tabPanel(
                    "Coherence vs Exclusivity",
                    value = "comparison",
                    br(),
                    uiOutput("model_comparison_plot_uiOutput")
                  ),
                  tabPanel(
                    "AI Recommendation",
                    value = "ai_rec",
                    br(),
                    uiOutput("ai_recommendation_output")
                  )
                )
              ),
              conditionalPanel(
                condition = "output.has_search_k_results == false && input.topic_modeling_path == 'probability'",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 400px; margin: 0 auto;",
                    tags$i(class = "fa fa-search-plus", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;", "aria-hidden" = "true"),
                    tags$p(
                      "Configure K range and click ",
                      tags$strong("'Search K'", style = "color: #4269BF;"),
                      " to find optimal topic numbers",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              ),
              conditionalPanel(
                condition = "input.topic_modeling_path == 'embedding'",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 400px; margin: 0 auto;",
                    tags$i(class = "fa fa-cogs", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;", "aria-hidden" = "true"),
                    tags$p(
                      "Configure settings and click ",
                      tags$strong("'Run Model'", style = "color: #4269BF;"),
                      " to discover topics",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              ),
            ),
            tabPanel(
              "2. Word-Topic",
              value = 5,
              shinyBS::bsCollapse(
                open = 0,
                shinyBS::bsCollapsePanel(
                  p(strong("Click to set plot dimensions"),
                    class = "plot-dimensions-text"
                  ),
                  value = 1,
                  style = "success",
                  p(strong("Dimensions of the plot")),
                  div(
                    style = "display:inline-block",
                    sliderInput(
                      inputId = "height",
                      post = " px",
                      label = "height",
                      min = 200,
                      max = 4000,
                      step = 5,
                      value = 1000
                    )
                  ),
                  div(
                    style = "display:inline-block",
                    sliderInput(
                      inputId = "width",
                      post = " px",
                      label = "width",
                      min = 500,
                      max = 3000,
                      step = 5,
                      value = 1000
                    )
                  )
                )
              ),
              tags$style(
                HTML(
                  ".plot-container {
                                max-height: 4000px;
                                max-width: 3000px;
                                overflow: auto; }"
                )
              ),
              conditionalPanel(
                condition = "output.has_word_topic_results == true",
                uiOutput("topic_term_message"),
                uiOutput("topic_term_plot_uiOutput"),
                br(),
                uiOutput("topic_term_table_uiOutput"),
                br()
              ),
              conditionalPanel(
                condition = "output.has_word_topic_results == false && input.topic_modeling_path == 'probability'",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 400px; margin: 0 auto;",
                    tags$i(class = "fa fa-project-diagram", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;", "aria-hidden" = "true"),
                    tags$p(
                      "Search K, and then click ",
                      tags$strong("'Display'", style = "color: #4269BF;"),
                      " to view word-topic distributions",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              ),
              conditionalPanel(
                condition = "output.has_word_topic_results == false && input.topic_modeling_path == 'embedding'",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 400px; margin: 0 auto;",
                    tags$i(class = "fa fa-project-diagram", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;", "aria-hidden" = "true"),
                    tags$p(
                      "Run model, then click ",
                      tags$strong("'Display'", style = "color: #4269BF;"),
                      " to view topic keywords",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              ),
            ),
            tabPanel(
              "3. Content Generation",
              value = "ai_content",
              div(
                style = "padding: 20px;",

                conditionalPanel(
                  condition = "output.has_generated_content == true",
                  DT::dataTableOutput("generated_content_table")
                ),
                conditionalPanel(
                  condition = "output.has_generated_content == false",
                  div(
                    style = "padding: 60px 40px; text-align: center;",
                    tags$i(class = "fas fa-file-alt", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                    tags$p(
                      "Run topic modeling first, then configure settings in the sidebar and click ",
                      tags$strong("'Generate Content'", style = "color: #4269BF;"),
                      " to create content from your topics.",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              )
            ),

            tabPanel(
              "4. Document-Topic",
              value = 6,
              shinyBS::bsCollapse(
                open = 0,
                shinyBS::bsCollapsePanel(
                  p(strong("Click to set plot dimensions"),
                    class = "plot-dimensions-text"
                  ),
                  value = 1,
                  style = "success",
                  p(strong("Dimensions of the plot")),
                  div(
                    style = "display:inline-block",
                    sliderInput(
                      inputId = "height_topic_prevalence",
                      post = " px",
                      label = "height",
                      min = 200,
                      max = 4000,
                      step = 5,
                      value = 500
                    )
                  ),
                  div(
                    style = "display:inline-block",
                    sliderInput(
                      inputId = "width_topic_prevalence",
                      post = " px",
                      label = "width",
                      min = 500,
                      max = 3000,
                      step = 5,
                      value = 1000
                    )
                  )
                )
              ),
              tags$style(
                HTML(
                  ".plot-container {
                                max-height: 4000px;
                                max-width: 3000px;
                                overflow: auto; }"
                )
              ),
              conditionalPanel(
                condition = "output.has_document_topic_results == true",
                uiOutput("topic_prevalence_plot_uiOutput"),
                br(),
                uiOutput("topic_prevalence_table_uiOutput")
              ),
              conditionalPanel(
                condition = "output.has_document_topic_results == false && input.topic_modeling_path == 'probability'",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 400px; margin: 0 auto;",
                    tags$i(class = "fa fa-file-alt", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;", "aria-hidden" = "true"),
                    tags$p(
                      "Complete ",
                      tags$strong("Word-Topic tab", style = "color: #4269BF;"),
                      " to view document-topic distributions",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              ),
              conditionalPanel(
                condition = "output.has_document_topic_results == false && input.topic_modeling_path == 'embedding'",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 400px; margin: 0 auto;",
                    tags$i(class = "fa fa-file-alt", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;", "aria-hidden" = "true"),
                    tags$p(
                      "Complete ",
                      tags$strong("Word-Topic tab", style = "color: #4269BF;"),
                      " to view document-topic distributions",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              ),
            ),
            tabPanel(
              "5. Quotes",
              value = 7,
              conditionalPanel(
                condition = "output.has_quotes == true && input.topic_modeling_path == 'probability'",
                DT::dataTableOutput("quote_table")
              ),
              conditionalPanel(
                condition = "output.has_quotes == true && input.topic_modeling_path == 'embedding'",
                DT::dataTableOutput("embedding_quote_table")
              ),
              conditionalPanel(
                condition = "output.has_quotes == false",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 400px; margin: 0 auto;",
                    tags$i(class = "fa fa-quote-right", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;", "aria-hidden" = "true"),
                    tags$p(
                      "Run topic model and select a topic to view ",
                      tags$strong("representative quotes", style = "color: #4269BF;"),
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              )
            ),
            tabPanel(
              "6. Estimated Effects",
              value = 8,
              conditionalPanel(
                condition = "output.has_effect_estimates == true",
                DT::dataTableOutput("effect_table")
              ),
              conditionalPanel(
                condition = "output.has_effect_estimates == false",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 400px; margin: 0 auto;",
                    tags$i(class = "fa fa-calculator", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                    tags$p(
                      "Click ",
                      tags$strong("'Estimate'", style = "color: #4269BF;"),
                      " button to generate effect estimates",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              )
            ),
            tabPanel(
              "7. Categorical Covariates",
              value = 9,
              shinyBS::bsCollapse(
                open = 0,
                shinyBS::bsCollapsePanel(
                  p(strong("Click to set plot dimensions"),
                    class = "plot-dimensions-text"
                  ),
                  value = 1,
                  style = "success",
                  p(strong("Dimensions of the plot")),
                  div(
                    style = "display:inline-block",
                    sliderInput(
                      inputId = "height_cat_plot",
                      post = " px",
                      label = "height",
                      min = 200,
                      max = 4000,
                      step = 5,
                      value = 500
                    )
                  ),
                  div(
                    style = "display:inline-block",
                    sliderInput(
                      inputId = "width_cat_plot",
                      post = " px",
                      label = "width",
                      min = 500,
                      max = 3000,
                      step = 5,
                      value = 1000
                    )
                  )
                )
              ),
              tags$style(
                HTML(
                  ".plot-container {
                                max-height: 4000px;
                                max-width: 3000px;
                                overflow: auto; }"
                )
              ),
              conditionalPanel(
                condition = "output.has_categorical_plot == true",
                uiOutput("cat_plot_uiOutput"),
                br(),
                uiOutput("cat_table_uiOutput")
              ),
              conditionalPanel(
                condition = "output.has_categorical_plot == false",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 400px; margin: 0 auto;",
                    tags$i(class = "fa fa-chart-bar", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;", "aria-hidden" = "true"),
                    tags$p(
                      "Estimate effects, select categorical covariate, then click ",
                      tags$strong("'Display'", style = "color: #4269BF;"),
                      " to visualize topic prevalence by categories",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              )
            ),
            tabPanel(
              "8. Continuous Covariates",
              value = 10,
              shinyBS::bsCollapse(
                open = 0,
                shinyBS::bsCollapsePanel(
                  p(strong("Click to set plot dimensions"),
                    class = "plot-dimensions-text"
                  ),
                  value = 1,
                  style = "success",
                  p(strong("Dimensions of the plot")),
                  div(
                    style = "display:inline-block",
                    sliderInput(
                      inputId = "height_con_plot",
                      post = " px",
                      label = "height",
                      min = 200,
                      max = 4000,
                      step = 5,
                      value = 500
                    )
                  ),
                  div(
                    style = "display:inline-block",
                    sliderInput(
                      inputId = "width_con_plot",
                      post = " px",
                      label = "width",
                      min = 500,
                      max = 3000,
                      step = 5,
                      value = 1000
                    )
                  )
                )
              ),
              tags$style(
                HTML(
                  ".plot-container {
                                max-height: 4000px;
                                max-width: 3000px;
                                overflow: auto; }"
                )
              ),
              conditionalPanel(
                condition = "output.has_continuous_plot == true",
                uiOutput("con_plot_uiOutput"),
                br(),
                uiOutput("con_table_uiOutput")
              ),
              conditionalPanel(
                condition = "output.has_continuous_plot == false",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 400px; margin: 0 auto;",
                    tags$i(class = "fa fa-chart-line", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;", "aria-hidden" = "true"),
                    tags$p(
                      "Estimate effects, select continuous covariate, then click ",
                      tags$strong("'Display'", style = "color: #4269BF;"),
                      " to visualize topic prevalence trends",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              )
            )
          )
        )
      )
}

lexical_analysis_ui_content <- function() {
      sidebarLayout(
        sidebarPanel(
          width = 3,
          class = "sidebar-panel",
          conditionalPanel(
            condition = "input.conditioned2 == 1 && input.linguistic_subtabs == 'lemmas'",
            tags$h5(
              HTML("<strong>Linguistic Analysis</strong> <a href='https://spacy.io/usage/linguistic-features#lemmatization' target='_blank' rel='noopener noreferrer' onclick='window.open(this.href); return false;' style='font-size: 16px;'>Source</a>"),
              tags$span("OPTIONAL", style = "background-color: #6c757d; color: white; padding: 2px 8px; border-radius: 3px; font-size: 13px; margin-left: 8px;"),
              style = "color: #4269BF; margin-bottom: 10px;"
            ),
            div(
              style = "display: flex; gap: 10px; margin-bottom: 15px;",
              div(
                style = "flex: 1;",
                actionButton("lemma", "Apply", class = "btn-primary btn-block")
              ),
              div(
                style = "flex: 1;",
                actionButton("skip", "Skip", class = "btn-secondary btn-block")
              )
            )
          ),
          conditionalPanel(
            condition = "input.conditioned2 == 1 && input.linguistic_subtabs == 'pos'",
            tags$div(
              style = "display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;",
              tags$h5(HTML("<strong>Part-of-Speech Configuration</strong> <a href='https://universaldependencies.org/u/pos/' target='_blank' rel='noopener noreferrer' onclick='window.open(this.href); return false;' style='font-size: 16px;'>Source</a>"), style = "color: #4269BF; margin: 0;"),
              actionLink("showPOSInfo", icon("info-circle"),
                        style = "color: #337ab7; font-size: 16px;",
                        title = "Click for POS tags guide",
                        `aria-label` = "POS tags guide")
            ),
            selectizeInput(
              "pos_filter",
              "Filter by POS tag",
              choices = NULL,
              selected = NULL,
              multiple = TRUE,
              options = list(
                placeholder = "All POS tags (optional)"
              )
            ),
            sliderInput(
              "pos_top_n",
              "Top tags to display",
              value = 15,
              min = 1,
              max = 20,
              step = 1
            ),
            uiOutput("spacy_mode_ui"),
            checkboxInput(
              "include_dependency",
              "Show dependency relations",
              value = FALSE
            ),
            div(
              style = "display: flex; gap: 10px; margin-bottom: 15px;",
              div(
                style = "flex: 1;",
                actionButton("apply_pos", "Apply", class = "btn-primary btn-block")
              ),
              div(
                style = "flex: 1;",
                actionButton("generate_pos_report", "Report", class = "btn-primary btn-block", icon = icon("file-alt"))
              )
            )
          ),
          # Morphological Features sidebar
          conditionalPanel(
            condition = "input.conditioned2 == 1 && input.linguistic_subtabs == 'morphology'",
            div(
              style = "display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;",
              tags$h5(HTML("<strong>Morphology Analysis</strong>"), style = "color: #4269BF; margin: 0;"),
              actionLink("showMorphInfo", icon("info-circle"),
                         style = "color: #337ab7; font-size: 16px;",
                         title = "Click for morphology guide",
                         `aria-label` = "Morphology guide")
            ),
            tags$p(
              "Run POS tagging first, then select features to analyze.",
              style = "font-size: 16px; color: #475569; margin-bottom: 10px;"
            ),
            uiOutput("morph_status_ui"),
            checkboxGroupInput(
              "morph_features",
              NULL,
              choices = c(
                "Number (Sing/Plur)" = "Number",
                "Tense (Past/Pres/Fut)" = "Tense",
                "Verb Form (Fin/Inf/Part)" = "VerbForm",
                "Person (1/2/3)" = "Person",
                "Case (Nom/Acc/Gen)" = "Case",
                "Mood (Ind/Imp/Sub)" = "Mood",
                "Aspect (Perf/Imp/Prog)" = "Aspect"
              ),
              selected = c("Number", "Tense", "VerbForm", "Person")
            ),
            actionButton(
              "analyze_morphology",
              "Analyze Morphology",
              class = "btn-primary btn-block",
              icon = icon("language")
            )
          ),
          # Dependency Parsing sidebar
          conditionalPanel(
            condition = "input.conditioned2 == 1 && input.linguistic_subtabs == 'dependencies'",
            div(
              style = "display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;",
              tags$h5(HTML("<strong>Dependency Parsing</strong> <a href='https://universaldependencies.org/u/dep/' target='_blank' rel='noopener noreferrer' onclick='window.open(this.href); return false;' style='font-size: 16px;'>Source</a>"), style = "color: #4269BF; margin: 0;"),
              actionLink("showDepInfo", icon("info-circle"),
                         style = "color: #337ab7; font-size: 16px;",
                         title = "Click for dependency relations guide",
                         `aria-label` = "Dependency relations guide")
            )
          ),
          conditionalPanel(
            condition = "input.conditioned2 == 2",
            tags$h5(strong("Word Frequency by Variable"), style = "color: #4269BF; margin-bottom: 10px;"),
            selectizeInput(
              "continuous_var_3",
              "Continuous variable",
              choices = NULL,
              multiple = FALSE
            ),
            selectizeInput(
              "type_terms",
              "Terms",
              choices = NULL,
              options = list(maxItems = 20, multiple = TRUE, placeholder = "Select or type terms")
            ),
            sliderInput(
              "height_line_con_var_plot",
              "Plot height",
              value = 500,
              min = 200,
              max = 2000,
              step = 50
            ),
            sliderInput(
              "width_line_con_var_plot",
              "Plot width",
              value = 1000,
              min = 500,
              max = 2000,
              step = 50
            ),
            actionButton("plot_term", "Plot Terms", class = "btn-primary btn-block")
          ),
          conditionalPanel(
            condition = "input.conditioned2 == 4",
            tags$div(
              style = "display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;",
              tags$h5(HTML("<strong>Lexical Diversity Analysis</strong> <a href='https://github.com/quanteda/quanteda.textstats' target='_blank' rel='noopener noreferrer' onclick='window.open(this.href); return false;' style='font-size: 16px; color: #337ab7;'>Source</a>"), style = "color: #4269BF; margin: 0;"),
              actionLink("showLexDivMetricsInfo", icon("info-circle"),
                        style = "color: #337ab7; font-size: 16px;",
                        title = "Click for detailed metric descriptions",
                        `aria-label` = "Lexical diversity metric descriptions")
            ),
            selectizeInput(
              "lexdiv_doc_id_var",
              "Document ID variable",
              choices = NULL,
              selected = "",
              options = list(
                allowEmptyOption = TRUE,
                persist = TRUE,
                placeholder = "Optional"
              )
            ),
            br(),
            checkboxGroupInput(
              "lexdiv_metrics",
              "Metrics to calculate",
              choices = c(
                "MTLD (Most Recommended)" = "MTLD",
                "MATTR (Recommended)" = "MATTR",
                "MSTTR (Mean Segmental TTR)" = "MSTTR",
                "TTR (Type-Token Ratio)" = "TTR",
                "CTTR (Corrected TTR)" = "CTTR",
                "Herdan's C" = "C",
                "Guiraud's R" = "R",
                "Maas" = "Maas",
                "Yule's K" = "K",
                "Simpson's D" = "D"
              ),
              selected = c("MTLD", "MATTR", "TTR")
            ),
            actionButton("run_lexdiv_analysis", "Analyze", class = "btn-primary btn-block")
          ),
          conditionalPanel(
            condition = "input.conditioned2 == 5",
            tags$div(
              style = "display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px;",
              tags$h5(HTML("<strong>Readability Analysis</strong> <a href='https://github.com/quanteda/quanteda.textstats' target='_blank' rel='noopener noreferrer' onclick='window.open(this.href); return false;' style='font-size: 16px;'>Source</a>"), style = "color: #4269BF; margin: 0;"),
              actionLink("showReadabilityMetricsInfo", icon("info-circle"),
                        style = "color: #337ab7; font-size: 16px;",
                        title = "Click for detailed metric descriptions",
                        `aria-label` = "Readability metric descriptions")
            ),
            selectizeInput(
              "readability_doc_id_var",
              "Document ID variable",
              choices = NULL,
              selected = "",
              options = list(
                allowEmptyOption = TRUE,
                persist = TRUE,
                placeholder = "Optional"
              )
            ),
            br(),
            checkboxGroupInput(
              "readability_metrics",
              "Metrics to calculate",
              choices = c(
                "Flesch Reading Ease" = "flesch",
                "Flesch-Kincaid Grade" = "flesch_kincaid",
                "Gunning Fog" = "gunning_fog",
                "SMOG" = "smog",
                "Automated Readability Index" = "ari",
                "Coleman-Liau" = "coleman_liau"
              ),
              selected = c("flesch", "flesch_kincaid", "gunning_fog")
            ),
            tags$hr(),
            tags$h5(strong("Visualization Options"), style = "color: #4269BF; margin-top: 10px; margin-bottom: 10px;"),
            radioButtons(
              "readability_view_type",
              "View:",
              choices = c(
                "Overall Distribution" = "distribution",
                "Group Comparison" = "group",
                "Top Documents" = "document"
              ),
              selected = "distribution"
            ),
            conditionalPanel(
              condition = "input.readability_view_type == 'group'",
              selectizeInput(
                "readability_group_var",
                "Group by:",
                choices = NULL,
                selected = NULL,
                multiple = FALSE
              )
            ),
            conditionalPanel(
              condition = "input.readability_view_type == 'document'",
              sliderInput(
                "readability_top_n",
                "Number of documents",
                value = 15,
                min = 1,
                max = 30,
                step = 1
              )
            ),
            actionButton("run_readability_analysis", "Analyze", class = "btn-primary btn-block")
          ),
          conditionalPanel(
            condition = "input.conditioned2 == 3",
            tags$h5(HTML("<strong>Keyword Extraction</strong> <a href='https://quanteda.io/reference/dfm_tfidf.html' target='_blank' rel='noopener noreferrer' onclick='window.open(this.href); return false;' style='font-size: 16px;'>Source</a>"), style = "color: #4269BF; margin-bottom: 10px;"),
            conditionalPanel(
              condition = "input.keywords_subtabs == 'tfidf'",
              sliderInput(
                "tfidf_top_n",
                "Number of keywords",
                value = 20,
                min = 1,
                max = 50,
                step = 1
              ),
              checkboxInput(
                "tfidf_normalize",
                "Normalize TF-IDF scores",
                value = TRUE
              ),
              tags$p(
                "Scales scores to 0-1 range.",
                style = "font-size: 16px; color: #475569; margin-top: -5px; margin-bottom: 10px;"
              )
            ),
            conditionalPanel(
              condition = "input.keywords_subtabs == 'textrank'",
              sliderInput(
                "textrank_top_n",
                "Number of keywords",
                value = 15,
                min = 1,
                max = 30,
                step = 1
              ),
              selectizeInput(
                "tfidf_group_var",
                "Compare groups (for keyness)",
                choices = NULL,
                multiple = FALSE,
                options = list(placeholder = "Select grouping variable")
              ),
              tags$p(
                "Finds words used significantly more in one group vs others.",
                style = "font-size: 16px; color: #475569; margin-top: -5px; margin-bottom: 10px;"
              )
            ),
            conditionalPanel(
              condition = "input.keywords_subtabs == 'comparison'",
              sliderInput(
                "comparison_top_n",
                "Keywords to display",
                value = 10,
                min = 1,
                max = 20,
                step = 1
              )
            ),
            actionButton("run_keyword_extraction", "Extract", class = "btn-primary btn-block")
          ),
          # Log Odds Ratio Analysis sidebar
          conditionalPanel(
            condition = "input.conditioned2 == 6",
            tags$h5(HTML("<strong>Log Odds Ratio Analysis</strong>"), style = "color: #4269BF; margin-bottom: 10px;"),
            tags$p(
              "Compare word frequencies between categories to identify distinctive terms.",
              style = "font-size: 16px; color: #475569; margin-bottom: 10px;"
            ),
            selectizeInput(
              "log_odds_group_var",
              "Categorical variable",
              choices = NULL,
              multiple = FALSE,
              options = list(placeholder = "Select category variable")
            ),
            radioButtons(
              "log_odds_method",
              "Method",
              choices = c(
                "Weighted (Fightin' Words)" = "weighted",
                "Simple (Laplace smoothing)" = "simple"
              ),
              selected = "weighted"
            ),
            conditionalPanel(
              condition = "input.log_odds_method == 'simple'",
              radioButtons(
                "log_odds_comparison_mode",
                "Comparison mode",
                choices = c(
                  "Binary (2 categories)" = "binary",
                  "Pairwise (all pairs)" = "pairwise"
                ),
                selected = "binary"
              )
            ),
            conditionalPanel(
              condition = "input.log_odds_method == 'simple' && input.log_odds_comparison_mode == 'binary'",
              selectizeInput(
                "log_odds_reference",
                "Reference category",
                choices = NULL,
                multiple = FALSE,
                options = list(placeholder = "First category")
              )
            ),
            sliderInput(
              "log_odds_top_n",
              "Top terms per direction",
              value = 10,
              min = 1,
              max = 25,
              step = 1
            ),
            sliderInput(
              "log_odds_min_count",
              "Minimum word count",
              value = 5,
              min = 1,
              max = 20,
              step = 1
            ),
            actionButton("run_log_odds", "Calculate", class = "btn-primary btn-block", icon = icon("balance-scale"))
          ),
          # Lexical Dispersion sidebar
          conditionalPanel(
            condition = "input.conditioned2 == 7",
            tags$h5(HTML("<strong>Lexical Dispersion</strong>"), style = "color: #4269BF; margin-bottom: 10px;"),
            tags$p(
              "Visualize where selected terms appear across documents in your corpus.",
              style = "font-size: 16px; color: #475569; margin-bottom: 10px;"
            ),
            selectizeInput(
              "dispersion_terms",
              "Terms",
              choices = NULL,
              multiple = TRUE,
              options = list(
                placeholder = "Type or select terms...",
                maxItems = 10,
                create = TRUE
              )
            ),
            radioButtons(
              "dispersion_scale",
              "Position scale",
              choices = c(
                "Relative (0-1)" = "relative",
                "Absolute (token position)" = "absolute"
              ),
              selected = "relative"
            ),
            checkboxInput(
              "dispersion_show_metrics",
              "Show dispersion metrics",
              value = TRUE
            ),
            actionButton("run_dispersion", "Analyze", class = "btn-primary btn-block", icon = icon("chart-line"))
          ),
          conditionalPanel(
            condition = "input.conditioned2 == 1 && input.linguistic_subtabs == 'dependencies'",
            selectizeInput(
              "dep_doc_id_var",
              "Document ID variable",
              choices = NULL,
              selected = "",
              options = list(
                allowEmptyOption = TRUE,
                persist = TRUE,
                placeholder = "Optional"
              )
            )
          ),
          conditionalPanel(
            condition = "input.conditioned2 == 1 && input.linguistic_subtabs == 'ner'",
            selectizeInput(
              "ner_doc_id_var",
              "Document ID variable",
              choices = NULL,
              selected = "",
              options = list(
                allowEmptyOption = TRUE,
                persist = TRUE,
                placeholder = "Optional"
              )
            )
          ),
          conditionalPanel(
            condition = "input.conditioned2 == 1 && input.linguistic_subtabs == 'ner'",
            tags$div(
              style = "margin-bottom: 15px;",
              tags$h5(
                HTML("<strong>spaCy NER Labels</strong>"),
                actionLink("showNERInfo", icon("info-circle"),
                          style = "color: #337ab7; font-size: 16px; margin-left: 8px;",
                          title = "Click for NER guide"),
                style = "color: #4269BF; margin-bottom: 10px;"
              ),
              div(
                style = "max-height: 300px; overflow-y: auto;",
                tags$details(
                  style = "margin-bottom: 6px; background: #f8f9fa; padding: 6px; border-radius: 4px;",
                  tags$summary(style = "cursor: pointer; font-weight: 600; font-size: 16px;", "People & Places"),
                  div(
                    style = "padding: 8px 0 0 8px;",
                    checkboxGroupInput("ner_named", HTML("<span class='sr-only'>Named entities</span>"), inline = FALSE,
                      choiceNames = list(
                        HTML("<span class='sidebar-color-picker' data-entity='PERSON' data-source='spacy' style='display:inline-block;width:12px;height:12px;background:#e91e63;border-radius:2px;margin-right:6px;cursor:pointer;'></span><span style='font-weight:500;'>PERSON</span> <span style='color:#475569;'>- People, including fictional</span>"),
                        HTML("<span class='sidebar-color-picker' data-entity='NORP' data-source='spacy' style='display:inline-block;width:12px;height:12px;background:#ff8f00;border-radius:2px;margin-right:6px;cursor:pointer;'></span><span style='font-weight:500;'>NORP</span> <span style='color:#475569;'>- Nationalities, religious/political groups</span>"),
                        HTML("<span class='sidebar-color-picker' data-entity='ORG' data-source='spacy' style='display:inline-block;width:12px;height:12px;background:#1565c0;border-radius:2px;margin-right:6px;cursor:pointer;'></span><span style='font-weight:500;'>ORG</span> <span style='color:#475569;'>- Companies, agencies, institutions</span>"),
                        HTML("<span class='sidebar-color-picker' data-entity='GPE' data-source='spacy' style='display:inline-block;width:12px;height:12px;background:#2e7d32;border-radius:2px;margin-right:6px;cursor:pointer;'></span><span style='font-weight:500;'>GPE</span> <span style='color:#475569;'>- Countries, cities, states</span>"),
                        HTML("<span class='sidebar-color-picker' data-entity='LOC' data-source='spacy' style='display:inline-block;width:12px;height:12px;background:#0277bd;border-radius:2px;margin-right:6px;cursor:pointer;'></span><span style='font-weight:500;'>LOC</span> <span style='color:#475569;'>- Non-GPE locations, mountains, water bodies</span>"),
                        HTML("<span class='sidebar-color-picker' data-entity='FAC' data-source='spacy' style='display:inline-block;width:12px;height:12px;background:#9e9d24;border-radius:2px;margin-right:6px;cursor:pointer;'></span><span style='font-weight:500;'>FAC</span> <span style='color:#475569;'>- Buildings, airports, highways, bridges</span>")
                      ),
                      choiceValues = c("PERSON", "NORP", "ORG", "GPE", "LOC", "FAC"),
                      selected = c("PERSON", "NORP", "ORG", "GPE", "LOC", "FAC")
                    )
                  )
                ),
                tags$details(
                  style = "margin-bottom: 6px; background: #f8f9fa; padding: 6px; border-radius: 4px;",
                  tags$summary(style = "cursor: pointer; font-weight: 600; font-size: 16px;", "Objects & Events"),
                  div(
                    style = "padding: 8px 0 0 8px;",
                    checkboxGroupInput("ner_objects", HTML("<span class='sr-only'>Object and event entities</span>"), inline = FALSE,
                      choiceNames = list(
                        HTML("<span class='sidebar-color-picker' data-entity='PRODUCT' data-source='spacy' style='display:inline-block;width:12px;height:12px;background:#5060D5;border-radius:2px;margin-right:6px;cursor:pointer;'></span><span style='font-weight:500;'>PRODUCT</span> <span style='color:#475569;'>- Objects, vehicles, foods, etc.</span>"),
                        HTML("<span class='sidebar-color-picker' data-entity='EVENT' data-source='spacy' style='display:inline-block;width:12px;height:12px;background:#c62828;border-radius:2px;margin-right:6px;cursor:pointer;'></span><span style='font-weight:500;'>EVENT</span> <span style='color:#475569;'>- Named hurricanes, battles, wars, sports</span>"),
                        HTML("<span class='sidebar-color-picker' data-entity='WORK_OF_ART' data-source='spacy' style='display:inline-block;width:12px;height:12px;background:#734FE2;border-radius:2px;margin-right:6px;cursor:pointer;'></span><span style='font-weight:500;'>WORK_OF_ART</span> <span style='color:#475569;'>- Titles of books, songs, etc.</span>"),
                        HTML("<span class='sidebar-color-picker' data-entity='LAW' data-source='spacy' style='display:inline-block;width:12px;height:12px;background:#03786A;border-radius:2px;margin-right:6px;cursor:pointer;'></span><span style='font-weight:500;'>LAW</span> <span style='color:#475569;'>- Named documents made into laws</span>"),
                        HTML("<span class='sidebar-color-picker' data-entity='LANGUAGE' data-source='spacy' style='display:inline-block;width:12px;height:12px;background:#558b2f;border-radius:2px;margin-right:6px;cursor:pointer;'></span><span style='font-weight:500;'>LANGUAGE</span> <span style='color:#475569;'>- Any named language</span>")
                      ),
                      choiceValues = c("PRODUCT", "EVENT", "WORK_OF_ART", "LAW", "LANGUAGE"),
                      selected = c("PRODUCT", "EVENT", "WORK_OF_ART", "LAW", "LANGUAGE")
                    )
                  )
                ),
                tags$details(
                  style = "margin-bottom: 6px; background: #f8f9fa; padding: 6px; border-radius: 4px;",
                  tags$summary(style = "cursor: pointer; font-weight: 600; font-size: 16px;", "Numeric & Temporal"),
                  div(
                    style = "padding: 8px 0 0 8px;",
                    checkboxGroupInput("ner_numeric", HTML("<span class='sr-only'>Numeric and temporal entities</span>"), inline = FALSE,
                      choiceNames = list(
                        HTML("<span class='sidebar-color-picker' data-entity='DATE' data-source='spacy' style='display:inline-block;width:12px;height:12px;background:#ef6c00;border-radius:2px;margin-right:6px;cursor:pointer;'></span><span style='font-weight:500;'>DATE</span> <span style='color:#475569;'>- Absolute or relative dates/periods</span>"),
                        HTML("<span class='sidebar-color-picker' data-entity='TIME' data-source='spacy' style='display:inline-block;width:12px;height:12px;background:#d84315;border-radius:2px;margin-right:6px;cursor:pointer;'></span><span style='font-weight:500;'>TIME</span> <span style='color:#475569;'>- Times smaller than a day</span>"),
                        HTML("<span class='sidebar-color-picker' data-entity='MONEY' data-source='spacy' style='display:inline-block;width:12px;height:12px;background:#9C3AD7;border-radius:2px;margin-right:6px;cursor:pointer;'></span><span style='font-weight:500;'>MONEY</span> <span style='color:#475569;'>- Monetary values including unit</span>"),
                        HTML("<span class='sidebar-color-picker' data-entity='PERCENT' data-source='spacy' style='display:inline-block;width:12px;height:12px;background:#00838f;border-radius:2px;margin-right:6px;cursor:pointer;'></span><span style='font-weight:500;'>PERCENT</span> <span style='color:#475569;'>- Percentage including %</span>"),
                        HTML("<span class='sidebar-color-picker' data-entity='QUANTITY' data-source='spacy' style='display:inline-block;width:12px;height:12px;background:#78909c;border-radius:2px;margin-right:6px;cursor:pointer;'></span><span style='font-weight:500;'>QUANTITY</span> <span style='color:#475569;'>- Measurements (weight, distance)</span>"),
                        HTML("<span class='sidebar-color-picker' data-entity='ORDINAL' data-source='spacy' style='display:inline-block;width:12px;height:12px;background:#866358;border-radius:2px;margin-right:6px;cursor:pointer;'></span><span style='font-weight:500;'>ORDINAL</span> <span style='color:#475569;'>- first, second, third, etc.</span>"),
                        HTML("<span class='sidebar-color-picker' data-entity='CARDINAL' data-source='spacy' style='display:inline-block;width:12px;height:12px;background:#546e7a;border-radius:2px;margin-right:6px;cursor:pointer;'></span><span style='font-weight:500;'>CARDINAL</span> <span style='color:#475569;'>- Numerals not in other category</span>")
                      ),
                      choiceValues = c("DATE", "TIME", "MONEY", "PERCENT", "QUANTITY", "ORDINAL", "CARDINAL"),
                      selected = c("DATE", "TIME", "MONEY", "PERCENT", "QUANTITY", "ORDINAL", "CARDINAL")
                    )
                  )
                )
              ),
              shinyjs::hidden(
                selectizeInput("spacy_default_entities", HTML("<span class='sr-only'>Default spaCy entities</span>"), choices = NULL)
              )
            ),
            tags$hr(style = "margin: 15px 0; border-color: #dee2e6;"),
            tags$div(
              style = "margin-bottom: 15px;",
              tags$h5(
                HTML("<strong>Domain Entities</strong>"),
                style = "color: #4269BF; margin-bottom: 10px;"
              ),
              selectizeInput(
                "ner_domain_preset",
                NULL,
                choices = c(
                  "None (Custom)" = "none",
                  "Special Education" = "special_education"
                ),
                selected = "special_education",
                multiple = FALSE,
                options = list(
                  placeholder = "Select a domain"
                )
              ),
              div(
                style = "display: flex; gap: 10px; margin-bottom: 8px;",
                div(
                  style = "flex: 1;",
                  downloadButton("export_domain_preset", "Export",
                    class = "btn-secondary btn-block", icon = icon("download"))
                ),
                div(
                  style = "flex: 1;",
                  actionButton("import_domain_trigger", "Import",
                    class = "btn-secondary btn-block", icon = icon("upload"))
                ),
                tags$input(type = "file", id = "import_domain_preset_file",
                  accept = ".xlsx,.csv", style = "display: none;"),
                tags$script(HTML("
                  $('#import_domain_trigger').on('click', function() {
                    $('#import_domain_preset_file').click();
                  });
                  $('#import_domain_preset_file').on('change', function() {
                    var file = this.files[0];
                    if (file) {
                      var reader = new FileReader();
                      reader.onload = function(e) {
                        Shiny.setInputValue('import_domain_preset_data', {
                          name: file.name,
                          dataurl: e.target.result
                        });
                      };
                      reader.readAsDataURL(file);
                      this.value = '';
                    }
                  });
                "))
              ),
              div(
                uiOutput("domain_entity_categories"),
                uiOutput("custom_entities_ui")
              )
            ),
            tags$hr(style = "margin: 15px 0; border-color: #dee2e6;"),
            tags$div(
              style = "margin-bottom: 15px;",
              tags$h5(
                HTML("<strong>Add Custom Entity</strong>"),
                style = "color: #4269BF; margin-bottom: 10px;"
              ),
              div(
                style = "display: flex; gap: 8px; align-items: flex-end;",
                div(
                  style = "flex: 0 0 36px;",
                  title = "Select the entity color",
                  if (requireNamespace("colourpicker", quietly = TRUE)) {
                    colourpicker::colourInput("custom_entity_color", HTML("<span class='sr-only'>Custom entity color</span>"),
                      value = "#607D8B", showColour = "background")
                  } else {
                    textInput("custom_entity_color", HTML("<span class='sr-only'>Custom entity color hex</span>"), value = "#607D8B", placeholder = "#HEX")
                  }
                ),
                div(
                  style = "flex: 1;",
                  textInput("custom_entity_name", HTML("<span class='sr-only'>Custom entity name</span>"), placeholder = "Type a New Entity")
                ),
                div(
                  style = "flex: 0 0 auto; margin-bottom: 15px;",
                  actionButton("apply_custom_entity", icon("plus"),
                    class = "btn-primary btn-sm",
                    style = "height: 34px; width: 34px; padding: 0;",
                    title = "Add custom entity")
                )
              ),
              selectizeInput("uncategorized_tokens", HTML("<span class='sr-only'>Uncategorized tokens to assign</span>"),
                choices = NULL, selected = NULL, multiple = TRUE,
                options = list(create = TRUE, placeholder = "Select terms for this entity")
              )
            ),
            tags$hr(style = "margin: 15px 0; border-color: #dee2e6;"),
            div(
              style = "display: flex; gap: 10px; margin-bottom: 15px;",
              div(
                style = "flex: 1;",
                actionButton("apply_ner_filter", "Apply",
                           class = "btn-primary btn-block",
                           icon = icon("play"))
              ),
              div(
                style = "flex: 1;",
                downloadButton("export_entities", "Export",
                           class = "btn-secondary btn-block",
                           icon = icon("download"))
              )
            ),
            shinyjs::hidden(
              selectizeInput("entity_type_filter", HTML("<span class='sr-only'>Entity type filter</span>"), choices = NULL),
              textAreaInput("batch_entity_text", HTML("<span class='sr-only'>Batch entity text input</span>")),
              checkboxInput("batch_include_lemmas", HTML("<span class='sr-only'>Include lemmas in batch</span>"), value = TRUE),
              actionButton("add_custom_entity_type", ""),
              actionButton("tab_type", ""),
              actionButton("tab_upload", ""),
              actionButton("add_batch_entities", ""),
              actionButton("add_batch_entities_bottom", ""),
              actionButton("process_codebook", ""),
              actionButton("process_codebook_bottom", ""),
              fileInput("codebook_upload", HTML("<span class='sr-only'>Upload codebook file</span>"))
            ),
            tags$script(HTML("
              $(document).on('click', '.sidebar-color-picker', function(e) {
                e.stopPropagation();
                e.preventDefault();
                var $el = $(this);
                var entity = $el.data('entity');
                var source = $el.data('source') || '';
                var category = $el.data('category') || '';
                var bgColor = $el.css('background-color');
                var hex = bgColor;
                if (bgColor.indexOf('rgb') === 0) {
                  var parts = bgColor.match(/\\d+/g);
                  if (parts && parts.length >= 3) {
                    hex = '#' +
                      ('0' + parseInt(parts[0]).toString(16)).slice(-2) +
                      ('0' + parseInt(parts[1]).toString(16)).slice(-2) +
                      ('0' + parseInt(parts[2]).toString(16)).slice(-2);
                  }
                }
                Shiny.setInputValue('sidebar_color_edit', {
                  entity: entity,
                  source: source,
                  category: category,
                  color: hex
                }, {priority: 'event'});
              });
            ")),
          tags$script(HTML("
            $(document).on('click', '.entity-visibility-chk', function(e) {
              e.stopPropagation();
              Shiny.setInputValue('entity_visibility_toggle', {
                category: $(this).data('category'),
                source: $(this).data('source'),
                checked: this.checked,
                time: new Date().getTime()
              }, {priority: 'event'});
            });
            $(document).on('click', '.entity-delete-btn', function(e) {
              e.stopPropagation();
              e.preventDefault();
              Shiny.setInputValue('delete_entity_category', {
                category: $(this).data('category'),
                source: $(this).data('source'),
                time: new Date().getTime()
              }, {priority: 'event'});
            });
            "))
          )
        ),
        mainPanel(
          width = 9,
          tabsetPanel(
            id = "conditioned2",
            tabPanel(
              "1. Linguistic Annotation",
              value = 1,
              br(),
              tabsetPanel(
                id = "linguistic_subtabs",
                tabPanel(
                  "Word Forms (Lemmas)",
                  value = "lemmas",
                  br(),
                  conditionalPanel(
                    condition = "output.lemma_ready == false",
                    div(
                      style = "padding: 60px 40px; text-align: center;",
                      div(
                        style = "max-width: 500px; margin: 0 auto;",
                        tags$i(class = "fa fa-info-circle", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                        tags$p(
                          "Click ",
                          tags$strong("'Apply'", style = "color: #4269BF;"),
                          " to run spaCy linguistic analysis, or ",
                          tags$strong("'Skip'", style = "color: #4269BF;"),
                          " to use standard tokenization.",
                          style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                        )
                      )
                    )
                  ),
                  conditionalPanel(
                    condition = "output.lemma_ready == true",
                    div(
                      style = "margin-bottom: 20px; overflow: visible;",
                      plotly::plotlyOutput("lemma_plot", height = 500, width = "100%")
                    ),
                    br(),
                    DT::dataTableOutput("lemma_table")
                  )
                ),
                tabPanel(
                  "Part-of-Speech Tags",
                  value = "pos",
                  br(),
                  conditionalPanel(
                    condition = "output.pos_ready == false",
                    div(
                      style = "padding: 60px 40px; text-align: center;",
                      div(
                        style = "max-width: 500px; margin: 0 auto;",
                        tags$i(class = "fa fa-info-circle", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                        tags$p(
                          "First, click ",
                          tags$strong("'Apply'", style = "color: #4269BF;"),
                          " on the Word Forms tab to run spaCy analysis. Then configure and click ",
                          tags$strong("'Apply'", style = "color: #4269BF;"),
                          " here to view POS tags.",
                          style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                        )
                      )
                    )
                  ),
                  conditionalPanel(
                    condition = "output.pos_ready == true",
                    uiOutput("pos_plot_uiOutput"),
                    br(),
                    DT::dataTableOutput("pos_table")
                  )
                ),
                tabPanel(
                  "Morphological Features",
                  value = "morphology",
                  br(),
                  conditionalPanel(
                    condition = "output.morph_ready == false",
                    div(
                      style = "padding: 60px 40px; text-align: center;",
                      div(
                        style = "max-width: 500px; margin: 0 auto;",
                        tags$i(class = "fa fa-info-circle", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                        tags$p(
                          "First, run POS tagging on the Word Forms tab. Then select morphological features in the sidebar and click ",
                          tags$strong("'Analyze Morphology'", style = "color: #4269BF;"),
                          " to extract features.",
                          style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                        )
                      )
                    )
                  ),
                  conditionalPanel(
                    condition = "output.morph_ready == true",
                    tabsetPanel(
                      id = "morph_subtabs",
                      tabPanel(
                        "Feature Distribution",
                        br(),
                        uiOutput("morph_warning_ui"),
                        fluidRow(
                          column(6, plotly::plotlyOutput("morph_number_plot", height = "300px")),
                          column(6, plotly::plotlyOutput("morph_tense_plot", height = "300px"))
                        ),
                        fluidRow(
                          column(6, plotly::plotlyOutput("morph_verbform_plot", height = "300px")),
                          column(6, plotly::plotlyOutput("morph_person_plot", height = "300px"))
                        )
                      ),
                      tabPanel(
                        "Additional Features",
                        br(),
                        conditionalPanel(
                          condition = "output.has_additional_features == true",
                          fluidRow(
                            conditionalPanel(
                              condition = "output.has_case_data == true",
                              column(4, plotly::plotlyOutput("morph_case_plot", height = "300px"))
                            ),
                            conditionalPanel(
                              condition = "output.has_mood_data == true",
                              column(4, plotly::plotlyOutput("morph_mood_plot", height = "300px"))
                            ),
                            conditionalPanel(
                              condition = "output.has_aspect_data == true",
                              column(4, plotly::plotlyOutput("morph_aspect_plot", height = "300px"))
                            )
                          )
                        ),
                        conditionalPanel(
                          condition = "output.has_additional_features == false",
                          div(style = "padding: 60px 40px; text-align: center;",
                            div(style = "max-width: 400px; margin: 0 auto;",
                              tags$i(class = "fa fa-language", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;", `aria-hidden` = "true"),
                              tags$p("Select Case, Mood, or Aspect checkboxes from the sidebar to display additional morphological features.",
                                style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;")
                            )
                          )
                        )
                      ),
                      tabPanel(
                        "Summary Table",
                        br(),
                        DT::dataTableOutput("morph_summary_table")
                      )
                    )
                  )
                ),
                tabPanel(
                  "Dependency Parsing",
                  value = "dependencies",
                  br(),
                  conditionalPanel(
                    condition = "output.dependencies_ready == false",
                    div(
                      style = "padding: 60px 40px; text-align: center;",
                      div(
                        style = "max-width: 500px; margin: 0 auto;",
                        tags$i(class = "fa fa-info-circle", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                        tags$p(
                          "Click ",
                          tags$strong("'Apply'", style = "color: #4269BF;"),
                          " on the Word Forms tab to run spaCy analysis and extract dependency parsing data.",
                          style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                        )
                      )
                    )
                  ),
                  conditionalPanel(
                    condition = "output.dependencies_ready == true",
                    # Document selector for displaCy visualization
                    div(
                      style = "margin-bottom: 15px;",
                      selectInput(
                        "dep_viz_doc",
                        "Select document to visualize dependencies:",
                        choices = NULL,
                        width = "100%"
                      )
                    ),
                    # Download button for Dependency Parsing
                    uiOutput("dep_download_button"),
                    uiOutput("dep_displacy_container"),
                    br(),
                    DT::dataTableOutput("dep_table")
                  )
                ),
                tabPanel(
                  "Named Entity Recognition",
                  value = "ner",
                  br(),
                  conditionalPanel(
                    condition = "output.spacy_ready == false",
                    div(
                      style = "padding: 60px 40px; text-align: center;",
                      div(
                        style = "max-width: 500px; margin: 0 auto;",
                        tags$i(class = "fa fa-info-circle", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                        tags$p(
                          "Click ",
                          tags$strong("'Apply'", style = "color: #4269BF;"),
                          " on the Word Forms tab to run spaCy analysis.",
                          style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                        )
                      )
                    )
                  ),
                  conditionalPanel(
                    condition = "output.ner_pending == true",
                    div(
                      style = "padding: 60px 40px; text-align: center;",
                      div(
                        style = "max-width: 500px; margin: 0 auto;",
                        tags$i(class = "fa fa-info-circle", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                        tags$p(
                          "Click ",
                          tags$strong("'Apply'", style = "color: #4269BF;"),
                          " to extract named entities.",
                          style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                        )
                      )
                    )
                  ),
                  conditionalPanel(
                    condition = "output.ner_ready == true",
                    # Document selector, displaCy visualization, and download button (conditional)
                    uiOutput("ner_doc_selector"),
                    uiOutput("ner_displacy_container"),
                    uiOutput("ner_download_button"),
                    # Existing frequency plot
                    uiOutput("entity_plot_uiOutput"),
                    br(),
                    uiOutput("entity_table_uiOutput")
                  )
                )
              )
            ),
            tabPanel(
              "2. Frequency Trends",
              value = 2,
              bsCollapse(
                open = 0,
                bsCollapsePanel(
                  p(strong("Click to set plot height"),
                    class = "plot-dimensions-text"
                  ),
                  value = 2,
                  style = "success",
                  p(strong("Height of the plot")),
                  sliderInput(
                    inputId = "height_frequency_trends",
                    post = " px",
                    label = "height",
                    min = 200,
                    max = 2000,
                    step = 50,
                    value = 500
                  )
                )
              ),
              conditionalPanel(
                condition = "output.has_frequency_plot == true",
                uiOutput("line_con_var_plot_uiOutput")
              ),
              conditionalPanel(
                condition = "output.has_frequency_plot == false",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 400px; margin: 0 auto;",
                    tags$i(class = "fa fa-chart-line", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                    tags$p(
                      "Select terms, continuous variable, and click ",
                      tags$strong("'Plot Terms'", style = "color: #4269BF;"),
                      " to analyze frequency trends",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              )
            ),
            tabPanel(
              "3. Keywords",
              value = 3,
              br(),
              tabsetPanel(
                id = "keywords_subtabs",
                tabPanel(
                  "TF-IDF",
                  value = "tfidf",
                  uiOutput("keywords_tfidf_uiOutput")
                ),
                tabPanel(
                  "Statistical Keyness",
                  value = "textrank",
                  uiOutput("keywords_textrank_uiOutput")
                ),
                tabPanel(
                  "Comparison",
                  value = "comparison",
                  uiOutput("keywords_comparison_uiOutput")
                )
              )
            ),
            tabPanel(
              "4. Lexical Diversity",
              value = 4,
              br(),
              uiOutput("lexical_diversity_uiOutput")
            ),
            tabPanel(
              "5. Readability",
              value = 5,
              br(),
              uiOutput("readability_results_uiOutput")
            ),
            tabPanel(
              "6. Log Odds Ratio",
              value = 6,
              br(),
              conditionalPanel(
                condition = "output.log_odds_ready == false",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 500px; margin: 0 auto;",
                    tags$i(class = "fa fa-balance-scale", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                    tags$p(
                      "Select a grouping variable and click ",
                      tags$strong("'Calculate'", style = "color: #4269BF;"),
                      " to compare word usage between categories.",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    ),

                  )
                )
              ),
              conditionalPanel(
                condition = "output.log_odds_ready == true",
                uiOutput("log_odds_plot_uiOutput"),
                br(),
                DT::dataTableOutput("log_odds_table")
              )
            ),
            tabPanel(
              "7. Lexical Dispersion",
              value = 7,
              br(),
              conditionalPanel(
                condition = "output.dispersion_ready == false",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 500px; margin: 0 auto;",
                    tags$i(class = "fa fa-chart-line", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                    tags$p(
                      "Select terms in the sidebar and click ",
                      tags$strong("'Analyze'", style = "color: #4269BF;"),
                      " to visualize their dispersion across documents.",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    ),

                  )
                )
              ),
              conditionalPanel(
                condition = "output.dispersion_ready == true",
                uiOutput("dispersion_plot_uiOutput"),
                conditionalPanel(
                  condition = "input.dispersion_show_metrics == true",
                  br(),
                  div(
                    style = "margin-top: 20px;",
                    tags$h5("Dispersion Metrics", style = "color: #4269BF; margin-bottom: 10px;"),
                    DT::dataTableOutput("dispersion_metrics_table")
                  )
                )
              )
            )
          )
        )
      )
}

semantic_analysis_ui_content <- function() {
      sidebarLayout(
        sidebarPanel(
          width = 3,
          class = "sidebar-panel",
          # Categorical variable - only for tabs that use it (Similarity and Comparative)
          conditionalPanel(
            condition = "input.semantic_analysis_tabs == 'similarity' || input.semantic_analysis_tabs == 'comparative'",
            div(
              style = "margin-bottom: 15px; padding-bottom: 15px; border-bottom: 1px solid #e2e8f0;",
              selectizeInput(
                "doc_category_var",
                "Categorical variable",
                choices = NULL,
                selected = "",
                options = list(
                  allowEmptyOption = TRUE,
                  persist = TRUE,
                  placeholder = "Optional"
                )
              )
            )
          ),
          conditionalPanel(
            condition = "input.semantic_analysis_tabs == 'summary'",
            tags$h5(strong("Configure document metadata"), style = "color: #4269BF; margin-bottom: 10px;"),
            div(class = "checkbox-margin",
              selectizeInput(
                "doc_id_var",
                "Document ID variable",
                choices = NULL,
                selected = "",
                options = list(
                  allowEmptyOption = TRUE,
                  persist = TRUE,
                  placeholder = "Optional"
                )
              )
            ),
            br(),
            div(
              style = "margin-bottom: 15px;",
              actionButton("process_documents", "Process", class = "btn-primary btn-block")
            )
          ),
          conditionalPanel(
            condition = "input.semantic_analysis_tabs != 'summary' && input.semantic_analysis_tabs != 'sentiment'",
            div(
              style = "margin-bottom: 8px;",
              uiOutput("semantic_feature_space_selector")
            )
          ),

          conditionalPanel(
            condition = "input.semantic_analysis_tabs == 'similarity'",
            tags$h5(HTML("<strong>Document Similarity</strong> <a href='https://www.sbert.net/' target='_blank' rel='noopener noreferrer' onclick='window.open(this.href); return false;' style='font-size: 16px;'>Source</a>"), style = "color: #4269BF; margin-bottom: 10px;"),

            conditionalPanel(
              condition = "input.semantic_feature_space == 'embeddings'",
              tags$label("Embedding Configuration", style = "font-weight: 600; margin-bottom: 8px; display: block; color: #4269BF;"),
              tags$p(style = "font-size: 16px; color: #666; margin-bottom: 10px;", "Document-to-document similarity using vector embeddings."),
              uiOutput("embedding_status_ui"),
              radioButtons(
                "embedding_provider",
                "AI Provider:",
                choices = c(
                  "Sentence Transformers (Python)" = "sentence-transformers",
                  "OpenAI (API Key Required)" = "openai",
                  "Gemini (API Key Required)" = "gemini"
                ),
                selected = "sentence-transformers",
                inline = FALSE
              ),
              conditionalPanel(
                condition = "input.embedding_provider == 'sentence-transformers'",
                selectizeInput(
                  "embedding_st_model",
                  "Model:",
                  choices = c(
                    "all-MiniLM-L6-v2 (Fast)" = "all-MiniLM-L6-v2",
                    "all-mpnet-base-v2 (Higher Quality)" = "all-mpnet-base-v2",
                    "BGE Small EN v1.5 (Fast + Strong Retrieval)" = "BAAI/bge-small-en-v1.5",
                    "BGE Base EN v1.5 (BERTopic Optimized)" = "BAAI/bge-base-en-v1.5",
                    "E5 Base v2 (Instructor-tuned)" = "intfloat/e5-base-v2",
                    "Nomic Embed Text v2 (Multilingual)" = "nomic-ai/nomic-embed-text-v2-moe",
                    "GTE Multilingual Base (Fast, Multilingual)" = "Alibaba-NLP/gte-multilingual-base"
                  ),
                  selected = NULL,
                  options = list(create = TRUE, placeholder = "Type your model...", onInitialize = I("function() { this.setValue(\"\"); }"))
                ),
                tags$p(
                  style = "font-size: 16px; color: #666;",
                  "Requires Python + sentence-transformers."
                )
              ),
              conditionalPanel(
                condition = "input.embedding_provider == 'openai'",
                selectizeInput(
                  "embedding_openai_model",
                  "OpenAI Model:",
                  choices = c("Text Embedding 3 Small (Default)" = "text-embedding-3-small", "Text Embedding 3 Large (Higher Quality)" = "text-embedding-3-large"),
                  selected = NULL,
                  options = list(create = TRUE, placeholder = "Type your model...", onInitialize = I("function() { this.setValue(\"\"); }"))
                ),
                .password_input("embedding_openai_api_key", "API Key:", placeholder = "sk-..."),
                conditionalPanel(
                  condition = "output.has_openai_key",
                  tags$div(style = "color: #0C795A; font-size: 13px; margin-top: -8px; margin-bottom: 8px;",
                    icon("check-circle"), " Key stored. Enter new key to override.")
                )
              ),
              conditionalPanel(
                condition = "input.embedding_provider == 'gemini'",
                selectizeInput(
                  "embedding_gemini_model",
                  "Gemini Model:",
                  choices = c("Gemini Embedding 001" = "gemini-embedding-001", "Gemini Embedding 2 Preview" = "gemini-embedding-2-preview"),
                  selected = NULL,
                  options = list(create = TRUE, placeholder = "Type your model...", onInitialize = I("function() { this.setValue(\"\"); }"))
                ),
                .password_input("embedding_gemini_api_key", "API Key:", placeholder = "AIza..."),
                conditionalPanel(
                  condition = "output.has_gemini_key",
                  tags$div(style = "color: #0C795A; font-size: 13px; margin-top: -8px; margin-bottom: 8px;",
                    icon("check-circle"), " Key stored. Enter new key to override.")
                )
              ),
              div(
                style = "margin-bottom: 15px;",
                actionButton("generate_embeddings", "Generate Embeddings", class = "btn-primary btn-block", icon = icon("brain"))
              ),
              tags$hr(style = "margin: 10px 0; border-color: #dee2e6;")
            ),
            conditionalPanel(
              condition = "input.semantic_feature_space == 'words'"
            ),
            conditionalPanel(
              condition = "input.semantic_feature_space == 'ngrams'"
            ),
            div(
              style = "margin-bottom: 15px;",
              actionButton("calculate_similarity", "Calculate", class = "btn-primary btn-block")
            ),
            div(
              style = "margin-bottom: 8px;",
              selectizeInput(
                "heatmap_category_filter",
                "Filter by category",
                choices = c("All Categories" = "all"),
                selected = "all"
              )
            ),
            tags$div(
              id = "words_similarity_ready_status",
              class = "status-sidebar-success",
              style = "display: none;",
              tags$i(class = "fa fa-check-circle status-icon status-icon-success"),
              tags$span("Words similarity data calculated and ready")
            ),
            tags$div(
              id = "ngrams_similarity_ready_status",
              class = "status-sidebar-success",
              style = "display: none;",
              tags$i(class = "fa fa-check-circle status-icon status-icon-success"),
              tags$span("N-grams similarity data calculated and ready")
            ),
            tags$div(
              id = "topics_similarity_ready_status",
              class = "status-sidebar-success",
              style = "display: none;",
              tags$i(class = "fa fa-check-circle status-icon status-icon-success"),
              tags$span("Topics similarity data calculated and ready")
            ),
            tags$div(
              id = "embeddings_similarity_ready_status",
              class = "status-sidebar-success",
              style = "display: none;",
              tags$i(class = "fa fa-check-circle status-icon status-icon-success"),
              tags$span("Embeddings similarity data calculated and ready")
            ),
            actionButton("visualize_similarity", "Visualize Results", class = "btn-primary btn-block", icon = icon("chart-bar"))
          ),

          # Comparative Analysis Controls (new subtab)
          conditionalPanel(
            condition = "input.semantic_analysis_tabs == 'comparative'",
            tags$label(HTML("<strong>Comparative Analysis</strong>"), style = "font-weight: 700; margin-bottom: 8px; display: block;"),
            tags$div(
              class = "status-main-info",
              "Compare reference category against others to identify unique content, gaps, and cross-category opportunities."
            ),
            uiOutput("gap_reference_category_ui"),
            sliderInput(
              "gap_unique_threshold",
              "Unique threshold",
              min = 0.3, max = 0.9, value = 0.6, step = 0.05
            ),
            sliderInput(
              "gap_cross_policy_max",
              "Cross-category max",
              min = 0.6, max = 1.0, value = 0.8, step = 0.05
            ),
            actionButton("run_gap_analysis", "Run Comparative Analysis", class = "btn-info btn-block", icon = icon("search-minus")),
            tags$div(
              id = "gap_analysis_ready_status",
              class = "status-sidebar-success",
              style = "display: none; margin-top: 10px;",
              tags$i(class = "fa fa-check-circle status-icon status-icon-success"),
              tags$span("Comparative analysis complete")
            )
          ),

          conditionalPanel(
            condition = "input.semantic_analysis_tabs == 'search'",
            tags$p(style = "font-size: 16px; color: #666; margin-bottom: 10px;", "Query-to-document retrieval across your corpus."),
            selectInput(
              "search_method",
              "Search method:",
              choices = c(
                "Keyword" = "keyword",
                "Words" = "words",
                "N-grams" = "ngrams",
                "Embeddings" = "embeddings",
                "RAG Q&A (LLM)" = "rag"
              ),
              selected = "keyword"
            ),
            conditionalPanel(
              condition = "input.search_method != 'keyword' && input.search_method != 'rag' && input.search_method != 'embeddings'",
              uiOutput("search_method_status_message")
            ),
            conditionalPanel(
              condition = "input.search_method == 'embeddings'",
              radioButtons(
                "search_embedding_provider",
                "AI Provider:",
                choices = c(
                  "Sentence Transformers (Python)" = "sentence-transformers",
                  "OpenAI (API Key Required)" = "openai",
                  "Gemini (API Key Required)" = "gemini"
                ),
                selected = "sentence-transformers"
              ),
              conditionalPanel(
                condition = "input.search_embedding_provider == 'sentence-transformers'",
                selectizeInput(
                  "search_embedding_st_model",
                  "Model:",
                  choices = c(
                    "all-MiniLM-L6-v2 (Fast)" = "all-MiniLM-L6-v2",
                    "all-mpnet-base-v2 (Higher Quality)" = "all-mpnet-base-v2",
                    "BGE Small EN v1.5 (Fast + Strong Retrieval)" = "BAAI/bge-small-en-v1.5",
                    "BGE Base EN v1.5 (BERTopic Optimized)" = "BAAI/bge-base-en-v1.5",
                    "E5 Base v2 (Instructor-tuned)" = "intfloat/e5-base-v2",
                    "Nomic Embed Text v2 (Multilingual)" = "nomic-ai/nomic-embed-text-v2-moe",
                    "GTE Multilingual Base (Fast, Multilingual)" = "Alibaba-NLP/gte-multilingual-base"
                  ),
                  selected = NULL,
                  options = list(create = TRUE, placeholder = "Type your model...", onInitialize = I("function() { this.setValue(\"\"); }"))
                ),
                tags$p(
                  style = "font-size: 16px; color: #666;",
                  "Requires Python + sentence-transformers."
                )
              ),
              conditionalPanel(
                condition = "input.search_embedding_provider == 'openai'",
                selectizeInput(
                  "search_embedding_openai_model",
                  "OpenAI Model:",
                  choices = c(
                    "Text Embedding 3 Small (Default)" = "text-embedding-3-small",
                    "Text Embedding 3 Large (Higher Quality)" = "text-embedding-3-large"
                  ),
                  selected = NULL,
                  options = list(create = TRUE, placeholder = "Type your model...", onInitialize = I("function() { this.setValue(\"\"); }"))
                ),
                .password_input("search_embedding_openai_api_key", "API Key:", placeholder = "sk-..."),
                conditionalPanel(
                  condition = "output.has_openai_key",
                  tags$div(style = "color: #0C795A; font-size: 13px; margin-top: -8px; margin-bottom: 8px;",
                    icon("check-circle"), " Key stored. Enter new key to override.")
                )
              ),
              conditionalPanel(
                condition = "input.search_embedding_provider == 'gemini'",
                selectizeInput(
                  "search_embedding_gemini_model",
                  "Gemini Model:",
                  choices = c("Gemini Embedding 001" = "gemini-embedding-001", "Gemini Embedding 2 Preview" = "gemini-embedding-2-preview"),
                  selected = NULL,
                  options = list(create = TRUE, placeholder = "Type your model...", onInitialize = I("function() { this.setValue(\"\"); }"))
                ),
                .password_input("search_embedding_gemini_api_key", "API Key:", placeholder = "AIza..."),
                conditionalPanel(
                  condition = "output.has_gemini_key",
                  tags$div(style = "color: #0C795A; font-size: 13px; margin-top: -8px; margin-bottom: 8px;",
                    icon("check-circle"), " Key stored. Enter new key to override.")
                )
              )
            ),
            conditionalPanel(
              condition = "input.search_method == 'rag'",
              radioButtons(
                "rag_provider",
                "AI Provider:",
                choices = c(
                  "OpenAI (API Key Required)" = "openai",
                  "Gemini (API Key Required)" = "gemini"
                ),
                selected = "openai"
              ),
              conditionalPanel(
                condition = "input.rag_provider == 'openai'",
                selectizeInput(
                  "rag_openai_model",
                  "OpenAI Model:",
                  choices = c("GPT-4.1 Mini (Default, fast)" = "gpt-4.1-mini", "GPT-4.1 (Accurate)" = "gpt-4.1", "GPT-4" = "gpt-4"),
                  selected = NULL,
                  options = list(create = TRUE, placeholder = "Type your model...", onInitialize = I("function() { this.setValue(\"\"); }"))
                ),
                .password_input("rag_openai_api_key", "API Key:", placeholder = "sk-..."),
                conditionalPanel(
                  condition = "output.has_openai_key",
                  tags$div(style = "color: #0C795A; font-size: 13px; margin-top: -8px; margin-bottom: 8px;",
                    icon("check-circle"), " Key stored. Enter new key to override.")
                )
              ),
              conditionalPanel(
                condition = "input.rag_provider == 'gemini'",
                selectizeInput(
                  "rag_gemini_model",
                  "Gemini Model:",
                  choices = c("Gemini 2.5 Flash Lite (Default, economy)" = "gemini-2.5-flash-lite", "Gemini 2.5 Flash" = "gemini-2.5-flash", "Gemini 2.5 Pro (Accurate)" = "gemini-2.5-pro"),
                  selected = NULL,
                  options = list(create = TRUE, placeholder = "Type your model...", onInitialize = I("function() { this.setValue(\"\"); }"))
                ),
                .password_input("rag_gemini_api_key", "API Key:", placeholder = "AIza..."),
                conditionalPanel(
                  condition = "output.has_gemini_key",
                  tags$div(style = "color: #0C795A; font-size: 13px; margin-top: -8px; margin-bottom: 8px;",
                    icon("check-circle"), " Key stored. Enter new key to override.")
                )
              )
            ),
            div(
              uiOutput("search_query_input"),
              sliderInput(
                "semantic_search_top_k",
                "Results to return",
                value = 5,
                min = 1,
                max = 20,
                step = 1
              ),
              br(),
              div(
                style = "margin-bottom: 15px;",
                actionButton("run_semantic_search", "Search", class = "btn-primary btn-block", icon = icon("search"))
              )
            )
          ),
          conditionalPanel(
            condition = "input.semantic_analysis_tabs == 'clustering'",
            tags$div(
              style = "display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px;",
              tags$h5(HTML("<strong>Find Document Groups</strong> <a href='https://scikit-learn.org/stable/modules/clustering.html' target='_blank' rel='noopener noreferrer' onclick='window.open(this.href); return false;' style='font-size: 16px;'>Source</a>"), style = "color: #4269BF; margin: 0;"),
              actionLink("showClusteringInfo", icon("info-circle"),
                        style = "color: #337ab7; font-size: 16px;",
                        title = "Learn about document grouping")
            ),

            uiOutput("clustering_warning"),

            wellPanel(
              style = "padding: 15px; margin-bottom: 15px;",
              tags$h5(strong("Configuration"), style = "color: #4269BF; margin-bottom: 10px;"),
              radioButtons(
                "clustering_approach",
                "Discovery mode",
                choices = list(
                  "Automatic" = "auto",
                  "Manual" = "manual",
                  "Method" = "method"
                ),
                selected = "auto"
              ),

              conditionalPanel(
                condition = "input.clustering_approach == 'manual'",
                numericInput(
                  "manual_n_clusters",
                  "Number of groups",
                  value = 4,
                  min = 2,
                  max = 15
                )
              ),

              conditionalPanel(
                condition = "input.clustering_approach == 'method'",
                selectInput(
                  "semantic_cluster_method",
                  "Algorithm",
                  choices = c(
                    "K-means" = "kmeans",
                    "Hierarchical" = "hierarchical",
                    "DBSCAN (Density-based)" = "dbscan",
                    "Neural Topic Model" = "neural",
                    "Semantic Topic Modeling (All-in-one)" = "semantic_unified"
                  ),
                  selected = "kmeans"
                ),
                tags$p(
                  tags$i(class = "fa fa-info-circle", style = "margin-right: 5px;"),
                  tags$strong("Note:"), " Neural and Semantic models use transformer embeddings.",
                  style = "font-size: 16px; color: #6b7280; margin-top: 5px;"
                ),
                conditionalPanel(
                  condition = "input.semantic_cluster_method == 'kmeans'",
                  numericInput(
                    "kmeans_n_clusters",
                    "Number of clusters",
                    value = 0,
                    min = 0,
                    max = 20,
                    step = 1
                  )
                ),
                conditionalPanel(
                  condition = "input.semantic_cluster_method == 'neural'",
                  numericInput(
                    "neural_n_topics",
                    "Number of topics",
                    value = 10,
                    min = 2,
                    max = 50,
                    step = 1
                  ),
                  numericInput(
                    "neural_hidden_size",
                    "Hidden layer size",
                    value = 100,
                    min = 50,
                    max = 500,
                    step = 50
                  )
                ),
                conditionalPanel(
                  condition = "input.semantic_cluster_method == 'semantic_unified'",
                  tags$h5("Unified Semantic Topic Modeling", style = "color: #10B981; font-weight: bold; margin-bottom: 10px;"),
                  tags$p("Combines multiple approaches for stable topic modeling",
                        style = "font-size: 16px; color: #6b7280; margin-bottom: 10px;"),
                  selectInput(
                    "unified_method",
                    "Base method",
                    choices = c(
                      "Embedding + Clustering" = "embedding_clustering",
                      "Similarity-based Grouping" = "similarity_grouping"
                    ),
                    selected = "embedding_clustering"
                  ),
                  numericInput(
                    "unified_n_topics",
                    "Number of topics",
                    value = 10,
                    min = 2,
                    max = 30,
                    step = 1
                  ),
                  sliderInput(
                    "unified_min_topic_size",
                    "Minimum topic size",
                    value = 3,
                    min = 2,
                    max = 10,
                    step = 1
                  )
                )
              )
            ),

            tags$details(
              style = "margin-top: 15px; margin-bottom: 15px;",
              tags$summary(
                style = "cursor: pointer; color: #596D80; font-weight: 600;",
                "More Settings"
              ),
              div(
                style = "margin-top: 10px; padding: 10px; border-radius: 4px;",
                sliderInput(
                  "semantic_cluster_seed",
                  "Random seed",
                  value = 123,
                  min = 1,
                  max = 1000,
                  step = 1
                ),
                selectInput(
                  "semantic_cluster_table_view",
                  "Table view",
                  choices = c(
                    "Cluster Summary" = "summary",
                    "Document Details" = "details"
                  ),
                  selected = "summary"
                )
              )
            ),

            div(
              style = "margin-bottom: 15px;",
              actionButton(
                "run_semantic_analysis_clustering",
                "Discover",
                class = "btn-primary btn-block",
                icon = icon("magnifying-glass-chart")
              )
            )
          ),
            conditionalPanel(
              condition = "output.has_clustering_results && input.semantic_analysis_tabs == 'clustering'",
            wellPanel(
              style = "padding: 12px; margin-top: 15px; background-color: #f8f9fa;",
            tags$h5(strong("AI-Powered Cluster Labels"), style = "color: #4269BF; margin-bottom: 15px;"),
            uiOutput("cluster_labeling_status"),

            radioButtons(
              "cluster_label_provider",
              "AI Provider:",
              choices = c(
                "OpenAI (API Key Required)" = "openai",
                "Gemini (API Key Required)" = "gemini"
              ),
              selected = "openai",
              inline = FALSE
            ),

            conditionalPanel(
              condition = "input.cluster_label_provider == 'openai'",
              selectizeInput(
                "cluster_openai_model",
                "OpenAI Model:",
                choices = c("GPT-4.1 Mini (Default, fast)" = "gpt-4.1-mini", "GPT-4.1 (Accurate)" = "gpt-4.1", "GPT-4" = "gpt-4"),
                selected = NULL,
                options = list(create = TRUE, placeholder = "Type your model...", onInitialize = I("function() { this.setValue(\"\"); }"))
              ),
              .password_input("cluster_openai_api_key", "API Key:", placeholder = "sk-..."),
              conditionalPanel(
                condition = "output.has_openai_key",
                tags$div(style = "color: #0C795A; font-size: 13px; margin-top: -8px; margin-bottom: 8px;",
                  icon("check-circle"), " Key stored. Enter new key to override.")
              )
            ),

            conditionalPanel(
              condition = "input.cluster_label_provider == 'gemini'",
              selectizeInput(
                "cluster_gemini_model",
                "Gemini Model:",
                choices = c("Gemini 2.5 Flash Lite (Default, economy)" = "gemini-2.5-flash-lite", "Gemini 2.5 Flash" = "gemini-2.5-flash", "Gemini 2.5 Pro (Accurate)" = "gemini-2.5-pro"),
                selected = NULL,
                options = list(create = TRUE, placeholder = "Type your model...", onInitialize = I("function() { this.setValue(\"\"); }"))
              ),
              .password_input("cluster_gemini_api_key", "API Key:", placeholder = "AIza..."),
              conditionalPanel(
                condition = "output.has_gemini_key",
                tags$div(style = "color: #0C795A; font-size: 13px; margin-top: -8px; margin-bottom: 8px;",
                  icon("check-circle"), " Key stored. Enter new key to override.")
              )
            ),

            numericInput(
              "top_terms_for_labels",
              "Top terms per cluster:",
              value = 10,
              min = 5,
              max = 20,
              step = 1
            ),

            actionButton(
              "generate_cluster_labels",
              "Generate Labels with AI",
              class = "btn-primary btn-block",
              icon = icon("wand-magic-sparkles")
            )
            )
          ),
          conditionalPanel(
            condition = "input.semantic_analysis_tabs == 'sentiment'",
            tags$h5(HTML("<strong>Sentiment & Emotion Analysis</strong> <a href='https://github.com/juliasilge/tidytext' target='_blank' rel='noopener noreferrer' onclick='window.open(this.href); return false;' style='font-size: 16px;'>Source</a>"), style = "color: #4269BF; margin-bottom: 10px;"),

            uiOutput("sentiment_status_message"),
            conditionalPanel(
              condition = "input.sentiment_subtabs == 'overall'",
              radioButtons(
                "sentiment_method",
                "Analysis method",
                choices = c(
                  "Lexicon-based (Fast)" = "lexicon",
                  "Pre-trained Classifier (Accurate)" = "neural",
                  "LLM (with Explanations)" = "llm"
                ),
                selected = "lexicon",
                inline = FALSE
              ),
              conditionalPanel(
                condition = "input.sentiment_method == 'lexicon'",
                selectInput(
                  "sentiment_lexicon",
                  "Sentiment lexicon",
                  choices = c(
                    "AFINN" = "afinn",
                    "Bing" = "bing",
                    "NRC" = "nrc"
                  ),
                  selected = "bing"
                ),
                actionButton("run_sentiment_analysis", "Analyze Sentiment", class = "btn-primary btn-block")
              ),
              conditionalPanel(
                condition = "input.sentiment_method == 'neural'",
                selectInput(
                  "neural_sentiment_model",
                  "Sentiment model",
                  choices = c(
                    "DistilBERT SST-2 (Fast)" = "distilbert-base-uncased-finetuned-sst-2-english",
                    "RoBERTa Sentiment (Accurate)" = "cardiffnlp/twitter-roberta-base-sentiment-latest",
                    "BERT Multilingual" = "nlptown/bert-base-multilingual-uncased-sentiment"
                  ),
                  selected = "distilbert-base-uncased-finetuned-sst-2-english"
                ),
                actionButton("run_neural_sentiment", "Analyze Sentiment", class = "btn-primary btn-block")
              ),
              conditionalPanel(
                condition = "input.sentiment_method == 'llm'",
                radioButtons(
                  "llm_sentiment_provider",
                  "AI Provider:",
                  choices = c(
                    "OpenAI (API Key Required)" = "openai",
                    "Gemini (API Key Required)" = "gemini"
                  ),
                  selected = "openai",
                  inline = FALSE
                ),
                uiOutput("llm_sentiment_model_ui"),
                checkboxInput(
                  "llm_sentiment_explanation",
                  "Include sentiment explanations",
                  value = TRUE
                ),
                numericInput(
                  "llm_sentiment_batch_size",
                  "Batch size",
                  value = 5,
                  min = 1,
                  max = 20,
                  step = 1
                ),
                actionButton("run_llm_sentiment", "Analyze with LLM", class = "btn-primary btn-block", icon = icon("robot"))
              )
            ),
            conditionalPanel(
              condition = "input.sentiment_subtabs == 'category'",
              selectizeInput(
                "sentiment_category_var",
                "Categorical variable",
                choices = NULL,
                multiple = FALSE
              ),
              selectInput(
                "category_plot_type",
                "Plot type",
                choices = c(
                  "Bar Chart" = "bar",
                  "Box Plot" = "box",
                  "Violin Plot" = "violin"
                ),
                selected = "bar"
              )
            ),
            conditionalPanel(
              condition = "input.sentiment_subtabs == 'document'",
              selectizeInput(
                "sentiment_doc_id_var",
                "Document ID variable",
                choices = NULL,
                selected = "",
                options = list(
                  allowEmptyOption = TRUE,
                  persist = TRUE,
                  placeholder = "Optional"
                )
              ),
              sliderInput(
                "sentiment_top_docs",
                "Documents to show",
                value = 10,
                min = 1,
                max = 50,
                step = 1
              )
            ),
            conditionalPanel(
              condition = "input.sentiment_subtabs == 'emotion'",
              uiOutput("emotion_ready_indicator"),
              uiOutput("emotion_warning_message"),
              checkboxInput(
                "normalize_emotions",
                "Normalize emotion scores",
                value = FALSE
              ),
              selectizeInput(
                "emotion_group_var",
                "Group by variable",
                choices = NULL,
                multiple = FALSE,
                options = list(placeholder = "Optional")
              )
            )
          ),
          conditionalPanel(
            condition = "input.semantic_analysis_tabs == 'cooccurrence'",
            tags$h5(HTML("<strong>Word Co-occurrence Networks</strong> <a href='https://igraph.org/r/' target='_blank' rel='noopener noreferrer' onclick='window.open(this.href); return false;' style='font-size: 16px;'>Source</a>"), style = "color: #4269BF; margin-bottom: 10px;"),
            selectizeInput(
              "doc_var_co_occurrence",
              "Categorical variable",
              choices = NULL,
              multiple = FALSE,
              options = list(placeholder = "Optional")
            ),
            div(
              id = "global_cooccur_params", class = "global-params",
              sliderInput(
                "co_occurence_number_global",
                "Minimum co-occurrences",
                value = 10,
                min = 2,
                max = 150,
                step = 1
              ),
              sliderInput(
                "top_node_n_co_occurrence_global",
                "Top N nodes",
                value = 20,
                min = 2,
                max = 150,
                step = 1
              ),
              sliderInput(
                "node_label_size_cooccur",
                "Node label size (0 hides labels)",
                value = 22,
                min = 0,
                max = 40,
                step = 2
              ),
              selectInput(
                "community_method_cooccur",
                "Community detection",
                choices = c("Leiden (recommended)" = "leiden",
                            "Louvain" = "louvain"),
                selected = "leiden"
              ),
              selectInput(
                "node_size_cooccur",
                "Node size by",
                choices = c(
                  "Degree (connections)" = "degree",
                  "Betweenness (bridging)" = "betweenness",
                  "Frequency (word count)" = "frequency"
                ),
                selected = "degree"
              ),
              selectInput(
                "node_color_cooccur",
                "Node color by",
                choices = c(
                  "Community" = "community",
                  "Frequency (word count)" = "frequency"
                ),
                selected = "community"
              ),
              sliderInput(
                "nrows_co_occurrence",
                "Row numbers",
                value = 1,
                min = 1,
                max = 10,
                step = 1
              ),
              numericInput(
                "seed_cooccur",
                "Layout seed",
                value = 2026, min = 1, max = 999999, step = 1
              ),
              div(style = "font-size: 16px;",
                  checkboxInput("showlegend_cooccur", "Show legend", value = TRUE)),
              div(
                class = "category-toggle",
                div(style = "font-size: 16px;", checkboxInput("use_category_cooccur",
                  "Use category-specific analysis",
                  value = FALSE
                ))
              )
            ),
            conditionalPanel(
              condition = "input.use_category_cooccur == true && input.doc_var_co_occurrence != 'None' && input.doc_var_co_occurrence != ''",
              tags$div(
                class = "status-main-warning",
                "Analyze top categories by document count for faster processing and clearer visualization."
              ),
              uiOutput("top_n_selector_cooccur"),
              uiOutput("category_selector_cooccur"),
              uiOutput("category_cooccur_controls")
            ),
            actionButton("plot_word_co_occurrence_network", "Plot Network", class = "btn-primary btn-block"),
            conditionalPanel(
              condition = "output.has_cooccurrence_plot == true",
              tags$div(
                style = "margin-top: 8px; display: flex; gap: 6px;",
                downloadButton("download_cooccur_html", "HTML",
                               class = "btn-default btn-block",
                               style = "margin: 0; flex: 1;")
              )
            )
          ),
          conditionalPanel(
            condition = "input.semantic_analysis_tabs == 'correlation'",
            tags$h5(HTML("<strong>Word Correlation Networks</strong> <a href='https://igraph.org/r/' target='_blank' rel='noopener noreferrer' onclick='window.open(this.href); return false;' style='font-size: 16px;'>Source</a>"), style = "color: #4269BF; margin-bottom: 10px;"),
            selectizeInput(
              "doc_var_correlation",
              "Categorical variable",
              choices = NULL,
              multiple = FALSE,
              options = list(placeholder = "Optional")
            ),
            div(
              id = "global_corr_params", class = "global-params",
              sliderInput(
                "common_term_n_global",
                "Common terms",
                value = 20,
                min = 5,
                max = 150,
                step = 1
              ),
              sliderInput(
                "corr_n_global",
                "Minimum correlation",
                value = 0.4,
                min = 0,
                max = 1,
                step = 0.1
              ),
              sliderInput(
                "top_node_n_correlation_global",
                "Top N nodes",
                value = 20,
                min = 2,
                max = 150,
                step = 1
              ),
              sliderInput(
                "node_label_size_corr",
                "Node label size (0 hides labels)",
                value = 22,
                min = 0,
                max = 40,
                step = 2
              ),
              selectInput(
                "community_method_corr",
                "Community detection",
                choices = c("Leiden (recommended)" = "leiden",
                            "Louvain" = "louvain"),
                selected = "leiden"
              ),
              selectInput(
                "node_size_corr",
                "Node size by",
                choices = c(
                  "Degree (connections)" = "degree",
                  "Betweenness (bridging)" = "betweenness",
                  "Frequency (word count)" = "frequency"
                ),
                selected = "degree"
              ),
              selectInput(
                "node_color_corr",
                "Node color by",
                choices = c(
                  "Community" = "community",
                  "Frequency (word count)" = "frequency"
                ),
                selected = "community"
              ),
              sliderInput(
                "nrows_correlation",
                "Row numbers",
                value = 1,
                min = 1,
                max = 10,
                step = 1
              ),
              numericInput(
                "seed_corr",
                "Layout seed",
                value = 2026, min = 1, max = 999999, step = 1
              ),
              div(style = "font-size: 16px;",
                  checkboxInput("showlegend_corr", "Show legend", value = TRUE)),
              div(
                class = "category-toggle",
                div(style = "font-size: 16px;", checkboxInput("use_category_corr",
                  "Use category-specific analysis",
                  value = FALSE
                ))
              )
            ),
            conditionalPanel(
              condition = "input.use_category_corr == true && input.doc_var_correlation != 'None' && input.doc_var_correlation != ''",
              tags$div(
                class = "status-main-warning",
                tags$i(class = "fa fa-info-circle status-icon status-icon-warning"),
                "Analyze top categories by document count for faster processing and clearer visualization."
              ),
              uiOutput("top_n_selector_corr"),
              uiOutput("category_selector_corr"),
              uiOutput("category_corr_controls")
            ),
            actionButton("plot_word_correlation_network", "Plot Network", class = "btn-primary btn-block"),
            conditionalPanel(
              condition = "output.has_correlation_plot == true",
              tags$div(
                style = "margin-top: 8px; display: flex; gap: 6px;",
                downloadButton("download_corr_html", "HTML",
                               class = "btn-default btn-block",
                               style = "margin: 0; flex: 1;")
              )
            )
          )
        ),
        mainPanel(
          width = 9,
          tabsetPanel(
            id = "semantic_analysis_tabs",
            tabPanel(
              "1. Setup",
              value = "summary",
              br(),
              conditionalPanel(
                condition = "output.has_documents == true",
                DT::dataTableOutput("document_summary_table")
              ),
              conditionalPanel(
                condition = "output.has_documents == false",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 400px; margin: 0 auto;",
                    tags$i(class = "fa fa-upload", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                    tags$p(
                      "Load data and process documents in the ",
                      tags$strong("1. Setup", style = "color: #4269BF;"),
                      " tab first",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              )
            ),
            tabPanel(
              "2. Word Co-occurrence",
              value = "cooccurrence",
              bsCollapse(
                open = 0,
                bsCollapsePanel(
                  p(strong("Click to set plot dimensions"),
                    class = "plot-dimensions-text"
                  ),
                  value = 4,
                  style = "success",
                  p(strong("Dimensions of the plot")),
                  div(
                    style = "display:inline-block",
                    sliderInput(
                      inputId = "height_word_co_occurrence_network_plot",
                      post = " px",
                      label = "height",
                      min = 200,
                      max = 4000,
                      step = 5,
                      value = 700
                    )
                  ),
                  div(
                    style = "display:inline-block",
                    sliderInput(
                      inputId = "width_word_co_occurrence_network_plot",
                      post = " px",
                      label = "width",
                      min = 500,
                      max = 3000,
                      step = 5,
                      value = 1000
                    )
                  )
                )
              ),
              tags$style(
                HTML(
                  ".plot-container {
                                max-height: 4000px;
                                max-width: 3000px;
                                overflow: auto; }"
                )
              ),
              conditionalPanel(
                condition = "output.has_cooccurrence_plot == true",
                tabsetPanel(
                  id = "word_co_occur_subTab",
                  tabPanel(
                    "Plot",
                    uiOutput("word_co_occurrence_network_plot_uiOutput")
                  ),
                  tabPanel(
                    "Table",
                    uiOutput("word_co_occurrence_network_table_uiOutput")
                  ),
                  tabPanel(
                    "Summary",
                    uiOutput("word_co_occurrence_network_summary_uiOutput")
                  )
                )
              ),
              conditionalPanel(
                condition = "output.has_cooccurrence_plot == false",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 400px; margin: 0 auto;",
                    tags$i(class = "fa fa-project-diagram", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                    tags$p(
                      "Configure settings and click ",
                      tags$strong("'Plot Network'", style = "color: #4269BF;"),
                      " to visualize word co-occurrence",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              )
            ),
            tabPanel(
              "3. Word Correlation",
              value = "correlation",
              bsCollapse(
                open = 0,
                bsCollapsePanel(
                  p(strong("Click to set plot dimensions"),
                    class = "plot-dimensions-text"
                  ),
                  value = 4,
                  style = "success",
                  p(strong("Dimensions of the plot")),
                  div(
                    style = "display:inline-block",
                    sliderInput(
                      inputId = "height_word_correlation_network_plot",
                      post = " px",
                      label = "height",
                      min = 200,
                      max = 4000,
                      step = 5,
                      value = 700
                    )
                  ),
                  div(
                    style = "display:inline-block",
                    sliderInput(
                      inputId = "width_word_correlation_network_plot",
                      post = " px",
                      label = "width",
                      min = 500,
                      max = 3000,
                      step = 5,
                      value = 1000
                    )
                  )
                )
              ),
              tags$style(
                HTML(
                  ".plot-container {
                                max-height: 4000px;
                                max-width: 3000px;
                                overflow: auto; }"
                )
              ),
              conditionalPanel(
                condition = "output.has_correlation_plot == true",
                tabsetPanel(
                  id = "word_correlation_subTab",
                  tabPanel(
                    "Plot",
                    uiOutput("word_correlation_network_plot_uiOutput")
                  ),
                  tabPanel(
                    "Table",
                    uiOutput("word_correlation_network_table_uiOutput")
                  ),
                  tabPanel(
                    "Summary",
                    uiOutput("word_correlation_network_summary_uiOutput")
                  )
                )
              ),
              conditionalPanel(
                condition = "output.has_correlation_plot == false",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 400px; margin: 0 auto;",
                    tags$i(class = "fa fa-share-alt", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                    tags$p(
                      "Configure settings and click ",
                      tags$strong("'Plot Network'", style = "color: #4269BF;"),
                      " to visualize word correlation",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              )
            ),
            tabPanel(
              "4. Document Similarity",
              value = "similarity",
              conditionalPanel(
                condition = "output.has_documents == false",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 400px; margin: 0 auto;",
                    tags$i(class = "fa fa-cog", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                    tags$p(
                      "Process documents in the ",
                      tags$strong("1. Setup", style = "color: #4269BF;"),
                      " tab first",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              ),
              conditionalPanel(
                condition = "output.has_documents == true && output.has_similarity_calculation == false",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 400px; margin: 0 auto;",
                    tags$i(class = "fa fa-calculator", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                    tags$p(
                      "Configure settings and click ",
                      tags$strong("'Calculate'", style = "color: #4269BF;"),
                      " to begin analysis",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              ),
              conditionalPanel(
                condition = "output.has_documents == true && output.has_similarity_calculation == true && output.has_similarity_analysis == false",
                verbatimTextOutput("similarity_calculation_summary")
              ),
              conditionalPanel(
                condition = "output.has_documents == true && output.has_similarity_analysis == true",
                plotly::plotlyOutput("semantic_similarity_plot",
                  height = "auto", width = "100%"
                ),
                br(),
                DT::dataTableOutput("semantic_similarity_stats")
              )
            ),
            tabPanel(
              "5. Comparative Analysis",
              value = "comparative",
              br(),
              conditionalPanel(
                condition = "output.has_documents == false",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 500px; margin: 0 auto;",
                    tags$i(class = "fa fa-balance-scale", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                    tags$p(
                      "Upload data to enable Comparative Analysis",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              ),
              conditionalPanel(
                condition = "output.has_documents == true && output.has_gap_analysis == false",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 500px; margin: 0 auto;",
                    tags$i(class = "fa fa-balance-scale", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                    tags$p(
                      "Run Comparative Analysis from the sidebar to compare categories and identify unique content, gaps, and cross-category opportunities.",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              ),
              conditionalPanel(
                condition = "output.has_documents == true && output.has_gap_analysis == true",
                tags$h5(HTML("<strong>Comparative Analysis Results</strong>"), style = "color: #4269BF; margin-bottom: 15px;"),
                uiOutput("gap_reference_category_label"),
                tabsetPanel(
                  id = "gap_analysis_tabs",
                  tabPanel(
                    "Summary",
                    br(),
                    DT::dataTableOutput("gap_summary_stats")
                  ),
                  tabPanel(
                    "Heatmap",
                    br(),
                    tags$p("Cross-category similarity heatmap comparing reference documents against other categories.",
                           style = "color: #475569; font-size: 16px; margin-bottom: 10px;"),
                    plotly::plotlyOutput("gap_cross_category_heatmap", height = "600px")
                  ),
                  tabPanel(
                    "Unique (Reference)",
                    br(),
                    tags$p("Reference items with low similarity to all comparison categories (distinctive content).",
                           style = "color: #475569; font-size: 16px; margin-bottom: 10px;"),
                    DT::dataTableOutput("gap_unique_items")
                  ),
                  tabPanel(
                    "Missing (Comparison)",
                    br(),
                    tags$p("Comparison category items not well-covered by reference category (content gaps).",
                           style = "color: #475569; font-size: 16px; margin-bottom: 10px;"),
                    DT::dataTableOutput("gap_missing_items")
                  ),
                  tabPanel(
                    "Cross-Category",
                    br(),
                    tags$p("Items with moderate similarity - potential for cross-category learning or transfer.",
                           style = "color: #475569; font-size: 16px; margin-bottom: 10px;"),
                    DT::dataTableOutput("gap_cross_policy")
                  )
                )
              )
            ),
            tabPanel(
              "6. Semantic Search",
              value = "search",
              conditionalPanel(
                condition = "output.has_documents == false",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 400px; margin: 0 auto;",
                    tags$i(class = "fa fa-cog", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                    tags$p(
                      "Process documents in the ",
                      tags$strong("1. Setup", style = "color: #4269BF;"),
                      " tab first",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              ),
              conditionalPanel(
                condition = "output.has_documents == true && output.has_search_results == false",
                div(
                  style = "padding: 60px 40px; text-align: center;",
                  div(
                    style = "max-width: 400px; margin: 0 auto;",
                    tags$i(class = "fa fa-search", style = "font-size: 48px; color: #CBD5E1; margin-bottom: 20px; display: block;"),
                    tags$p(
                      "Enter a search query and click ",
                      tags$strong("'Search'", style = "color: #4269BF;"),
                      " to see results",
                      style = "font-size: 18px; font-weight: 400; line-height: 1.7; color: #475569; margin: 0;"
                    )
                  )
                )
              ),
              conditionalPanel(
                condition = "output.has_documents == true && output.has_search_results == true",
                br(),
                uiOutput("search_method_indicator"),
                br(),
                DT::dataTableOutput("semantic_search_results")
              )
            ),
            tabPanel(
              "7. Sentiment & Emotion",
              value = "sentiment",
              br(),
              tabsetPanel(
                id = "sentiment_subtabs",
                tabPanel(
                  "Overall Sentiment",
                  value = "overall",
                  uiOutput("sentiment_overall_uiOutput")
                ),
                tabPanel(
                  "By Category",
                  value = "category",
                  uiOutput("sentiment_category_uiOutput")
                ),
                tabPanel(
                  "Document-Level",
                  value = "document",
                  uiOutput("sentiment_document_uiOutput")
                ),
                tabPanel(
                  "Emotion",
                  value = "emotion",
                  uiOutput("sentiment_emotion_uiOutput")
                )
              )
            ),
            tabPanel(
              "8. Document Groups",
              value = "clustering",
              br(),
              uiOutput("clustering_warning"),
              uiOutput("clustering_quality_metrics"),
              br(),
              tabsetPanel(
                id = "clustering_subtabs",
                tabPanel(
                  "Map",
                  br(),
                  plotly::plotlyOutput("semantic_cluster_plot", height = "600px")
                ),
                tabPanel(
                  "Groups Table",
                  br(),
                  DT::dataTableOutput("semantic_cluster_table")
                ),
                tabPanel(
                  "Group Details",
                  br(),
                  selectInput("selected_cluster_group", "Select a group", choices = NULL),
                  uiOutput("cluster_group_summary"),
                  br(),
                  plotly::plotlyOutput("cluster_terms_plot", height = "400px"),
                  br(),
                  DT::dataTableOutput("cluster_sample_docs")
                )
              )
            )
          )
        )
      )
}
