// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { CommonModule } from "@angular/common";
import { Component, DestroyRef, Input, OnInit } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormBuilder, FormControl, ReactiveFormsModule, Validators } from "@angular/forms";
import { concatMap, firstValueFrom, map } from "rxjs";

// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
// eslint-disable-next-line no-restricted-imports
import { CollectionTypes, CollectionView } from "@bitwarden/admin-console/common";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { OrganizationUserType, PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { CollectionId, OrganizationId, UserId } from "@bitwarden/common/types/guid";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import {
  CardComponent,
  FormFieldModule,
  IconButtonModule,
  SectionHeaderComponent,
  SelectItemView,
  SelectModule,
  TypographyModule,
} from "@bitwarden/components";

import {
  CipherFormConfig,
  OptionalInitialValues,
} from "../../abstractions/cipher-form-config.service";
import { CipherFormContainer } from "../../cipher-form-container";

@Component({
  selector: "vault-item-details-section",
  templateUrl: "./item-details-section.component.html",
  imports: [
    CardComponent,
    TypographyModule,
    FormFieldModule,
    ReactiveFormsModule,
    SelectModule,
    SectionHeaderComponent,
    IconButtonModule,
    JslibModule,
    CommonModule,
  ],
})
export class ItemDetailsSectionComponent implements OnInit {
  itemDetailsForm = this.formBuilder.group({
    name: ["", [Validators.required]],
    organizationId: [null],
    folderId: [null],
    collectionIds: new FormControl([], [Validators.required]),
    favorite: [false],
  });

  /**
   * Collection options available for the selected organization.
   * @protected
   */
  protected collectionOptions: SelectItemView[] = [];

  /**
   * Collections that are already assigned to the cipher and are read-only. These cannot be removed.
   * @protected
   */
  protected readOnlyCollections: CollectionView[] = [];

  protected showCollectionsControl: boolean;

  /** The email address associated with the active account */
  protected userEmail$ = this.accountService.activeAccount$.pipe(map((account) => account.email));

  protected organizations: Organization[] = [];

  protected userId: UserId;

  protected favoriteButtonDisabled = false;

  @Input({ required: true })
  config: CipherFormConfig;

  @Input()
  originalCipherView: CipherView;

  get readOnlyCollectionsNames(): string[] {
    return this.readOnlyCollections.map((c) => c.name);
  }
  /**
   * Whether the form is in partial edit mode. Only the folder and favorite controls are available.
   */
  get partialEdit(): boolean {
    return this.config.mode === "partial-edit";
  }

  get allowPersonalOwnership() {
    return this.config.organizationDataOwnershipDisabled;
  }

  get collections(): CollectionView[] {
    return this.config.collections;
  }

  get initialValues(): OptionalInitialValues | undefined {
    return this.config.initialValues;
  }

  /**
   * Show the personal ownership option in the Owner dropdown when any of the following:
   * - personal ownership is allowed
   * - `organizationId` control is disabled
   * - personal ownership is not allowed AND the user is editing a cipher that is not
   * currently owned by an organization
   */
  get showPersonalOwnershipOption() {
    return (
      this.allowPersonalOwnership ||
      this.itemDetailsForm.controls.organizationId.disabled ||
      (!this.allowPersonalOwnership &&
        this.config.originalCipher &&
        this.itemDetailsForm.controls.organizationId.value === null)
    );
  }

  constructor(
    private cipherFormContainer: CipherFormContainer,
    private formBuilder: FormBuilder,
    private i18nService: I18nService,
    private destroyRef: DestroyRef,
    private accountService: AccountService,
    private configService: ConfigService,
    private policyService: PolicyService,
  ) {
    this.cipherFormContainer.registerChildForm("itemDetails", this.itemDetailsForm);
    this.itemDetailsForm.valueChanges
      .pipe(
        takeUntilDestroyed(),
        // getRawValue() because organizationId can be disabled for edit mode
        map(() => this.itemDetailsForm.getRawValue()),
      )
      .subscribe((value) => {
        this.cipherFormContainer.patchCipher((cipher) => {
          Object.assign(cipher, {
            name: value.name,
            organizationId: value.organizationId,
            folderId: value.folderId,
            collectionIds: [
              ...(value.collectionIds?.map((c) => c.id) || []),
              ...this.readOnlyCollections.map((c) => c.id),
            ],
            favorite: value.favorite,
          } as CipherView);
          return cipher;
        });
      });
  }

  get favoriteIcon() {
    return this.itemDetailsForm.controls.favorite.value ? "bwi-star-f" : "bwi-star";
  }

  toggleFavorite() {
    this.itemDetailsForm.controls.favorite.setValue(!this.itemDetailsForm.controls.favorite.value);
  }

  get allowOwnershipChange() {
    // Do not allow ownership change in edit mode and the cipher is owned by an organization
    if (this.config.mode === "edit" && this.originalCipherView?.organizationId != null) {
      return false;
    }

    // If personal ownership is allowed and there is at least one organization, allow ownership change.
    if (this.allowPersonalOwnership) {
      return this.organizations.length > 0;
    }

    // Personal ownership is not allowed, only allow ownership change if there is more than one organization.
    return this.organizations.length > 1;
  }

  get showOwnership() {
    // Don't show ownership field for archived ciphers
    if (this.originalCipherView?.isArchived) {
      return false;
    }

    // Show ownership field when editing with available orgs
    const isEditingWithOrgs = this.organizations.length > 0 && this.config.mode === "edit";

    // When in admin console, ownership should not be shown unless cloning
    const isAdminConsoleEdit = this.config.isAdminConsole && this.config.mode !== "clone";

    return this.allowOwnershipChange || (isEditingWithOrgs && !isAdminConsoleEdit);
  }

  get defaultOwner() {
    return this.allowPersonalOwnership ? null : this.organizations[0].id;
  }

  async ngOnInit() {
    this.organizations = this.config.organizations.sort(
      Utils.getSortFunction(this.i18nService, "name"),
    );

    this.userId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));

    if (!this.allowPersonalOwnership && this.organizations.length === 0) {
      throw new Error("No organizations available for ownership.");
    }

    const prefillCipher = this.cipherFormContainer.getInitialCipherView();

    if (prefillCipher) {
      await this.initFromExistingCipher(prefillCipher);
    } else {
      const orgId = this.initialValues?.organizationId;
      this.itemDetailsForm.setValue({
        name: this.initialValues?.name || "",
        organizationId: orgId || this.defaultOwner,
        folderId: this.initialValues?.folderId || null,
        collectionIds: [],
        favorite: false,
      });
      await this.updateCollectionOptions(this.initialValues?.collectionIds);
    }

    this.setFormState();

    this.itemDetailsForm.controls.organizationId.valueChanges
      .pipe(
        takeUntilDestroyed(this.destroyRef),
        concatMap(async () => {
          await this.updateCollectionOptions();
          this.setFormState();
        }),
      )
      .subscribe();
  }

  /**
   * Updates the global form and organizationId control states.
   */
  private setFormState() {
    if (this.config.originalCipher && !this.allowPersonalOwnership) {
      // When editing a cipher and the user cannot have personal ownership
      // and the cipher is is not within the organization - force the user to
      // move the cipher within the organization first before editing any other field
      if (this.itemDetailsForm.controls.organizationId.value === null) {
        this.cipherFormContainer.disableFormFields();
        this.itemDetailsForm.controls.organizationId.enable();
        this.favoriteButtonDisabled = true;
      } else {
        // The "after" from the above: When editing a cipher and the user cannot have personal ownership
        // and the organization is populated - re-enable the global form.
        this.cipherFormContainer.enableFormFields();
        this.favoriteButtonDisabled = false;
        this.setCollectionControlState();
      }
    } else if (!this.allowOwnershipChange) {
      // When the user cannot change the organization field, disable the organizationId control.
      // This could be because they aren't a part of an organization
      this.itemDetailsForm.controls.organizationId.disable({ emitEvent: false });
    }
  }

  /**
   * Gets the default collection IDs for the selected organization.
   * Returns null if any of the following apply:
   * - the feature flag is disabled
   * - the "no private data policy" doesn't apply to the user
   * - no org is currently selected
   * - the selected org doesn't have the "no private data policy" enabled
   */
  private async getDefaultCollectionId(orgId?: OrganizationId) {
    if (!orgId || this.allowPersonalOwnership) {
      return;
    }

    const isFeatureEnabled = await this.configService.getFeatureFlag(
      FeatureFlag.CreateDefaultLocation,
    );

    if (!isFeatureEnabled) {
      return;
    }

    const selectedOrgHasPolicyEnabled = (
      await firstValueFrom(
        this.policyService.policiesByType$(PolicyType.OrganizationDataOwnership, this.userId),
      )
    ).find((p) => p.organizationId);

    if (!selectedOrgHasPolicyEnabled) {
      return;
    }

    const defaultUserCollection = this.collections.find(
      (c) => c.organizationId === orgId && c.type === CollectionTypes.DefaultUserCollection,
    );
    // If the user was added after the policy was enabled as they will not have any private data
    // and will not have a default collection.
    return defaultUserCollection?.id;
  }

  private async initFromExistingCipher(prefillCipher: CipherView) {
    const { name, folderId, collectionIds } = prefillCipher;

    this.itemDetailsForm.patchValue({
      name: name ? name : (this.initialValues?.name ?? ""),
      organizationId: prefillCipher.organizationId, // We do not allow changing ownership of an existing cipher.
      folderId: folderId ? folderId : (this.initialValues?.folderId ?? null),
      collectionIds: [],
      favorite: prefillCipher.favorite,
    });

    const orgId = this.itemDetailsForm.controls.organizationId.value as OrganizationId;
    const initializedWithCachedCipher = this.cipherFormContainer.initializedWithCachedCipher();

    // Configure form for clone mode.
    if (this.config.mode === "clone") {
      if (!initializedWithCachedCipher) {
        this.itemDetailsForm.controls.name.setValue(
          prefillCipher.name + " - " + this.i18nService.t("clone"),
        );
      }

      if (!this.allowPersonalOwnership && prefillCipher.organizationId == null) {
        this.itemDetailsForm.controls.organizationId.setValue(this.defaultOwner);
      }
    }

    const prefillCollections = collectionIds?.length
      ? (collectionIds as CollectionId[])
      : (this.initialValues?.collectionIds ?? []);

    await this.updateCollectionOptions(prefillCollections);

    this.setCollectionControlState();

    if (this.partialEdit) {
      this.itemDetailsForm.disable();
      this.itemDetailsForm.controls.favorite.enable();
      this.itemDetailsForm.controls.folderId.enable();
    } else if (this.config.mode === "edit") {
      if (!this.config.isAdminConsole || !this.config.admin) {
        this.readOnlyCollections = this.collections.filter(
          // When the configuration is set up for admins, they can alter read only collections
          (c) =>
            c.organizationId === orgId &&
            c.readOnly &&
            this.originalCipherView.collectionIds.includes(c.id as CollectionId),
        );
      }
    }
  }

  private setCollectionControlState() {
    const initialCipherView = this.cipherFormContainer.getInitialCipherView();
    const orgId = this.itemDetailsForm.controls.organizationId.value as OrganizationId;
    const organization = this.organizations.find((o) => o.id === orgId);
    if (!organization || !initialCipherView) {
      return;
    }
    // Disable the collection control if either of the following apply:
    // 1. The organization does not allow editing all ciphers and the existing cipher cannot be assigned to
    // collections
    // 2. When Owners/Admins access setting is turned on.
    // AND either:
    //   a. Disable Collections Options if Owner/Admin does not have Edit/Manage permissions on item
    //   b. Disable Collections Options if Custom user does not have Edit/Manage permissions on item
    if (
      (!organization.canEditAllCiphers && !initialCipherView.canAssignToCollections) ||
      (organization.allowAdminAccessToAllCollectionItems &&
        (!initialCipherView.viewPassword || !initialCipherView.edit)) ||
      (organization.type === OrganizationUserType.Custom && !initialCipherView.viewPassword)
    ) {
      this.itemDetailsForm.controls.collectionIds.disable();
    }
  }

  /**
   * Updates the collection options based on the selected organization.
   * @param startingSelection - Optional starting selection of collectionIds to be automatically selected.
   * @private
   */
  private async updateCollectionOptions(startingSelection: CollectionId[] = []) {
    const orgId = this.itemDetailsForm.controls.organizationId.value as OrganizationId;
    const collectionsControl = this.itemDetailsForm.controls.collectionIds;

    // No organization selected, disable/hide the collections control.
    if (orgId == null) {
      this.collectionOptions = [];
      collectionsControl.reset();
      collectionsControl.disable();
      this.showCollectionsControl = false;
      return;
    } else {
      collectionsControl.enable();
      this.showCollectionsControl = true;
    }

    /**
     * Determine if the the cipher is only assigned to shared collections.
     * i.e. The cipher is not assigned to a default collections.
     * Note: `.every` will return true for an empty array
     */
    const cipherIsOnlyInOrgCollections =
      (this.originalCipherView?.collectionIds ?? []).length > 0 &&
      this.originalCipherView.collectionIds.every(
        (cId) =>
          this.collections.find((c) => c.id === cId)?.type === CollectionTypes.SharedCollection,
      );
    this.collectionOptions = this.collections
      .filter((c) => {
        // The collection belongs to the organization
        if (c.organizationId !== orgId) {
          return false;
        }

        // When in partial edit mode, show all org collections because the control is disabled.
        if (this.partialEdit) {
          return true;
        }

        // When viewing as an admin, all collections should be shown, even readonly. (AC Only)
        if (this.config.admin) {
          return true;
        }

        // When the cipher is only assigned to shared collections, do not allow a user to
        // move it back to a default collection. Exclude the default collection from the list.
        if (cipherIsOnlyInOrgCollections && c.type === CollectionTypes.DefaultUserCollection) {
          return false;
        }

        // Non-admins can only select assigned collections that are not read only. (Non-AC)
        return c.assigned && !c.readOnly;
      })
      .sort((a, b) => {
        // Show default collection first
        const aIsDefaultCollection = a.type === CollectionTypes.DefaultUserCollection ? -1 : 0;
        const bIsDefaultCollection = b.type === CollectionTypes.DefaultUserCollection ? -1 : 0;
        return aIsDefaultCollection - bIsDefaultCollection;
      })
      .map((c) => ({
        id: c.id,
        name: c.name,
        listName: c.name,
        labelName: c.name,
      }));

    collectionsControl.reset();
    collectionsControl.enable();
    this.showCollectionsControl = true;

    // If there is only one collection, select it by default.
    if (this.collectionOptions.length === 1) {
      collectionsControl.setValue(this.collectionOptions);
      return;
    }

    if (startingSelection.filter(Boolean).length > 0) {
      collectionsControl.setValue(
        this.collectionOptions.filter((c) => startingSelection.includes(c.id as CollectionId)),
      );
    } else {
      const defaultCollectionId = await this.getDefaultCollectionId(orgId);
      if (defaultCollectionId) {
        collectionsControl.setValue(
          this.collectionOptions.filter((c) => c.id === defaultCollectionId),
        );
      }
    }
  }
}
