"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.SetupWidgetState = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _mobx = require("mobx");
var _TaskState = require("./TaskState");
/**
 * State for the Setup widget, created via the `useSetupWidget` hook and passed
 * to the `SetupWidget` component (the same shape as `useCollection` →
 * `CollectionState`).
 *
 * Owns the async lifecycle via {@link TaskState}, seeds an observable copy of
 * the steps from the resolved {@link SetupWidgetData}, and exposes a public API to
 * read the steps and change a step's status.
 *
 * The consumer API is `steps`, `getStep`, `setStepStatus`, `refresh` and
 * `isRefreshing`. Underscore-prefixed members are internal to the
 * `@wix/patterns` rendering layer — not consumer API.
 *
 * Render-time concerns (how inline content is supplied, navigation handling)
 * stay in the cairo component — this class never imports from `@wix/patterns`,
 * preserving the upward dependency direction.
 */
class SetupWidgetState {
  constructor(params) {
    (0, _defineProperty2.default)(this, "container", void 0);
    (0, _defineProperty2.default)(this, "fetchData", void 0);
    /** @internal Read by the `SetupWidget` component for the page status mapping. */
    (0, _defineProperty2.default)(this, "_loadTask", new _TaskState.TaskState());
    /** Mutable copy of the steps, seeded from `fetchData` and editable via the public API. */
    (0, _defineProperty2.default)(this, "seededSteps", []);
    (0, _defineProperty2.default)(this, "seededTitle", null);
    /** The step the user explicitly expanded; `null` once explicitly collapsed. */
    (0, _defineProperty2.default)(this, "focusedStepId", null);
    /** Whether the user has interacted with inline expansion yet. */
    (0, _defineProperty2.default)(this, "userToggled", false);
    /** True while a `refresh()` is in flight (for an optional consumer indicator). */
    (0, _defineProperty2.default)(this, "isRefreshing", false);
    /** The most recently issued `refresh()` fetch, for last-write-wins. */
    (0, _defineProperty2.default)(this, "latestRefresh", void 0);
    this.container = params.container;
    this.fetchData = params.fetchData;
    (0, _mobx.makeObservable)(this, {
      seededSteps: _mobx.observable.ref,
      seededTitle: _mobx.observable.ref,
      focusedStepId: _mobx.observable.ref,
      userToggled: _mobx.observable.ref,
      isRefreshing: _mobx.observable.ref,
      steps: _mobx.computed,
      _title: _mobx.computed,
      _isLoading: _mobx.computed,
      _isError: _mobx.computed,
      _leafSteps: _mobx.computed,
      _completedCount: _mobx.computed,
      _totalCount: _mobx.computed,
      _progressPercentage: _mobx.computed,
      _isAllComplete: _mobx.computed,
      _primaryStepId: _mobx.computed,
      _expandedStepId: _mobx.computed,
      _focusedStepId: _mobx.computed,
      seedFromData: _mobx.action.bound,
      setStepStatus: _mobx.action.bound,
      _toggleStep: _mobx.action.bound,
      _retry: _mobx.action.bound,
      refresh: _mobx.action.bound
    });
  }
  init() {
    this.load();

    // Seed the editable step list whenever fresh data resolves through the load
    // task (initial load + `_retry`). Consumer edits via `setStepStatus` survive
    // until the next successful fetch reseeds.
    const disposeSeed = (0, _mobx.reaction)(() => this._loadTask.status.data, data => this.seedFromData(data), {
      fireImmediately: true
    });
    return () => disposeSeed();
  }
  load() {
    this._loadTask.runOnce(async () => {
      // Wait for the container init (i18n, experiments) alongside the data,
      // so the widget's translated chrome is ready when it leaves the
      // loading state — same as CollectionState / EntityPageState.
      const [data] = await Promise.all([this.fetchData(), this.container.initTask.status.promise]);
      return data;
    }, error => this.onError(error));
  }

  /**
   * @internal Re-run the initial load after an error (shows the loading
   * skeleton again). Consumers receive this as the `retry` param of
   * `renderError`.
   */
  _retry() {
    this._loadTask.reset();
    this.load();
  }

  /**
   * Re-fetch the data on demand and swap in the result — without flipping back
   * to the loading skeleton, so the current steps stay visible during the
   * refetch (mirrors `onDashboardWidgetDataChange` in dashboard-setup). Use this
   * to reflect status changes that happen outside the widget (a `navigate` step
   * finishing elsewhere, a real-time/server push). Discards local
   * `setStepStatus` edits in favour of the freshly fetched server truth. A
   * failed refresh keeps the current data (the error is reported, not surfaced).
   *
   * `refresh()` is not error recovery: it never touches the load task, so a
   * refresh after a failed initial load seeds data but leaves the error state
   * (and the component's error UI) up. Error recovery is {@link _retry} —
   * surfaced to consumers as the `retry` param of `renderError`.
   */
  async refresh() {
    const promise = this.fetchData();
    this.latestRefresh = promise;
    this.isRefreshing = true;
    try {
      const data = await promise;
      // Last-write-wins: a stale fetch that resolves after a newer refresh was
      // issued must not clobber it (mirrors TaskState.run's promise guard).
      if (promise === this.latestRefresh) {
        this.seedFromData(data);
      }
    } catch (error) {
      this.onError(error);
    } finally {
      if (promise === this.latestRefresh) {
        (0, _mobx.runInAction)(() => {
          this.isRefreshing = false;
        });
      }
    }
  }
  onError(error) {
    this.container.internalMonitor.reportError({
      error: error
    });
  }
  seedFromData(data) {
    if (!data) {
      return;
    }
    this.seededSteps = data.steps;
    this.seededTitle = data.title;
    // Fresh data → drop any explicit expansion so focus recomputes from the new
    // steps (matches the focus reset on refetch in dashboard-setup).
    this.userToggled = false;
    this.focusedStepId = null;
  }

