package dev.cf.recipe;

import dev.cf.Tones;

import dev.crossfly.ui.*;
import dev.crossfly.ui.color.*;
import dev.crossfly.ui.graphics.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.style.*;

import static dev.crossfly.ui.Widgets.*;

/**
 * The four states every screen has, and the three nobody designs.
 *
 * <p>A screen is drawn once, for the case where the data arrived and there is plenty of it. Then it
 * ships, and users meet the other three: nothing yet, nothing matching, and something went wrong.
 * All three render as an empty rectangle unless somebody decided otherwise, and an empty rectangle
 * says only one thing to the person looking at it — <b>this is broken</b>.</p>
 *
 * <p>The distinction that matters most is the one between the first two. "You have not added
 * anything yet" invites an action and is a good first impression. "Nothing matches that search" is
 * an answer to a question the user asked. Showing the first when the second is true tells a
 * long-standing customer their account is empty.</p>
 */
public class R30Empty extends Recipe
{
   private enum State
   {
      LOADED, FIRST_RUN, NO_MATCH, FAILED
   }

   private State state = State.FIRST_RUN;
   private Component stage;

   public String title()
   {
      return "Empty, loading and broken";
   }

   public String note()
   {
      return "Three of these are usually left to render as blank space. Blank space reads as a bug,"
         + " never as an answer.";
   }

   public void build(Component body) throws Exception
   {
      Component switcher = new Component();
      switcher.setLayout(new HorizontalLayout(Position.START, Position.CENTER));
      for (final State s : State.values())
      {
         Chip c = new Chip(s.name()
            .toLowerCase()
            .replace('_', ' '));
         c.setSelected(s == state);
         c.getStyle()
            .outgap(Gap.ofFont(0, 0, .4f, 0));
         c.onAction(e ->
         {
            state = s;
            rebuild();
            Screen.repaint();
         });
         switcher.add(LAY_AUTO, c);
      }
      body.add(LAY_AUTO, outgap(switcher, 0, 0, 0, 1f));

      stage = new Component();
      stage.setLayout(new VerticalLayout(Position.START, Position.START));
      stage.testTag("recipe.states");
      body.add(LAY_AUTO, stage);
      rebuild();
   }

   private void rebuild()
   {
      try
      {
         stage.removeAll();
         // removeAll() takes the layout with it.
         stage.setLayout(new VerticalLayout(Position.START, Position.START));

         switch (state)
         {
            case LOADED:
               for (int i = 1; i <= 4; i++)
               {
                  Card c = new Card();
                  c.setLayout(new GridLayout(Position.START, Position.CENTER));
                  c.getStyle()
                     .outgap(Gap.ofFont(0, 0, 0, .5f));
                  Component t = new Component();
                  t.setLayout(new VerticalLayout(Position.START, Position.START));
                  t.add(LAY_AUTO, label("Order " + (4180 + i), 1.2f, true, Styles.textColor,
                     Position.START));
                  t.add(LAY_AUTO, label("Placed this week", 1f, false, Styles.captionColor,
                     Position.START));
                  c.add(0f, 1f, Tones.tile(0xE8CC, Tones.cycle(i)), outgap(t, .6f, 0, 0, 0));
                  stage.add(LAY_AUTO, c);
               }
               break;

            case FIRST_RUN:
               // An invitation, with the action right there. This is a first impression, and the
               // one empty state worth spending a sentence on.
               // An invitation is not a problem: it gets the tone of the thing being offered.
               stage.add(LAY_AUTO, state(0xE8CC, Tones.accent(), "No orders yet",
                  "When you place one, it will show up here.", "Place your first order"));
               break;

            case NO_MATCH:
               // An ANSWER to what the user typed, not an invitation. Offering "add your first
               // order" to somebody who searched for one they know exists reads as being ignored.
               // Nothing is broken — the search simply has no answer. Not red.
               stage.add(LAY_AUTO, state(0xE8B6, Tones.support(), "Nothing matches “ironvale”",
                  "Check the spelling, or clear the search to see everything.",
                  "Clear the search"));
               break;

            case FAILED:
               // What happened, and what they can do. Not the exception text: it names something
               // the user has no way to act on, and it frightens them.
               // The only one of the three that actually failed, and the only one in the error
               // tone. Three identical grey glyphs said all three were the same event.
               stage.add(LAY_AUTO, state(0xE000, Tones.danger(), "We could not load your orders",
                  "Nothing was lost. Check your connection and try again.", "Try again"));
               break;

            default:
               break;
         }
         stage.setValidLayout(false);
      }
      catch (Exception e)
      {
         dev.crossfly.util.Debug.handle(e);
      }
   }

   /** One shape for all three: a glyph, a sentence, a reason, and exactly one way forward. */
   private Component state(int glyph, PalColor tom, String title, String detail, String action)
      throws Exception
   {
      Component c = new Component();
      c.setLayout(new VerticalLayout(Position.CENTER, Position.CENTER));
      c.getStyle()
         .ingap(Gap.ofFont(1f, 2f, 1f, 2f));
      c.add(LAY_AUTO, outgap(matglyph(glyph, tom, 3f), 0, 0, 0, .8f));
      c.add(LAY_AUTO, outgap(label(title, 1.5f, true, Styles.textColor, Position.CENTER), 0, 0, 0,
         .3f));
      c.add(LAY_AUTO, outgap(label(detail, 1f, false, Styles.captionColor, Position.CENTER), 0, 0,
         0, 1f));
      Button b = Tones.action(action, tom);
      b.onAction(e -> Hint.show(action, 1500));
      c.add(LAY_AUTO, b);
      return c;
   }
}
