/* The journey builder — the product's one creation flow big enough to need a
   screen of its own, plus the step editor the draft record reuses.

   Commit model: STAGED on both screens. Every control is a form field, nothing
   applies until Save, Save and Cancel are disabled until dirty. That is why step
   editing lives here rather than on the journey record: the record is instant
   (publish, pause, stop apply immediately) and §3 refuses to mix the two on one
   screen.
   Data state: one branch — a form. The step list has an empty state, which is a
   state of the form, not of the screen.
   The summary under the editor is arithmetic over the steps as written, so it
   moves as you build. */
(() => {
  const {
    Button, Input, Select, Field, FieldLabel, FieldDescription, FieldError, Badge, Alert, AlertTitle, AlertDescription,
  } = window.OriginDesignSystem_7c998b;

  let stepSeq = 0;
  const newStep = (kind) => {
    stepSeq += 1;
    const id = "s_new_" + stepSeq;
    if (kind === "wait") return { id: id, kind: "wait", waitHours: 48, label: "Wait 2 days" };
    if (kind === "branch") return { id: id, kind: "branch", label: "" };
    return { id: id, kind: "email", contentId: "", label: "" };
  };

  /* One list of what is wrong, read by the editor's alert and by the submit gate.
     Whether a step can send is `stepSendProblem` from enums.js — the same
     predicate the journey's Publish button reads, so the two screens cannot
     disagree about whether the ladder is finished. */
  function journeyStepErrors(steps, content) {
    const problems = [];
    const emails = steps.filter((s) => s.kind === "email");
    if (!emails.length) problems.push("A journey has to send something — add at least one email.");
    if (steps.length && steps[0].kind !== "email") problems.push("The first step has to be a send, or nobody entering it hears anything.");
    steps.forEach((step, index) => {
      const problem = stepSendProblem(step, content);
      if (problem) problems.push("Step " + (index + 1) + (step.label ? " “" + step.label + "” " : " ") + problem + ".");
    });
    if (steps.some((s) => s.kind === "branch" && !s.label.trim())) problems.push("Every branch needs a question, or the ladder cannot be read.");
    return problems;
  }

  /* The editor. Sendable templates only: an archived or draft template cannot go
     out, so offering it would be a promise the product cannot keep. */
  function StepEditor({ steps, content, onChange }) {
    const sendable = content.filter((c) => c.status === "published");
    const move = (index, delta) => {
      const next = steps.slice();
      const target = index + delta;
      if (target < 0 || target >= next.length) return;
      const held = next[index];
      next[index] = next[target];
      next[target] = held;
      onChange(next);
    };
    const patchStep = (id, fields) => onChange(steps.map((s) => (s.id === id ? Object.assign({}, s, fields) : s)));

    return (
      <div className="md-editor">
        {steps.length === 0 ? (
          <p className="md-empty md-editor__empty">No steps yet. A journey starts with an email, then waits, then sends again.</p>
        ) : steps.map((step, index) => {
          const kind = BUILDER_STEP_KINDS.find((k) => k.value === step.kind) || { label: step.kind, icon: "circle" };
          return (
            <div className="md-editor__row" key={step.id}>
              <span className="md-step__rail">
                <span className="md-step__dot"><Icon name={kind.icon} size={14} /></span>
              </span>
              <span className="md-editor__body">
                <span className="md-step__kind">{kind.label} · step {index + 1}</span>
                <span className="md-editor__control">
                  {step.kind === "email" ? (() => {
                    /* A value the record already holds always appears in the
                       list, labelled as unsendable — otherwise the control shows
                       an empty field over a step that does have a template. */
                    const items = [{ value: "", label: "Pick a template…" }].concat(sendable.map((c) => ({ value: c.id, label: c.name })));
                    const current = step.contentId ? contentById(content, step.contentId) : null;
                    if (current && current.status !== "published") {
                      items.splice(1, 0, { value: current.id, label: current.name + " — " + statusValue(CONTENT_STATUS, current.status).label.toLowerCase() + ", cannot send" });
                    }
                    return (
                      <Select
                        size="sm"
                        items={items}
                        value={step.contentId || ""}
                        aria-label={"Template for step " + (index + 1)}
                        onValueChange={(value) => patchStep(step.id, { contentId: value, label: value ? contentName(content, value) : "", ready: undefined })}
                      />
                    );
                  })() : step.kind === "wait" ? (
                    <Select
                      size="sm"
                      items={WAIT_OPTIONS}
                      value={String(step.waitHours)}
                      aria-label={"How long step " + (index + 1) + " waits"}
                      onValueChange={(value) => patchStep(step.id, { waitHours: Number(value), label: "Wait " + labelOf(WAIT_OPTIONS, value) })}
                    />
                  ) : (
                    <Input
                      size="sm"
                      value={step.label}
                      placeholder="Sent a campaign yet?"
                      aria-label={"Question for step " + (index + 1)}
                      onChange={(e) => patchStep(step.id, { label: e.target.value })}
                    />
                  )}
                </span>
              </span>
              <span className="md-editor__tools">
                <Button variant="ghost" size="icon-sm" aria-label={"Move step " + (index + 1) + " up"} disabled={index === 0} onClick={() => move(index, -1)}><Icon name="arrow-up" /></Button>
                <Button variant="ghost" size="icon-sm" aria-label={"Move step " + (index + 1) + " down"} disabled={index === steps.length - 1} onClick={() => move(index, 1)}><Icon name="arrow-down" /></Button>
                <Button variant="ghost" size="icon-sm" aria-label={"Remove step " + (index + 1)} onClick={() => onChange(steps.filter((s) => s.id !== step.id))}><Icon name="trash-2" /></Button>
              </span>
            </div>
          );
        })}
        <div className="md-editor__add">
          {BUILDER_STEP_KINDS.map((kind) => (
            <Button key={kind.value} variant="outline" size="sm" onClick={() => onChange(steps.concat([newStep(kind.value)]))}>
              <Icon name={kind.icon} />Add {kind.label.toLowerCase()}
            </Button>
          ))}
        </div>
      </div>
    );
  }

  function PlanSummary({ steps }) {
    const plan = journeyPlanSummary(steps);
    return (
      <Figures three items={[
        {
          label: "Steps",
          value: formatCount(plan.steps),
          qualifier: plan.emails + (plan.emails === 1 ? " email" : " emails") + " · " + (plan.steps - plan.emails) + (plan.steps - plan.emails === 1 ? " that does not send" : " that do not send"),
        },
        {
          label: "Entry to last send",
          value: plan.waitHours ? plan.days + (plan.days === 1 ? " day" : " days") : "Same day",
          qualifier: plan.waitHours ? "Adding up every wait in the ladder" : "Nothing waits, so everything sends at once",
        },
        {
          label: "Emails a person gets",
          value: formatCount(plan.emails),
          qualifier: plan.emails ? "One per email step, unless a branch takes them out" : "Nothing would send",
        },
      ]} />
    );
  }

  function StepProblems({ problems }) {
    if (!problems.length) return null;
    return (
      <div className="md-band">
        <Alert variant="warning">
          <Icon name="triangle-alert" size={16} />
          <AlertTitle>{problems.length === 1 ? "One thing to finish" : problems.length + " things to finish"}</AlertTitle>
          <AlertDescription>{problems.join(" ")}</AlertDescription>
        </Alert>
      </div>
    );
  }

  function NewJourneyScreen({ data, onCreate, onNavigate }) {
    const [draft, setDraft] = React.useState({ name: "", trigger: JOURNEY_TRIGGERS[0].value, audienceId: data.audiences[0].id });
    const [steps, setSteps] = React.useState([]);
    const [submitted, setSubmitted] = React.useState(false);

    const problems = journeyStepErrors(steps, data.content);
    const nameError = draft.name.trim().length < 3 ? "Name it — at least three characters." : null;
    const valid = !nameError && problems.length === 0;
    const dirty = draft.name !== "" || steps.length > 0;
    const audience = audienceById(data.audiences, draft.audienceId);

    return (
      <>
        <PageHeader path="/journeys/new" title="New journey" />

        <div className="md-scope">
          <p className="md-scope__note">
            It is created as a draft: nothing enters it and nothing sends until somebody publishes it. Everything here can be changed before then.
          </p>
        </div>

        <div className="md-form">
          <Field>
            <FieldLabel htmlFor="cd-nj-name">What the journey is called</FieldLabel>
            <Input id="cd-nj-name" placeholder="Trial onboarding" value={draft.name} invalid={submitted && !!nameError}
              onChange={(e) => setDraft(Object.assign({}, draft, { name: e.target.value }))} />
            {submitted && nameError ? <FieldError>{nameError}</FieldError> : <FieldDescription>Only your team sees this.</FieldDescription>}
          </Field>
          <Field>
            <FieldLabel>What puts somebody in it</FieldLabel>
            <Select items={JOURNEY_TRIGGERS} value={draft.trigger} aria-label="Trigger" onValueChange={(value) => setDraft(Object.assign({}, draft, { trigger: value }))} />
            <FieldDescription>The journey listens for this and enrols whoever matches.</FieldDescription>
          </Field>
          <Field>
            <FieldLabel>Who it can reach</FieldLabel>
            <Select
              items={data.audiences.map((a) => ({ value: a.id, label: a.name + " — " + formatCount(a.size) + " people" }))}
              value={draft.audienceId}
              aria-label="Audience"
              onValueChange={(value) => setDraft(Object.assign({}, draft, { audienceId: value }))}
            />
            <FieldDescription>{audience.rule}</FieldDescription>
          </Field>
        </div>

        <section className="md-band md-band--flush">
          <div className="md-band__head">
            <div>
              <h2 className="md-band__title">The ladder</h2>
              <p className="md-band__note">Steps run top to bottom. Only published templates can be picked, because a draft or archived one cannot go out.</p>
            </div>
          </div>
          <StepEditor steps={steps} content={data.content} onChange={setSteps} />
        </section>

        {steps.length ? <PlanSummary steps={steps} /> : null}
        {submitted ? <StepProblems problems={problems} /> : null}

        <FormBar
          dirty={dirty && valid}
          label="New journey"
          note={dirty
            ? (valid
              ? "Creates a draft journey against " + audience.name + ". Nothing sends until it is published."
              : "Finish the ladder before saving — the summary above says what is missing.")
            : "Name it, choose what triggers it, then build the ladder."}
          onCancel={() => { setDraft({ name: "", trigger: JOURNEY_TRIGGERS[0].value, audienceId: data.audiences[0].id }); setSteps([]); setSubmitted(false); }}
          onSave={() => { setSubmitted(true); if (valid) onCreate(draft, steps); }}
        />
      </>
    );
  }

  /* Editing the steps of a draft, reached from the record. Its own screen because
     it is staged and the record is not. */
  function JourneyStepsScreen({ journey, data, onSave, onNavigate }) {
    const initial = journey.steps.map((s) => Object.assign({}, s));
    const [steps, setSteps] = React.useState(initial);
    const [submitted, setSubmitted] = React.useState(false);
    React.useEffect(() => { setSteps(journey.steps.map((s) => Object.assign({}, s))); setSubmitted(false); }, [journey]);

    const problems = journeyStepErrors(steps, data.content);
    const dirty = JSON.stringify(steps.map((s) => [s.kind, s.contentId || "", s.waitHours || 0, s.label])) !== JSON.stringify(initial.map((s) => [s.kind, s.contentId || "", s.waitHours || 0, s.label]));
    const valid = problems.length === 0;

    return (
      <>
        <PageHeader path="/journeys/:id/steps" params={{ id: journey.id }} title={"Steps — " + journey.name} />

        <div className="md-scope">
          <p className="md-scope__note">
            {journey.name} has never been published, so its ladder can be changed freely — nobody is standing in it. Once it is live, editing steps is a different job with people mid-journey.
          </p>
        </div>

        <section className="md-band md-band--flush">
          <StepEditor steps={steps} content={data.content} onChange={setSteps} />
        </section>

        <PlanSummary steps={steps} />
        {submitted ? <StepProblems problems={problems} /> : null}

        <FormBar
          dirty={dirty && valid}
          label="Save steps"
          note={dirty
            ? (valid ? "Nothing is applied until you save." : "Finish the ladder before saving.")
            : problems.length
              ? problems.join(" ")
              : "The ladder is complete — publish it from the journey itself."}
          onCancel={() => { setSteps(journey.steps.map((s) => Object.assign({}, s))); setSubmitted(false); }}
          onSave={() => { setSubmitted(true); if (valid) onSave(steps); }}
        />
      </>
    );
  }

  Object.assign(window, { StepEditor, NewJourneyScreen, JourneyStepsScreen, journeyStepErrors, PlanSummary });
})();
