import type { AtomicFilter, ContextListResult } from '@api-client/core/events/BaseEvents.js'
import type { DataCatalogListOptions } from '@api-client/core/sdk/DataCatalogSdk.js'
import type { DataCatalogScope, DataCatalogSchemaWithVersion } from '@api-client/core/models/DataCatalog.js'
import type { DataCatalogLifecycle } from '@api-client/core/models/DataCatalogVersion.js'
import { Model } from '../core/Model.js'
import { exponentialBackoffDelay, type RetryOptions, shouldRetryApiError, retry } from './retry_logic.js'
import { fromError } from '../exceptions/builder.js'
import services from '../services/api_service.js'
import { LiveData } from '../reactive/LiveData.js'
import type SdkService from '../services/SdkService.js'

export enum ModelState {
  /**
   * Not doing anything.
   */
  idle,
  /**
   * Currently querying for file data.
   */
  reading,
  /**
   * Data was received and the result yielded no results or less than the limit.
   */
  ended,
}

export class DataCatalogModel extends Model<LiveData<DataCatalogSchemaWithVersion[]>> {
  /**
   * The current state of the model.
   */
  state: ModelState = ModelState.idle
  /**
   * The pagination cursor for files to use with the next query.
   */
  cursor?: string

  /**
   * Optional organization ID to filter the data catalog by.
   */
  oid?: string

  /**
   * The SDK service instance.
   */
  sdk: SdkService

  /**
   * When set, the model will only return files that match this query.
   */
  #query?: string

  get query(): string | undefined {
    return this.#query
  }

  set query(value: string | undefined) {
    if (this.#query === value) {
      return
    }
    this.#query = value
    this.resetPageState()
  }

  #scope: DataCatalogScope = 'public'

  get scope(): DataCatalogScope {
    return this.#scope
  }

  set scope(value: DataCatalogScope) {
    if (this.#scope === value) {
      return
    }
    this.#scope = value
    this.resetPageState()
  }

  #sort?: 'lifecycle' | 'updated_at' | 'published_at'

  get sort(): 'lifecycle' | 'updated_at' | 'published_at' | undefined {
    return this.#sort
  }

  set sort(value: 'lifecycle' | 'updated_at' | 'published_at' | undefined) {
    if (this.#sort === value) {
      return
    }
    this.#sort = value
    this.resetPageState()
  }

  #lifecycle: DataCatalogLifecycle = 'stable'

  get lifecycle(): DataCatalogLifecycle {
    return this.#lifecycle
  }

  set lifecycle(value: DataCatalogLifecycle) {
    if (this.#lifecycle === value) {
      return
    }
    this.#lifecycle = value
    this.resetPageState()
  }

  override get value(): LiveData<DataCatalogSchemaWithVersion[]> {
    return this.raw as LiveData<DataCatalogSchemaWithVersion[]>
  }

  constructor(sdk: SdkService, oid?: string) {
    super()
    this.oid = oid
    this.sdk = sdk
    this.raw = new LiveData<DataCatalogSchemaWithVersion[]>([])
  }

  resetPageState(): void {
    this.cursor = undefined
    this.state = ModelState.idle
    this.value.set([])
  }

  async scheduleQuery(): Promise<void> {
    this.debounce(this.#runQuery)
    await this.taskComplete
  }

  async #runQuery(): Promise<void> {
    if (this.state !== ModelState.idle) {
      return
    }
    this.state = ModelState.reading
    try {
      const result = await this.#listFiles()
      this.#processResults(result)
    } catch (e) {
      const err = fromError(e as Error)
      this.notifyError(err)
      this.state = ModelState.idle
    }
    this.dispatchEvent(new Event('querycomplete'))
  }

  async #listFiles(): Promise<ContextListResult<DataCatalogSchemaWithVersion>> {
    const sdk = await services.get('sdk')
    const operation = async (): Promise<ContextListResult<DataCatalogSchemaWithVersion>> => {
      const opts = this.getPageQueryOptions()
      return sdk.dataCatalog.list(opts)
    }
    const retryOptions: RetryOptions = {
      retries: this.retryCount,
      delayMs: exponentialBackoffDelay,
      shouldRetry: shouldRetryApiError,
    }
    return retry(operation, retryOptions)
  }

  getPageQueryOptions(): DataCatalogListOptions {
    const result: DataCatalogListOptions = {
      cursor: this.cursor,
      limit: this.limit,
      oid: this.oid,
      scope: this.scope,
    }
    if (this.sort) {
      result.sort = this.sort
    }
    const { lifecycle, query } = this
    const filters: AtomicFilter[] = []
    if (query) {
      filters.push({ field: 'name', value: query, operator: 'con' })
    }
    if (lifecycle) {
      filters.push({ field: 'lifecycle', value: lifecycle, operator: 'eq' })
    }
    if (filters.length === 1) {
      result.filter = filters[0]
    } else if (filters.length > 1) {
      result.filter = {
        op: 'AND',
        filters,
      }
    }
    return result
  }

  #processResults(result: ContextListResult<DataCatalogSchemaWithVersion>): void {
    const { items, cursor } = result
    if (cursor) {
      this.cursor = cursor
    }
    if (!items.length) {
      this.state = ModelState.ended
      return
    }
    this.value.set(this.value.value.concat(items))
    this.state = ModelState.idle
  }
}
