/* eslint-disable @typescript-eslint/no-unused-vars */
import { nothing, TemplateResult } from 'lit'
import { ActivityLifecycle, IntentResult, type Intent } from './ActivityManager.js'
import { FragmentManager } from './FragmentManager.js'
import type { Fragment } from './Fragment.js'
import type { Application, UpdateRequest } from './Application.js'
import { bound } from '../decorators/bound.js'
import type { ActivityDetail, ActivityWithResultDetail } from '../events/IntentEvents.js'
import { EventTypes } from '../events/EventTypes.js'

/**
 * ## Activity
 *
 * `Activity` is a core building block of your application, representing a single,
 * focused operation that a user can perform.
 * Activities manage their own lifecycle, UI, and fragments, and communicate with the application
 * and other activities via intents.
 *
 * Activities are managed by the application and can be started, paused, resumed, stopped,
 * and destroyed according to the app's navigation and user actions.
 * They can also start other activities for results, manage fragments, and handle intent
 * events dispatched by hosted web components.
 *
 * ### Lifecycle Methods
 * - `onCreate(intent?)` - Called when the activity is first created.
 * - `onStart()` - Called when the activity becomes visible to the user.
 * - `onResume()` - Called when the activity gains focus and becomes interactive.
 * - `onPause()` - Called when the activity loses focus but is still visible.
 * - `onStop()` - Called when the activity is no longer visible.
 * - `onDestroy()` - Called before the activity is destroyed.
 * - `onRestart()` - Called after the activity has been stopped, just prior to it being started again.
 *
 * ### Example
 * ```typescript
 * class MyActivity extends Activity {
 *   async onCreate(intent?: Intent) {
 *     super.onCreate(intent);
 *     // Initialization logic here
 *   }
 *
 *   render() {
 *     return html`<h1>Hello from MyActivity!</h1>`;
 *   }
 * }
 * ```
 */
export class Activity extends EventTarget {
  /**
   * The root element where the activity's content should be rendered.
   * Set this if you want to render outside the application's default render root (e.g., for modals).
   */
  renderRoot?: HTMLElement

  /**
   * An optional static action name for the activity.
   * Use this as the registration key for referencing the activity in intents.
   *
   * @example
   * ```typescript
   * class LoginActivity extends Activity {
   *   static action = 'login';
   * }
   * ```
   */
  static action?: string

  /** The current lifecycle state of the activity. */
  public lifecycle: ActivityLifecycle = ActivityLifecycle.Initialized

  /** The parent application instance. */
  protected parent: Application

  /** The fragment manager for managing fragments within this activity. */
  protected manager: FragmentManager

  /** Data to be returned as a result to the calling activity. */
  protected resultData?: unknown

  /** The exit code for the activity result. */
  protected exitCode = IntentResult.RESULT_CANCELED

  /** Gets the current result code for the activity. */
  get resultCode(): IntentResult {
    return this.exitCode
  }

  /** Tracks pending request codes for activities started for result. */
  protected pendingRequestCodes: number[] = []

  /**
   * Constructs a new Activity.
   * @param parent The parent application instance.
   */
  constructor(parent: Application) {
    super()
    this.parent = parent
    this.manager = new FragmentManager(this)
  }

  /**
   * Checks if the activity is in the `Destroyed` state.
   * @returns `true` if destroyed, `false` otherwise.
   */
  isDestroyed(): boolean {
    return this.lifecycle === ActivityLifecycle.Destroyed
  }

  /**
   * Checks if the activity is in the `Resumed` state.
   * @returns `true` if resumed, `false` otherwise.
   */
  isResumed(): boolean {
    return this.lifecycle === ActivityLifecycle.Resumed
  }

  /**
   * Called when the activity is starting. Override to perform initialization logic.
   * @param intent Optional intent data.
   * @example
   * ```typescript
   * async onCreate(intent?: Intent) {
   *   await fetchData();
   * }
   * ```
   */
  onCreate(intent?: Intent): void | Promise<void> {
    this.dispatchEvent(new CustomEvent('activity:create', { detail: intent }))
  }

