import { html, TemplateResult, LitElement, nothing } from 'lit'
import { property } from 'lit/decorators.js'
import { consume } from '@lit/context'
import { type ClassInfo, classMap } from 'lit/directives/class-map.js'
import type { AuthProviders } from '@api-client/core/sdk/AuthSdk.js'
import type { InvitationSchema } from '@api-client/core/models/store/Invitation.js'
import { github, google, linkedin } from './icons.js'
import { type IOrganization, organizationContext } from '../../../providers/organization.js'

export enum State {
  /**
   * The initial state (default).
   *
   * Next possible states:
   * - `Initialized`
   * - `Error`
   */
  Initializing = 'initializing',
  /**
   * The state when an error occurred.
   * In this state, set the `errors` property to show error details.
   * There are no next possible states from here, the parent app has to reset the flow.
   */
  Error = 'error',
  /**
   * State after the app initialization is complete.
   * It means that the app has read all necessary config but has not yet fetched invitation data.
   *
   * Next possible states:
   * - `ReadingStatus`
   */
  Initialized = 'initialized',
  /**
   * The state when the application is fetching invitation data.
   * The component renders the loading indicator in this state.
   *
   * Next possible states:
   * - `Login`
   * - `Error`
   */
  ReadingStatus = 'reading-status',
  /**
   * The main interaction state where the user can choose to log in (accept) or decline.
   *
   * Next possible states:
   * - `Rejecting`
   * - `InvitationAccepted`
   * - `InvitationDeclined`
   * - `Error`
   */
  Login = 'login',
  /**
   * The state when the rejection request is being processed.
   *
   * Next possible states:
   * - `Rejected`
   * - `Error`
   */
  Rejecting = 'rejecting',
  /**
   * The state when the invitation has been rejected by the user in the current session.
   *
   * There are no next possible states from here, the parent app has to reset the flow.
   */
  Rejected = 'rejected',
  /**
   * The state when the invitation has been successfully accepted.
   * This state can be set after reading the invitation status that was already accepted previously.
   *
   * There are no next possible states from here, the parent app has to reset the flow.
   */
  InvitationAccepted = 'invitation-accepted',
  /**
   * The state when the invitation is found to be deleted or invalid.
   *
   * There are no next possible states from here, the parent app has to reset the flow.
   */
  InvitationDeleted = 'invitation-deleted',
  /**
   * The state when the invitation was already declined previously.
   *
   * There are no next possible states from here, the parent app has to reset the flow.
   */
  InvitationDeclined = 'invitation-declined',
  /**
   * The state when the invitation has expired.
   *
   * There are no next possible states from here, the parent app has to reset the flow.
   */
  InvitationExpired = 'invitation-expired',
}

/**
 * An App screen (HTML Element) allowing the user to accept an invitation to join an organization.
 *
 * This component handles the UI for invitation acceptance flow.
 * It does not implement the actual invitation logic but dispatches events. However, the state can be set
 * via the `state` property to reflect the current status.
 *
 * The parent app has to handle the invitation acceptance logic and update the state accordingly.
 *
 * When it comes to the user flow, the user can either accept or decline the invitation. To accept the
 * initivation the user has to log in through one of the supported providers (Google, GitHub, LinkedIn).
 * After successful login, the invitation is accepted. If the user declines the invitation, the invitation
 * is rejected immediately.
 *
 * @fires authenticate - Dispatched when the user clicks on one of the authentication buttons.
 *                       The detail object `CustomEvent<{ provider: AuthProviders }>` contains the selected provider.
 * @fires reject - Dispatched when the user clicks the "Decline" button.
 * @fires open - Dispatched when the user clicks the "Open organization" button
 *               after a successful acceptance (state `InvitationAccepted`).
 *
 * @example
 * ```html
 * <invitation-screen
 *   .state="${this.invitationState}"
 *   .invitation="${this.invitationData}"
 *   ?google="${true}"
 *   @authenticate="${this.handleAuth}"
 *   @reject="${this.handleReject}"
 * ></invitation-screen>
 * ```
 */
