// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "components/autofill/ios/browser/autofill_driver_ios_factory.h"

#import <memory>
#import <ranges>

#import "base/check.h"
#import "base/check_deref.h"
#import "base/metrics/histogram_functions.h"
#import "components/autofill/ios/browser/autofill_client_ios.h"
#import "components/autofill/ios/browser/autofill_driver_ios.h"
#import "components/autofill/ios/browser/autofill_java_script_feature.h"
#import "ios/web/public/web_state.h"

namespace autofill {

void AutofillDriverIOSFactory::Observer::OnAutofillDriverFactoryDestroyed(
    AutofillDriverFactory& factory) {
  OnAutofillDriverIOSFactoryDestroyed(
      static_cast<AutofillDriverIOSFactory&>(factory));
}

void AutofillDriverIOSFactory::Observer::OnAutofillDriverCreated(
    AutofillDriverFactory& factory,
    AutofillDriver& driver) {
  OnAutofillDriverIOSCreated(static_cast<AutofillDriverIOSFactory&>(factory),
                             static_cast<AutofillDriverIOS&>(driver));
}

void AutofillDriverIOSFactory::Observer::OnAutofillDriverStateChanged(
    AutofillDriverFactory& factory,
    AutofillDriver& driver,
    LifecycleState old_state,
    LifecycleState new_state) {
  OnAutofillDriverIOSStateChanged(
      static_cast<AutofillDriverIOSFactory&>(factory),
      static_cast<AutofillDriverIOS&>(driver), old_state, new_state);
}

AutofillDriverIOSFactory::AutofillDriverIOSFactory(
    AutofillClientIOS* client,
    id<AutofillDriverIOSBridge> bridge)
    : client_(CHECK_DEREF(client)), bridge_(bridge) {
  web_state()->AddObserver(this);
  GetWebFramesManager().AddObserver(this);
}

AutofillDriverIOSFactory::~AutofillDriverIOSFactory() {
  CHECK(web_state_destroyed_);
  for (auto& observer : AutofillDriverFactory::observers()) {
    observer.OnAutofillDriverFactoryDestroyed(*this);
  }
  if (web_state() && web_state()->IsRealized()) {
    // Only count the max number of drivers for realized web states because
    // unrealized web states do not have loaded frames which can heavily skew
    // the data towards 0 frames.
    base::UmaHistogramCounts1000("Autofill.NumberOfDriversPerFactory",
                                max_drivers_);
  }
}

// The AutofillClientIOS contract guarantees that WebStateDestroyed() is called
// and that `client_` is still alive.
void AutofillDriverIOSFactory::WebStateDestroyed(
    web::WebState* destroyed_web_state) {
  CHECK(web_state());
  if (web_state()) {
    for (const auto& [frame_id, driver] : driver_map_) {
      if (driver) {
        SetLifecycleStateAndNotifyObservers(*driver,
                                            LifecycleState::kPendingDeletion);
      }
    }
    driver_map_.clear();
    GetWebFramesManager().RemoveObserver(this);
    web_state()->RemoveObserver(this);
  }
  web_state_destroyed_ = true;
}

web::WebState* AutofillDriverIOSFactory::web_state() {
  return client_->web_state();
}

web::WebFramesManager& AutofillDriverIOSFactory::GetWebFramesManager() {
  CHECK(web_state());
  auto* web_frames_manager =
      AutofillJavaScriptFeature::GetInstance()->GetWebFramesManager(
          web_state());
  CHECK(web_frames_manager) << "Tests must set the WebFramesManager before "
                               "instantiating AutofillDriverIOSFactory";
  return *web_frames_manager;
}

void AutofillDriverIOSFactory::WebFrameBecameAvailable(
    web::WebFramesManager* web_frames_manager,
    web::WebFrame* web_frame) {
  // Remove the null driver for `web_frame` to unblock DriverForFrame() from
  // creating a driver for the available frame.
  // Also clean up the null drivers for deleted WebFrames.
  std::erase_if(driver_map_, [&](const auto& p) {
    const std::string& frame_id = p.first;
    const AutofillDriverIOS* driver = p.second.get();
    return driver == nullptr &&
           (web_frames_manager->GetFrameWithId(frame_id) == nullptr ||
            frame_id == web_frame->GetFrameId());
  });
}

void AutofillDriverIOSFactory::WebFrameBecameUnavailable(
    web::WebFramesManager* web_frames_manager,
    const std::string& frame_id) {
  // Keep a null driver for `frame_id` in the map to block DriverForFrame() from
  // creating a driver for the unavailable frame.
  std::unique_ptr<AutofillDriverIOS>& driver = driver_map_[frame_id];
  if (driver) {
    SetLifecycleStateAndNotifyObservers(*driver,
                                        LifecycleState::kPendingDeletion);
  }
  driver = nullptr;
  DCHECK_EQ(&driver_map_[frame_id], &driver);
}

AutofillDriverIOS* AutofillDriverIOSFactory::DriverForFrame(
    web::WebFrame* web_frame) {
  if (web_state_destroyed_) {
    return nullptr;
  }
  std::string web_frame_id = web_frame->GetFrameId();
  auto [iter, insertion_happened] = driver_map_.emplace(web_frame_id, nullptr);
  std::unique_ptr<AutofillDriverIOS>& driver = iter->second;
  if (insertion_happened) {
    driver = std::make_unique<AutofillDriverIOS>(
        web_state(), web_frame, &*client_, &router_, bridge_,
        base::PassKey<AutofillDriverIOSFactory>());
    for (auto& observer : observers()) {
      observer.OnAutofillDriverCreated(*this, *driver);
    }
    DCHECK(driver->IsActive());
    SetLifecycleStateAndNotifyObservers(*driver, LifecycleState::kActive);
    DCHECK_EQ(&driver_map_[web_frame_id], &driver);
  }
  max_drivers_ = std::max(max_drivers_, driver_map_.size());
  // `driver` may be null if WebFrameBecameUnavailable() has been called for its
  // `web_frame` already.
  return driver.get();
}

AutofillDriverIOS* AutofillDriverIOSFactory::DriverForMainFrame() {
  if (web_state_destroyed_) {
    return nullptr;
  }
  web::WebFrame* main_frame = GetWebFramesManager().GetMainWebFrame();
  return main_frame ? DriverForFrame(main_frame) : nullptr;
}

std::vector<AutofillDriver*> AutofillDriverIOSFactory::GetExistingDrivers() {
  std::vector<AutofillDriver*> drivers;
  drivers.reserve(driver_map_.size());
  for (const auto& [frame_id, driver] : driver_map_) {
    if (driver) {
      drivers.push_back(driver.get());
    }
  }
  return drivers;
}

}  //  namespace autofill