  /**
   * Called when the activity becomes visible to the user.
   * Override to start animations or prepare UI.
   */
  onStart(): void | Promise<void> {
    this.dispatchEvent(new CustomEvent('activity:start', { detail: null }))
  }

  /**
   * Called when the activity gains focus and becomes interactive.
   * Override to start input handling or resume tasks.
   */
  onResume(): void | Promise<void> {
    this.dispatchEvent(new CustomEvent('activity:resume', { detail: null }))
  }

  /**
   * Called when the activity loses focus but is still visible.
   * Override to pause ongoing tasks or save state.
   */
  onPause(): void | Promise<void> {
    this.dispatchEvent(new CustomEvent('activity:pause', { detail: null }))
  }

  /**
   * Called when the activity is no longer visible to the user.
   * Override to stop UI updates or animations.
   */
  onStop(): void | Promise<void> {
    this.dispatchEvent(new CustomEvent('activity:stop', { detail: null }))
  }

  /**
   * Called after the activity has been stopped, just prior to it being started again.
   */
  onRestart(): void | Promise<void> {
    this.dispatchEvent(new CustomEvent('activity:restart', { detail: null }))
  }

  /**
   * Called before the activity is destroyed. Override to clean up resources.
   */
  onDestroy(): void | Promise<void> {
    this.dispatchEvent(new CustomEvent('activity:destroy', { detail: null }))
    this.manager.onDestroy()
  }

  /**
   * Called when a new intent is delivered to the activity.
   * Override to handle new intents.
   * @param intent New intent data.
   */
  onNewIntent(intent: Intent): void | Promise<void> {
    // Override in subclass if needed
  }

  /**
   * Called by the renderer when the activity is rendered for the first time.
   * Override to perform actions after first render.
   */
  onFirstRender(): void {
    // Override in subclass if needed
  }

  /**
   * Renders the activity's view. Override to provide UI.
   * @returns A Lit TemplateResult or `nothing` if nothing should be rendered.
   * @example
   * ```typescript
   * render() {
   *   return html`<div>Hello, world!</div>`;
   * }
   * ```
   */
  render(): TemplateResult | typeof nothing {
    return nothing
  }

  /**
   * Finishes the activity and notifies the application.
   * @example
   * ```typescript
   * await this.finish();
   * ```
   */
  async finish(): Promise<void> {
    await this.getApplication().manager.finishActivity(this)
  }

  /**
   * Requests the parent application to update the UI.
   * @param opts Update options.
   * @example
   * ```typescript
   * this.requestUpdate();
   * ```
   */
  requestUpdate(opts: UpdateRequest = {}): void {
    const { activity = true } = opts
    if (activity) {
      this.manager.updateActiveFragments()
    }
    this.parent.requestUpdate(opts)
  }

  /**
   * Registers a new fragment with this activity.
   * @param key The fragment's key.
   * @param fragment The fragment instance.
   * @param data Optional data to pass to the fragment.
   * @example
   * ```typescript
   * await this.addFragment('profile', new ProfileFragment());
   * ```
   */
  async addFragment(key: string, fragment: Fragment, data?: unknown): Promise<void> {
    await this.manager.attachFragment(key, fragment, this, data)
  }

  /**
   * Shows a registered fragment.
   * @param key The fragment's key.
   * @param root Optional root element to render into.
   */
  async showFragment(key: string, root?: HTMLElement): Promise<void> {
    await this.manager.showFragment(key, root)
  }

  /**
   * Hides a registered fragment.
   * @param key The fragment's key.
   */
  async hideFragment(key: string): Promise<void> {
    const fragment = this.manager.findFragment(key)
    if (fragment) {
      await this.manager.hideFragment(fragment)
    }
  }