export default class InvitationScreen extends LitElement {
  /**
   * This is the main state definition that controls what is rendered on the screen.
   * It can be one of the `State` enum values.
   *
   * @attribute
   */
  @property({ type: String }) accessor state = State.Initializing
  /**
   * When set it renders the Google login button. Disabled by default.
   * @attribute
   */
  @property({ type: Boolean }) accessor google = false

  /**
   * When set it renders the LinkedIn login button. Disabled by default.
   * @attribute
   */
  @property({ type: Boolean }) accessor linkedin = false

  /**
   * When set it renders the GitHub login button. Disabled by default.
   * @attribute
   */
  @property({ type: Boolean }) accessor github = false

  /**
   * The list of errors to display.
   */
  @property({ type: Array }) accessor errors: string[] | undefined
  /**
   * The invitation details retreived from the backend.
   * This property must be set for the component to function correctly.
   */
  @property({ type: Object }) accessor invitation: InvitationSchema | undefined

  /**
   * The fetched organization details for which the invitation is valid.
   *
   * This property consumes the `organizationContext`.
   */
  @property({ type: Object, attribute: false })
  @consume({ context: organizationContext })
  accessor organization: IOrganization | undefined

  /**
   * Dispatches the authentication event for the selected provider.
   *
   * @param provider The authentication provider to use.
   */
  protected dispatchAuth(provider: AuthProviders): void {
    this.dispatchEvent(
      new CustomEvent('authenticate', {
        detail: { provider },
        bubbles: false,
        composed: false,
      })
    )
  }

  protected authenticateGoogle(): void {
    this.dispatchAuth('google')
  }

  protected authenticateGithub(): void {
    this.dispatchAuth('github')
  }

  protected authenticateLinkedin(): void {
    this.dispatchAuth('linkedin')
  }

  /**
   * Handles the click on the "Open organization" button after accepting the invitation.
   */
  protected handleAcceptedOpen(): void {
    this.dispatchEvent(
      new CustomEvent('open', {
        bubbles: false,
        composed: false,
      })
    )
  }

  /**
   * Handles the click on the "Decline" button to reject the invitation.
   */
  protected handleReject(): void {
    this.dispatchEvent(
      new CustomEvent('reject', {
        bubbles: false,
        composed: false,
      })
    )
  }

  protected override render(): unknown {
    const classes: ClassInfo = {
      'slide': true,
      'login-slide': true,
      'error': [
        State.Error,
        State.InvitationDeclined,
        State.InvitationDeleted,
        State.InvitationDeclined,
        State.InvitationExpired,
      ].includes(this.state),
    }
    return html`
      <div class="content">
        <div class="${classMap(classes)}">${this.renderPage()}</div>
      </div>
    `
  }

  renderPage(): TemplateResult {
    switch (this.state) {
      case State.Initializing:
        return this.renderCheckingUser()
      case State.Error:
        return this.renderError()
      case State.Initialized:
        // This is only an intermediate state before fetching data
        return this.renderCheckingUser()
      case State.ReadingStatus:
        return this.renderCheckingUser()
      case State.Login:
        return html`${this.renderInto()} ${this.renderLoginForm()}`
      case State.Rejecting:
        return this.renderRejecting()
      case State.Rejected:
        return this.renderRejected()
      case State.InvitationAccepted:
        return this.renderInvitationAccepted()
      case State.InvitationDeclined:
        return this.renderInvitationDeclined()
      case State.InvitationDeleted:
        return this.renderInvitationDeleted()
      case State.InvitationExpired:
        return this.renderInvitationExpired()
      default:
        return html`<p>Unknown state.</p>`
    }
  }

  protected renderCheckingUser(): TemplateResult {
    return html`<div class="login">
      <p class="body-large">Loading...</p>
      <ui-progress indeterminate class="login-progress"></ui-progress>
    </div>`
  }

  protected renderInto(): TemplateResult {
    const { invitation } = this
    const name = invitation?.name
    return html` <div class="intro">
      <h1 class="display-small">${name ? html`Hi ${name}` : 'Welcome to API Now!'}</h1>
      <p class="body-large">You've been invited to join an organization.</p>
    </div>`
  }

