#!/usr/bin/env ruby

class Sorbet; end
module Sorbet::Private; end

require_relative '../lib/t'
require_relative '../lib/step_interface'
require_relative '../lib/create-config'
require_relative '../lib/gem-generator-tracepoint'
require_relative '../lib/hidden-definition-finder'
require_relative '../lib/fetch-rbis'
require_relative '../lib/find-gem-rbis'
require_relative '../lib/suggest-typed'
require_relative '../lib/todo-rbi'

module Sorbet::Private::Main
  def self.usage
    "Usage: srb rbi <command>
    This script gets your current directory ready for using Sorbet by making all sorts of files in ./sorbet/. You should commit them to version control.

    We recommend running it without any options which will execute all the commands in order. If you only need a certain piece, you can pass a command to just run that part.

    You should re-run this script if your program ever stops typechecking due to dynamic code in your project.

    Common Commands:
      help                Print this message
      <empty> | update    Run all initialization commands

    Specific Commands:
      config              Recreate sorbet/config
      sorbet-typed        Download community maintained type definitions for gems
      gems                Generate arity-only type definitions by requiring the gem
      hidden-definitions  Load all your code and generate type definitions for any dynamic code
      todo                Run Sorbet and generate constants which Sorbet errors on
      suggest-typed       Put the highest `typed:` sigil in each of your files
      find-gem-rbis       Find all `rbi/` directories in your gems and list them in `~/.cache/sorbet`
      "
  end

  def self.deprecation
    <<~MESSAGE
      #{emojify("⚠️ ", yellow("The"))} #{cyan("srb rbi")} #{yellow("command is in maintenance mode, please use")} #{cyan("Tapioca")} #{yellow("instead.")}

      To switch, add Tapioca to your Gemfile then run #{cyan("bundle install")} to install it:

          #{cyan("gem \"tapioca\", require: false, :group => [:development, :test]")}

      Once Tapioca is installed, simply run #{cyan("tapioca init")} to initialize your project with Sorbet and generate the necessary RBI files:

          #{cyan("bundle exec tapioca init")}

      For more information, please see https://github.com/Shopify/tapioca.

    MESSAGE
  end

  def self.emojify(emoji, msg)
    if STDOUT.isatty && RUBY_PLATFORM =~ /darwin/
      "#{emoji} #{msg}"
    else
      msg
    end
  end

  def self.yellow(msg)
    if STDOUT.isatty
      "\u001b[0;33m#{msg}\u001b[0m"
    else
      msg
    end
  end

  def self.cyan(msg)
    if STDOUT.isatty
      "\u001b[0;36m#{msg}\u001b[0m"
    else
      msg
    end
  end

  def self.init
    puts "
#{emojify("👋", "Hey there!")}

This script will get this project ready to use with Sorbet by creating a
sorbet/ folder for your project. It will contain:

- a config file
- a bunch of 'RBI files'

RBI stands for 'Ruby Interface'; these files define classes, methods, and
constants that exist, but which Sorbet doesn't always know about.

#{emojify("⚠️ ", "Heads up:")}

To set up your project, this script will take two potentially destructive
actions:

1.  It will #{yellow("require every file in your project")}. Specifically, every script in
    your project will be run, unless that script checks #{cyan("if __FILE__ == $PROGRAM_NAME")}
    before running any code, or has the magic comment #{cyan("# typed: ignore")} in it.

2.  It will add a #{yellow("comment to the top of every file")} (like #{cyan("# typed: false")} or
    #{cyan("# typed: true")}, depending on how many errors were found in that file.)

"
    if ENV['SRB_YES'] != nil
      puts "SRB_YES set, continuing"
    else
      STDOUT.write(emojify("❔", "Would you like to continue? [Y/n] "))
      if STDIN.isatty && STDOUT.isatty
        begin
          input = STDIN.gets&.strip
          if input.nil? || (input != '' && input != 'y' && input != 'Y')
            puts "\nAborting"
            Kernel.exit(1)
          end
        rescue Interrupt
          puts "\nAborting"
          Kernel.exit(1)
        end
      else
        puts "\nNot running interactively. Set SRB_YES=1 environment variable to proceed"
        Kernel.exit(1)
      end
    end

    # Create sorbet/config file
    make_step(Sorbet::Private::CreateConfig).call

    # Pull in the hand-written RBIs
    make_step(Sorbet::Private::FetchRBIs).call

    # Generate the RBIs from bundler
    make_step(Sorbet::Private::GemGeneratorTracepoint).call

    # Find the hidden methods
    make_step(Sorbet::Private::HiddenMethodFinder).call

    # Run sorbet and make constants to fix errors
    make_step(Sorbet::Private::TodoRBI).call

    # Run type suggestion once, and then generate the todo.rbi again.
    #
    # The first time we suggest typed sigils, a few files may be ignored if they
    # use Ruby features Sorbet does not support. However, since we ran the
    # todo.rbi generation before they were ignored, we discovered the constants
    # that they defined and did not add the constants to todo.rbi file.
    #
    # Regenerating after one run of sigil suggestion will put those constants
    # into the todo.rbi file.
    Sorbet::Private::SuggestTyped.suggest_typed
    Sorbet::Private::TodoRBI.main

    # Put some `typed:` sigils
    make_step(Sorbet::Private::SuggestTyped).call

    puts "