  /**
   * Returns the parent application instance.
   * @returns The application.
   */
  getApplication(): Application {
    return this.parent
  }

  /**
   * Starts a new activity.
   * @param intent The intent to start.
   * @example
   * ```typescript
   * await this.startActivity({ action: 'login' });
   * ```
   */
  startActivity(intent: Intent): Promise<void> {
    return this.parent.manager.startActivity(intent)
  }

  /**
   * Starts a new activity for a result.
   * @param intent The intent to start.
   * @returns The request code for the started activity.
   * @example
   * ```typescript
   * const code = await this.startActivityForResult({ action: 'pickFile' });
   * ```
   */
  async startActivityForResult(intent: Intent): Promise<number> {
    const { manager } = this.getApplication()
    const code = manager.createRequestCode()
    this.pendingRequestCodes.push(code)
    await manager.startActivityForResult(intent, code)
    return code
  }

  /**
   * Sets the result to be returned to the calling activity.
   * @param resultCode The result code.
   * @param data Optional result data.
   * @example
   * ```typescript
   * this.setResult(IntentResult.RESULT_OK, { userId: 123 });
   * ```
   */
  setResult(resultCode: IntentResult, data?: unknown): void {
    this.exitCode = resultCode
    this.resultData = data
  }

  /**
   * Called when an activity you launched exits, giving you the request code, result code, and intent.
   * Override to handle results from started activities.
   * @param requestCode The request code.
   * @param resultCode The result code.
   * @param intent The intent that was used to start the activity.
   * @example
   * ```typescript
   * async onActivityResult(requestCode, resultCode, intent) {
   *   if (resultCode === IntentResult.RESULT_OK) {
   *     // handle result
   *   }
   * }
   * ```
   */
  async onActivityResult(requestCode: number, resultCode: IntentResult, intent: Intent): Promise<void> {
    const index = this.pendingRequestCodes.indexOf(requestCode)
    if (index !== -1) {
      this.pendingRequestCodes.splice(index, 1)
    }
    // First we check whether any of the fragments has the code.
    const fragment = this.manager.findByRequestCode(requestCode)
    if (fragment) {
      return fragment.onActivityResult(requestCode, resultCode, intent)
    }
    if (this.constructor === Activity) {
      // eslint-disable-next-line no-console
      console.info(
        `Activity#onActivityResult not implemented. Request code: ${requestCode}, result code: ${resultCode}`,
        intent
      )
    }
  }

  /**
   * Returns the current activity instance.
   * @returns This activity.
   */
  getActivity(): Activity {
    return this
  }

  /**
   * Checks if this activity initiated the activity for result with the given code.
   * @param code The request code.
   * @returns `true` if the code is pending, `false` otherwise.
   */
  hasRequestCode(code: number): boolean {
    return this.pendingRequestCodes.includes(code)
  }

  /**
   * Gets the result data set by `setResult`.
   * @returns The result data or `undefined`.
   */
  getResult(): unknown | undefined {
    return this.resultData
  }

  /**
   * Handles intent events dispatched by web components hosted by this activity.
   *
   * Usage example:
   * ```html
   * <custom-element @startactivity="${this.handleIntentEvent}"></custom-element>
   * <custom-element @startactivityforresult="${this.handleIntentEvent}"></custom-element>
   * ```
   *
   * @param event The intent event.
   * @throws Error if the event type is not recognized.
   */
  @bound
  async handleIntentEvent(event: CustomEvent<ActivityDetail | ActivityWithResultDetail>): Promise<void> {
    if (event.type === EventTypes.Intent.startActivityForResult) {
      const info = event.detail as ActivityWithResultDetail
      await this.startActivityForResult(info.intent)
    } else if (event.type === EventTypes.Intent.startActivity) {
      await this.startActivity(event.detail.intent)
    } else {
      throw new Error(`Unrecognized intent event: ${event.type}`)
    }
  }
}