  // ---- Public read API ----

  get steps() {
    return this.seededSteps;
  }
  getStep(id) {
    const find = steps => {
      for (const step of steps) {
        if (step.id === id) {
          return step;
        }
        const inChild = step.steps && find(step.steps);
        if (inChild) {
          return inChild;
        }
      }
      return undefined;
    };
    return find(this.seededSteps);
  }

  // ---- Internal read API (consumed by the SetupWidget component) ----

  /** @internal */
  get _title() {
    return this.seededTitle;
  }

  /** @internal */
  get _isLoading() {
    const {
      status
    } = this._loadTask;
    return status.isIdle || status.isLoading;
  }

  /** @internal */
  get _isError() {
    return this._loadTask.status.isError;
  }

  /**
   * @internal Steps flattened to their leaves — an aggregated step (with
   * `steps`) is replaced by its sub-steps. Progress is measured over the
   * leaves so a group counts as its child steps, not as one row (matches
   * dashboard-setup).
   */
  get _leafSteps() {
    return this.seededSteps.flatMap(step => {
      var _step$steps;
      return (_step$steps = step.steps) != null && _step$steps.length ? step.steps : [step];
    });
  }

  /** @internal */
  get _completedCount() {
    return this._leafSteps.filter(step => step.status === 'completed').length;
  }

  /** @internal */
  get _totalCount() {
    return this._leafSteps.length;
  }

  /** @internal */
  get _progressPercentage() {
    return this._totalCount === 0 ? 0 : Math.round(this._completedCount / this._totalCount * 100);
  }

  /** @internal */
  get _isAllComplete() {
    return this._totalCount > 0 && this._completedCount === this._totalCount;
  }

  /**
   * @internal The recommended next step — highlighted as "focused" and given
   * the primary CTA. Derived, never pinned by the data (matches
   * dashboard-setup's client-side focus): the first upcoming step, then the
   * first step that isn't already done or disabled.
   */
  get _primaryStepId() {
    var _this$seededSteps$fin, _this$seededSteps$fin2;
    return ((_this$seededSteps$fin = this.seededSteps.find(step => step.status === 'upcoming')) == null ? void 0 : _this$seededSteps$fin.id) ?? ((_this$seededSteps$fin2 = this.seededSteps.find(step => step.status !== 'completed' && step.status !== 'disabled')) == null ? void 0 : _this$seededSteps$fin2.id) ?? null;
  }

  /**
   * @internal The step whose inline content is currently expanded
   * (single-open). Until the user interacts, defaults to the primary step when
   * its content is inline — mirroring `isFocused && isExtensionStep` in
   * dashboard-setup.
   */
  get _expandedStepId() {
    if (this.userToggled) {
      return this.focusedStepId;
    }
    const primary = this.seededSteps.find(step => step.id === this._primaryStepId);
    return (primary == null ? void 0 : primary.expansionMode) === 'inline' ? primary.id : null;
  }

  /**
   * @internal The single focused (highlighted) step. Production keeps focus and
   * inline-expansion unified in one `focusedStepId`, so only ever one step is
   * highlighted; here `_primaryStepId` (recommended next) and `_expandedStepId`
   * (what the user toggled open) are separate, and can diverge once the user
   * opens a non-primary inline step. Collapsing them to a single id — the
   * expanded step when one is open, otherwise the primary — preserves the
   * single-focus invariant (avoids highlighting both the primary and the
   * expanded row).
   */
  get _focusedStepId() {
    return this._expandedStepId ?? this._primaryStepId;
  }

  // ---- Public write API ----

  /**
   * Change a step's status (e.g. mark it completed after the consumer flow
   * finishes). Works for top-level steps and for sub-steps of an aggregated
   * group, so a group's counter + the leaf progress stay in sync.
   */
  setStepStatus(id, status) {
    const wasExpanded = this._expandedStepId === id;
    const update = steps => steps.map(step => {
      if (step.id === id) {
        return {
          ...step,
          status
        };
      }
      if (step.steps) {
        return {
          ...step,
          steps: update(step.steps)
        };
      }
      return step;
    });
    this.seededSteps = update(this.seededSteps);

    // When the expanded step gets completed, drop the explicit focus so the
    // now-completed row collapses and focus recomputes to the next step —
    // mirroring `setStepCompleted` in dashboard-setup's widget context.
    if (status === 'completed' && wasExpanded) {
      this.userToggled = false;
      this.focusedStepId = null;
    }
  }

  /** @internal Toggle a step's inline expansion (single-open). */
  _toggleStep(id) {
    // Read the effective expanded step *before* flipping `userToggled`, since
    // doing so changes which branch `_expandedStepId` returns.
    const currentlyExpanded = this._expandedStepId;
    this.userToggled = true;
    this.focusedStepId = currentlyExpanded === id ? null : id;
  }
}
exports.SetupWidgetState = SetupWidgetState;
//# sourceMappingURL=SetupWidgetState.js.map