lib/clacky/ui2/components/welcome_banner.rb



# frozen_string_literal: true

require "pastel"
require_relative "../../version"
require_relative "../block_font"
require_relative "../../utils/workspace_rules"

module Clacky
  module UI2
    module Components
      # WelcomeBanner displays the startup screen with ASCII logo, tagline, tips, and agent info.
      #
      # When a product_name is configured via BrandConfig, the hardcoded OPENCLACKY
      # ASCII art is replaced by a dynamically generated logo using artii (FIGlet).
      # Falls back to plain text when the terminal is too narrow or artii fails.
      class WelcomeBanner
        LOGO = <<~'LOGO'
           ██████╗ ██████╗ ███████╗███╗   ██╗ ██████╗██╗      █████╗  ██████╗██╗  ██╗██╗   ██╗
          ██╔═══██╗██╔══██╗██╔════╝████╗  ██║██╔════╝██║     ██╔══██╗██╔════╝██║ ██╔╝╚██╗ ██╔╝
          ██║   ██║██████╔╝█████╗  ██╔██╗ ██║██║     ██║     ███████║██║     █████╔╝  ╚████╔╝
          ██║   ██║██╔═══╝ ██╔══╝  ██║╚██╗██║██║     ██║     ██╔══██║██║     ██╔═██╗   ╚██╔╝
          ╚██████╔╝██║     ███████╗██║ ╚████║╚██████╗███████╗██║  ██║╚██████╗██║  ██╗   ██║
           ╚═════╝ ╚═╝     ╚══════╝╚═╝  ╚═══╝ ╚═════╝╚══════╝╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝   ╚═╝
        LOGO

        TAGLINE = "[>] Your personal Assistant & Technical Co-founder"

        TIPS = [
          "[*] Ask questions, edit files, or run commands",
          "[*] Be specific for the best results",
          "[*] Create .clackyrules to customize interactions",
          "[*] Type /help for more commands"
        ].freeze

        # Minimum terminal width required for full logo display
        MIN_WIDTH_FOR_LOGO = 90

        def initialize
          @pastel = Pastel.new
        end

        # Get current theme from ThemeManager
        def theme
          UI2::ThemeManager.current_theme
        end

        # Render only the logo (ASCII art or simple text based on terminal width)
        # @param width [Integer] Terminal width
        # @return [String] Formatted logo only
        def render_logo(width:)
          lines = []
          lines << ""
          lines << logo_content(width)
          lines << ""
          lines.join("\n")
        end

        # Render startup banner
        # @param width [Integer] Terminal width
        # @return [String] Formatted startup banner
        def render_startup(width:)
          lines = []
          lines << ""
          lines << logo_content(width)
          lines << ""
          lines << @pastel.bright_cyan(TAGLINE)
          lines << @pastel.dim("    Version #{Clacky::VERSION}")
          lines << ""
          TIPS.each do |tip|
            lines << @pastel.dim(tip)
          end
          lines << ""
          lines.join("\n")
        end

        # Render agent welcome section
        # @param working_dir [String] Working directory
        # @param mode [String] Permission mode
        # @return [String] Formatted agent welcome section
        def render_agent_welcome(working_dir:, mode:)
          lines = []
          lines << ""
          lines << separator("=")
          lines << @pastel.bright_green("[+] AGENT MODE INITIALIZED")
          lines << separator("=")
          lines << ""
          lines << info_line("Working Directory", working_dir)
          lines << info_line("Permission Mode", mode)

          # Show loaded project rules file if present
          main = Utils::WorkspaceRules.find_main(working_dir)
          lines << info_line("Project Rules", "#{main[:name]} ✓") if main

          lines << ""
          lines << theme.format_text("[!] Type 'exit' or 'quit' to terminate session", :thinking)
          lines << separator("-")
          lines << ""

          # Show sub-project agents block if any sub-dirs have .clackyrules
          sub_projects = Utils::WorkspaceRules.find_sub_projects(working_dir)
          unless sub_projects.empty?
            lines << @pastel.bright_cyan("[>] SUB-PROJECT AGENT MODE")
            lines << @pastel.dim("    #{sub_projects.size} sub-project(s) detected with rules:")
            sub_projects.each do |sp|
              first_line = sp[:summary].lines.first&.strip&.delete_prefix("#")&.strip
              label = @pastel.cyan("    • #{sp[:sub_name]}/")
              desc = first_line && !first_line.empty? ? @pastel.dim(" — #{first_line}") : ""
              lines << "#{label}#{desc}"
            end
            lines << @pastel.dim("    AI will read each sub-project's full .clackyrules before working in it.")
            lines << separator("-")
            lines << ""
          end

          lines.join("\n")
        end

        # Render full welcome (startup + agent info)
        # @param working_dir [String] Working directory
        # @param mode [String] Permission mode
        # @param width [Integer] Terminal width
        # @return [String] Full welcome content
        def render_full(working_dir:, mode:, width:)
          render_startup(width: width) + render_agent_welcome(
            working_dir: working_dir,
            mode: mode
          )
        end


        # Returns the colourised logo block.
        # - Branded install: dynamically generated artii ASCII art for the brand name
        # - Standard install: hardcoded OPENCLACKY block-letter logo
        # Falls back to plain text when the terminal is too narrow or artii fails.
        private def logo_content(width)
          brand = brand_config
          if brand.branded?
            generate_brand_logo(brand, width)
          else
            if width >= MIN_WIDTH_FOR_LOGO
              @pastel.bright_green(LOGO)
            else
              @pastel.bright_green("Welcome, OpenClacky is here")
            end
          end
        end

        # Generate a brand logo using BlockFont (Unicode █ ╗ ╔ style).
        # Renders package_name as the big ASCII art logo.
        # Shows product_name as a subtitle when it differs from package_name.
        # Falls back to plain product_name text when terminal is too narrow.
        private def generate_brand_logo(brand, width)
          # Use package_name as the renderable ASCII-safe identifier for the logo.
          # product_name may contain CJK or special characters unsuitable for block art.
          render_key = brand.package_name.to_s.strip
          render_key = brand.product_name.to_s.strip if render_key.empty?

          art = UI2::BlockFont.render(render_key)

          lines = []
          if !art.strip.empty? && art_fits?(art, width)
            lines << @pastel.bright_green(art)
          else
            lines << @pastel.bright_green(render_key)
          end

          # Show product_name as subtitle when it differs from the render key
          if brand.product_name.to_s.strip != render_key
            lines << @pastel.bright_cyan("    #{brand.product_name}")
          end

          lines.join("\n")
        end

        # Check whether the ASCII art fits within the terminal width.
        private def art_fits?(art, width)
          art.lines.map { |l| l.chomp.length }.max.to_i <= width
        end

        # Lazily load and cache BrandConfig to avoid circular require issues.
        private def brand_config
          require_relative "../../brand_config"
          Clacky::BrandConfig.load
        rescue LoadError, StandardError
          # Return a neutral stub when brand_config is unavailable
          Object.new.tap { |o| o.define_singleton_method(:branded?) { false } }
        end

        private def info_line(label, value)
          label_text = @pastel.cyan("[#{label}]")
          value_text = theme.format_text(value, :info)
          "    #{label_text} #{value_text}"
        end

        private def separator(char = "-")
          theme.format_text(char * 80, :thinking)
        end
      end
    end
  end
end