// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import {
  Component,
  EventEmitter,
  Inject,
  Input,
  OnDestroy,
  OnInit,
  Output,
  ViewChild,
} from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { Router } from "@angular/router";
import { combineLatest, firstValueFrom, map, Subject, switchMap, takeUntil } from "rxjs";
import { debounceTime } from "rxjs/operators";

import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import {
  getOrganizationById,
  OrganizationService,
  singleOrganizationPolicyApplies$,
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { PlanInterval, PlanType, ProductTierType } from "@bitwarden/common/billing/enums";
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { OrganizationId } from "@bitwarden/common/types/guid";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import {
  CardComponent,
  DIALOG_DATA,
  DialogConfig,
  DialogRef,
  DialogService,
  ToastService,
} from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management";
import {
  OrganizationSubscriptionPlan,
  SubscriberBillingClient,
  PreviewInvoiceClient,
} from "@bitwarden/web-vault/app/billing/clients";
import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/organizations/warnings/services";
import {
  EnterBillingAddressComponent,
  EnterPaymentMethodComponent,
  getBillingAddressFromForm,
} from "@bitwarden/web-vault/app/billing/payment/components";
import {
  BillingAddress,
  getCardBrandIcon,
  MaskedPaymentMethod,
} from "@bitwarden/web-vault/app/billing/payment/types";
import { BitwardenSubscriber } from "@bitwarden/web-vault/app/billing/types";

import { BillingNotificationService } from "../services/billing-notification.service";
import { BillingSharedModule } from "../shared/billing-shared.module";

type ChangePlanDialogParams = {
  organizationId: string;
  productTierType: ProductTierType;
  subscription?: OrganizationSubscriptionResponse;
};

// FIXME: update to use a const object instead of a typescript enum
// eslint-disable-next-line @bitwarden/platform/no-enums
export enum ChangePlanDialogResultType {
  Closed = "closed",
  Submitted = "submitted",
}

// FIXME: update to use a const object instead of a typescript enum
// eslint-disable-next-line @bitwarden/platform/no-enums
export enum PlanCardState {
  Selected = "selected",
  NotSelected = "not_selected",
  Disabled = "disabled",
}

export const openChangePlanDialog = (
  dialogService: DialogService,
  dialogConfig: DialogConfig<ChangePlanDialogParams>,
) =>
  dialogService.open<ChangePlanDialogResultType, ChangePlanDialogParams>(
    ChangePlanDialogComponent,
    dialogConfig,
  );

type PlanCard = {
  name: string;
  selected: boolean;
};

interface OnSuccessArgs {
  organizationId: string;
}

// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
@Component({
  templateUrl: "./change-plan-dialog.component.html",
  imports: [
    BillingSharedModule,
    EnterPaymentMethodComponent,
    EnterBillingAddressComponent,
    CardComponent,
  ],
})
export class ChangePlanDialogComponent implements OnInit, OnDestroy {
  // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
  // eslint-disable-next-line @angular-eslint/prefer-signals
  @ViewChild(EnterPaymentMethodComponent) enterPaymentMethodComponent: EnterPaymentMethodComponent;

  // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
  // eslint-disable-next-line @angular-eslint/prefer-signals
  @Input() acceptingSponsorship = false;
  // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
  // eslint-disable-next-line @angular-eslint/prefer-signals
  @Input() organizationId: string;
  // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
  // eslint-disable-next-line @angular-eslint/prefer-signals
  @Input() showFree = false;
  // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
  // eslint-disable-next-line @angular-eslint/prefer-signals
  @Input() showCancel = false;

  // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
  // eslint-disable-next-line @angular-eslint/prefer-signals
  @Input()
  get productTier(): ProductTierType {
    return this._productTier;
  }

  set productTier(product: ProductTierType) {
    this._productTier = product;
    this.formGroup?.controls?.productTier?.setValue(product);
  }

  protected estimatedTax: number = 0;
  protected estimatedTotal?: number;
  private _productTier = ProductTierType.Free;
  private _familyPlan: PlanType;

  // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
  // eslint-disable-next-line @angular-eslint/prefer-signals
  @Input()
  get plan(): PlanType {
    return this._plan;
  }

  set plan(plan: PlanType) {
    this._plan = plan;
    this.formGroup?.controls?.plan?.setValue(plan);
  }

  private _plan = PlanType.Free;
  // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
  // eslint-disable-next-line @angular-eslint/prefer-signals
  @Input() providerId?: string;
  // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
  // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref
  @Output() onSuccess = new EventEmitter<OnSuccessArgs>();
  // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
  // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref
  @Output() onCanceled = new EventEmitter<void>();
  // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
  // eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref
  @Output() onTrialBillingSuccess = new EventEmitter();

  protected loading = true;
  protected planCards: PlanCard[];
  protected ResultType = ChangePlanDialogResultType;

  selfHosted = false;
  productTypes = ProductTierType;
  formPromise: Promise<string>;
  singleOrgPolicyAppliesToActiveUser = false;
  isInTrialFlow = false;
  discount = 0;

  formGroup = this.formBuilder.group({
    name: [""],
    billingEmail: ["", [Validators.email]],
    businessOwned: [false],
    premiumAccessAddon: [false],
    additionalSeats: [0, [Validators.min(0), Validators.max(100000)]],
    clientOwnerEmail: ["", [Validators.email]],
    plan: [this.plan],
    productTier: [this.productTier],
  });

  billingFormGroup = this.formBuilder.group({
    paymentMethod: EnterPaymentMethodComponent.getFormGroup(),
    billingAddress: EnterBillingAddressComponent.getFormGroup(),
  });

  planType: string;
  selectedPlan: PlanResponse;
  selectedInterval: number = 1;
  planIntervals = PlanInterval;
  passwordManagerPlans: PlanResponse[];
  secretsManagerPlans: PlanResponse[];
  organization: Organization;
  sub: OrganizationSubscriptionResponse;
  dialogHeaderName: string;
  currentPlanName: string;
  showPayment: boolean = false;
  totalOpened: boolean = false;
  currentPlan: PlanResponse;
  isCardStateDisabled = false;
  focusedIndex: number | null = null;
  plans: ListResponse<PlanResponse>;
  isSubscriptionCanceled: boolean = false;
  secretsManagerTotal: number;

  paymentMethod: MaskedPaymentMethod | null;
  billingAddress: BillingAddress | null;

  private destroy$ = new Subject<void>();

  constructor(
    @Inject(DIALOG_DATA) private dialogParams: ChangePlanDialogParams,
    private dialogRef: DialogRef<ChangePlanDialogResultType>,
    private toastService: ToastService,
    private apiService: ApiService,
    private i18nService: I18nService,
    private keyService: KeyService,
    private router: Router,
    private syncService: SyncService,
    private policyService: PolicyService,
    private organizationService: OrganizationService,
    private messagingService: MessagingService,
    private formBuilder: FormBuilder,
    private organizationApiService: OrganizationApiServiceAbstraction,
    private accountService: AccountService,
    private billingNotificationService: BillingNotificationService,
    private subscriberBillingClient: SubscriberBillingClient,
    private previewInvoiceClient: PreviewInvoiceClient,
    private organizationWarningsService: OrganizationWarningsService,
  ) {}

  async ngOnInit(): Promise<void> {
    if (this.dialogParams.organizationId) {
      this.currentPlanName = this.resolvePlanName(this.dialogParams.productTierType);
      this.sub =
        this.dialogParams.subscription ??
        (await this.organizationApiService.getSubscription(this.dialogParams.organizationId));
      this.dialogHeaderName = this.resolveHeaderName(this.sub);
      this.organizationId = this.dialogParams.organizationId;
      this.currentPlan = this.sub?.plan;
      this.selectedPlan = this.sub?.plan;
      const userId = await firstValueFrom(
        this.accountService.activeAccount$.pipe(map((a) => a?.id)),
      );
      this.organization = await firstValueFrom(
        this.organizationService
          .organizations$(userId)
          .pipe(getOrganizationById(this.organizationId)),
      );
      if (this.sub?.subscription?.status !== "canceled") {
        try {
          const subscriber: BitwardenSubscriber = { type: "organization", data: this.organization };
          const [paymentMethod, billingAddress] = await Promise.all([
            this.subscriberBillingClient.getPaymentMethod(subscriber),
            this.subscriberBillingClient.getBillingAddress(subscriber),
          ]);

          this.paymentMethod = paymentMethod;
          this.billingAddress = billingAddress;
        } catch (error) {
          this.billingNotificationService.handleError(error);
        }
      }
    }

    if (!this.selfHosted) {
      this.plans = await this.apiService.getPlans();
      this.passwordManagerPlans = this.plans.data.filter((plan) => !!plan.PasswordManager);
      this.secretsManagerPlans = this.plans.data.filter((plan) => !!plan.SecretsManager);

      if (
        this.productTier === ProductTierType.Enterprise ||
        this.productTier === ProductTierType.Teams
      ) {
        this.formGroup.controls.businessOwned.setValue(true);
      }
    }

    this._familyPlan = PlanType.FamiliesAnnually;
    if (this.currentPlan && this.currentPlan.productTier !== ProductTierType.Enterprise) {
      const upgradedPlan = this.passwordManagerPlans.find((plan) =>
        this.currentPlan.productTier === ProductTierType.Free
          ? plan.type === this._familyPlan
          : plan.upgradeSortOrder == this.currentPlan.upgradeSortOrder + 1,
      );

      this.plan = upgradedPlan.type;
      this.productTier = upgradedPlan.productTier;
    }
    this.upgradeFlowPrefillForm();

    this.accountService.activeAccount$
      .pipe(
        getUserId,
        switchMap((userId) => singleOrganizationPolicyApplies$(userId, this.policyService)),
        takeUntil(this.destroy$),
      )
      .subscribe((policyAppliesToActiveUser) => {
        this.singleOrgPolicyAppliesToActiveUser = policyAppliesToActiveUser;
      });

    if (!this.selfHosted) {
      this.changedProduct();
    }

    this.planCards = [
      {
        name: this.i18nService.t("planNameTeams"),
        selected: true,
      },
      {
        name: this.i18nService.t("planNameEnterprise"),
        selected: false,
      },
    ];

    await this.setInitialPlanSelection();
    if (!this.isSubscriptionCanceled) {
      await this.refreshSalesTax();
    }

    combineLatest([
      this.billingFormGroup.controls.billingAddress.controls.country.valueChanges,
      this.billingFormGroup.controls.billingAddress.controls.postalCode.valueChanges,
      this.billingFormGroup.controls.billingAddress.controls.taxId.valueChanges,
    ])
      .pipe(
        debounceTime(1000),
        switchMap(async () => await this.refreshSalesTax()),
        takeUntil(this.destroy$),
      )
      .subscribe();

    this.loading = false;
  }

  resolveHeaderName(subscription: OrganizationSubscriptionResponse): string {
    if (subscription.subscription != null) {
      this.isSubscriptionCanceled =
        subscription.subscription.cancelled && this.sub?.plan.productTier !== ProductTierType.Free;
      if (this.isSubscriptionCanceled) {
        return this.i18nService.t("restartSubscription");
      }
    }

    return this.i18nService.t(
      "upgradeFreeOrganization",
      this.resolvePlanName(this.dialogParams.productTierType),
    );
  }

  async setInitialPlanSelection() {
    this.focusedIndex = this.selectableProducts.length - 1;
    if (!this.isSubscriptionCanceled) {
      await this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
    } else {
      await this.selectPlan(this.reSubscribablePlan);
    }
  }

  getPlanByType(productTier: ProductTierType) {
    return this.selectableProducts.find((product) => product.productTier === productTier);
  }

  isSecretsManagerTrial(): boolean {
    // A schedule-derived discount (e.g. a deferred price-migration coupon) is not an SM trial,
    // even when it applies to a subscription product.
    if (this.sub?.customerDiscount?.isFromSchedule) {
      return false;
    }

    return (
      this.sub?.subscription?.items?.some((item) =>
        this.sub?.customerDiscount?.appliesTo?.includes(item.productId),
      ) ?? false
    );
  }

  async planTypeChanged() {
    await this.selectPlan(this.getPlanByType(ProductTierType.Enterprise));
  }

  async updateInterval(event: number) {
    this.selectedInterval = event;
    await this.planTypeChanged();
  }

  protected getPlanIntervals() {
    return [
      {
        name: PlanInterval[PlanInterval.Annually],
        value: PlanInterval.Annually,
      },
      {
        name: PlanInterval[PlanInterval.Monthly],
        value: PlanInterval.Monthly,
      },
    ];
  }

  optimizedNgForRender(index: number) {
    return index;
  }

  protected getPlanCardContainerClasses(plan: PlanResponse, index: number) {
    let cardState: PlanCardState;

    if (plan == this.currentPlan) {
      cardState = PlanCardState.Disabled;
      this.isCardStateDisabled = true;
      this.focusedIndex = index;
    } else if (plan == this.selectedPlan) {
      cardState = PlanCardState.Selected;
      this.isCardStateDisabled = false;
      this.focusedIndex = index;
    } else if (
      this.selectedInterval === PlanInterval.Monthly &&
      plan.productTier == ProductTierType.Families
    ) {
      cardState = PlanCardState.Disabled;
      this.isCardStateDisabled = true;
      this.focusedIndex = this.selectableProducts.length - 1;
    } else {
      cardState = PlanCardState.NotSelected;
      this.isCardStateDisabled = false;
    }

    switch (cardState) {
      case PlanCardState.Selected: {
        return [
          "tw-cursor-pointer",
          "tw-block",
          "tw-rounded",
          "tw-border",
          "tw-border-solid",
          "tw-border-primary-600",
          "hover:tw-border-primary-700",
          "tw-border-2",
          "!tw-border-primary-700",
          "tw-rounded-lg",
        ];
      }
      case PlanCardState.NotSelected: {
        return [
          "tw-cursor-pointer",
          "tw-block",
          "tw-rounded",
          "tw-border",
          "tw-border-solid",
          "tw-border-secondary-300",
          "hover:tw-border-text-main",
          "focus:tw-border-2",
          "focus:tw-border-primary-700",
        ];
      }
      case PlanCardState.Disabled: {
        if (this.isSubscriptionCanceled) {
          return [
            "tw-cursor-not-allowed",
            "tw-bg-secondary-100",
            "tw-font-normal",
            "tw-bg-blur",
            "tw-text-muted",
            "tw-block",
            "tw-rounded",
            "tw-w-80",
          ];
        }

        return [
          "tw-cursor-not-allowed",
          "tw-bg-secondary-100",
          "tw-font-normal",
          "tw-bg-blur",
          "tw-text-muted",
          "tw-block",
          "tw-rounded",
        ];
      }
    }
  }

  protected async selectPlan(plan: PlanResponse) {
    if (
      this.selectedInterval === PlanInterval.Monthly &&
      plan.productTier == ProductTierType.Families
    ) {
      return;
    }

    if (plan === this.currentPlan && !this.isSubscriptionCanceled) {
      return;
    }
    this.selectedPlan = plan;
    // Clear the previous plan's server total so the summary falls back to the client
    // estimate for the newly selected plan until refreshSalesTax() resolves.
    this.estimatedTotal = undefined;
    this.formGroup.patchValue({ productTier: plan.productTier });

    try {
      await this.refreshSalesTax();
    } catch {
      this.estimatedTax = 0;
      this.estimatedTotal = undefined;
    }
  }

  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }

  get upgradeRequiresPaymentMethod() {
    const isFreeTier = this.organization?.productTierType === ProductTierType.Free;
    const shouldHideFree = !this.showFree;
    const hasNoPaymentSource = !this.paymentMethod;

    return isFreeTier && shouldHideFree && hasNoPaymentSource;
  }

  get selectedPlanInterval() {
    if (this.isSubscriptionCanceled) {
      return this.currentPlan.isAnnual ? "year" : "month";
    }
    return this.selectedPlan.isAnnual ? "year" : "month";
  }

  get reSubscribablePlan() {
    if (!this.currentPlan) {
      throw new Error(
        "Current plan must be set to find the re-subscribable plan for a cancelled subscription.",
      );
    }
    if (!this.currentPlan.disabled) {
      return this.currentPlan;
    }
    return (
      this.passwordManagerPlans.find(
        (plan) =>
          plan.productTier === this.currentPlan.productTier &&
          plan.isAnnual === this.currentPlan.isAnnual &&
          !plan.disabled,
      ) ?? this.currentPlan
    );
  }

  get selectableProducts() {
    if (this.isSubscriptionCanceled) {
      return [this.reSubscribablePlan];
    }

    if (this.acceptingSponsorship) {
      const familyPlan = this.passwordManagerPlans.find((plan) => plan.type === this._familyPlan);
      this.discount = familyPlan.PasswordManager.basePrice;
      return [familyPlan];
    }

    const businessOwnedIsChecked = this.formGroup.controls.businessOwned.value;

    const result = this.passwordManagerPlans.filter(
      (plan) =>
        plan.type !== PlanType.Custom &&
        (!businessOwnedIsChecked || plan.canBeUsedByBusiness) &&
        (this.showFree || plan.productTier !== ProductTierType.Free) &&
        (plan.productTier === ProductTierType.Free ||
          plan.productTier === ProductTierType.TeamsStarter ||
          (this.selectedInterval === PlanInterval.Annually && plan.isAnnual) ||
          (this.selectedInterval === PlanInterval.Monthly && !plan.isAnnual)) &&
        (plan.productTier !== ProductTierType.Families || plan.type === this._familyPlan) &&
        (!this.currentPlan || this.currentPlan.upgradeSortOrder < plan.upgradeSortOrder) &&
        this.planIsEnabled(plan),
    );

    if (
      this.currentPlan.productTier === ProductTierType.Free &&
      this.selectedInterval === PlanInterval.Monthly &&
      !this.organization.useSecretsManager
    ) {
      const familyPlan = this.passwordManagerPlans.find(
        (plan) => plan.productTier == ProductTierType.Families,
      );
      result.push(familyPlan);
    }

    if (
      this.organization.useSecretsManager &&
      this.currentPlan.productTier === ProductTierType.Free
    ) {
      const familyPlanIndex = result.findIndex(
        (plan) => plan.productTier === ProductTierType.Families,
      );

      if (familyPlanIndex !== -1) {
        result.splice(familyPlanIndex, 1);
      }
    }

    if (this.currentPlan.productTier !== ProductTierType.Free) {
      result.push(this.currentPlan);
    }

    result.sort((planA, planB) => planA.displaySortOrder - planB.displaySortOrder);

    return result;
  }

  get selectablePlans() {
    const selectedProductTierType = this.formGroup.controls.productTier.value;
    const result =
      this.passwordManagerPlans?.filter(
        (plan) => plan.productTier === selectedProductTierType && this.planIsEnabled(plan),
      ) || [];

    result.sort((planA, planB) => planA.displaySortOrder - planB.displaySortOrder);
    return result;
  }

  get storageGb() {
    return Math.max(
      0,
      (this.sub?.maxStorageGb ?? 0) - this.selectedPlan.PasswordManager.baseStorageGb,
    );
  }

  passwordManagerSeatTotal(plan: PlanResponse): number {
    if (!plan.PasswordManager.hasAdditionalSeatsOption || this.isSecretsManagerTrial()) {
      return 0;
    }

    return plan.PasswordManager.seatPrice * Math.abs(this.sub?.seats || 0);
  }

  secretsManagerSeatTotal(plan: PlanResponse, seats: number): number {
    if (!plan.SecretsManager.hasAdditionalSeatsOption) {
      return 0;
    }

    return plan.SecretsManager.seatPrice * Math.abs(seats || 0);
  }

  additionalStorageTotal(plan: PlanResponse): number {
    if (!plan.PasswordManager.hasAdditionalStorageOption) {
      return 0;
    }

    return plan.PasswordManager.additionalStoragePricePerGb * this.storageGb;
  }

  additionalStoragePriceMonthly(selectedPlan: PlanResponse) {
    return selectedPlan.PasswordManager.additionalStoragePricePerGb;
  }

  additionalServiceAccountTotal(plan: PlanResponse): number {
    if (
      !plan.SecretsManager.hasAdditionalServiceAccountOption ||
      this.additionalServiceAccount == 0
    ) {
      return 0;
    }

    return plan.SecretsManager.additionalPricePerServiceAccount * this.additionalServiceAccount;
  }

  get passwordManagerSubtotal() {
    if (!this.selectedPlan || !this.selectedPlan.PasswordManager) {
      return 0;
    }

    let subTotal = this.selectedPlan.PasswordManager.basePrice;
    if (this.selectedPlan.PasswordManager.hasAdditionalSeatsOption) {
      subTotal += this.passwordManagerSeatTotal(this.selectedPlan);
    }
    if (this.selectedPlan.PasswordManager.hasPremiumAccessOption) {
      subTotal += this.selectedPlan.PasswordManager.premiumAccessOptionPrice;
    }
    if (this.selectedPlan.PasswordManager.hasAdditionalStorageOption) {
      subTotal += this.additionalStorageTotal(this.selectedPlan);
    }
    return subTotal - this.discount;
  }

  secretsManagerSubtotal() {
    const plan = this.selectedPlan;
    if (!plan || !plan.SecretsManager) {
      return this.secretsManagerTotal || 0;
    }

    if (this.secretsManagerTotal) {
      return this.secretsManagerTotal;
    }

    this.secretsManagerTotal =
      plan.SecretsManager.basePrice +
      this.secretsManagerSeatTotal(plan, this.sub?.smSeats) +
      this.additionalServiceAccountTotal(plan);
    return this.secretsManagerTotal;
  }

  get passwordManagerSeats() {
    if (!this.selectedPlan) {
      return 0;
    }

    if (this.selectedPlan.productTier === ProductTierType.Families) {
      return this.selectedPlan.PasswordManager.baseSeats;
    }
    return this.sub?.seats;
  }

  get total() {
    if (!this.organization || !this.selectedPlan) {
      return 0;
    }

    if (this.organization.useSecretsManager) {
      return this.passwordManagerSubtotal + this.secretsManagerSubtotal() + this.estimatedTax;
    }
    return this.passwordManagerSubtotal + this.estimatedTax;
  }

  get teamsStarterPlanIsAvailable() {
    return this.selectablePlans.some((plan) => plan.type === PlanType.TeamsStarter);
  }

  get additionalServiceAccount() {
    if (!this.currentPlan || !this.currentPlan.SecretsManager) {
      return 0;
    }

    const baseServiceAccount = this.currentPlan.SecretsManager?.baseServiceAccount || 0;
    const usedServiceAccounts =
      (this.sub?.smServiceAccounts || 0) - (this.sub?.smServiceAccountsGrace || 0);

    const additionalServiceAccounts = baseServiceAccount - usedServiceAccounts;

    return additionalServiceAccounts <= 0 ? Math.abs(additionalServiceAccounts) : 0;
  }

  changedProduct() {
    const selectedPlan = this.selectablePlans[0];

    this.setPlanType(selectedPlan.type);
    this.handlePremiumAddonAccess(selectedPlan.PasswordManager.hasPremiumAccessOption);
    this.handleAdditionalSeats(selectedPlan.PasswordManager.hasAdditionalSeatsOption);
  }

  setPlanType(planType: PlanType) {
    this.formGroup.controls.plan.setValue(planType);
  }

  handlePremiumAddonAccess(hasPremiumAccessOption: boolean) {
    this.formGroup.controls.premiumAccessAddon.setValue(!hasPremiumAccessOption);
  }

  handleAdditionalSeats(selectedPlanHasAdditionalSeatsOption: boolean) {
    if (!selectedPlanHasAdditionalSeatsOption) {
      this.formGroup.controls.additionalSeats.setValue(0);
      return;
    }

    if (this.currentPlan && !this.currentPlan.PasswordManager.hasAdditionalSeatsOption) {
      this.formGroup.controls.additionalSeats.setValue(this.currentPlan.PasswordManager.baseSeats);
      return;
    }

    if (this.organization) {
      this.formGroup.controls.additionalSeats.setValue(this.organization.seats);
      return;
    }

    this.formGroup.controls.additionalSeats.setValue(1);
  }

  submit = async () => {
    this.formGroup.markAllAsTouched();
    this.billingFormGroup.markAllAsTouched();
    if (this.formGroup.invalid || (this.billingFormGroup.invalid && !this.paymentMethod)) {
      return;
    }

    const doSubmit = async (): Promise<string> => {
      let orgId: string;
      const sub = this.sub?.subscription;
      const isCanceled = sub?.status === "canceled";
      const isCancelledDowngradedToFreeOrg =
        sub?.cancelled && this.organization.productTierType === ProductTierType.Free;

      if (isCanceled || isCancelledDowngradedToFreeOrg) {
        await this.restartSubscription();
        orgId = this.organizationId;
      } else {
        orgId = await this.updateOrganization();
      }
      this.toastService.showToast({
        variant: "success",
        title: null,
        message: this.isSubscriptionCanceled
          ? this.i18nService.t("restartOrganizationSubscription")
          : this.i18nService.t("organizationUpgraded"),
      });

      await this.syncService.fullSync(true);

      if (!this.acceptingSponsorship && !this.isInTrialFlow) {
        await this.router.navigate(["/organizations/" + orgId + "/billing/subscription"]);
      }

      if (this.isInTrialFlow) {
        this.onTrialBillingSuccess.emit({
          orgId: orgId,
          subLabelText: this.billingSubLabelText(),
        });
      }

      return orgId;
    };

    this.formPromise = doSubmit();
    const organizationId = await this.formPromise;
    this.onSuccess.emit({ organizationId: organizationId });
    // TODO: No one actually listening to this message?
    this.messagingService.send("organizationCreated", { organizationId });
    await this.dialogRef.close();
  };

  private async restartSubscription() {
    const paymentMethod = await this.enterPaymentMethodComponent.tokenize();
    const billingAddress = getBillingAddressFromForm(this.billingFormGroup.controls.billingAddress);
    await this.subscriberBillingClient.restartSubscription(
      { type: "organization", data: this.organization },
      paymentMethod,
      billingAddress,
    );
    this.organizationWarningsService.refreshInactiveSubscriptionWarning();
  }

  private async updateOrganization() {
    const request = new OrganizationUpgradeRequest();
    if (this.selectedPlan.productTier !== ProductTierType.Families) {
      request.additionalSeats = this.sub?.seats;
    }
    if (this.sub?.maxStorageGb > this.selectedPlan.PasswordManager.baseStorageGb) {
      request.additionalStorageGb =
        this.sub?.maxStorageGb - this.selectedPlan.PasswordManager.baseStorageGb;
    }
    request.premiumAccessAddon =
      this.selectedPlan.PasswordManager.hasPremiumAccessOption &&
      this.formGroup.controls.premiumAccessAddon.value;
    request.planType = this.selectedPlan.type;
    if (this.showPayment) {
      request.billingAddressCountry = this.billingFormGroup.controls.billingAddress.value.country;
      request.billingAddressPostalCode =
        this.billingFormGroup.controls.billingAddress.value.postalCode;
    }

    // Secrets Manager
    this.buildSecretsManagerRequest(request);

    if (this.upgradeRequiresPaymentMethod || this.showPayment || !this.paymentMethod) {
      const paymentMethod = await this.enterPaymentMethodComponent.tokenize();
      const billingAddress = getBillingAddressFromForm(
        this.billingFormGroup.controls.billingAddress,
      );

      const subscriber: BitwardenSubscriber = { type: "organization", data: this.organization };
      // These need to be synchronous so one of them can create the Customer in the case we're upgrading from Free.
      await this.subscriberBillingClient.updateBillingAddress(subscriber, billingAddress);
      await this.subscriberBillingClient.updatePaymentMethod(subscriber, paymentMethod, null);
    }

    // Backfill pub/priv key if necessary
    if (!this.organization.hasPublicAndPrivateKeys) {
      const userId = await firstValueFrom(
        this.accountService.activeAccount$.pipe(map((a) => a?.id)),
      );
      const orgShareKey = await firstValueFrom(
        this.keyService
          .orgKeys$(userId)
          .pipe(map((orgKeys) => orgKeys?.[this.organizationId as OrganizationId] ?? null)),
      );
      const orgKeys = await this.keyService.makeKeyPair(orgShareKey);
      request.keys = new OrganizationKeysRequest(orgKeys[0], orgKeys[1].encryptedString);
    }

    await this.organizationApiService.upgrade(this.organizationId, request);
    return this.organizationId;
  }

  private billingSubLabelText(): string {
    const selectedPlan = this.selectedPlan;
    const price =
      selectedPlan.PasswordManager.basePrice === 0
        ? selectedPlan.PasswordManager.seatPrice
        : selectedPlan.PasswordManager.basePrice;
    let text = "";

    if (selectedPlan.isAnnual) {
      text += `${this.i18nService.t("annual")} ($${price}/${this.i18nService.t("yr")})`;
    } else {
      text += `${this.i18nService.t("monthly")} ($${price}/${this.i18nService.t("monthAbbr")})`;
    }

    return text;
  }

  private buildSecretsManagerRequest(request: OrganizationUpgradeRequest): void {
    request.useSecretsManager = this.organization.useSecretsManager;
    if (!this.organization.useSecretsManager) {
      return;
    }

    if (
      this.selectedPlan.SecretsManager.hasAdditionalSeatsOption &&
      this.currentPlan.productTier === ProductTierType.Free
    ) {
      request.additionalSmSeats = this.organization.seats;
    } else {
      request.additionalSmSeats = this.sub?.smSeats;
      request.additionalServiceAccounts = this.additionalServiceAccount;
    }
  }

  private upgradeFlowPrefillForm() {
    if (this.acceptingSponsorship) {
      this.formGroup.controls.productTier.setValue(ProductTierType.Families);
      this.changedProduct();
      return;
    }

    if (this.currentPlan && this.currentPlan.productTier !== ProductTierType.Enterprise) {
      const upgradedPlan = this.passwordManagerPlans.find((plan) => {
        if (this.currentPlan.productTier === ProductTierType.Free) {
          return plan.type === this._familyPlan;
        }

        if (
          this.currentPlan.productTier === ProductTierType.Families &&
          !this.teamsStarterPlanIsAvailable
        ) {
          return plan.type === PlanType.TeamsAnnually;
        }

        return plan.upgradeSortOrder === this.currentPlan.upgradeSortOrder + 1;
      });

      this.plan = upgradedPlan.type;
      this.productTier = upgradedPlan.productTier;
      this.changedProduct();
    }
  }

  private planIsEnabled(plan: PlanResponse) {
    return !plan.disabled && !plan.legacyYear;
  }

  toggleShowPayment() {
    this.showPayment = true;
  }

  toggleTotalOpened() {
    this.totalOpened = !this.totalOpened;
  }

  resolvePlanName(productTier: ProductTierType) {
    switch (productTier) {
      case ProductTierType.Enterprise:
        return this.i18nService.t("planNameEnterprise");
      case ProductTierType.Free:
        return this.i18nService.t("planNameFree");
      case ProductTierType.Families:
        return this.i18nService.t("planNameFamilies");
      case ProductTierType.Teams:
        return this.i18nService.t("planNameTeams");
      case ProductTierType.TeamsStarter:
        return this.i18nService.t("planNameTeamsStarter");
    }
  }

  onKeydown(event: KeyboardEvent, index: number) {
    const cardElements = Array.from(document.querySelectorAll(".product-card")) as HTMLElement[];
    let newIndex = index;
    const direction = event.key === "ArrowRight" || event.key === "ArrowDown" ? 1 : -1;

    if (["ArrowRight", "ArrowDown", "ArrowLeft", "ArrowUp"].includes(event.key)) {
      do {
        newIndex = (newIndex + direction + cardElements.length) % cardElements.length;
      } while (this.isCardDisabled(newIndex) && newIndex !== index);

      event.preventDefault();

      setTimeout(() => {
        const card = cardElements[newIndex];
        if (!(
          card.classList.contains("tw-bg-secondary-100") && card.classList.contains("tw-text-muted")
        )) {
          card?.focus();
        }
      }, 0);
    }
  }

  async onFocus(index: number) {
    this.focusedIndex = index;
    await this.selectPlan(this.selectableProducts[index]);
  }

  isCardDisabled(index: number): boolean {
    const card = this.selectableProducts[index];
    return card === (this.currentPlan || this.isCardStateDisabled);
  }

  manageSelectableProduct(index: number) {
    return index;
  }

  private async refreshSalesTax(): Promise<void> {
    if (this.billingFormGroup.controls.billingAddress.invalid && !this.billingAddress) {
      return;
    }

    const getPlanFromLegacyEnum = (planType: PlanType): OrganizationSubscriptionPlan => {
      switch (planType) {
        case PlanType.FamiliesAnnually:
        case PlanType.FamiliesAnnually2025:
          return { tier: "families", cadence: "annually" };
        case PlanType.TeamsMonthly:
          return { tier: "teams", cadence: "monthly" };
        case PlanType.TeamsAnnually:
          return { tier: "teams", cadence: "annually" };
        case PlanType.EnterpriseMonthly:
          return { tier: "enterprise", cadence: "monthly" };
        case PlanType.EnterpriseAnnually:
          return { tier: "enterprise", cadence: "annually" };
      }
    };

    const billingAddress = this.billingFormGroup.controls.billingAddress.valid
      ? getBillingAddressFromForm(this.billingFormGroup.controls.billingAddress)
      : this.billingAddress;

    const taxAmounts =
      await this.previewInvoiceClient.previewTaxForOrganizationSubscriptionPlanChange(
        this.organizationId,
        getPlanFromLegacyEnum(this.selectedPlan.type),
        billingAddress,
      );

    this.estimatedTax = taxAmounts.tax;
    this.estimatedTotal = taxAmounts.total;
  }

  protected canUpdatePaymentInformation(): boolean {
    return (
      this.upgradeRequiresPaymentMethod ||
      this.showPayment ||
      !this.paymentMethod ||
      this.isSubscriptionCanceled
    );
  }

  get submitButtonLabel(): string {
    if (
      this.organization &&
      this.sub &&
      this.organization.productTierType !== ProductTierType.Free &&
      this.sub.subscription?.status === "canceled"
    ) {
      return this.i18nService.t("restart");
    } else {
      return this.i18nService.t("upgrade");
    }
  }

  get supportsTaxId() {
    return this.formGroup.value.productTier !== ProductTierType.Families;
  }

  getCardBrandIcon = () => getCardBrandIcon(this.paymentMethod);
}