  protected renderLoginForm(): TemplateResult {
    return html`
      <div class="login">
        <p class="body-large">To respond to the invitation, please log in.</p>
        <div class="login-option">${this.renderGoogleButton()}</div>
        <div class="login-option">${this.renderGithubButton()}</div>
        <div class="login-option">${this.renderLinkedinButton()}</div>
        <div class="login-option">
          <ui-button class="decline" color="text" @click="${this.handleReject}">Decline invitation</ui-button>
        </div>
      </div>
    `
  }

  protected renderRejecting(): TemplateResult {
    return html`<div class="login">
      <h1 class="display-medium">Thank you for responding</h1>
      <p class="body-large">We will let them know you were not the intended recipient.</p>
      <ui-progress indeterminate class="login-progress"></ui-progress>
    </div>`
  }

  protected renderRejected(): TemplateResult {
    return html`<div class="login">
      <h1 class="display-medium">Thank you for responding</h1>
      <p class="body-large">We've let them know you were not the intended recipient.</p>
    </div>`
  }

  protected renderError(): TemplateResult {
    const { errors = [] } = this
    return html`<div class="login">
      <h1 class="display-medium">Responding error</h1>
      <p class="body-large">An error occurred while processing your request.</p>
      <ul>
        ${errors.map((error) => html`<li>${error}</li>`)}
      </ul>
    </div>`
  }

  protected renderInvitationAccepted(): TemplateResult {
    return html`<div class="login">
      <h1 class="display-medium">Invitation accepted</h1>
      <p class="body-large">You have successfully accepted the invitation.</p>
      <ui-button class="open" color="filled" @click="${this.handleAcceptedOpen}">Open organization</ui-button>
    </div>`
  }

  protected renderInvitationDeclined(): TemplateResult {
    const { invitation } = this
    const time = invitation?.respondedAt
    let info: TemplateResult
    if (time) {
      const d = new Date(time)
      info = html`<p class="body-large">
        You declined the invitation <relative-time datetime="${d.toISOString()}">${d.toLocaleString()}</relative-time>.
      </p>`
    } else {
      info = html`<p class="body-large">You have declined the invitation.</p>`
    }
    return html`<div class="login">
      <h1 class="display-medium">Invitation declined</h1>
      ${info}
      <p class="body-large">You can safely close this page.</p>
    </div>`
  }

  protected renderInvitationDeleted(): TemplateResult {
    const { invitation } = this
    const time = invitation?.deletedInfo?.time
    let info: TemplateResult
    if (time) {
      const d = new Date(time)
      info = html`<p class="body-large">
        The invitation has been deleted
        <relative-time datetime="${d.toISOString()}">${d.toLocaleString()}</relative-time>.
      </p>`
    } else {
      info = html`<p class="body-large">The invitation has been deleted.</p>`
    }
    return html`<div class="login">
      <h1 class="display-medium">Invitation deleted</h1>
      ${info}
      <p class="body-large">You can safely close this page.</p>
    </div>`
  }

  protected renderInvitationExpired(): TemplateResult {
    const { invitation } = this
    const time = invitation?.expiresAt
    let info: TemplateResult
    if (time) {
      const d = new Date(time)
      info = html`<p class="body-large">
        The invitation expired <relative-time datetime="${d.toISOString()}">${d.toLocaleString()}</relative-time>.
      </p>`
    } else {
      info = html`<p class="body-large">The invitation has expired.</p>`
    }

    return html`<div class="login">
      <h1 class="display-medium">Invitation expired</h1>
      ${info}
      <p class="body-large">You can safely close this page.</p>
    </div>`
  }

  protected renderGoogleButton(): TemplateResult | typeof nothing {
    if (!this.google) {
      return nothing
    }
    return html`<ui-button class="auth-button" color="outlined" @click="${this.authenticateGoogle}"
      >${google} Google</ui-button
    >`
  }

  protected renderGithubButton(): TemplateResult | typeof nothing {
    if (!this.github) {
      return nothing
    }
    return html`<ui-button class="auth-button" color="outlined" @click="${this.authenticateGithub}"
      >${github} Github</ui-button
    >`
  }

  protected renderLinkedinButton(): TemplateResult | typeof nothing {
    if (!this.linkedin) {
      return nothing
    }
    return html`<ui-button class="auth-button" color="outlined" @click="${this.authenticateLinkedin}"
      >${linkedin} LinkedIn</ui-button
    >`
  }
}
