<script lang="ts">
  import Screen from "./Screen.svelte";
  import type { CompleteWorkflowNavigateArgs } from "../../types/components/button";
  import type { ColorScheme } from "../../types/colors";
  import type { InitialInputSelections } from "../../stores/inputValidation";
  import type { OnComponentInteraction } from "../../types/paywall-component-interaction";
  import type { VariableDictionary } from "../../types/variables";
  import type { WalletButtonRender } from "../../types/wallet";
  import type { UIConfig } from "../../types/ui-config";
  import type { ReservedAttribute } from "../../types/components/input-text";
  import type {
    WorkflowNavData,
    WorkflowStepChangeEvent,
  } from "../../types/workflow-nav";
  import type {
    WorkflowStepTriggerAction,
    WorkflowStep,
  } from "../../types/workflow";

  type TriggerActionValue = WorkflowStep["trigger_actions"][string];

  function isStepTriggerAction(
    a: TriggerActionValue,
  ): a is WorkflowStepTriggerAction {
    return (
      a.type === "step" &&
      typeof (a as WorkflowStepTriggerAction).step_id === "string"
    );
  }

  interface Props {
    workflow: WorkflowNavData;
    uiConfig: UIConfig;
    selectedLocale?: string;
    globalVariables?: VariableDictionary;
    variablesPerPackage?: Record<string, VariableDictionary>;
    maxContentWidth?: string;
    initialInputSelections?: InitialInputSelections;
    containerId?: string;
    walletButtonRender?: WalletButtonRender;
    safeAreaFallbackColor?: ColorScheme | null;
    onPurchaseClicked?: (
      packageId: string,
      actionId: string,
    ) => void | Promise<void>;
    onActionTriggered?: (actionId: string) => void;
    onInputChanged?: (
      fieldId: string,
      value: string,
      actionId?: string,
    ) => void;
    /**
     * Called for sensitive inputs (keyboard_type: "password") instead of
     * onInputChanged, so hosts can keep the value in memory only.
     */
    onSensitiveInputChanged?: (fieldId: string, value: string) => void;
    onReservedAttributeChanged?: (
      reservedAttribute: ReservedAttribute,
      value: string,
    ) => void;
    onCompleteWorkflowNavigate?: (
      args: CompleteWorkflowNavigateArgs,
    ) => void | Promise<void>;
    onNavigateToUrlClicked?: (url: string) => void;
    onRestorePurchasesClicked?: () => void;
    onVisitCustomerCenterClicked?: () => void;
    onComponentInteraction?: OnComponentInteraction;
    onClose?: () => void;
    onExitBack?: () => void;
    /**
     * Called whenever the current workflow step changes — on initial load,
     * forward navigation, back navigation, and when leaving a step (close,
     * purchase, error). Used by the host to fire workflow lifecycle events.
     */
    onStepChanged?: (event: WorkflowStepChangeEvent) => void;
  }

  const {
    workflow,
    uiConfig,
    selectedLocale,
    globalVariables,
    variablesPerPackage,
    maxContentWidth,
    initialInputSelections,
    containerId,
    walletButtonRender,
    safeAreaFallbackColor,
    onPurchaseClicked,
    onActionTriggered,
    onInputChanged,
    onSensitiveInputChanged,
    onReservedAttributeChanged,
    onCompleteWorkflowNavigate,
    onNavigateToUrlClicked,
    onRestorePurchasesClicked,
    onVisitCustomerCenterClicked,
    onComponentInteraction,
    onClose,
    onExitBack,
    onStepChanged,
  }: Props = $props();

  // ── Nav stack ─────────────────────────────────────────────────────────────
  // Each entry pairs the screen ID (used to look up the page to render) with
  // the step ID (used to resolve trigger_actions on button press). The current
  // page is always the last entry; workflow actions push, navigate_back pops.
  //
  // We key the reset off workflow.initial_page_id (a stable string) rather
  // than workflow object identity, so hosts that recompute the prop object on
  // unrelated reactive updates don't accidentally snap the user back to the
  // first page mid-flow.
  interface NavEntry {
    pageId: string;
    stepId: string;
  }
  let navStack = $state<NavEntry[]>([]);
  let trackedInitialPageId = $state<string>();

  const current = $derived(
    navStack[navStack.length - 1] ?? {
      pageId: workflow.initial_page_id,
      stepId: workflow.initial_step_id,
    },
  );
  const currentScreen = $derived(workflow.pages[current.pageId]);

  $effect.pre(() => {
    if (trackedInitialPageId !== workflow.initial_page_id) {
      trackedInitialPageId = workflow.initial_page_id;
      navStack = [
        {
          pageId: workflow.initial_page_id,
          stepId: workflow.initial_step_id,
        },
      ];
      emitStepChange({
        workflowId: workflow.workflowId,
        stepId: workflow.initial_step_id,
        fromStepId: undefined,
        toStepId: undefined,
        reason: "start",
        ...stepFlags(workflow.initial_step_id),
      });
    }
  });

  function stepFlags(stepId: string): {
    isFirstStep: boolean;
    isLastStep: boolean;
  } {
    const step = workflow.steps[stepId];
    return {
      isFirstStep: stepId === workflow.initial_step_id,
      // A step is "last" when it has no step-type trigger actions — i.e. no
      // client-resolvable forward navigation. Condition-based actions fall back
      // to onActionTriggered and are not counted here.
      isLastStep:
        step !== undefined &&
        !Object.values(step.trigger_actions).some((a) =>
          isStepTriggerAction(a),
        ),
    };
  }

  function emitStepChange(event: WorkflowStepChangeEvent) {
    onStepChanged?.(event);
  }

  /**
   * Resolves a "workflow"-type button action and navigates to the next screen.
   *
   * Flow: component ID → step.triggers[].action_id → trigger_actions[action_id]
   *       → step_id → step.screen_id → push onto nav stack.
   */
  function handleActionTriggered(actionId: string) {
    const currentStep = workflow.steps[current.stepId];
    if (!currentStep) {
      console.warn(
        `[Workflow] onActionTriggered: no current step found for step_id "${current.stepId}"`,
      );
      return;
    }

    // Resolve the component ID to an action ID via the step's triggers array.
    const resolvedActionId = currentStep.triggers.find(
      (t) => t.component_id === actionId,
    )?.action_id;

    if (!resolvedActionId) {
      console.warn(
        `[Workflow] onActionTriggered: no trigger entry found for component "${actionId}" on step "${current.stepId}"`,
      );
      onActionTriggered?.(actionId);
      return;
    }

    const triggerAction = currentStep.trigger_actions[resolvedActionId];
    if (!triggerAction) {
      console.warn(
        `[Workflow] onActionTriggered: no trigger_action found for action_id "${resolvedActionId}" on step "${current.stepId}"`,
      );
      onActionTriggered?.(actionId);
      return;
    }

    if (!isStepTriggerAction(triggerAction)) {
      // Condition-based actions require a full workflow engine — fall back to
      // the host's onActionTriggered for anything we can't resolve client-side.
      console.warn(
        `[Workflow] onActionTriggered: trigger_action is not a simple step action, falling back to onActionTriggered`,
      );
      onActionTriggered?.(actionId);
      return;
    }

    const nextStep = workflow.steps[triggerAction.step_id];
    if (!nextStep) {
      console.warn(
        `[Workflow] onActionTriggered: step_id "${triggerAction.step_id}" not found in workflow steps`,
      );
      return;
    }

    const nextPageId = nextStep.screen_id;
    if (!nextPageId) {
      console.warn(
        `[Workflow] onActionTriggered: step "${triggerAction.step_id}" has no screen_id (logic/experiment step?)`,
      );
      return;
    }

    if (!workflow.pages[nextPageId]) {
      console.warn(
        `[Workflow] onActionTriggered: screen_id "${nextPageId}" not found in workflow pages`,
      );
      return;
    }

    const fromStepId = current.stepId;
    const toStepId = triggerAction.step_id;
    emitStepChange({
      workflowId: workflow.workflowId,
      stepId: fromStepId,
      fromStepId: undefined,
      toStepId,
      reason: "completed",
      ...stepFlags(fromStepId),
    });
    navStack = [...navStack, { pageId: nextPageId, stepId: toStepId }];
    emitStepChange({
      workflowId: workflow.workflowId,
      stepId: toStepId,
      fromStepId,
      toStepId: undefined,
      reason: "forward",
      ...stepFlags(toStepId),
    });
  }

  function handleBackClicked() {
    if (navStack.length > 1) {
      const fromStepId = current.stepId;
      const toStepId = navStack[navStack.length - 2].stepId;
      emitStepChange({
        workflowId: workflow.workflowId,
        stepId: fromStepId,
        fromStepId: undefined,
        toStepId,
        reason: "completed",
        ...stepFlags(fromStepId),
      });
      navStack = navStack.slice(0, -1);
      emitStepChange({
        workflowId: workflow.workflowId,
        stepId: toStepId,
        fromStepId,
        toStepId: undefined,
        reason: "back",
        ...stepFlags(toStepId),
      });
    } else {
      // Already at root — bubble up to the host.
      emitStepChange({
        workflowId: workflow.workflowId,
        stepId: current.stepId,
        fromStepId: undefined,
        toStepId: undefined,
        reason: "completed",
        ...stepFlags(current.stepId),
      });
      onExitBack?.();
    }
  }
</script>

{#key current.pageId}
  <Screen
    paywallComponents={currentScreen}
    {uiConfig}
    {selectedLocale}
    {globalVariables}
    {variablesPerPackage}
    {maxContentWidth}
    {initialInputSelections}
    {containerId}
    {walletButtonRender}
    {safeAreaFallbackColor}
    {onPurchaseClicked}
    onActionTriggered={handleActionTriggered}
    {onInputChanged}
    {onSensitiveInputChanged}
    {onReservedAttributeChanged}
    {onCompleteWorkflowNavigate}
    {onNavigateToUrlClicked}
    {onRestorePurchasesClicked}
    {onVisitCustomerCenterClicked}
    {onComponentInteraction}
    onBackClicked={handleBackClicked}
    onClose={() => {
      emitStepChange({
        workflowId: workflow.workflowId,
        stepId: current.stepId,
        fromStepId: undefined,
        toStepId: undefined,
        reason: "completed",
        ...stepFlags(current.stepId),
      });
      onClose?.();
    }}
  />
{/key}