#{emojify("✅", "Done!")}

This project is now set up for use with Sorbet. The sorbet/ folder should exist
and look something like this:

    sorbet/
    #{emojify("├──", " ")}config                      # Default options to be passed to sorbet on every run
    #{emojify("└──", " ")}rbi/
        #{emojify("├──", " ")}sorbet-typed/           # Community written type definition files for your gems
        #{emojify("├──", " ")}gems/                   # Autogenerated type definitions for your gems (from reflection)
        #{emojify("├──", " ")}hidden-definitions/     # All definitions that exist at runtime, but Sorbet couldn't see statically
        #{emojify("└──", " ")}todo.rbi                # Constants which were still missing, even after the three steps above.

Please check this whole folder into version control.

#{emojify("➡️ ", "What's next?")}

Up to you! First things first, you'll probably want to typecheck your project:

    #{cyan("srb tc")}

Other than that, it's up to you!
We recommend skimming these docs to get a feel for how to use Sorbet:

- Gradual Type Checking (#{cyan("https://sorbet.org/docs/gradual")})
- Enabling Static Checks (#{cyan("https://sorbet.org/docs/static")})
- RBI Files (#{cyan("https://sorbet.org/docs/rbi")})

If instead you want to explore your files locally, here are some things to try:

- Upgrade a file marked #{cyan("# typed: false")} to #{cyan("# typed: true")}.
  Then, run #{cyan("srb tc")} and try to fix any errors.
- Add signatures to your methods with `sig`.
  For how, read: #{cyan("https://sorbet.org/docs/sigs")}
- Check whether things that show up in the TODO RBI file actually exist in your project.
  It's possible some of these constants are typos.
- Upgrade a file marked #{cyan("# typed: ignore")} to #{cyan("# typed: false")}.
  Then, run #{cyan("srb rbi hidden-definitions && srb tc")} and try to fix any errors.

#{emojify("🙌", "Please don't hesitate to give us your feedback!")}
    "
  end

  def self.main(argv)
    puts self.deprecation

    if argv.length == 0
      puts self.usage
      exit(1)
    end

    command = case (argv[0])
    when 'help', '--help'
      puts self.usage
      exit(1)
    when 'init', 'update'
      self.init
      return
    when 'config', Sorbet::Private::CreateConfig.output_file
      make_step(Sorbet::Private::CreateConfig)
    when 'sorbet-typed', Sorbet::Private::FetchRBIs.output_file, Sorbet::Private::FetchRBIs::SORBET_RBI_LIST
      make_step(Sorbet::Private::FetchRBIs)
    when 'gems', Sorbet::Private::GemGeneratorTracepoint.output_file
      -> do
        make_step(Sorbet::Private::GemGeneratorTracepoint).call
        make_step(Sorbet::Private::SuggestTyped).call
      end
    when 'hidden-definitions', Sorbet::Private::HiddenMethodFinder.output_file
      make_step(Sorbet::Private::HiddenMethodFinder)
    when 'todo', Sorbet::Private::TodoRBI.output_file
      make_step(Sorbet::Private::TodoRBI)
    when 'suggest-typed'
      make_step(Sorbet::Private::SuggestTyped)

    when 'find-gem-rbis', "#{ENV['HOME']}/.cache/sorbet/gem-rbis", "#{ENV['HOME']}/.cache/sorbet/gem-rbis/"
      make_step(Sorbet::Private::FindGemRBIs)

    else
      puts "Unknown command: #{argv[0]}"
      puts self.usage
      exit(1)
    end

    puts "Running command: #{argv[0]}"
    command.call
    nil
  end

  T::Sig::WithoutRuntime.sig {params(step: Sorbet::Private::StepInterface).returns(T.proc.void)}
  def self.make_step(step)
    -> do
      puts "Generating: #{step.output_file}" if step.output_file
      step.main
    end
  end
end

Sorbet::Private::Main.main(ARGV)
